从SQL Server更新工作表的最快方法是什么

时间:2015-05-03 20:32:20

标签: sql-server excel vba

每周,我的分析师都会有一张发票的电子表格,他们需要使用支票号码和支票日期进行更新。检查表存在于SQL Server中。

我已经写了一个宏来迭代电子表格的每一行,使用如下语句打开一个ADO记录集:

SELECT CheckNumber, CheckDate FROM CHECKS WHERE Invoice_Number = " & cells (i,2)

...然后使用记录集中的字段将数字和日期写入Excel电子表格中该行的前两列。

代码可以接受几百行,但是当有数千行时代码很慢。

更新Excel电子表格的方法是否比使用ADO的逐行查找更快?例如,有没有办法在电子表格和SQL Server中的表之间进行SQL连接?

编辑:回答Jeeped的问题,这里有一点澄清。

我真正想要做的就是找到一种方法来批量生产"使用来自SQL Server的信息更新Excel电子表格,而不是执行SQL查找并一次一行地写入结果。有没有办法做一个等效的连接并在单个记录集中返回整个结果集?

上面的Invoice示例实际上代表了我每天遇到的一类问题。最终用户有一个包含其工作数据(例如发票)的电子表格,他们希望我从SQL服务器表中添加信息。例如,"使用C列中的发票编号,在A列中添加该发票的支票编号,在B"栏中添加检查日期。另一个示例可能是"对于b列中的每个发票,将采购订单编号添加到a列。"

Excel源列可以是数字或文本。 "匹配" SQL表中的列将具有相应的数据类型,varchar或integer。数据被正确地标准化,索引等。更新通常会影响几百或几千行,尽管有时会有多达二十到三万。

如果我能找到批量行的方法,我可能会将其转换为Excel加载项以简化流程。出于这个原因,我希望留在VBA中,因为我的超级用户可以扩展或修改它以满足他们的需求 - 我宁愿不用.NET语言编写它因为那时我们需要专门为开发人员服务时间来修改和部署它。此处不关心Excel应用程序的安全性,因为用户已经可以通过MS Access数据库中的ODBC链接表访问数据,并且我们已在SQL Server上采取了适当的安全预防措施。

将流程移至SSIS需要在实际业务流程中不存在的可重复性。

2 个答案:

答案 0 :(得分:1)

过去,我已成功将所有数据从SQL服务器提取到客户端断开的ADO记录集中。然后我循环遍历整个记录集以创建一个VBA字典,将ID值(在本例中为InvoiceNum)存储为键,将记录集书签存储为对项目。然后使用" Exists"循环检查每个值,根据字典检查发票号。功能。如果找到匹配项,则可以将记录集设置为书签,然后从记录集更新电子表格中的值。假设发票表不是几百万行,这种方法应该证明是快速的。

编辑:添加批处理以尝试限制来自大型数据集的返回记录。 (未经测试的代码示例)

Public Sub UpdateInvoiceData(invoiceNumRng As Range)
'References: Microsoft ActiveX Data Objects x.x
'References: Microsoft Scripting Runtime

    Dim cell As Range
    Dim tempCells As Collection
    Dim sqlRS As ADODB.Recordset
    Dim dict As Scripting.Dictionary
    Dim iCell As Range
    Dim testInvoiceNum As String
    Dim inClause As String
    Dim i As Long

    i = 1

    For Each cell In invoiceNumRng

        If i Mod 25 = 0 Or i = invoiceNumRng.cells.Count Then 'break up loop into batches of 25:: Modify batch size here, try to find an optimal size.

            inClause = CreateInClause(tempCells) 'limit sql query with our test values
            Set sqlRS = GetInvoiceRS(inClause) 'retrieve batch results
            Set dict = CreateInvoiceDict(sqlRS) 'create our lookup dictionary

            For Each iCell In tempCells

                testInvoiceNum = iCell.Value 'get the invoice number to test

                If dict.Exists(testInvoiceNum) Then 'test for match

                    sqlRS.Bookmark = dict.Item(testInvoiceNum) 'move our recordset pointer to the correct item
                    iCell.Offset(0, 1).Value = sqlRS.Fields("CheckNum").Value
                    iCell.Offset(0, 2).Value = sqlRS.Fields("CheckDate").Value

                End If
            Next iCell

            'prepare for next batch of cells
            Set tempCells = Nothing
            Set tempCells = New Collection
        Else

            tempCells.Add cell
        End If

        i = i + 1 'our counter to determine batch size
    Next cell


End Sub

Private Function CreateInClause(cells As Collection) As String

    Dim retStr As String
    Dim tempCell As Range

    retStr = ""

    For Each tempCell In cells
        retStr = retStr & "'" & tempCell.Value & "'" & ", " 'assumes textual value, omit single quotes if numeric/int
    Next tempCell

    If Len(retStr) > 0 Then
        CreateInClause = Left(retStr, Len(retStr) - 2) 'trim off last comma value
    Else
        CreateInClause = "" 'no items
    End If
End Function

Private Function GetInvoiceRS(inClause As String) As ADODB.Recordset
'returns the listing of InvoiceData from SQL

    Dim cn As ADODB.Connection
    Dim rs As ADODB.Recordset
    Dim sql As String

    Set cn = New ADODB.Connection
    cn.ConnectionString = "Your Connection String"

    sql = "SELECT * FROM [Invoices] WHERE InvoiceID IN(" & incluase & ")"

    cn.Open

    rs.CursorLocation = adUseClient 'use clientside cursor since we will want to loop in memory
    rs.CursorType = adOpenDynamic

    rs.Open sql, cn

    Set rs.ActiveConnection = Nothing 'disconnect from connection here

    cn.Close

    Set GetInvoiceRS = rs
End Function

Private Function CreateInvoiceDict(dataRS As ADODB.Recordset) As Dictionary

    Dim dict As Scripting.Dictionary

    Set dict = New Scripting.Dictionary

    If dataRS.BOF And dataRS.EOF Then
        'no data to process
    Else
        dataRS.MoveFirst 'make sure we are on first item in recordset
    End If

    Do While Not dataRS.EOF

        dict.Add CStr(dataRS.Fields("InvoiceNum").Value), dataRS.Bookmark

        dataRS.MoveNext
    Loop

    Set CreateInvoiceDict = dict
End Function

答案 1 :(得分:0)

执行此操作的最佳方法是使用SSIS并将信息(通过SSIS)插入电子表格中的范围。请记住,SSIS期望目标范围为空,并且目标范围之上的一行也应为空。如果这样做,您可以通过Windows计划程序安排SSIS作业。