Excel VBA中寫SQL,這些問題的方法你一定要牢記 Excel VBA中寫SQL,這些問題你一定為此頭痛過
小爬之前的文章 【Excel VBA中寫SQL,這些問題你一定為此頭痛過】中詳細講訴了一些常見的VBA 中使用SQL遇到的問題,這裡再補充兩個常見的問題場景及對應的解決方案,希望你們看了後能夠思路開闊些,少走些彎路。
一、資料來源Excel檔案的工作表有些列的列名相同
我們知道,在商業資料庫中建立表時,同一個表名下是不允許建立相同的欄位的,且欄位名要遵從一定規範。但在Excel檔案中則無此限制,同一個工作表下,支援多列的列名相同。那麼當我們用SQL來操作這類頭疼的Excel檔案時,該如何區分不同欄位呢?
假設小爬有這樣一個Excel表(資料樣本通過python的第三方庫faker來生成),【源資料】表中,A列和C列的列名都為【公司】,但其實一個指代【公司名稱】,另一個指代【公司程式碼】,這樣不規範的excel模板例子在現實工作中很常見。我們該如何區分它倆呢?
小爬試著先輸出recordSet中所有欄位名,看VBA的sql引擎是如何貼心處理這個問題的,示例程式碼如下:
1 Sub myQuery() 2 Dim conn As Object, rs As Object, rs1 As Object, sht1 As Worksheet, sht2 As Worksheet, sql As String 3 Set conn = CreateObject("ADODB.Connection") 4 Set rs = CreateObject("ADODB.recordset") 5 Set sht1 = ThisWorkbook.Sheets("源資料") 6 Set sht2 = ThisWorkbook.Sheets("結果") 7 conn.Open "provider=Microsoft.ACE.OLEDB.12.0;extended properties=excel 12.0;data source=" & ThisWorkbook.FullName 8 sql = "SELECT * FROM [源資料$]" 9 Set rs = conn.Execute(sql) 10 For i = 0 To rs.Fields.Count - 1 '輸出recordset欄位名到【結果】表 11 sht2.Cells(1, i + 1) = rs.Fields(i).Name 12 Next 13 sht2.Cells(2, 1).CopyFromRecordset rs '輸出recordset結果到【結果】表 14 conn.Close 15 Set conn = Nothing 16 17 End Sub
輸出的結果如下圖所示:
可以看到,重名後的列名被sql解析成欄位名後,預設跟上阿拉伯數字1,2,3……知道了sql 引擎的解析規則,我們就可以直接根據解析後的列名 如【公司】【公司1】來操作不同的欄位了,沒輸出所有欄位名前就可以做到心中有數。
二、Excel“表格”不是真正的表格檔案格式
有的時候,我們從ERP系統匯出的報表Excel檔案,雖然是xls(xlsx)字尾,可出於種種原因,他們並非真正的Excel表格格式,可能底層依然是txt檔案,小爬在工作中就沒少遇到過這種奇葩問題。
解決思路如下:先利用sql的方法獲取當前資料庫的所有表名,如果表名是亂碼,如("?????"),則該Excel檔案可能底層是txt檔案,至少不是規範格式的Excel檔案。此時,我們可以用VBA原生的workbooks.open方法來顯式開啟該工作簿,自動儲存,然後用SQL引擎來重新連線該工作簿即可。示例程式碼如下:
Const adSchemaTables = 20 '這句很重要,一定要提前定義adSchemaTables常量的值 Sub myQuery() Dim conn As Object, rs As Object, rs1 As Object, sht1 As Worksheet, sht2 As Worksheet, sql As String, sourceFileName As String Set conn = CreateObject("ADODB.Connection") Set rs = CreateObject("ADODB.recordset") sourceFileName = ThisWorkbook.Path & "\資料來源\" & "測試.xls" conn.Open "provider=Microsoft.ACE.OLEDB.12.0;extended properties=excel 12.0;data source=" & sourceFileName Set rs = conn.openschema(adSchemaTables) TableName = rs.Fields(2).Value If TableName <> "源資料" Then '假定當工作簿格式規範時,工作表名為【源資料】 conn.Close Set wb = Workbooks.Open(sourceFileName) wb.Save wb.Close conn.Open "provider=Microsoft.Ace.oledb.12.0;Extended Properties=Excel 12.0;data source=" & sourceFileName Set rs = conn.openschema(adSchemaTables) TableName = rs.Fields(2).Value End If End Sub
這裡麵包含兩個技巧:
1、當小爬用wb.save時,Excel會自動將不規範的xls檔案(本質是txt)儲存為規範的xls檔案;
2、利用conn.openschema(adSchemaTables)輸出該資料庫下所有的表名,程式碼如下:
CONN.Open "provider=Microsoft.Ace.oledb.12.0;Extended Properties=Excel 12.0;data source=" & sourceFullName Set rs = CONN.openschema(adSchemaTables) Do While Not rs.EOF tableName = rs.Fields(2).Value Debug.Print rs.Fields(2).Value '表名,對於Excel中的表或(工作表名)後面會自動加一個$ rs.MoveNext Loop
至於上面的例子中,為啥不每次預設用VBA語法開啟某個工作簿,再儲存為xls檔案,再用CONN來連線,自然是為了改善指令碼的效能,畢竟workbooks.open相比較於CONN來連線表格,速度太慢了。
歡迎掃碼關注我的公眾號 獲取更多爬蟲、資料分析的知識!