【问题标题】:Query will not run with variables, will work when variable's definitions are pasted in查询不会与变量一起运行,将在粘贴变量的定义时工作
【发布时间】:2010-10-16 01:11:57
【问题描述】:

这是 VBA 中的查询 (Access 2007) 我定义了 3 个字符串:

str_a = "db.col1 = 5"
str_b = " and db.col2 = 123"
str_c = " and db.col3 = 42"

然后我在查询的 WHERE 部分使用这些:

"WHERE '" & str_a & "' '" & str_b & "' '" & str_c & "' ;"

这失败了,但是如果我像这样粘贴字符串:

"WHERE db.col1 = 5 and db.col2 = 123 and db.col3 = 42;"

完美运行。我猜在字符串中使用多个变量时语法是错误的。
有人有什么提示吗?

【问题讨论】:

  • 当您编写 MsgBox(myQuery) 时,您是否完全了解粘贴的示例有什么?如果没有,那是你的问题。
  • 这是您使用的实际代码吗?如果是,那么递归的建议应该有效。如果不是,那就贴上实际的代码,看一些没有错误的代码,很难找到错误...
  • 在服务器上发送 SQL 请求之前,请在构建字符串后添加 debug.print。语法错误就会很明显

标签: sql string ms-access vba


【解决方案1】:
"WHERE '" & str_a & "' '" & str_b & "' '" & str_c & "' ;"

将在您完成的 WHERE 子句中包含单引号。而你向我们展示的工作版本没有:

"WHERE db.col1 = 5 and db.col2 = 123 and db.col3 = 42;"

所以,尝试构造不带单引号的WHERE 子句:

"WHERE " & str_a & " " & str_b & " " & str_c & ";"

出于调试目的,在 VBA 完成构建后查看最终字符串很有用。如果您将字符串存储在名为strSQL 的变量中,您可以使用:

Debug.Print strSQL

在 VB 编辑器的即时窗口中显示完成的字符串。 (您可以使用 CTRL+g 键盘快捷键进入即时窗口。)

或者,您可以在消息框窗口中显示完成的字符串:

MsgBox strSQL

【讨论】:

  • 我认为这会奏效,我认为您在这里的内容看起来是正确的,但我必须等到星期一回去工作才能尝试。感谢您的帮助,调试将非常有用。我只是对 VBA 不太熟悉
【解决方案2】:

你有一些额外的单引号。

试试这个:

"WHERE " & str_a & str_b  & str_c

注意:通常,您不应通过连接字符串来构建查询字符串,因为这会使您容易受到 SQL 注入的攻击,并且会错误处理特殊字符。更好的解决方案是使用准备好的语句。但是假设您在一个非常受控的环境中操作,我提供的解决方案应该可以工作。

【讨论】:

  • 不,这给出了一个错误,我认为 '" 是必要的,从我在网上看到的。
  • thegreekness:我相当愿意打赌你的表不叫 db,至少,我希望不是。摆脱分贝。看看你是否仍然收到错误。
  • 不,这不是我的桌子的名字,我只是为了这篇文章而写的,我使用的是我的桌子的实际名称。
【解决方案3】:

关于对动态构建的 SQL 进行故障排除的快速提示:echo SQL 字符串由所有连接和插值产生,而不是盯着您的代码。

WHERE 'db.col1 = 5' ' and db.col2 = 123' ' and db.col3 = 42';

十分之九,问题变得更加清晰。

【讨论】:

  • 你知道我如何在 VBA 中做到这一点吗?
  • 只需将您的 SQL 字符串放入变量中并使用 Wscript.echo 或 Cscript.echo。
  • 使用 Debug.Print,wscript 不适合 VBA。
  • 另请参阅我对递归帖子的评论。
【解决方案4】:

对于 VB6/VBA 动态 SQL,我总是觉得创建一个 SQL 模板更易读,然后使用 Replace() 函数添加动态部分。试试这个:

Dim sql As String
Dim condition1 As String
Dim condition2 As String
Dim condition3 As String

sql = "SELECT db.col1, db.col2, db.col3 FROM db WHERE <condition1> AND <condition2> AND <condition3>;"

condition1 = "db.col1 = 5"
condition2 = "db.col2 = 123"
condition3 = "db.col3 = 'ABCXYZ'"

sql = Replace(sql, "<condition1>", condition1)
sql = Replace(sql, "<condition2>", condition2)
sql = Replace(sql, "<condition3>", condition3)

但是,在这种情况下,WHERE 子句中的值会发生变化,而不是字段本身,因此您可以将其重写为:

Dim sql As String

sql = "SELECT col1, col2, col3 FROM db "
sql = sql & "WHERE col1 = <condition1> AND col2 = <condition2> AND col3 = '<condition3>';"

sql = Replace(sql, "<condition1>", txtCol1.Text)
sql = Replace(sql, "<condition2>", txtCol2.Text)
sql = Replace(sql, "<condition3>", txtCol3.Text)

【讨论】:

  • 这实际上与我实际所做的非常接近,我只是没有将其包含在帖子中。我非常感谢您的帮助,我可能会改变我必须更接近这一点的内容。
  • 这种方法对我来说唯一的问题是,根据用户先前的选择条件 3 可能是 "" 这样用户可以看到更多结果,所以你的方法会返回一个错误 bc在查询字符串中是一个额外的“与”
  • 嗯,当然,您必须将查询演变成有用的东西。但是您仍然可以构建一个动态字符串变量模板并使用 Replace() 方法。可能性是无限的;)
