当前位置: 首页
数据库
SQL中JOIN与UNION的区别详解

SQL中JOIN与UNION的区别详解

热心网友 时间:2026-07-01
转载

先说结论

很多刚开始学习 SQL 的朋友,常常在 JOIN 和 UNION 之间感到困惑。虽然两者都涉及“合并数据表”的操作,但它们的合并方向完全不同,理解这一点是掌握 SQL 关联查询与数据整合的基础。

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 医生表(同一患者档案的不同维度)
   ✅ 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 如同“排队”(两批数据排成一列,行数增加)。想清楚你需要的是“更宽的表”还是“更长的表”,就能轻松做出正确选择。

来源:https://www.jb51.net/database/365906pk9.htm

游乐网为非赢利性网站,所展示的游戏/软件/文章内容均来自于互联网或第三方用户上传分享,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系youleyoucom@outlook.com。

同类文章
更多
Redis 7.0增量AOF重写RDB前导码配置详解

Redis 7.0增量AOF重写RDB前导码配置详解

先说一个几乎所有人都踩过的典型误区:很多人把 aof-use-rdb-preamble yes 当作开启“增量重写”的开关。实际上,这个配置只干了一件事——让重写后的 AOF 文件头部带上 RDB 快照。它解决的是加载速度问题,跟“增量重写”本身的概念压根不是一回事。真正的增量重写,依赖的是 Red

时间:2026-07-02 09:05
在Python Tornado异步框架中安全执行SQL命令的方法与最佳实践

在Python Tornado异步框架中安全执行SQL命令的方法与最佳实践

直接在Tornado里用SQLAlchemy同步执行SQL,结果就是阻塞IOLoop,所谓“异步框架里写同步数据库代码”,等于白搭。安全执行的关键不是“怎么写SQL”,而是“怎么不卡住事件循环”。 为什么不能在RequestHandler里直接调用session execute() 因为sessio

时间:2026-07-02 09:04
利用SQL触发器实现在INSERT数据时自动同步到审计表

利用SQL触发器实现在INSERT数据时自动同步到审计表

先说结论:可以用触发器把 INSERT 数据同步到审计表,但必须用 AFTER INSERT,并且审计表的字段顺序、类型、字符集得和源表严格一致。否则,轻则写入错位、数据截断,重则直接报错、丢数据。下面把这些坑一个一个掰开说。 能,但必须用 AFTER INSERT,且审计表字段顺序、类型、字符集要

时间:2026-07-02 09:04
如何用SQL编写按不同工作日统计员工出勤率

如何用SQL编写按不同工作日统计员工出勤率

在实际业务中,统计不同工作日的出勤率是HR系统里的高频需求。如果直接按日期函数分组,很容易掉进语言环境、索引失效或分母口径的坑里。下面就来拆解具体的实现要点。 必须用 CASE WHEN 将日期映射为固定 weekday 标签(如 Mon )再分组,避免语言环境导致的分组断裂;需过滤 DOW IN

时间:2026-07-02 09:03
Spring Boot 3动态拼接SQL为何引发严重安全漏洞

Spring Boot 3动态拼接SQL为何引发严重安全漏洞

SQL注入漏洞的核心成因,本质上是因为用户输入直接参与了SQL语句的字符串拼接,而未采用参数化绑定机制。在MyBatis中使用${}、QueryWrapper中调用apply()与last()、JPA的@Query注解进行拼接等操作,都会绕过PreparedStatement的安全防护。动态字段必须

时间:2026-07-02 09:03
热门专题
更多
刀塔传奇破解版无限钻石下载大全 刀塔传奇破解版无限钻石下载大全
洛克王国正式正版手游下载安装大全 洛克王国正式正版手游下载安装大全
思美人手游下载专区 思美人手游下载专区
好玩的阿拉德之怒游戏下载合集 好玩的阿拉德之怒游戏下载合集
不思议迷宫手游下载合集 不思议迷宫手游下载合集
百宝袋汉化组游戏最新合集 百宝袋汉化组游戏最新合集
jsk游戏合集30款游戏大全 jsk游戏合集30款游戏大全
宾果消消消原版下载大全 宾果消消消原版下载大全
  • 日榜
  • 周榜
  • 月榜