2015-05-03 37 views
0

我的分析师每周都有一张发票的电子表格,他们需要使用支票号码和支票日期进行更新。检查表存在于SQL服务器中。什么是从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查找并将结果写入一行。有没有办法做一个连接的等价物并将整个结果集返回到单个记录集中?

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

Excel来源列可以是数字或文本。 SQL表中的“匹配”列将是相应的数据类型,即varchar或integer。数据正常化,索引等。更新通常会影响几百或几千行,但有时会有多达二万到三万。

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

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

+1

您的问题是个别记录的重复调用。将所有记录一次拉入变体数组,并处理来自内存块的工作表。 – Jeeped

+0

这就是我正在寻找的解决方案 - 批处理,但我不知道如何将数千个请求发送到批处理中的SQL服务器。 – DataWriter

+0

在提供答案之前,您必须提供更多具体细节。是否处理了工作表中的所有发票号码或者是否有子集?发票号码在哪一栏?他们是数字还是字母数字?您需要根据发票编号更新信息还有哪些其他字段?典型更新有多少条记录.. 1K,10K,100K?我可以继续,但你可以看到你目前的问题缺乏很多重要的因素。 – Jeeped

回答

1

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

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

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 
+0

谢谢,这是一个绝对可以使用的好主意!不幸的是,在这种情况下,我的支票和采购单表格有数百万行:( – DataWriter

+0

嗯,对于那么大的一个数据集来说有点棘手),你可以尝试去适应这种情况,以批量生成25-50个物品,动态SQL'IN'子句试图限制返回的行数我会尝试对我的答案进行编辑演示 – Fink

+0

谢谢,我真的很喜欢你的方法,我一直在考虑如何批量和如果我可以在电子表格中使用日期,供应商等,我可以肯定地从检查表中取回更小的记录集,如果可以的话,我会在今天晚些时候对它进行实验, – DataWriter

0

这样做的最好方法是使用SSIS并将信息(通过SSIS)插入到电子表格的范围中。请记住,SSIS预计目标范围是空的,并且目标范围之上的一行也应该是空的。如果你这样做,你可以通过Windows调度程序来安排SSIS作业。

+0

如何将Excel电子表格“加入”SQL表格并将发票与其支票进行匹配? – DataWriter

+0

SSIS可以使用数据流任务将现有的电子表格导入到表格中。 –

相关问题