我正在使用 VBA adodb 根据 Excel 行内容在 SQL Server 中编写
INSERT
语句。
我正在使用参数化查询,因此我的 VBA 代码具有以下形式:
sqlStatement = "INSERT INTO dbo.mytable (" & Join(insertElement(0), ", ") & ") VALUES (" & Join(insertElement(2), ", ") & ")"
Set cm = New ADODB.Command
With cm
Debug.Print (sqlStatement)
.ActiveConnection = conn
.CommandText = sqlStatement
.CommandType = adCmdText
For Each e In insertElement(1)
Set Pm = .CreateParameter(, adVarChar, 3, 1024, e)
.Parameters.Append Pm
Next e
Set rs = .Execute
End With
其中
insertElement(0)
是字段名称数组,...(1) 是值数组,...(2) 是占位符 ?
的数组,用于支持参数化
当我运行此代码时,出现错误
[Microsoft][ODBC Driver 13 for SQL Server][SQL Server]“输出”附近的语法不正确
但是,当我询问
sqlStatement
文本时,语句中的任何位置都没有“输出”。文本的形式为:
INSERT INTO dbo.mytable ([my_field_1],[my_field_2],[somefieldshaveweirdcharslike+#], ...) VALUES (?, ?, ?, ...)
那么,如果我没有直接提供“输出”命令,并且由于它正在服务器端处理而无法直接看到该语句,那么我如何诊断语法?
您应该能够通过迭代数组并以您认为合适的方式连接值来简单地构建 SQL 语句,而不是尝试将一堆参数插入一堆问号。像这样的东西(我假设
insertElement
实际上是一个二维数组而不是一个集合;如果它确实是一个集合,那么你需要对 For Each
做类似的事情):
sqlStatement = "INSERT INTO dbo.mytable (" & Join(insertElement(0), ", ") & ") VALUES ("
Set cm = New ADODB.Command
With cm
.ActiveConnection = conn
.CommandType = adCmdText
Dim aCount As Integer
aCount = Ubound(insertElement(1))
For i = 0 To aCount - 1
sqlStatement = sqlStatement & "'" & insertElement(1,i) & "'" & _
Iif(i <> aCount - 1, ",", "")
Next
.CommandText = sqlStatement
Set rs = .Execute
End With
总而言之,如果您想更深入地了解错误发生的位置,您可以尝试迭代
Connection
对象的 Errors 集合。由于尝试使用连接可能会产生多个错误,因此仅使用 Err
对象进行故障排除可能会有点困难。
最后,最好在服务器端的存储过程中设置
INSERT
语句,并以这种方式将值作为参数传递,除非您没有理由担心注入攻击。
我在 SQL Server 中没有跟踪权限,因此我探索服务器端日志记录的选项有限。
我最终以暴力方式解决了这个问题,一次添加一个字段,直到收到错误。
就我而言,根本问题与使用 adodb
adNumeric
参数类型表示十进制值有关。我将参数类型切换为 adDecimal
,然后在各个参数对象上设置 NumericScale
和 Precision
值。 vba 代码结构如下所示
For Each p In paramArr
If p(1) = adVarChar Then
Set Pm = .CreateParameter(, p(1), 1, Len(p(2)), p(2))
Else
Set Pm = .CreateParameter(, p(1), 1, , p(2))
End If
If p(1) = adDecimal Then
Pm.NumericScale = 3
Pm.Precision = 13
End If
.Parameters.Append Pm
Next p
我参加聚会迟到了,但是本周我在新代码和一些代码中遇到了同样的问题,这些代码几个月后都无法上线,同时我也抓耳挠腮,但我找不到它。然后因为不知道就放在一边了。互联网并没有那么有帮助。好吧,不是那么大的问题,因为以前存在相同功能的版本,曾经使用 ADODB.Recordset,后来构建 INSERT 字符串并使用
Connection.Execute
拍摄它(比使用 Recordset 稍快)。但我知道,使用 ADODB.Command 是最快的方法,特别是如果您在运行程序时经常使用它,因为这样您就可以使用带有 Command.Prepared = True
的预编译选项,这实际上是加力器。所以这只是一个性能改进,无法上线。但新代码让我又回到了这个问题。
长话短说:这个错误和当时对我来说显而易见的解决方案是:我们使用的字段名称是 SQL 保留字,因此将其放在方括号中。
这是有错误的代码:
Function INSERT_Category(ID, Type, Text)
Static CMD As ADODB.Command
If CMD Is Nothing Then
Set CMD = New ADODB.Command
With CMD
.ActiveConnection = CurrentProject.Connection
.CommandType = adCmdText
.CommandText = "INSERT INTO CATEGORIES ID, ID_TYPE, TEXT) VALUES (?,?,?)"
.Prepared = True
.Parameters.Append .CreateParameter("ID", adInteger)
.Parameters.Append .CreateParameter("ID_TYPE", adVarWChar, , 50)
.Parameters.Append .CreateParameter("TEXT", adVarWChar, , 255)
End With
End If
CMD("ID") = CLng(ID)
CMD("ID_TYPE") = CStr(Type)
CMD("TEXT") = CStr(Text)
CMD.Execute , , adExecuteNoRecords
End Function
当我意识到错误在哪里(TEXT 是保留字)时,我只更改了一行,在字段名称两边加上方括号:
.CommandText = "INSERT INTO CATEGORIES ID, ID_TYPE, [TEXT]) VALUES (?,?,?)"
它成功了!您不必在定义参数和最后设置值时也这样做。也许这会很痛,我只是没有尝试过。
当字段名称中包含空格或其他特殊字符时(下划线除外,这从来都不是问题),这也适用。特别是如果您在字段名称中使用数学运算符(-、+、/、*)。
嗯,您可以通过用括号将每个字段名称括起来来避免这种情况。但在我看来,这是矫枉过正并降低了可读性。此外,当您例如构建一个等待字段名称作为参数的函数,为了方便起见,为了不要忘记括号(如果需要),您可以将它们放在函数中字段名称周围。然后有一天,您想到可以使用该函数来输入像
COUNT(Fieldname)
这样的表达式。然后它崩溃了,因为整个表达式被解释为字段名称。
最好的选择始终是首先避免使用此类字段名称。但有时你发现自己处于一个其他人很久以前定义的环境中,并且由于所有的影响,你无法或只是不想改变它。或者你从外部得到奇怪的东西,比如 Excel 工作表,你必须导入其中,人们可以自由地用一些野生文本命名他们的列标题。