每周,我的分析师都会有一张发票的电子表格,他们需要使用支票号码和支票日期进行更新。检查表存在于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需要在实际业务流程中不存在的可重复性。
答案 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作业。