Excel 多 sheet 跨表 JOIN 怎么做:用 SQL 替代 VLOOKUP / Power Query

· 约 5 分钟 🦆 DuckDB SQL 工作台

Excel 用户每天都在做”两表合并”——订单表 + 客户表,把客户名替换 ID;销售流水 + 商品库,加上分类。Excel 的标配解决方案是 VLOOKUP / INDEX-MATCH / Power Query

  • VLOOKUP 单条匹配可以,但条件超过 1 个、嵌套超过 2 层就开始踩坑——参数顺序记不住,相对引用 / 绝对引用一不小心就错位
  • INDEX-MATCH 比 VLOOKUP 灵活但语法更绕,新手学不会
  • Power Query 能做合并查询但操作链不可读——20 步操作交给同事,他得逐步点开看,改一步可能让下游全断

SQL 是这件事最自然的工具——它就是为”多表关联 + 聚合”设计的语言。问题只是 SQL 一直跟”安装数据库 + Python + pandas”绑定,普通 Excel 用户够不着。

DuckDB SQL 查询台 把这个鸿沟填上了:把 .xlsx 拖到浏览器里,每个 sheet 变成一张 SQL 表,立刻能 JOIN / 聚合 / 透视。底层用 SheetJS(解 xlsx)+ DuckDB-Wasm(OLAP 引擎),都跑在浏览器本地,文件不出电脑。

5 个高频实战场景

1. 把客户 ID 替换成客户名(VLOOKUP 替代)

orders sheet 里只有 customer_id,想让结果直接显示客户名 + 城市——Excel 要写两个 VLOOKUP:

=VLOOKUP(B2, customers!A:E, 2, FALSE)  ' 客户名
=VLOOKUP(B2, customers!A:E, 4, FALSE)  ' 城市

SQL 一行:

SELECT
  o.order_id,
  o.amount,
  c.name        AS customer_name,
  c.city        AS customer_city,
  o.order_date
FROM orders     o
JOIN customers  c ON o.customer_id = c.id
ORDER BY o.order_date DESC
LIMIT 100;

oc 是别名,让后面引用列时不用重复写整个表名。

2. 找漏匹配的订单(LEFT JOIN + IS NULL)

订单里有些 customer_id 在客户主数据里查不到——可能是数据质量问题。Excel 要 IFERROR(VLOOKUP(...), "缺失") 然后筛选;SQL 一句更直接:

SELECT o.order_id, o.customer_id, o.amount
FROM orders o
LEFT JOIN customers c ON o.customer_id = c.id
WHERE c.id IS NULL;       -- 客户主数据里没匹配上的订单

LEFT JOIN 保留所有订单,匹配不上的客户字段为 NULL,再过滤 c.id IS NULL 就拿到所有”漏匹配”行。

3. 跨 sheet 汇总同比(UNION ALL + 条件聚合)

Q1 / Q2 两个 sheet,想算各区域季度环比:

WITH all_q AS (
  SELECT 'Q1' AS quarter, * FROM sales_q1
  UNION ALL
  SELECT 'Q2', *           FROM sales_q2
)
SELECT
  region,
  SUM(total) FILTER (WHERE quarter = 'Q1')                          AS q1_gmv,
  SUM(total) FILTER (WHERE quarter = 'Q2')                          AS q2_gmv,
  ROUND(
    (SUM(total) FILTER (WHERE quarter = 'Q2'))
      / NULLIF(SUM(total) FILTER (WHERE quarter = 'Q1'), 0) * 100 - 100,
    1
  ) AS qoq_pct
FROM all_q
GROUP BY region
ORDER BY q2_gmv DESC;

FILTER (WHERE ...) 是”条件聚合”——比 SUM(CASE WHEN quarter='Q1' THEN total ELSE 0 END) 简洁得多。Excel 里要做同样的事,得先用透视表分别算两个 quarter,然后用辅助列做除法,至少 3 步。

4. 多对多关联(订单 × 商品 × 类目)

SELECT
  c.category_name,
  COUNT(*)                AS orders,
  SUM(o.qty * p.price)    AS revenue,
  SUM(o.qty)              AS units
FROM orders     o
JOIN products   p ON o.product_id  = p.id
JOIN categories c ON p.category_id = c.id
WHERE o.order_date BETWEEN '2026-04-01' AND '2026-04-30'
GROUP BY c.category_name
ORDER BY revenue DESC;

3 个 sheet 同时 JOIN——Excel 要套 3 层 VLOOKUP + SUMIFS,公式长到一行写不完;SQL 缩进对齐之后非常清楚每张表用谁的什么字段。

