SQL核心概念:JOIN与UNION的区别详解

作者:技术拾光时间:2026-06-18 08:40:13浏览:184

JOIN是左右横向合并,通过关联字段匹配行来扩展列数,常用于同一实体的不同维度,包括内连接、外连接等;UNION是上下纵向合并,要求各查询列数和类型一致以增加行数,适用于同类数据的不同来源,默认去重,UNIONALL保留重复。

核心结论

对于刚接触 SQL 的开发者,JOIN 与 UNION 这两个操作经常被混淆。两者都涉及表的合并,但合并方向截然不同。

SQL核心概念:JOIN与UNION的区别详解

JOIN 是左右合并,相当于将两张 A4 纸并排粘贴,得到一张更宽的纸。

UNION 是上下合并,相当于将两张纸上下拼接,得到一张更长的纸。

以下示意图可快速理解:

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

一、JOIN —— 横向拼接

核心思想

JOIN 的核心在于将同一业务实体的不同侧面粘合在一起。两张表之间必须具有明确的关联关系,例如通过某个 ID 字段,将符合条件的行左右合并为一行。

典型场景

例如,有一张订单表,需要同时展示订单信息 + 下单患者的姓名。这就是 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    张三

每一行都包含一条订单 + 该订单对应的患者,数据横向扩展,列数增加,行数基本不变(可能因匹配关系略有调整)。

JOIN 的几种类型

-- 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 —— 纵向堆叠

核心思想

UNION 用于处理结构相同但来源不同的数据集。它将多个查询结果上下堆叠,前提是各查询的列数和数据类型必须完全一致。

典型场景

例如,有体重日常记录表和 InBody 专业测量表,需要在趋势图中将两边的体重数据按时间顺序显示在同一条折线上。这正是 UNION 的应用场景。

这两张表的记录之间没有对应关系——体重日常记录表可能一年有 365 条,InBody 表只有 12 条,它们都是独立的测量事件。用户需要将所有体重数据按时间排列,而不是某个记录对应哪个 InBody 结果。因此,操作为 纵向堆叠:

-- 体重趋势图:将两张表的数据纵向合并
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 和 UNION ALL 的区别

-- 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,横向拼接

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

练习:判断下列需求应使用 JOIN 还是 UNION

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

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

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

 查询某患者所有沟通记录(患者发送、医生发送、系统消息)
   → 实际位于同一张表,按 sender_type 字段区分,无需 UNION

五、常见误区

误区一:认为 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 会产生笛卡尔积,结果无意义。

误区二:UNION 时列不对齐

--  错误:两个查询列数不一致
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

误区三:UNION 后忘记加 ORDER BY

--  注意: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 是排队(两批数据排成一队,行增加)。想清楚需要的是“更宽的表”还是“更长的表”,即可确定使用哪一种。