在Excel VBA中使用SQL到底优势在哪儿

  小爬在之前的博文中多次提到,可以在VBA中写SQL来操作Excel文件,实现各类数据处理和分析需求。那么,你可能有这样的疑问:Excel原生的VBA,数据透视表,数据分析功能不够吗,为啥一定要用SQL来实现呢?在Excel VBA中使用SQL到底优势在哪儿?今天小爬就来带大家好好捋一捋这些问题。

  首先,在VBA中写SQL,这基本算VBA的高阶功能,不是所有人都掌握,因此,上述的场景除了SQL,确实都有其它技术方案。其次,如果我们对别的技术运用的更熟练,我们完全可以用自己最擅长的技术栈来解决问题。以结果论来说,只要能解决实际问题的方案,都是好方案。

  不过,小爬这里想讨论的是,假如我们对VBA原生方法,数据透视表,SQL等知识都很熟悉的前提下,我们该如何选择技术栈来解决实际问题呢,这个时候,SQL的优势在于什么?我认为有以下几点:

一、执行效率最高

跟VBA原生方法和Excel公式比较起来,SQL执行时速度要快很多。当表格数量超过一万行时,这种优势越发明显。数据量越大,越适合用SQL来解决问题,越发高效;

二、代码极为简洁

①、数据匹配场景:我们如果有多个字段要匹配时,原则上需要多个Vlookup或者VBA中建立多个字典,但是,如果我们用SQL的外连接,可能一行代码足够了;

②、数据分组聚合场景:如果我们在VBA中录制宏生成数据透视表代码,来分组聚合求最大值,EXCEL后台会自动生成大量代码,这些代码可读性极差,可如果我们使用SQL Group By,结合max,min,average等聚合函数,我们也只用一行SQL语句即可。

三、实现较为简单

  有一些特殊场景,使用原生VBA方法将极为复杂,但是使用SQL可能就一行代码。以我之前一篇博文VBA如何实现筛选条件之“排除某些值”举例,这个场景,那篇文章用到了很大篇幅来使用纯VBA技巧实现这样一个看似很简单的需求,单的一个“筛选——不包含某些值”的VBA场景,我们需要用到录制宏功能,一维数组、二维数组功能、数组的转置方法、字典的remove方法、字典键快速存入数组方法等。但是其实,如果我们使用SQL,需要用到的知识点就少多了。

  我们重新回顾下这个场景,【源数据】表含有【姓名】【通讯地址】【邮政编码】三列,【例外清单】表中存储着待排除在外的姓名,【结果】表要求返回【源数据】表中不包含【例外清单】姓名的所有数据。我们使用Python faker库生成20000行数据,方便比对大样本时使用不同方法效率上的差异。

 

 

   如果用SQL,本质上我们需要将【源数据】表左外连接(Left Join)【例外清单】表,基于【姓名】列,再加上where条件即可。

 

 

具体代码如下:

 1 Sub myQuery()
 2   Dim conn As Object, rs As Object, sht1 As Worksheet, sht2 As Worksheet, sht3 As Worksheet, sql As String, startTime As Date, endTime As Date
 3   startTime = Timer
 4   Set conn = CreateObject("ADODB.Connection")
 5   Set rs = CreateObject("ADODB.recordset")
 6   Set sht1 = ThisWorkbook.Sheets("源数据")
 7   Set sht2 = ThisWorkbook.Sheets("例外清单")
 8   Set sht3 = ThisWorkbook.Sheets("结果")
 9   conn.Open "provider=Microsoft.ACE.OLEDB.12.0;extended properties=excel 12.0;data source=" & ThisWorkbook.FullName
10   sql = "SELECT a.* FROM [源数据$]a LEFT JOIN [例外清单$]b ON a.姓名=b.姓名 WHERE b.姓名 IS NULL"
11   Set rs = conn.Execute(sql)
12   For i = 0 To rs.Fields.Count - 1 '输出recordset字段名到【结果】表
13     sht3.Cells(1, i + 1) = rs.Fields(i).Name
14   Next
15   sht3.Cells(2, 1).CopyFromRecordset rs '输出recordset结果到【结果】表
16   conn.Close
17   Set conn = Nothing
18   endTime = Timer
19   sht3.Activate
20   MsgBox "累计运行" & (endTime - startTime) & ""
21 
22 End Sub

运行结果如下所示,耗时约0.63秒:

 

我们再回顾下使用VBA字典来实现这一需求的方法,代码如下:

 1 Sub dictWay()
 2   Dim conn As Object, rs As Object, sht1 As Worksheet, sht2 As Worksheet, sht3 As Worksheet, sql As String, startTime As Date, endTime As Date, maxRow1 As Integer, myDic As Object, maxRow2 As Integer
 3   startTime = Timer
 4   Application.ScreenUpdating = False
 5   Set sht1 = ThisWorkbook.Sheets("源数据")
 6   Set sht2 = ThisWorkbook.Sheets("例外清单")
 7   Set sht3 = ThisWorkbook.Sheets("结果")
 8   Set myDic = CreateObject("scripting.dictionary")
 9   maxRow1 = sht1.Cells(Rows.Count, 1).End(xlUp).Row
10   maxRow2 = sht2.Cells(Rows.Count, 1).End(xlUp).Row
11   Dim i As Integer, j As Integer, k As Integer
12   For i = 2 To maxRow2
13     myDic.Add sht2.Cells(i, 1).Value, ""
14   Next
15   
16   k = 1
17   For i = 1 To maxRow1
18     If myDic.exists(sht1.Cells(i, 1).Value) = False Then
19         For j = 1 To 3
20             sht3.Cells(k, j).Value = sht1.Cells(i, j).Value
21         Next
22         k = k + 1
23     End If
24   
25   Next
26 
27   endTime = Timer
28   sht3.Activate
29   Application.ScreenUpdating = True
30   MsgBox "累计运行" & (endTime - startTime) & ""
31 
32 End Sub

为了让该方法尽可能提速,我们使用了字典,同时关掉了Excel的屏幕刷新。最终20000行数据时,耗时约4秒:

 

可以看到,与之前VBA的方法比起来,只需要一行SQL语句,逻辑清晰易懂,代码简洁明了,程序运行耗时是传统方法的1/6不到,这就是小爬力推在VBA中使用SQL的主要原因~~

欢迎扫码关注我的公众号 获取更多爬虫、数据分析的知识!