5. 找每个区域销售 Top 3(窗口函数 + QUALIFY)

SELECT
  region,
  salesperson,
  total
FROM sales
QUALIFY ROW_NUMBER() OVER (
  PARTITION BY region
  ORDER BY total DESC
) <= 3
ORDER BY region, total DESC;

QUALIFY 是 DuckDB / Snowflake 的便利语法——窗口函数版本的 WHERE,不需要套子查询。Excel 里这种”分组取 Top N”要么排序后人工筛、要么用复杂的数组公式 LARGE + INDEX-MATCH,SQL 直接表达”每组排名前 3”。

VLOOKUP / Power Query → SQL 速查

Excel 写法SQL 等价
VLOOKUP(A2, t!A:B, 2, FALSE)INNER JOIN ... ON a = b
IFERROR(VLOOKUP(...), "")LEFT JOIN ...(缺失行 = NULL)
数组公式 SUMIFSSUM(x) FILTER (WHERE ...) GROUP BY ...
透视表GROUP BY + PIVOT(DuckDB 支持原生 PIVOT 语法)
Power Query「合并查询」JOIN
Power Query「追加查询」UNION ALL
Power Query「分组依据 → 求和」GROUP BY ... SUM(...)
INDEX(MATCH(...), ...)JOIN 或子查询
嵌套 IFCASE WHEN ... THEN ... ELSE ... END

何时还是回 Excel 自己处理

SQL 不是万金油——以下场景 Excel 仍然更合适:

  • 小数据 + 一次性:5 行 × 3 列的数据写 SQL 不如点几下 Excel
  • 要带着格式给老板:颜色、边框、合并单元格、批注——Excel 原生支持,SQL 输出后还要回 Excel 美化
  • 公式依赖原 sheet 内动态变化:业务同事想”改了 A 列让 B 列实时变”——这是 Excel 的强项,SQL 是一次性查询
  • 数据透视图 / 切片器:Excel 透视图的交互探索能力 SQL 短期内追不上

真正能用 SQL 替代 Excel 的场景:(1) 多 sheet 跨表合并;(2) 聚合 + 关联同时做;(3) 数据质量自检;(4) 给下游交付 CSV/Parquet。

隐私 + 不上传

整个工具没有后端——文件用 File.arrayBuffer() 读到内存,SheetJS 在 Web Worker 解析成 CSV bytes,直接喂给 DuckDB-Wasm。可以断网用,可以打开 DevTools Network 面板验证。适合敏感数据:客户订单、未脱敏的 KOL 流水、内部财务表,都不会离开你的电脑。

DuckDB-Wasm 引擎本身(约 35 MB wasm)从同源静态资源加载一次,之后浏览器缓存命中。SheetJS 用的是开源 @sheet/xlsx 社区版,pure JS,无依赖。

配套链路

  • 想直接看 .parquet 文件结构(不写 SQL),用更轻量的 Parquet 在线预览(hyparquet 50 KB,秒开)
  • SQL 跑完想格式化、压缩、加注释,用 代码格式化(含 sql-formatter)
  • 跑完导出的 CSV 想转 JSON / YAML / XML,用 YAML/CSV/XML 互转

❓ 常见问题

Excel 多个 sheet 之间真能直接 JOIN 吗?

能,浏览器里就行。把 .xlsx 拖进 DuckDB SQL 查询台,每个非空 sheet 自动注册成一张表(命名 <文件名>_<sheet名>),写 SELECT * FROM orders_q1 o JOIN customers c ON o.cust_id = c.id 就跨 sheet 关联了。底层是 SheetJS 把 sheet 转成 CSV 注册到 DuckDB-Wasm 的虚拟文件系统,DuckDB 当成普通表查。和 Excel 内置的 Power Query 比:(1) Power Query 是"操作链"——点了几步合并、几步透视,过两周自己都看不懂;SQL 是声明式,一行代码意图明确;(2) Power Query 的合并对中文列名 / 中文 sheet 名经常出 bug,SQL 完全不踩。

VLOOKUP 用得好好的,为什么要换 SQL?

单表小场景 VLOOKUP 仍然好用——查一列、范围明确、就一两次。SQL 真正的优势在 3 种场景:(1) 多次条件——VLOOKUP(A2, ...) + VLOOKUP(B2, ...) 嵌到第 3 层就没法维护,SQL 写 JOIN ... ON a=b AND c=d 一行;(2) 聚合 + 关联同时做——SQL SELECT region, SUM(amount)::DECIMAL FROM ... JOIN ... GROUP BY region 一句搞定,Excel 要先 SUMIFS 再 VLOOKUP 两步;(3) 找漏数据——SQL LEFT JOIN ... WHERE other.id IS NULL 找出主表里没有匹配的行,Excel 用 VLOOKUP+IFERROR 拼也行但容易漏。

