改进 Excel VBA 中的 SQL 查询

Posted

技术标签:

【中文标题】改进 Excel VBA 中的 SQL 查询【英文标题】:Improve SQL query in Excel VBA 【发布时间】:2019-07-24 15:02:49 【问题描述】:

我在 Excel 工作簿中有 3 个可以使用 SQL 访问的表。 Inscriptions 表包含 AGENT_IDMLS_IDPHOTOS 表包含 MLS_ID 的最近提要中的所有照片,PHOTOS_CURRENT 包含当前系统中的所有照片MLS_ID. 目标是查找新提要中是否有当前不在系统中的照片。

我尝试使用NOT EXISTSNOT IN 方法进行查询。两者都需要很长时间才能运行(有时每个 AGENT_ID 需要 2 分钟)。

NOT EXISTS 方法:

sqlQuery = "SELECT DISTINCT INSCR.MLS_ID FROM [INSCRIPTIONS_CURRENT$] INSCR, [PHOTOS$] P1 " & _
                "WHERE INSCR.AGENT_ID = " & inpAgentId & _
                " AND INSCR.MLS_ID = P1.MLS_ID AND NOT exists (select 1 from [PHOTOS_CURRENT$] PC1 where PC1.MLS_ID = P1.MLS_ID and PC1.PHOTO_ID = P1.PHOTO_ID)"

NOT IN 方法:

sqlQuery = "SELECT DISTINCT INSCR.MLS_ID FROM [INSCRIPTIONS_CURRENT$] INSCR, [PHOTOS$] P1 " & _
                "WHERE INSCR.AGENT_ID = " & inpAgentId & _
                " AND INSCR.MLS_ID = P1.MLS_ID AND INSCR.MLS_ID NOT IN (select MLS_ID from [PHOTOS_CURRENT$] PC1 where PC1.MLS_ID = P1.MLS_ID and PC1.PHOTO_ID = P1.PHOTO_ID)"

数据库连接如下:

Sub Connect()

    Set objConnection = CreateObject("ADODB.Connection")
    objConnection.CommandTimeout = 120

End Sub

查询发送到程序处理如下:

Function select_query(sqlQuery As String) As ADODB.Recordset

    Dim objRecordset As ADODB.Recordset

    Const adOpenStatic = 3
    Const adLockOptimistic = 3
    Const adCmdText = &H1

    Set objRecordset = CreateObject("ADODB.Recordset")

    objConnection.Open "Provider=Microsoft.Jet.OLEDB.4.0;" & _
    "Data Source=" & ThisWorkbook.FullName & _
    ";Extended Properties=""Excel 8.0;HDR=Yes;IMEX=1"";"

    objRecordset.Open sqlQuery, objConnection, adOpenStatic, adLockOptimistic, 
    adCmdText

    Set select_query = objRecordset

End Function

有什么提高性能的建议吗?

【问题讨论】:

桌子有多大? @TimWilliams PHOTOS 和 PHOTOS_CURRENT 中约有 20,000 条记录,INSCRIPTIONS 中约有 2,000 条记录 请提供更完整的代码块而不是行 sn-ps 以便我们可以看到整个过程。否则,请记住 excel is not a database,因此请使用与其兄弟 ms access 类似的实际名称,它可以索引字段以加快表扫描! @Parfait 在我已经提供的内容中添加任何其他内容没有多大意义。如果查询找到任何记录,则将它们插入输出数组并发送回调用例程。我知道 Excel 不是一个真正的数据库,我在公司强加给我的限制条件下工作。这个 Excel 有大约 20 个工作表,用作数据库,取它或留下它。我向公司提出了改进建议,但当他们做出正确的决定时,我必须让这段代码工作。 如果您在循环中运行它,您似乎可以从 NOT EXISTS 查询中创建一个表并加入它,而不是为每个代理重复该查询。 【参考方案1】:

考虑以下可能有帮助的提示:

Explicit JOIN:现在您正在运行过时的隐式连接,它与 WHERE 子句中的 ID 匹配,而不是显式 JOIN 子句的当前标准。在大多数数据库引擎中,这不应该改变性能,但轶事证据表明它可以:

SELECT DISTINCT INSCR.MLS_ID 
FROM [INSCRIPTIONS_CURRENT$] INSCR
INNER JOIN [PHOTOS$] P1 ON INSCR.MLS_ID = P1.MLS_ID 
WHERE INSCR.AGENT_ID = " & inpAgentId & _
  AND NOT EXISTS (select 1 from [PHOTOS_CURRENT$] PC1  
                  where PC1.MLS_ID = P1.MLS_ID and PC1.PHOTO_ID = P1.PHOTO_ID)

