【问题标题】:Improve SQL query in Excel VBA改进 Excel VBA 中的 SQL 查询
【发布时间】: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 查询中创建一个表并加入它,而不是为每个代理重复该查询。

标签: sql excel vba


【解决方案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,甚至LockTypeadLockOptimistic 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 秒。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2021-05-22
    • 2023-03-17
    • 1970-01-01
    • 2011-09-16
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多