在Access / VBA中构建SQL字符串

时间:2009-11-26 07:41:24

标签: sql ms-access vba

有时,我必须在VBA中构建一个SQL字符串并使用Docmd.RunSql()执行它。我总是通过将变量连接到字符串中来构建这些字符串,例如:

Dim mysqlstring as String
mysqlstring = "INSERT INTO MyTable (Field1, Field2, Field3 ...) VALUES ("
mysqlstring = mysqlstring + Me.TextMyField1 + ", " 'parameter comments
mysqlstring = mysqlstring + Me.TextMyField2 + ", " 
mysqlstring = mysqlstring + Me.TextMyField3 + ", " 
...
mysqlstring = mysqlstring + ");"
Docmd.RunSql mysqlstring

VBA似乎没有一元连接运算符(如+ =),虽然这看起来并不理想,但至少我可以评论每个参数并单独更改它们。它比一个怪物串联字符串更容易阅读和更改。但它似乎仍然是一种构建SQL字符串的可怕方法。我有一个约50个参数在工作,所以有50行mysqlstring = mysqlstring +...。不可爱。

顺便说一句,这排除了使用行继续来格式化字符串,因为有limit on the number of line-continuations you can use on a single string(提示:小于50)。此外,VBA不允许你在行继续之后发表评论,grr!

直到最近,我认为这是构建这些字符串的唯一方法。但是最近我看到了一个不同的模式,在{@ 3}}之类的字符串中注入了我发布答案的参数,并且想知道VBA是否有等价的Parameters.AddWithValue(),或者甚至是比字符串连接方法更好。所以我认为这值得提出自己的问题。也许我在这里缺少一些东西。

有些Access专家可以澄清在Access / VBA中构建SQL字符串的最佳做法。

7 个答案:

答案 0 :(得分:9)

我有一个时间表应用程序,其中包含相当复杂的未绑定人工交易输入表单。有很多数据验证,速率计算和其他代码。我决定使用以下内容来创建SQL插入/更新字段。

变量strSQLInsert,strSQLValues,strSQLUpdate是表单级别的字符串。

以下许多内容:

Call CreateSQLString("[transJobCategoryBillingTypesID]", lngJobCategoryBillingTypesID)

接下来是:

If lngTransID = 0 Then
    strSQL = "INSERT into Transactions (" & Mid(strSQLInsert, 3) & ") VALUES (" & Mid(strSQLValues, 3) & ")"
Else
    strSQL = "UPDATE Transactions SET " & Mid(strSQLUpdate, 3) & " WHERE transID=" & lngTransID & ";"
End If

conn.Open
conn.Execute strSQL, lngRecordsAffected, adCmdText

请注意,Mid行会删除前导“,”。 lngTrans是autonumber primamy kay的价值。

Sub CreateSQLString(strFieldName As String, varFieldValue As Variant, Optional blnZeroAsNull As Boolean)
'    Call CreateSQLString("[<fieldName>]", <fieldValue>)

Dim strFieldValue As String, OutputValue As Variant

    On Error GoTo tagError

    ' if 0 (zero) is supposed to be null
    If Not IsMissing(blnZeroAsNull) And blnZeroAsNull = True And varFieldValue = 0 Then
        OutputValue = "Null"
    ' if field is null, zero length or ''
    ElseIf IsNull(varFieldValue) Or Len(varFieldValue) = 0 Or varFieldValue = "''" Then
        OutputValue = "Null"
    Else
        OutputValue = varFieldValue
    End If

    ' Note that both Insert and update strings are updated as we may need the insert logic for inserting
    '    missing auto generated transactions when updating the main transaction
    ' This is an insert
    strSQLInsert = strSQLInsert & ", " & strFieldName
    strSQLValues = strSQLValues & ", " & OutputValue
    ' This is an update
    strSQLUpdate = strSQLUpdate & ", " & strFieldName & " = " & OutputValue

    On Error GoTo 0
    Exit Sub

tagError:

    MsgBox "Error " & Err.Number & " (" & Err.Description & ") in procedure CreateSQLString of VBA Document Form_LabourEntry"
    Exit Sub
End Sub

我看到其他海报都使用了Execute方法。 DoCmd.RunSQL的问题在于它可以忽略错误。以下任一项都将显示查询收到的任何错误消息。如果使用DAO,请使用Currentdb.Execute strSQL,dbfailonerror ..对于ADO,请使用CurrentProject.Connection.Execute strCommand,lngRecordsAffected,adCmdText然后可以删除docmd.setwarnings行。

如果您要使用docmd.setwarnings,请确保将True语句放在任何错误处理代码中。否则可能会在以后发生奇怪的事情,尤其是在您使用应用程序时。例如,如果关闭对象,则不会再显示“是否要保存更改”消息。这可能意味着不需要的更改,删除或添加将保存到您的MDB。

两种方法之间的表现也可能有很大差异。一篇帖子称currentdb.execute花了两秒钟,而docmd.runsql花了八秒钟。一如既往YMMV。

答案 1 :(得分:4)

    Private Sub Command0_Click()
Dim rec As Recordset2
Dim sql As String
Dim queryD As QueryDef

    'create a temp query def.
    Set queryD = CurrentDb.CreateQueryDef("", "SELECT * FROM [Table] WHERE Val = @Val")
    'set param vals
    queryD.Parameters("@Val").Value = "T"
    'execute query def
    Set rec = queryD.OpenRecordset
End Sub

答案 2 :(得分:4)

添加@astander所说的内容,你可以创建一个querydef(带参数)并将其保存为数据库的一部分。

e.g。

Parameters dtBegin DateTime, dtEnd DateTime;
INSERT into myTable (datebegin, dateend) values (dtBegin, dtEnd)

