从0学Excel VBA编程 · 番外篇8:SQL 跨文件/跨表 JOIN——多表关联,一行搞定
学习目标
搞懂 SQL 的 JOIN 是干什么的(按"关键列"把两张表拼成一张宽表) 掌握同工作簿跨表 JOIN、跨文件 UNION ALL、跨文件 JOIN 三种写法 通过 3 个实战案例,用 JOIN 替代 VLookup 循环、用 UNION 替代多文件拷贝合并
知识点精讲
番外篇 7 我们用 SQL 在一张表里筛选、汇总。实际工作中数据常分散在多张表、甚至多个文件:比如"销售表"只有产品 ID 和数量,"产品表"才有产品名称和单价。要把它们拼到一起算金额,用 VLookup 循环又慢又啰嗦;SQL 的 JOIN 一行就能把两表按"关键列"对齐合并。
三个必须知道的关键点:
① JOIN 是什么:用两张表共有的列(如 产品ID)当"胶水",把对应行拼成一行。INNER JOIN(内连接)只保留两边都能对上的;LEFT JOIN(左连接)保留左表全部、右表对不上的留空。本篇用最常用、最稳的INNER JOIN。② 同文件 vs 跨文件:同一工作簿里多张表,直接写 [表1$] a INNER JOIN [表2$] b ON …;跨文件时,被引用的那个文件要用"外部表"语法写在方括号里:[Excel 12.0;DATABASE=完整路径.xlsx;HDR=Yes;IMEX=1].[Sheet$]。连接串仍连当前工作簿即可,SQL 里的外部表会自己去找那个文件。③ 多文件合并用 UNION ALL:结构相同的多个分店文件,用 SELECT … FROM 文件A UNION ALL SELECT … FROM 文件B首尾相接合成一张大表(相当于番外篇 3 的 FSO 合并,但一句 SQL 完事),外层再GROUP BY汇总。
坑提醒:第一,运行前相关文件要存在、路径写对;跨文件查询不需要"先合并",但被查文件不能被别人独占打开。第二,外部表语法里
DATABASE=后面是完整绝对路径,路径里尽量别出现]这类会破坏方括号的字符。第三,连接串里的运行SQL公共函数沿用番外篇 7,案例 2、3 直接复用,不用再写一遍。
3 个实战案例
通用准备:本工作簿里案例 1 需要"销售"表和"产品"表(都有
产品ID列);案例 2、3 需要在工作簿同目录(或待合并\)放好对应分店/销售/产品 Excel。**运行前先保存本工作簿**(ADO 读磁盘)。下面案例 1 给出公共函数运行SQL,案例 2、3 直接复用它(同一标准模块即可)。
案例 1(简单):同文件跨表 JOIN,算每笔销售金额(替代 VLookup)
功能说明:用 INNER JOIN 把"销售"表(产品ID、数量)和"产品"表(产品ID、产品名称、单价)按 产品ID 拼起来,直接算出 数量×单价 的金额,写进"销售明细"表。比一行行 VLookup 再乘清爽太多。
操作步骤:
插入「标准模块」,先粘贴下面的 运行SQL公共函数(案例 2、3 复用)。再粘贴 案例1_跨表JOIN,确保本工作簿有"销售""产品"两张表,运行它。多出"销售明细"表:产品ID、产品名称、数量、单价、金额。
' ===== 公共函数:执行 SQL,把结果写进目标表(沿用番外篇7) =====Function 运行SQL(SQL语句 As String, 目标表 As Worksheet) As Long Dim conn As Object, rs As Object Dim connStr As String, i As Long connStr = "Provider=Microsoft.ACE.OLEDB.12.0;" & _ "Data Source=" & ThisWorkbook.FullName & ";" & _ "Extended Properties=""Excel 12.0;HDR=Yes;IMEX=1"";" Set conn = CreateObject("ADODB.Connection") conn.Open connStr Set rs = CreateObject("ADODB.Recordset") rs.Open SQL语句, conn 目标表.Cells.Clear For i = 0 To rs.Fields.Count - 1 目标表.Cells(1, i + 1).Value = rs.Fields(i).Name Next i 目标表.Range("A2").CopyFromRecordset rs 运行SQL = rs.RecordCount rs.Close conn.CloseEnd FunctionSub 案例1_跨表JOIN() Dim 结果表 As Worksheet On Error Resume Next Set 结果表 = ThisWorkbook.Sheets("销售明细") On Error GoTo 0 If 结果表 Is Nothing Then Set 结果表 = ThisWorkbook.Sheets.Add 结果表.Name = "销售明细" End If Dim SQL As String SQL = "SELECT a.[产品ID], b.[产品名称], a.[数量], b.[单价], " & _ "a.[数量]*b.[单价] AS 金额 " & _ "FROM [销售$] a INNER JOIN [产品$] b ON a.[产品ID]=b.[产品ID]" 运行SQL SQL, 结果表 MsgBox "跨表 JOIN 完成,已算出金额。", vbInformationEnd Sub要点:
a和b是两张表的别名,用来区分两边同名的产品ID;ON a.[产品ID]=b.[产品ID]就是"胶水列";a.[数量]*b.[单价] AS 金额在拼表的同时直接算金额。这就是一行替代"VLookup 找名称 + 乘单价"的完整循环。
案例 2(中等):跨文件 UNION ALL 合并多个分店,再汇总
功能说明:番外篇 3 我们用 FSO 一个一个打开文件再拷贝合并。这里用 UNION ALL 把"待合并\分店A.xlsx""分店B.xlsx"两个结构相同的文件直接首尾拼接成"合并"表,再 GROUP BY 汇总成"汇总"表——两步都是一句 SQL。
操作步骤:
运行SQL函数已在案例 1 就位。在工作簿目录建 待合并\文件夹,放入分店A.xlsx、分店B.xlsx(第 1 列产品、第 3 列销量)。粘贴 案例2_跨文件合并,运行它。先看"合并"表(两店拼一起),再看"汇总"表(按产品合计)。
Sub 案例2_跨文件合并() Dim 合并表 As Worksheet, 汇总表 As Worksheet Dim 路径 As String, SQL As String, SQL2 As String 路径 = ThisWorkbook.Path & "\待合并\" On Error Resume Next Set 合并表 = ThisWorkbook.Sheets("合并") Set 汇总表 = ThisWorkbook.Sheets("汇总") On Error GoTo 0 If 合并表 Is Nothing Then Set 合并表 = ThisWorkbook.Sheets.Add: 合并表.Name = "合并" End If If 汇总表 Is Nothing Then Set 汇总表 = ThisWorkbook.Sheets.Add: 汇总表.Name = "汇总" End If ' 第一步:UNION ALL 把两个文件拼成一张大表 SQL = "SELECT [产品],[销量] FROM " & _ "[Excel 12.0;DATABASE=" & 路径 & "分店A.xlsx;HDR=Yes;IMEX=1].[Sheet1$] " & _ "UNION ALL " & _ "SELECT [产品],[销量] FROM " & _ "[Excel 12.0;DATABASE=" & 路径 & "分店B.xlsx;HDR=Yes;IMEX=1].[Sheet1$]" 运行SQL SQL, 合并表 ' 第二步:对"合并"表按产品汇总(UNION 出来的表也能直接查) SQL2 = "SELECT [产品], SUM([销量]) AS 销量合计 FROM [合并$] GROUP BY [产品]" 运行SQL SQL2, 汇总表 MsgBox "跨文件 UNION 合并并汇总完成。", vbInformationEnd Sub要点:外部表语法
[Excel 12.0;DATABASE=路径;HDR=Yes;IMEX=1].[Sheet1$]让 SQL 直接读另一个文件,无需先打开;UNION ALL把两店数据首尾相接(用UNION不带 ALL 会自动去重,合并明细别用)。合并后的"合并"表就是普通工作表,第二步照常GROUP BY汇总。要加更多分店,照着再UNION ALL一段即可。
案例 3(实用小案例):跨文件 JOIN,直接关联两个独立文件统计
功能说明:销售记录在一个文件 销售.xlsx、产品信息在另一个文件 产品.xlsx。不用先合并,直接用跨文件 INNER JOIN 把两文件按 产品ID 关联,按产品名称汇总销售额,写进"销售统计"表。
操作步骤:
在工作簿同目录放好 销售.xlsx(含销售$:产品ID、数量)和产品.xlsx(含产品$:产品ID、产品名称、单价)。运行SQL函数仍在。粘贴案例3_跨文件JOIN,运行它。"销售统计"表输出:每个产品名称的销售额合计(数量×单价求和)。
Sub 案例3_跨文件JOIN() Dim 结果表 As Worksheet Dim 路径 As String, SQL As String 路径 = ThisWorkbook.Path & "\" On Error Resume Next Set 结果表 = ThisWorkbook.Sheets("销售统计") On Error GoTo 0 If 结果表 Is Nothing Then Set 结果表 = ThisWorkbook.Sheets.Add 结果表.Name = "销售统计" End If ' 直接关联两个不同文件,按产品名称汇总销售额 SQL = "SELECT b.[产品名称], SUM(a.[数量]*b.[单价]) AS 销售额 " & _ "FROM [Excel 12.0;DATABASE=" & 路径 & "销售.xlsx;HDR=Yes;IMEX=1].[销售$] a " & _ "INNER JOIN [Excel 12.0;DATABASE=" & 路径 & "产品.xlsx;HDR=Yes;IMEX=1].[产品$] b " & _ "ON a.[产品ID]=b.[产品ID] " & _ "GROUP BY b.[产品名称]" 运行SQL SQL, 结果表 MsgBox "跨文件 JOIN 统计完成。", vbInformationEnd Sub要点:两个文件各用一段"外部表"语法当
a、b,ON a.[产品ID]=b.[产品ID]跨文件对齐,SUM(a.[数量]*b.[单价])边关联边汇总。这一句顶了"打开文件→VLookup→循环乘→汇总"一长串代码,而且数据分散在不同文件也照样查。
本篇小结
JOIN = 按关键列拼表: INNER JOIN … ON 关键列相等,一行替代 VLookup 循环;LEFT JOIN可保留左表全部。同文件直接 JOIN: [表A$] a INNER JOIN [表B$] b ON …,别名a/b区分同名列。跨文件两种姿势: UNION ALL把多文件结构相同的数据首尾合并(替代 FSO 拷贝);外部表语法[Excel 12.0;DATABASE=路径;HDR=Yes;IMEX=1].[Sheet$]让 SQL 直接读别的文件做 JOIN/汇总。公共函数复用: 运行SQL一次封装(连当前簿 + 执行 + 写回),跨文件时 SQL 用外部表语法即可,连接串不用改。三道铁律: DATABASE=写完整绝对路径且路径避开];被查文件存在且未被独占打开;运行前先保存本工作簿。
下一篇内容预告
本篇是《从0学Excel VBA编程》番外篇8(SQL 跨文件/跨表 JOIN),属可选进阶补充。若继续深造,可选:
番外篇9:连接外部数据库(Access / CSV / 甚至 SQL Server),用 VBA 直接查公司数据库做更重分析; 或到此为止——主系列 20 篇 + 番外篇 1~8 + 收尾篇 已覆盖从 0 到自动化工具的完整路径。 需要哪个,告诉我就行。
夜雨聆风