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

id

product

amount

1

手机

3000

2

电脑

5000

sales_2024

id

product

amount

1

手机

3200

2

平板

2000

3

电脑

4800

使用 UNION(去重)

SELECT product FROM sales_2023
UNION
SELECT product FROM sales_2024;

结果

product

手机

电脑

平板

注意:“手机”和“电脑”虽然在两张表中都出现,但 UNION 只保留了一次。

使用 UNION ALL(保留所有)

SELECT product FROM sales_2023
UNION ALL
SELECT product FROM sales_2024;

结果

product

手机

电脑

手机

平板

电脑

“手机”和“电脑”各出现两次,因为 UNION ALL 保留了所有行。

三、UNION 与 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(图书):idtitleauthor

  • magazines(杂志):idtitleissue

  • newspapers(报纸):idtitledate

需求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 工具箱中的重要成员。


所有评论

关于我 备案号:蜀ICP备2023042032号-1