【问题标题】:Parameterized Queries in VBA + ADODB + OracleVBA + ADODB + Oracle 中的参数化查询
【发布时间】:2021-06-15 07:27:44
【问题描述】:

我是使用 Oracle 11g 的新手,在让参数化查询顺利运行时遇到了很多问题。

此代码有效:

    Dim rs As ADODB.Recordset
    Dim con As ADODB.Connection
    Dim cmd As ADODB.Command
    Dim prm As ADODB.Parameter

    Set con = New ADODB.Connection
    With con
        .ConnectionString = GetConnection() '<-- the driver here is Driver={Oracle in OraClient11g_home1_32bit}
        .Open
    End With

    Set cmd = New ADODB.Command

    With cmd
        Set .ActiveConnection = con
        .CommandType = adCmdText
        .CommandText = "SELECT * FROM MPA_LINEPLAN.REF_BRAND_SEASON_DROP WHERE DROP_ID = ?" 
        Set prm = .CreateParameter("dropID", adVarChar, adParamInput, 50, "P_SP19_5")
        .Parameters.Append prm
        Set rs = .Execute
    End With

但我要运行的实际查询会多次引用 dropID 参数。为了让它工作,我必须一遍又一遍地添加相同的参数。告诉我有没有更好的方法?我尝试了以下方法:

    With cmd
        Set .ActiveConnection = con
        .CommandType = adCmdText
        .CommandText = "SELECT * FROM MPA_LINEPLAN.REF_BRAND_SEASON_DROP WHERE DROP_ID = :dropID" 
        Set prm = .CreateParameter("dropID", adVarChar, adParamInput, 50, "P_SP19_5")
        .Parameters.Append prm
        Set rs = .Execute
    End With

但是当我尝试执行 rs 时,它会命中 unspecified error

另外,假设对于我的特殊情况,存储过程不是最好的选择(即使它应该是最好的选择:-/)

编辑: 实际查询很长,为了不让您搜索所有 :dropID 引用,我在此处对其进行了缩减,但留出足够显示多个引用。

WITH 
--...
DropDim AS (
SELECT DROP_ID
     , DROP_NAME
     , SEASON_ID
     , SEASON_NAME
     , BRAND_ID
     , SEASON_YEAR
     , 'DROP_' || substr(DROP_ID, LENGTH(DROP_ID),1) AS LP_Join_Drop
     , SEASON_NAME || '_' || SEASON_YEAR AS LP_Join_Season
FROM MPA_LINEPLAN.REF_BRAND_SEASON_DROP
WHERE DROP_ID = :dropID),
--...
LYMap AS
(SELECT DC.DROP_ID
     , DC.CHANNEL_ID
     , BSD.SEASON_YEAR
     , BSD.SEASON_NAME
     , BSD.DROP_NAME
     , FW.WEEKENDINGDATE  AS LY_WEEKENDING_DATE
     , FW.YEARWEEK AS LY_YEARWEEK
FROM MPA_LINEPLAN.REF_DROP_CHANNEL DC
  INNER JOIN MPA_MASTER.FISCALWEEK FW
    ON FW.YEARWEEK BETWEEN DC.LY_START_DT AND DC.LY_END_DT
  INNER JOIN MPA_LINEPLAN.REF_BRAND_SEASON_DROP BSD ON BSD.DROP_ID = dc.DROP_ID
WHERE DC.DROP_ID = :dropID),

LLYMap AS
(SELECT DC.DROP_ID
     , DC.CHANNEL_ID
     , BSD.SEASON_YEAR
     , BSD.SEASON_NAME
     , BSD.DROP_NAME
     , FW.WEEKENDINGDATE  AS LLY_WEEKENDING_DATE
     , FW.YEARWEEK AS LLY_YEARWEEK
FROM MPA_LINEPLAN.REF_DROP_CHANNEL DC
  INNER JOIN MPA_MASTER.FISCALWEEK FW
    ON FW.YEARWEEK BETWEEN DC.LLY_START_DT AND DC.LLY_END_DT
  INNER JOIN MPA_LINEPLAN.REF_BRAND_SEASON_DROP BSD ON BSD.DROP_ID = dc.DROP_ID
WHERE DC.DROP_ID = :dropID  ),
--....

