SQL 核心概念:JOIN 和 UNION 到底有什么区别?

📖 先说结论

很多初学者容易把 JOIN 和 UNION 搞混,因为它们都是"把两张表合在一起"。但合并的方向完全不同:

📄 JOIN 是把两张表左右合并 ,像把两张纸并排贴在一起

📄 UNION 是把两张表上下合并 ,像把两张纸上下拼接成一张长纸

用一张图来理解:

css 复制代码
JOIN(左右合并):                UNION(上下合并):

表A      表B      结果            表A       结果
─────  + ─────  = ───────────     ─────     ─────
行1      行X      行1 | 行X        行1       行1
行2      行Y      行2 | 行Y        行2       行2
行3      行Z      行3 | 行Z        ─────  =  行3
                                  表B       ─────
                                  行A       行A
                                  行B       行B

AI写代码text
12345678910

🔗 一、JOIN ------ 横向拼接

核心思想

JOIN 用于将同一业务实体的不同维度 拼在一起。两张表之间必须存在可以对齐的关联关系(通常是外键),把符合条件的行左右合并成一行。

典型场景

我有一张订单表,想同时看到订单信息 + 下单患者的姓名

订单表和患者表之间存在 patient_id 关联,每一条订单对应一个患者,这就是典型的横向拼接场景。

vbnet 复制代码
-- 查询订单,同时展示患者姓名
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'

AI写代码sql
12345678

结果长这样:

arduino 复制代码
订单号          金额      患者姓名
ORDER_001      199.00   张三
ORDER_002      299.00   李四
ORDER_003      99.00    张三

AI写代码text
1234

每一行都是一条订单 + 这条订单对应的患者,数据是横向扩展的,列变多了,行数不变(或者因为匹配关系有所变化)。

JOIN 的几种类型

css 复制代码
-- 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

AI写代码sql
12345678

💡 记忆口诀 :JOIN 是"找对象",两行数据牵手合并成一行,牵手的条件就是 ON 后面的关联字段。


🔀 二、UNION ------ 纵向堆叠

核心思想

UNION 用于将结构相同、但来源不同 的数据集纵向堆叠在一起。两个查询的列数和数据类型必须一致,把多个查询的结果上下拼成一张更长的表。

典型场景

我有体重日常记录表和 InBody 专业测量表,想在趋势图里把两边的体重数据按时间顺序展示在同一条折线上。

这两张表的记录之间没有对应关系,体重记录表有365条,InBody表有12条,它们都是独立的测量事件。我需要的是把它们纵向堆叠,按时间排列:

sql 复制代码
-- 体重趋势图:把两张表的数据纵向合并
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

AI写代码sql
123456789101112

结果长这样:

yaml 复制代码
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测量   ← 来自另一张表

AI写代码
1234567

数据是纵向扩展的,列数不变,行数变多了

UNION 和 UNION ALL 的区别

sql 复制代码
-- 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

AI写代码sql
123456789

⚠️ 原则 :如果你确定两个结果集不会有重复数据,或者你不在乎重复,优先用 UNION ALL,性能更好。只有在明确需要去重时才用 UNION。


⚖️ 三、核心对比

对比维度 JOIN UNION
📐 合并方向 横向(列变多) 纵向(行变多)
🔗 前提条件 两表有关联字段 两个查询列数和类型一致
📊 结果形态 行数取决于匹配关系 行数 = 两个查询结果之和
🎯 适用场景 同一实体的不同维度 同类数据的不同来源
🔑 关键字 ON 指定关联条件 无需关联条件
⚡ 典型用法 订单 + 用户信息 多个来源的同类数据合并

🎯 四、如何判断用哪个?

遇到"把两张表合在一起"的需求时,问自己两个问题:

问题一:我想要的结果,列数是变多了,还是行数变多了?

列变多 → JOIN,行变多 → UNION。

问题二:两张表的数据是"同一件事的不同角度",还是"同类事情的不同来源"?

markdown 复制代码
同一件事的不同角度(一条订单 + 这条订单的用户名)
    → JOIN,横向拼接

同类事情的不同来源(体脂秤的体重记录 + InBody的体重记录)
    → UNION,纵向堆叠

AI写代码text
12345

实际案例判断练习

sql 复制代码
❓ 查询患者信息,同时展示主管医生的姓名
→ 患者表 JOIN 医生表(同一患者档案的不同维度)✅ JOIN

❓ 查询本月所有健康指标的预警记录(血压预警 + 血糖预警 + 体重预警分别在不同表)
→ 三张预警表纵向合并(同类事件的不同来源)✅ UNION ALL

❓ 查询运动记录,同时展示当天的饮食热量
→ 运动表 LEFT JOIN 饮食表(同一天的不同健康维度)✅ JOIN

❓ 查询某患者所有沟通记录(患者发的 + 医生发的 + 系统消息)
→ 这个其实在同一张表里,按 sender_type 区分,不需要 UNION

AI写代码text
1234567891011

🚫 五、常见误区

❌ 误区一:以为 JOIN 可以代替 UNION

sql 复制代码
-- ❌ 错误思路:用 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条,完全错误!

AI写代码sql
12345

两张表没有行级别的对应关系,JOIN 会产生笛卡尔积,结果毫无意义。

❌ 误区二:UNION 的列不对齐

sql 复制代码
-- ❌ 错误:两个查询列数不一致
SELECT record_time, weight FROM patient_weight_record
UNION ALL
SELECT record_time, weight, bmi FROM patient_body_composition
-- 报错:列数不匹配

AI写代码sql
12345
sql 复制代码
-- ✅ 正确:列数和类型对齐,缺少的列用 NULL 补位
SELECT record_time, weight, NULL AS bmi FROM patient_weight_record
UNION ALL
SELECT record_time, weight, bmi FROM patient_body_composition

AI写代码sql
1234

❌ 误区三:UNION 后忘记加 ORDER BY

sql 复制代码
-- ⚠️ 注意: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  -- ✅ 放在最后,对合并后的全部结果排序

AI写代码sql
12345

📌 六、一句话总结

🎯 JOIN 是找对象 (两行数据牵手变成一行,列变多);UNION 是排队(两批数据排成一队,行变多)。搞清楚你要的是"更宽的表"还是"更长的表",就知道该用哪个了。

相关推荐
沙湖遇雨2 分钟前
8.ByteBuf 的数据模型、生命周期和内存分配
后端
by组态5 分钟前
Ricon组态系统API参考手册
前端·后端·物联网
企业数字化笔记18 分钟前
固定资产财务账与实物账怎么对账?折旧快照、差异检测与SQL核对
android·数据库·sql
再吃一根胡萝卜21 分钟前
Agent 如何选择与生成 SQL —— Text2SQL 全链路详解
后端
苍何24 分钟前
把操作流程直接录成 Skill,豆包太夯了!
后端
Go_error43 分钟前
在 Go 服务层中如何优雅地管理事务?
后端·go
爱读源码的大都督1 小时前
DeepSeek面试官问:多租户 RAG 系统怎样实现细粒度权限控制?
后端·面试·架构
苍何1 小时前
我们终于成立了 AgentWork 开源社区,16.4 万字豆包工作蓝皮书同步开源(建议收藏)
后端