VBA DAO 直通查询意外行为
Posted
技术标签:
【中文标题】VBA DAO 直通查询意外行为【英文标题】:DAO passthrough query using VBA: "Error 3131 Syntax error in from clause" 【发布时间】:2022-01-24 00:50:38 【问题描述】:我刚刚复制粘贴在 *** 中找到的解决方案,做了一些调整以适应我的需要,但......它不起作用:| 代码如下:
Public Function getAssortmentTypes(Optional personId As Variant) As DAO.Recordset 'personId is integer
Dim strQuery As String
Dim qdf As DAO.QueryDef
Dim rst As DAO.Recordset
If IsMissing(personId) Then
strQuery = "SELECT assortment_type.type_id, assortment_type.type_name AS qryTest FROM assortment_type"
Else
strQuery = "SELECT * FROM get_non_deleted_assortment_types_by_user(" & personId & ")"
End If
Set qdf = CurrentDb.CreateQueryDef("")
With qdf
.SQL = strQuery
.Connect = getDBConnectionString
.ReturnsRecords = True
End With
Set rst = qdf.OpenRecordset
Debug.Print rst!qryTest
Set getAssortmentTypes = rst
End Function
在我的 postgresql 数据库中,我确实有工作功能和适当的表。我已经用 DBEaver 测试了 sql 查询并且它们有效。当我调用没有参数的函数时,我只收到一行(应该是大约 30 行)。使用参数,我期望过滤结果集,但收到“来自子句中的错误 3131 语法错误”。 有人可以帮我吗? TY
【问题讨论】:
您正在使用 DAO 从 MS Access 的 VBA 宏中查询 PostgreSQL 数据库? 哇(" & personId & ")"
sql 注入问题仅适用于用户输入——如果 personID 是用户输入,那么建议很好——但我们没有理由怀疑甚至暗示 personID 是用户输入的结果。很可能 personID 是来自其他代码的 PK 记录 ID - 不是基于用户输入,而是基于记录的 PK 行 - 因此该代码很好。
顺便说一下,使用DAO(即ODBC)是推荐使用的技术堆栈。 OLEdb 是一种仅限 Windows 的技术——而 ODBC 和 DAO 是前进的道路,因为其他平台不再支持 ADO。事实上,即使是微软 10 年前也推荐过这个。然而,在 2018 年,他们推翻了这一决定?使用 ODBC 堆栈是推荐的选择 - 因为这是一个行业标准 - 而 ADO 不是。 (不要与 ado.net 混淆)。
@Dai - 是的,你是对的。好吧,MS Access 根本不是安全重点,正如 Albert 提到的,我正在使用用户输入未提供的 TempVars 存储的 var。本例中可以使用 DAO。也许在一些更复杂的情况下它无法工作,但这里没关系。
【参考方案1】:
始终设置连接字符串在设置 SQL 之前。
当您设置 SQL 时,DAO 不知道这稍后会成为直通查询,因此它会尝试将其解析为 Access SQL,但显然失败了,因为它不是有效的 Access SQL。
只需更改顺序:
With qdf
.Connect = getDBConnectionString
.ReturnsRecords = True
.SQL = strQuery
End With
请注意,您应该是using parameters,并且通常在处理外部数据源时使用 ADO 而不是 DAO。 DAO 非常适合 Access,但提供的外部数据源功能较少。例如,ADO 不会在实际需要之前尝试解析 SQL 字符串。
【讨论】:
【参考方案2】:放弃对 DAO 和 QueryDefs 的需求,并使用带有命令参数化的 ADO,然后可以将其绑定到记录集:
' SET REFERENCE TO Microsoft ActiveX Data Object #.# Library
Public Function getAssortmentTypes(Optional personId As Variant) As ADODB.Recordset
Dim conn As ADODB.Connection
Dim rst As ADODB.Recordset
Dim cmd As ADODB.Command
Set conn As New ADODB.Connection
conn.Open getDBConnectionString
' PREPARED STATEMENT WITH QMARKS ?
If IsMissing(personId) Then
strQuery = "SELECT assortment_type.type_id, assortment_type.type_name AS qryTest FROM assortment_type"
Else
strQuery = "SELECT * FROM get_non_deleted_assortment_types_by_user(?)"
End If
Set cmd = New ADODB.Command
With cmd
.ActiveConnection = conn
.CommandText = strQuery
.CommandType = adCmdText
' BIND PARAMETER
.Parameters.Append .CreateParameter("user_param", adInteger, adParamInput, , personId)
' EXECUTE QUERY AND BIND INTO RECORDSET
Set rst = .Execute
End With
Set cmd = Nothing
Set getAssortmentTypes = rst
End Function
【讨论】:
【参考方案3】:我不建议引入 ADO。
问题看起来是您的第一个 SQL 查询将(并且确实)作为链接表工作,因此可以工作,因为它不是传递查询。
第二个 sql 失败,因为它仍然尝试使用“访问”sql,而不是 postgresSQL 语法。
我建议您创建一个 PT 查询(使用 Access UI)。在设计器中,确保选择 pass-though:
因此,就像链接表一样 - 将连接字符串放入该 PT 查询中。
不要在代码中放置或尝试放置连接字符串。因此,您的重新链接例程也可以包括重新链接 PT 查询。
您现在可以使用此代码:
Public Function getAssortmentTypes(Optional personId As Variant) As DAO.Recordset 'personId is integer
Dim rst As DAO.Recordset
Dim strQuery As String
If IsMissing(personId) Then
strQuery = "SELECT assortment_type.type_id, assortment_type.type_name AS qryTest FROM assortment_type"
Else
strQuery = "SELECT * FROM get_non_deleted_assortment_types_by_user(" & personId & ")"
End If
With CurrentDb.QueryDefs("qryPT")
.SQL = strQuery
Set rst = .OpenRecordset
End With
Debug.Print rst!qryTest
Set getAssortmentTypes = rst
End Function
因此,创建一个名为(对于本例)qryPT 的 PT 查询
【讨论】:
这是一个非常“MS Access”特定的方法;)我不喜欢在我的程序中创建不必要的对象而且我很固执,所以我决定采用“仅代码”解决方案。请参阅下面 Eric 的回答,它解决了我的问题。 我没有在代码中创建对象 - 所以不在代码中创建对象会建议我发布的解决方案,对吗?如您所见,我什至不必在代码中触摸、编写或设置连接。 Eric 的优秀解决方案在代码中创建了一个 querydef 对象(你说你不喜欢它),而且你还必须在代码中建立一个连接——再次在代码中创建更多的东西。奇怪的是,您建议您不想在代码中创建对象,然后转身采用一种解决方案,在该解决方案中,您既可以创建查询对象,又可以在代码中设置连接,而我的解决方案两者都没有?【参考方案4】:尝试省略“DAO”。 Recordset 和维度语句中的前缀。更高版本的 Access 了解您想要什么。
【讨论】:
以上是关于VBA DAO 直通查询意外行为的主要内容,如果未能解决你的问题,请参考以下文章