【问题讨论】:

  • 声明一个变量? Set @foo = ? 然后在脚本中使用@foo?关于 Oracle 的 IDK,但这可能是我在 SQL Server 上要做的。
  • @MathieuGuindon 这也是我的想法,但我没有尝试,因为 Oracle 等效项似乎有效。我得到了各种not all variables bound 错误。令人惊讶的是,我在互联网上找不到任何工作示例:-(
  • 我不理解问题。在哪里使用相同的参数一遍又一遍。为什么qmarks不起作用?在两个准备好的语句中,只使用一个参数。你的意思是一条 SQL 语句有多个 qmark 吗?
  • @Parfait 为简单起见,我只放了一个简单的sql语句。实际语句引用 dropID(或 qmark)变量 10 次以上。关键是我不想添加相同的参数 10+ 次以使 qmark 版本工作。
  • 多次相同 SQL 查询?请显示实际的肯定不止一个dropID 参考。

标签: vba oracle11g adodb


【解决方案1】:

继续使用 qmarks 占位符并简单地使用 for 循环来附加相同的参数对象。具体来说,qmarks 对应于查询中放置的位置。假设以下查询

sql = "SELECT * FROM MPA_LINEPLAN.REF_BRAND_SEASON_DROP" _
        & " WHERE DROP_ID = ? AND DROP_ID2 = ? AND DROP_ID3 = ?" 

With cmd
   Set .ActiveConnection = con
   .CommandType = adCmdText
   .CommandText = sql
   For i = 1 To 3  ' ADJUST TO NUMBER OF PARAMS
      Set prm = .CreateParameter("prm" & i, adVarChar, adParamInput, 50, "P_SP19_5")
      .Parameters.Append prm
   Next i
   Set rs = .Execute
End With

或者,将您的查询转换为stored procedure(避免在 VBA 中读取非常大的 SQL 字符串或文本文件),然后定义 一个 参数。

甲骨文

CREATE OR REPLACE PROCEDURE my_procedure_name(dropID IN VARCHAR2) IS
BEGIN
   ...long query using dropID (without any symbol)...
END;
/

VBA

With cmd
   Set .ActiveConnection = con
   .Properties("PLSQLRSet") = TRUE  
   .CommandType = adCmdText
   .CommandText = "{CALL my_procedure_name(?)}"       
   Set prm = .CreateParameter("prm", adVarChar, adParamInput, 50, "P_SP19_5")
   .Parameters.Append prm

   Set rs = .Execute
End With

【讨论】:

  • 这行得通,但是如果/当您开始需要另一个参数时,很快就会变得混乱。我不认为我曾经使用过它们,但是 ADODB 不支持命名参数吗?
  • @MathieuGuindon,很遗憾没有。但我添加了一个 OP 可以考虑的存储过程版本
  • 哈!然后删除我的草稿 - 将推荐一个存储过程... 应该 是在 VBA(或任何语言,真的)中执行 anything SQL 的首选方式无论如何=)
  • 感谢您的建议。我公司的 DBA 很难开发存储过程(我必须通过官僚程序才能完成简单的 DDL,即使对于开发人员也是如此)。我实际上找到了另一种效果更好的解决方案。我将发布它以防其他人偶然发现同样的问题,但只是想感谢您抽出时间回答我的问题!
  • 使用cmd.NamedParameters = True重试不带循环但带有:dropID的ODBC版本。
【解决方案2】:

解决这个问题的最佳方法就是停止使用 Oracle 的 ODBC 驱动程序并开始使用 Oracle 的 OLEDB 作为提供程序。

旧连接字符串: .ConnectionString = "Driver={Oracle in OraClient11g_home1_32bit};UID=MyUname;PWD=MyPW;DBQ=MyDB;"

新的连接字符串: .ConnectionString = "Provider=OraOLEDB.Oracle;Data Source=MyDB; User ID=MyUname;Password=MyPW;"

OraOLEDB 支持命名参数,这正是我最初想要实现的。现在,我可以在 SQL 语句中引用带有 : 前缀的参数名称了。

With cmd
    Set .ActiveConnection = con
    .CommandType = adCmdText
    .CommandText = "SELECT * FROM MPA_LINEPLAN.REF_BRAND_SEASON_DROP WHERE DROP_ID =:dropID"
    Set prm = .CreateParameter("dropID", adVarChar, adParamInput, 50, "P_SP19_5")
    .Parameters.Append prm
    Set rs = .Execute
End With

现在可以了!

【讨论】:

  • 有趣!但是再一次,您的示例仅使用一个参数。这是否适用于具有许多 :dropID 的较大查询?请在您的回答中提及。
  • 是的,这适用于对同一参数变量的多次引用。我会用一个更好的例子来更新我的答案。
  • 您的示例实际上与近 10 年前的 SO answer 背道而驰,即 OraOleDB 仅支持位置绑定而不是命名参数。不过,这可能是 OLEDB 提供商的版本问题。
  • @Parfait 好吧,您说得对,先生。我跳了枪——即使在这种情况下,oraOLEDB 也使用位置绑定。 撞到桌子上
【解决方案3】:

另一种方法是使用 CTE 获取所有标量参数,然后连接回您感兴趣的表:

-- Outer SELECT * due to limitations of ODBC drivers in VBA
SELECT *
FROM
(
 WITH lu as (
   SELECT ? as drop_id 
   FROM dual
    )
  )
  SELECT t1.*
  FROM mpa_lineplan.ref_brand_season_drop t1
  CROSS JOIN lu -- could be inner join or whatever type you are interested in
  WHERE t1.drop_id = lu.drop_id
)

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2010-11-11
    • 2014-10-03
    • 1970-01-01
    • 2014-05-01
    • 1970-01-01
    • 2019-05-31
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多