GROUP BY vs DISTINCT:这是 SQL 中的常规辩论,不同的数据库引擎以不同的方式处理非重复查询。从理论上讲,性能应该没有差异,但轶事证据表明并非如此。因此,考虑一个等效的GROUP BY 版本:

SELECT INSCR.MLS_ID 
FROM [INSCRIPTIONS_CURRENT$] INSCR
INNER JOIN [PHOTOS$] P1 ON INSCR.MLS_ID = P1.MLS_ID 
WHERE INSCR.AGENT_ID = " & inpAgentId & _
  AND NOT EXISTS (select 1 from [PHOTOS_CURRENT$] PC1  
                  where PC1.MLS_ID = P1.MLS_ID and PC1.PHOTO_ID = P1.PHOTO_ID)
GROUP BY INSCR.MLS_ID 

DAO 连接:由于查询工作簿使用 JET/ACE SQL 引擎,因此将 DAO 视为可以利用该引擎的许多优点的特定接口,而不是 ADO 跨任何数据源的更通用的接口( Oracle、SQL Server、Postgres 等)。

' ADD REFERENCE: Microsoft Office #.# Access Database Engine Object Library
Dim conn As New DAO.DBEngine, db As DAO.Database, qdef As DAO.QueryDef, rst As DAO.Recordset

Set db = conn.OpenDatabase("C:\Path\To\Workbook.xls", False, True, "Excel 8.0;HDR=Yes;")
Set rst = db.OpenRecordset(sqlQuery)

...    

rst.Close: db.Close
Set rst = Nothing: Set db = Nothing: Set conn = Nothing

OLEDB (ACE) 连接:考虑更新的 OLEDB 提供程序,它仍然可以与任何版本的 Excel(.xls 或 .xlsx、.xlsb、.xlsm)一起使用。使用此PowerShell script 检查可用的提供商。

objConnection.Open "Provider=Microsoft.ACE.OLEDB.12.0;" ...

objConnection.Open "Provider=Microsoft.ACE.OLEDB.16.0;" ...

ODBC 连接:在传闻证据与理论不同的情况下,连接接口可能会带来不同的查询执行性能。因此,请考虑更换 OLEDB 提供程序以进行 ODBC 驱动程序连接:

' DRIVER VERSION
objConnection.Open "DRIVER=Microsoft Excel Driver (*.xls, *.xlsx, *.xlsm, *.xlsb);" _
                       & "DBQ=C:\Path\To\Excel.xls;"

' DSN VERSION
objConnection.Open "DSN=Excel Files;DBQ=C:\Path\To\Excel.xls;"

光标/锁定类型:试用cursor types,因为性能可能会有所不同,例如adOpenForwardOnlyvs adOpenStatic,甚至LockType 和adLockOptimistic vs adLockReadOnly .

【讨论】:

谢谢@Parfait。如果我有能力改变整个系统,你的建议会有所帮助。不幸的是,正如我所说,我必须使用现有的 Connect-Select-Disconnect 方法,此 Excel 数据库中的数百个查询都使用该方法。完整的回归测试是毫无疑问的。我宁愿等待他们批准迁移到真正的数据库并让它在那里工作。 我不理解您的评论。所有这些解决方案都可以在 Excel 中处理。【参考方案2】:

感谢@TimWilliams,您的评论对解决这个问题很有帮助。 我最终做的是编写一个单独的例程,该例程在提要加载期间创建一个包含所有更改的照片的表格,如下所示:

sqlQuery = "INSERT INTO [PHOTO_UPDATES$] SELECT P1.* " & _
                "FROM [PHOTOS$] P1 LEFT JOIN [PHOTOS_CURRENT$] PC1 " & _
                "ON P1.MLS_ID = PC1.MLS_ID AND P1.PHOTO_ID = PC1.PHOTO_ID WHERE PC1.PHOTO_ID is NULL"

然后,在为每个代理创建工作列表时,将完成以下操作:

sqlQuery = "SELECT DISTINCT INSCR.MLS_ID " & _
                "FROM [PHOTO_UPDATES$] PU1 , [INSCRIPTIONS_CURRENT$] INSCR " & _
                "WHERE INSCR.AGENT_ID = " & inpAgentId & " " & _
                "AND PU1.MLS_ID = INSCR.MLS_ID "

这两个例程的运行时间都不到 1 秒。

【讨论】:

以上是关于改进 Excel VBA 中的 SQL 查询的主要内容,如果未能解决你的问题,请参考以下文章

EXCEL VBA combobox 模糊查询触发后 退格键功能改变

EXCEL VBA combobox 模糊查询触发后 退格键功能改变

更新表时如何改进 Spark 中的 SQL 查询? (子查询中的'NOT IN')

使用 VBA 在 Excel 中的 SQL 表上使用参数化查询

改进 sql 中的文本搜索

通过 Excel VBA 进行 SQL 查询的复杂 JOIN 子句中的语法错误