mysql菜鸟教程
12.5 联合查询:UNION和UNION ALL
在之前的章节中,我们学习了如何通过连接查询(JOIN)将多个表的列水平地组合在一起。然而,有时我们需要将多个查询的结果垂直地堆叠在一起,也就是将一个查询的结果集追加到另一个查询的结果集后面。这就是联合查询,在 SQL 中通过 UNION 和 UNION ALL 操作符实现。
一、什么是联合查询?
联合查询用于将两个或多个 SELECT 语句的结果集合并成一个结果集。每个 SELECT 语句返回的列数必须相同,并且对应列的数据类型必须兼容。
基本语法
SELECT 列1, 列2, ... FROM 表1 UNION [ALL] SELECT 列1, 列2, ... FROM 表2 [UNION [ALL] SELECT 列1, 列2, ... FROM 表3] ... [ORDER BY 列];
UNION:合并结果集并自动去重(移除重复行)。
UNION ALL:合并结果集但保留所有行(包括重复行),通常性能更好,因为没有去重开销。
最终结果集的列名取自第一个 SELECT 语句的列名。
可以在最后使用 ORDER BY 对整个联合结果排序。
二、基础示例
假设我们有两张表:sales_2023 和 sales_2024,分别存储 2023 年和 2024 年的销售记录。
sales_2023
sales_2024
使用 UNION(去重)
SELECT product FROM sales_2023 UNION SELECT product FROM sales_2024;
结果:
注意:“手机”和“电脑”虽然在两张表中都出现,但 UNION 只保留了一次。
使用 UNION ALL(保留所有)
SELECT product FROM sales_2023 UNION ALL SELECT product FROM sales_2024;
结果:
“手机”和“电脑”各出现两次,因为 UNION ALL 保留了所有行。
三、UNION 与 UNION ALL 的对比
经验法则:如果你确定两个结果集之间没有重复,或者你需要保留所有重复(如日志合并),应使用 UNION ALL 以获得最佳性能。只有当确实需要去重时才使用 UNION。
四、列数和数据类型的要求
1. 列数必须相等
所有 SELECT 语句必须返回相同数量的列。
-- 错误:列数不相等 SELECT name, age FROM students UNION SELECT name FROM teachers; -- 第二句只有一列
2. 数据类型必须兼容
对应列的数据类型必须可以隐式转换。通常,MySQL 会尝试自动转换,但为了清晰,最好确保对应列类型一致或兼容。
-- 可能出错或产生意外结果 SELECT id, price FROM products -- price 是 DECIMAL UNION SELECT id, name FROM categories; -- name 是 VARCHAR
虽然 MySQL 可能允许这样写,但结果可能不符合预期。应确保语义上对应列是同一类数据。
3. 列名由第一个 SELECT 决定
联合结果的列名取自第一个 SELECT 语句的列名或别名。
SELECT product AS item FROM sales_2023 UNION ALL SELECT product FROM sales_2024;
结果的列名是 item。
五、使用 ORDER BY 对联合结果排序
如果要对整个联合结果排序,ORDER BY 必须放在最后一个 SELECT 语句之后。并且,排序时可以使用第一个 SELECT 语句中的列名或列的位置(但推荐使用列名)。
SELECT product, amount FROM sales_2023 UNION ALL SELECT product, amount FROM sales_2024 ORDER BY amount DESC;
注意:不能在每个 SELECT 内部使用单独的 ORDER BY,除非结合 LIMIT(因为子查询中的 ORDER BY 通常被优化器忽略)。如果需要对每个子查询先排序再合并,可以使用派生表。
六、注意事项与常见陷阱
1. 去重的代价
UNION 的去重过程需要对结果集进行排序或创建哈希表,如果数据量很大,会消耗大量内存和 CPU。如果不需要去重,务必使用 UNION ALL。
2. 对 NULL 的处理
UNION 在去重时,会将多个 NULL 视为相等,只保留一个 NULL。这是符合 SQL 标准的。
3. 与 JOIN 的区别
JOIN 水平地合并列(增加列数)。
UNION 垂直地合并行(增加行数)。两者用途完全不同,不能相互替代。
4. 使用括号组合多个 UNION
当有多个 UNION 操作时,可以使用括号明确顺序,但通常 UNION 是从左到右执行的。如果混合使用 UNION 和 UNION ALL,注意 UNION 的优先级高于 UNION ALL?实际上,所有 UNION 操作符具有相同的优先级,按出现顺序执行。但为了清晰,建议用括号分组。
七、实际应用场景
1. 合并历史数据与当前数据
例如,将去年的销售记录与今年的销售记录合并,生成一份完整的报表。
2. 补全维度表
有时需要从多个表中提取维度值的唯一列表,比如获取系统中所有出现过的人员名单(从员工表、客户表、供应商表等)。
SELECT name FROM employees UNION SELECT name FROM customers UNION SELECT contact_name FROM suppliers;
3. 实现全外连接
MySQL 不支持 FULL OUTER JOIN,但可以通过 LEFT JOIN UNION RIGHT JOIN 模拟。
SELECT * FROM table1 LEFT JOIN table2 ON table1.id = table2.id UNION SELECT * FROM table1 RIGHT JOIN table2 ON table1.id = table2.id;
4. 分页合并
将多个分页查询的结果合并,但这种情况较少见。
八、综合练习
假设有三个表:
books(图书):id, title, author
magazines(杂志):id, title, issue
newspapers(报纸):id, title, date
需求1:获取所有出版物(图书、杂志、报纸)的标题列表(去重)。
SELECT title FROM books UNION SELECT title FROM magazines UNION SELECT title FROM newspapers;
需求2:获取所有出版物及其来源类型(保留重复,并添加一个标识列)。
SELECT title, 'book' AS source FROM books UNION ALL SELECT title, 'magazine' AS source FROM magazines UNION ALL SELECT title, 'newspaper' AS source FROM newspapers;
需求3:找出在图书和杂志中都出现过的标题(即交集)。
SELECT title FROM books INTERSECT -- MySQL 8.0 开始支持 INTERSECT,但本例我们用 UNION 模拟 SELECT title FROM magazines;
如果 MySQL 版本不支持 INTERSECT,可以用 IN 或 EXISTS 实现,但这不是本节重点。
九、小结
UNION 和 UNION ALL 用于将多个查询结果垂直合并。
所有 SELECT 语句必须返回相同数量的列,且对应列类型兼容。
UNION 会自动去重,UNION ALL 保留所有行。
性能上 UNION ALL 优于 UNION,无去重需求时应优先使用。
最终结果集的列名由第一个 SELECT 决定。
ORDER BY 必须放在最后,对整个合并结果排序。
联合查询为我们提供了一种灵活的方式,将多个来源的数据整合在一起,是 SQL 工具箱中的重要成员。

发表评论
所有评论