【解决方案5】:

关于在 VBA 中构造 WHERE 子句的一些方法。

根据定义,您的示例是不正确的,因为您将单引号放在不需要的地方。这个:

str_a = "db.col1 = 5"
str_b = " and db.col2 = 123"
str_c = " and db.col3 = 42"
"WHERE '" & str_a & "' '" & str_b & "' '" & str_c & "' ;"

...会产生这样的结果:

WHERE 'db.col1 = 5' ' and db.col2 = 123' ' and db.col3 = 42' ;

这显然行不通。

去掉单引号,它应该可以工作。

现在,也就是说,我永远不会那样做。我永远不会将 AND 放在用于构造 WHERE 子句的子字符串中,因为如果我有第二个字符串的值但没有第一个字符串的值,我该怎么办?

当您必须使用分隔符连接多个字符串并且有些可以未分配时,要做的一件事就是将它们全部连接起来,而不用担心连接之前的字符串是否未分配:

str_a = "db.col1 = 5"
str_b = "db.col2 = 123"
str_c = "db.col3 = 42"

要连接它,你会这样做:

If Len(str_a) > 0 Then
   strWhere = strWhere & " AND " str_a
End If
If Len(str_b) > 0 Then
   strWhere = strWhere & " AND " str_b
End If
If Len(str_c) > 0 Then
   strWhere = strWhere & " AND " str_c
End If

当所有三个字符串都被分配时,这会给你:

" AND db.col1 = 5 AND db.col2 = 123 AND db.col3 = 42"

只需使用 Mid() 删除前 5 个字符,无论哪个变量分配了值,它都会始终正确:

strWhere = Mid(strWhere, 6)

如果它们都没有被分配,你会得到一个长度为零的字符串,这就是你想要的。如果分配了其中任何一个,您将首先得到“AND ...”,这是一个错误的前导运算符,您只需使用 Mid() 命令将其删除。这是有效的,因为您知道 Mid() 之前的所有结果都将以“ AND ”开头,无论如何 - 无需对 strWhere 是否已经分配了值进行不必要的测试 - 只需将 AND 插入其中并切碎它最后关闭。

另外,有人提到了 SQL 注入。关于 Access,有一个冗长的讨论,其中考虑了很多与此线程相关的问题:

Non-Web SQL Injection

【讨论】:

  • 这真的很有帮助,我会在周一回去工作时尝试使用类似的东西。
  • 另外我这样做的原因是因为在代码中我将它设置为要求第一个字符串存在,然后有一些如果最后两个字符串可能只是“ " 我知道这看起来不安全,但它是一个非常受控的环境
  • 如果你得到一个零长度的字符串,你需要去掉'WHERE'
  • 在我的示例中,我的任何字符串中都没有存储“WHERE”。显然,在 strWhere = Mid(strWhere, 6) 之后,您将检查 Len(strWhere)>0 是否与“WHERE”连接。
【解决方案6】:

我有我最喜欢的“addANDclause”函数,带有以下参数:

public addANDclause( _ 
    m_originalQuery as string, _
    m_newClause as string) _
as string
  • 如果 m_originalQuery 不包含 WHERE 关键字,则 addANDClause() 将返回添加了“WHERE”的原始查询。
  • 如果 m_orginalQuery 已经包含 WHERE 关键字,那么 addANDClause() 将返回添加了“AND”的原始查询。

所以我可以添加尽可能多的“AND”子句。使用您的示例,我将编写以下内容来动态创建我的 SQL 查询:

m_SQLquery = "SELECT db.* FROM db"
m_SQLquery = addANDClause(m_SQLQuery, "db.col1 = 5")
m_SQLQuery = addANDClause(m_SQLQuery, "db.col2 = 123")
m_SQLQuery = addANDClause(m_SQLQuery, "db.col3 = 42")

当然,代替这些固定值,这样的函数可以获取绑定或未绑定表单控件中可用的值,以动态构建记录集过滤器。也可以发送参数如:

m_SQLQuery = addANDClause(m_SQLQuery, "db.text1 like 'abc*'")

【讨论】:

    【解决方案7】:

    虽然动态 SQL 对于引擎来说可能更高效,但这里的一些 cmets 似乎赞同我的观点,即动态 SQL 可能会让人类读者感到困惑,尤其是当他们没有编写代码时(想想那些将继承您的代码)。

    我更喜欢 PROCEDURE 中的静态 SQL,并通过选择适当的值在运行时动态调用 proc;如果您使用 SQL DDL(下面的示例)来定义 proc,您可以为参数指定 DEFAULT 值(例如 NULL),以便调用者可以简单地省略不需要的参数,例如看看你是否可以遵循这个过程中的逻辑:

    CREATE PROCEDURE MyProc
    (
       arg_col1 INTEGER = NULL, 
       arg_col2 INTEGER = NULL, 
       arg_col3 INTEGER = NULL
    )
    AS 
    SELECT col1, col2, col3
      FROM db 
     WHERE col1 = IIF(arg_col1 IS NULL, col1, arg_col1) 
           AND col2 = IIF(arg_col2 IS NULL, col2, arg_col2) 
           AND col3 = IIF(arg_col3 IS NULL, col3, arg_col3);
    

    当然,它可能不会产生最佳的执行计划,但 IMO 你必须平衡优化与良好的设计原则(它在我的机器上运行得非常快:)

    【讨论】:

      猜你喜欢
      • 2021-09-26
      • 2022-06-14
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2016-10-05
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多