假设您使用名称myTableInsert保存,您可以编写如下代码

dim qd as QueryDef
set qd = CurrentDB.QueryDefs("myTableInsert")
qd.Parameters("dtBegin").Value = myTextFieldHavingBeginDate
qd.Parameters("dtEnd").Value = myTextFieldHavingEndDate    
qd.Execute

注意:我没有测试过这段代码。但是,我猜这应该是它 希望这能为您提供足够的信息以便开始使用。

答案 3 :(得分:1)

正如其他人所说,首先利用参数可能更好。但是,......

我也错过了一个连接运算符,已经习惯了。= PHP。在少数情况下,我编写了一个函数来完成它,虽然不是特定于连接SQL字符串。这是我用于为HTTP GET创建查询字符串的代码:

  Public Sub AppendQueryString(strInput As String, _
       ByVal strAppend As String, Optional ByVal strOperator As String = "&")
    strAppend = StringReplace(strAppend, "&", "&amp;")
    strInput = strInput & strOperator & strAppend
  End Sub

我称之为的一个例子:

  AppendQueryString strOutput, "InventoryID=" & frm!InventoryID, vbNullstring
  AppendQueryString strOutput, "Author=" & URLEncode(frm!Author)

......等等。

现在,为了构造SQL WHERE子句,您可以考虑使用类似于Application.BuildCriteria的包装器:

  Public Sub ConcatenateWhere(ByRef strWhere As String, _
      strField As String, intDataType As Integer, ByVal varValue As Variant)
    If Len(strWhere) > 0 Then
       strWhere = strWhere & " AND "
    End If
    strWhere = strWhere & Application.BuildCriteria(strField, _
       intDataType, varValue)
  End Sub

然后你会将其称为:

  Dim strWhere As String

  ConcatenateWhere strWhere,"tblInventory.InventoryID", dbLong, 10036
  ConcatenateWhere strWhere,"tblInventory.OtherAuthors", dbText, "*Einstein*"
  Debug.Print strWhere
  strSQL = "SELECT tblInventory.* FROM tblInventory"
  strSQL = strSQL & " WHERE " & strWhere

...并且Debug.Print将输出此字符串:

  tblInventory.InventoryID=10036 AND tblInventory.OtherAuthors Like "*Einstein*"

对它的变化对你来说可能更有用,也就是说,你可能想要一个可选的连接运算符(所以你可以有OR),但我可能会通过构造一系列WHERE字符串并将它们连接起来来实现。或者在代码中逐行,因为您可能需要仔细放置括号以确保正确执行AND / OR优先级。

现在,这些都没有真正解决INSERT语句的VALUES连接问题,但我质疑你实际在Access应用程序中插入文字值的频率。除非您使用未绑定的表单来插入记录,否则您将使用表单来插入记录,因此根本不会使用SQL语句。因此,对于VALUES子句,似乎在Access应用程序中,您不应经常需要它。如果您发现自己需要编写这样的VALUES子句,我建议您不要正确使用Access。

那就是说,你可以使用这样的东西:

  Public Sub ConcatenateValues(ByRef strValues As String, _
      intDatatype As Integer, varValue As Variant)
    Dim strValue As String

    If Len(strValues) > 0 Then
       strValues = strValues & ", "
    End If
    Select Case intDatatype
      Case dbChar, dbMemo, dbText
        ' you might want to change this to escape internal double/single quotes
        strValue = Chr(34) & varValue & Chr(34)
      Case dbDate, dbTime
        strValue = "#" & varValue & "#"
      Case dbGUID
        ' this is only a guess
        strValues = Chr(34) & StringFromGUID(varValue) & Chr(34)
      Case dbBinary, dbLongBinary, dbVarBinary
        ' numeric?
      Case dbTimeStamp
        ' text? numeric?
      Case Else
        ' dbBigInt , dbBoolean, dbByte, dbCurrency, dbDecimal, 
        '   dbDouble, dbFloat, dbInteger, dbLong, dbNumeric, dbSingle
        strValue = varValue
    End Select
    strValues = strValues & strValue
  End Sub

...它会连接你的值列表,然后你可以连接成你的整个SQL字符串(在VALUES()子句的parens之间)。

但正如其他人所说,首先利用参数可能更好。

答案 4 :(得分:1)

FWIW,我使用稍微不同的格式,使用Access的换行符“_”。我还使用连接运算符“&amp;”。主要原因是可读性:

Dim db as Database: Set db = Current Db
Dim sql$
sql= "INSERT INTO MyTable (Field1, Field2, Field3 ...Fieldn) " & _
     "VALUES (" & _
     Me.TextMyField1 & _
     "," & Me.TextMyField2 & _
     "," & Me.TextMyField3 & _
     ...
     "," & Me.TextMyFieldn & _
     ");"
db.Execute s
Set db = nothing

答案 5 :(得分:0)

我会使用上面的方法,每个参数都在一个单独的行上,它很好并且易于调试和添加。

但是,如果您真的不喜欢这种方式,那么您可以查看参数查询。稍微不那么灵活,但在某些情况下会稍快一些。

或者另一种方法是定义一个公共函数以插入该表并将值作为参数传递给它。

但是我会坚持你所拥有的,但如果VBA理解= +

那就太好了

答案 6 :(得分:0)

我过去做过的一件事就是创建一个系统,用于解析SQL代码以查找参数并将参数存储在表中。我会在Access之外编写我的MySQL查询。然后我所要做的就是从Access打开文件,每次我想运行它时都可以随时更新。

这是一个非常复杂的过程,但是如果你有兴趣,我很乐意在下周重新开始工作。