2015-05-03 39 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可以使用數據流任務將現有的電子表格導入到表格中。 –

相關問題