SQL 核心概念:JOIN 和 UNION 的区别
SQL 中的 JOIN 和 UNION 是两种不同的数据合并方式。JOIN 用于横向拼接,将具有关联关系的表通过 ON 条件左右合并,增加列数;UNION 用于纵向堆叠,将结构相同的查询结果上下合并,增加行数。选择依据在于需求是扩展维度还是合并数据集。JOIN 需匹配字段,UNION 需列对齐。常见误区包括用 JOIN 产生笛卡尔积或 UNION 列不匹配。

SQL 中的 JOIN 和 UNION 是两种不同的数据合并方式。JOIN 用于横向拼接,将具有关联关系的表通过 ON 条件左右合并,增加列数;UNION 用于纵向堆叠,将结构相同的查询结果上下合并,增加行数。选择依据在于需求是扩展维度还是合并数据集。JOIN 需匹配字段,UNION 需列对齐。常见误区包括用 JOIN 产生笛卡尔积或 UNION 列不匹配。

一个比喻彻底搞懂,再也不混淆
很多初学者容易把 JOIN 和 UNION 搞混,因为它们都是"把两张表合在一起"。但合并的方向完全不同:
JOIN 是把两张表左右合并,像把两张纸并排贴在一起。
UNION 是把两张表上下合并,像把两张纸上下拼接成一张长纸。
用逻辑来理解:
JOIN 用于将同一业务实体的不同维度拼在一起。两张表之间必须存在可以对齐的关联关系(通常是外键),把符合条件的行左右合并成一行。
我有一张订单表,想同时看到订单信息 + 下单患者的姓名。
订单表和患者表之间存在 patient_id 关联,每一条订单对应一个患者,这就是典型的横向拼接场景。
-- 查询订单,同时展示患者姓名
SELECT o.order_no AS 订单号,o.amount AS 金额,u.real_name AS 患者姓名
FROM orders o
JOIN patient_user u ON o.patient_id = u.id
WHERE o.create_time >= '2024-01-01'
结果长这样:
| 订单号 | 金额 | 患者姓名 |
|---|---|---|
| ORDER_001 | 199.00 | 张三 |
| ORDER_002 | 299.00 | 李四 |
| ORDER_003 | 99.00 | 张三 |
每一行都是一条订单 + 这条订单对应的患者,数据是横向扩展的,列变多了,行数不变(或者因为匹配关系有所变化)。
-- INNER JOIN(内连接):只返回两边都能匹配上的行
SELECT * FROM A INNER JOIN B ON A.id = B.a_id
-- LEFT JOIN(左连接):左表全返回,右表没匹配到的填 NULL
SELECT * FROM A LEFT JOIN B ON A.id = B.a_id
-- RIGHT JOIN(右连接):右表全返回,左表没匹配到的填 NULL
SELECT * FROM A RIGHT JOIN B ON A.id = B.a_id
💡 记忆口诀:JOIN 是"找对象",两行数据牵手合并成一行,牵手的条件就是
ON后面的关联字段。
UNION 用于将结构相同、但来源不同的数据集纵向堆叠在一起。两个查询的列数和数据类型必须一致,把多个查询的结果上下拼成一张更长的表。
我有体重日常记录表和 InBody 专业测量表,想在趋势图里把两边的体重数据按时间顺序展示在同一条折线上。
这两张表的记录之间没有对应关系,体重记录表有 365 条,InBody 表有 12 条,它们都是独立的测量事件。我需要的是把它们纵向堆叠,按时间排列:
-- 体重趋势图:把两张表的数据纵向合并
SELECT record_time, weight, '日常测量' AS source
FROM patient_weight_record
WHERE patient_id = 1
UNION ALL
SELECT record_time, weight, 'InBody 测量' AS source
FROM patient_body_composition
WHERE patient_id = 1
ORDER BY record_time
结果长这样:
| record_time | weight | source |
|---|---|---|
| 2024-01-01 07:00 | 85.50 | 日常测量 |
| 2024-01-05 09:00 | 85.20 | 日常测量 |
| 2024-01-10 10:00 | 84.80 | InBody 测量 |
| 2024-01-15 07:30 | 84.50 | 日常测量 |
| 2024-01-20 08:00 | 84.10 | 日常测量 |
| 2024-02-10 10:00 | 83.60 | InBody 测量 |
数据是纵向扩展的,列数不变,行数变多了。
-- UNION:自动去重(性能较差,需要额外排序去重)
SELECT name FROM table_a
UNION
SELECT name FROM table_b
-- UNION ALL:不去重,直接合并(性能更好)
SELECT name FROM table_a
UNION ALL
SELECT name FROM table_b
⚠️ 原则:如果你确定两个结果集不会有重复数据,或者你不在乎重复,优先用 UNION ALL,性能更好。只有在明确需要去重时才用 UNION。
| 对比维度 | JOIN | UNION |
|---|---|---|
| 📐 合并方向 | 横向(列变多) | 纵向(行变多) |
| 🔗 前提条件 | 两表有关联字段 | 两个查询列数和类型一致 |
| 📊 结果形态 | 行数取决于匹配关系 | 行数 = 两个查询结果之和 |
| 🎯 适用场景 | 同一实体的不同维度 | 同类数据的不同来源 |
| 🔑 关键字 | ON 指定关联条件 | 无需关联条件 |
| ⚡ 典型用法 | 订单 + 用户信息 | 多个来源的同类数据合并 |
遇到"把两张表合在一起"的需求时,问自己两个问题:
问题一:我想要的结果,列数是变多了,还是行数变多了?
列变多 → JOIN,行变多 → UNION。
问题二:两张表的数据是"同一件事的不同角度",还是"同类事情的不同来源"?
-- ❌ 错误思路:用 JOIN 合并两张体重表
SELECT w.record_time, w.weight, b.weight
FROM patient_weight_record w
JOIN patient_body_composition b ON w.patient_id = b.patient_id
-- 结果是笛卡尔积:365 条 × 12 条 = 4380 条,完全错误!
两张表没有行级别的对应关系,JOIN 会产生笛卡尔积,结果毫无意义。
-- ❌ 错误:两个查询列数不一致
SELECT record_time, weight FROM patient_weight_record
UNION ALL
SELECT record_time, weight, bmi FROM patient_body_composition
-- 报错:列数不匹配
-- ✅ 正确:列数和类型对齐,缺少的列用 NULL 补位
SELECT record_time, weight, NULL AS bmi FROM patient_weight_record
UNION ALL
SELECT record_time, weight, bmi FROM patient_body_composition
-- ⚠️ 注意:ORDER BY 要放在最后,对整体结果排序
SELECT record_time, weight FROM patient_weight_record WHERE patient_id = 1
UNION ALL
SELECT record_time, weight FROM patient_body_composition WHERE patient_id = 1
ORDER BY record_time
-- ✅ 放在最后,对合并后的全部结果排序
🎯 JOIN 是找对象(两行数据牵手变成一行,列变多);UNION 是排队(两批数据排成一队,行变多)。搞清楚你要的是"更宽的表"还是"更长的表",就知道该用哪个了。

微信公众号「极客日志」,在微信中扫描左侧二维码关注。展示文案:极客日志 zeeklog
使用加密算法(如AES、TripleDES、Rabbit或RC4)加密和解密文本明文。 在线工具,加密/解密文本在线工具,online
在线格式化和美化您的 SQL 查询(它支持各种 SQL 方言)。 在线工具,SQL 美化和格式化在线工具,online
解析 INSERT 等受限 SQL,导出为 CSV、JSON、XML、YAML、HTML 表格(见页内语法说明)。 在线工具,SQL转CSV/JSON/XML在线工具,online
CSV 与 JSON/XML/HTML/TSV/SQL 等互转,单页多 Tab。 在线工具,CSV 工具包在线工具,online
将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online
将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online