那 Power Query 呢?SQL 比它强在哪?

Power Query 最大问题是"操作历史不可读"。你点了 20 步合并查询/透视/分组聚合,文件交给同事,他打开看到的是一串图标按钮,要逐步点开看每步在干啥;改一步还可能让下游全部断链。SQL 是声明式 + 文本——一段查询贴出来就是完整逻辑,git diff 能看出改了什么,复制到任何能跑 SQL 的地方都通用(DuckDB CLI / Postgres / BigQuery 大同小异)。反过来 Power Query 的优势:(1) UI 操作不用学语法,业务同事更友好;(2) 能直接连接外部数据源(数据库 / API)。但做"多 sheet JOIN + 聚合"这种纯计算任务,SQL 几乎完胜。

xlsx 文件类型推断不准怎么办(把数字识别成字符串)?

有两道防线。第一道:DuckDB 的 read_csv_auto 已经会嗅探类型,看到全数字列会推 INT/DOUBLE。第二道:万一推错(比如某列前几行是空、后面才有数字,被当 VARCHAR),用 CAST 显式转:SELECT cust_id::INT FROM orders WHERE ...还有一种坑:Excel 里"明明是数字"但单元格格式是文本(左上角小绿三角),SheetJS 输出的就是字符串。处理:在 Excel 里先选中列 → 右键设置单元格格式 → 数字 → 重新保存 xlsx。或者直接 SQL 里 TRIM(col)::DECIMAL 强转,DuckDB 容错挺好。

Excel 文件多大能跑?

10 万行 30 列以内顺畅。瓶颈在 SheetJS 解析 xlsx(zip 解压 + XML 解析)这一步,10 万行 30 列大约 30-60 秒(在 Web Worker 里跑,主线程不卡)。注册到 DuckDB 后查询毫秒级。100 万行(Excel 单 sheet 上限)以上——超出 SheetJS 在浏览器舒服跑的范围了,建议先在 Excel 里用"另存为 CSV"导出再拖进来(CSV 解析比 xlsx 快 10 倍),或者直接用本地 DuckDB CLI。

SQL 跑完结果想存回 Excel 怎么办?

结果区右上角直接"Excel"按钮。SQL 跑完 → 点 Excel 导出 → 下载 .xlsx。底层是 SheetJS 把结果集转成 sheet 写成 xlsx,保留日期类型 / 数字格式。如果改了已注册的表(INSERT/UPDATE/DELETE),顶部的"未保存"提示条里每张脏表都有 Parquet/CSV/JSON/Excel 四种格式按钮——点对应的就下载。注意:浏览器版的所有改动都在内存里,关闭页面不导出就丢了,工具会在你刷新前弹原生 confirm 提示。

多个 sheet 之间名字相同的列怎么办?

显式起别名——和普通 SQL 多表查询完全一样:SELECT o.id AS order_id, c.id AS customer_id FROM orders o JOIN customers c ON o.cust_id = c.id。或者在 SELECT 里用 * EXCLUDE (id) 排除冲突列:SELECT o.*, c.* EXCLUDE (id) FROM ... —— DuckDB 的 EXCLUDE / REPLACE 修饰符比标准 SQL 简洁很多。最好习惯:JOIN 前给每个表起短别名(orders ocustomers c),后面所有列引用都带前缀,避免歧义。

这工具比 pandas merge 强在哪?

装环境的差距。pandas 要装 Python + pandas + pyarrow(看 xlsx)+ openpyxl,至少 5 分钟;浏览器工具拖文件 3 秒就能查。给非工程同事:浏览器工具发链接他们就能跑,pandas 要先装 Anaconda、配 Jupyter、教 pd.merge 语法。SQL 也比 pandas 表达力强:聚合 + 窗口函数 + CTE 比 groupby().apply() + transform 直观得多。反过来 pandas 强在哪:(1) 数据量超过 1-2 GB;(2) 需要调用 sklearn / numpy 做特征工程;(3) 输出需要复杂可视化(matplotlib)。这些场景 pandas 还是首选。

🦆 打开 DuckDB SQL 工作台 拖入 Parquet/CSV/JSON/Excel→SQL 查询/修改·多文件 JOIN·导出 Parquet/CSV/JSON/Excel·DuckDB-Wasm 浏览器内运行

📖 同一工具的其他教程