MySQL UNION 操作符(深入浅出)

什么是 MySQL UNION 操作符

在数据库查询中,我们经常需要从多个表中提取数据,然后将它们合并成一个结果集。这时候,MySQL 提供了一个非常实用的工具——UNION 操作符。它就像一个“数据拼接工”,能把两个或多个 SELECT 查询的结果合并成一张大表,前提是这些结果的列结构要“长得一样”。

想象一下你在整理快递单据。一个快递员负责 A 区,另一个负责 B 区,你手上有两份清单。如果你希望把两个区域的包裹信息合在一起查看,就不需要分别看两份清单,而是把它们“拼”成一份完整的清单。MySQL UNION 操作符就是这个“拼接工”。

UNION 操作符的核心作用是合并多个查询的结果,但它有严格的条件:

  • 每个 SELECT 语句必须有相同数量的列
  • 对应列的数据类型要兼容(比如都是字符串、整数等)
  • 列的顺序必须一致

如果你不满足这些条件,MySQL 会报错。所以使用前一定要检查结构是否对齐。

UNION 操作符的基本语法与使用

UNION 操作符的语法非常直观,基本形式如下:

SELECT 列名1, 列名2, ... FROM 表名1
UNION
SELECT 列名1, 列名2, ... FROM 表名2;

我们来看一个实际例子。假设你有两个表:orders_2023orders_2024,它们的结构完全相同,都包含 order_idcustomer_nameamount 三列。

-- 查询 2023 年和 2024 年的所有订单信息
SELECT order_id, customer_name, amount
FROM orders_2023
UNION
SELECT order_id, customer_name, amount
FROM orders_2024;

这段代码的作用是:

  • 第一个 SELECT 查询从 2023 年的订单表中提取数据
  • 第二个 SELECT 查询从 2024 年的订单表中提取数据
  • 使用 UNION 把这两个结果“拼”在一起,形成一个包含两年订单的完整列表

注意:UNION 默认会自动去除重复行。如果某条订单在两个表中都存在(比如客户在两年都下了相同的订单),它只会保留一条。这就像你整理快递单时,如果发现同一个包裹被重复登记,系统会自动帮你去重。

UNION ALL:保留重复数据的版本

有时候,你并不想“去重”,反而需要保留所有记录。比如你要统计全年每个订单的完整流水,哪怕客户重复下单,你也得记下来。

这时,就要用到 UNION ALL。它和 UNION 的区别只有一点:不自动去重

-- 查询 2023 年和 2024 年的所有订单,包括重复的
SELECT order_id, customer_name, amount
FROM orders_2023
UNION ALL
SELECT order_id, customer_name, amount
FROM orders_2024;

这里的关键是:

  • UNION ALL 不会检查重复,直接把两个结果集“粘”在一起
  • 性能更高,因为省去了去重的计算过程
  • 适合需要完整数据的场景,比如报表分析、审计日志等

举个生活化的例子:
如果你在超市收银台结账,系统要记录每一笔交易,哪怕客户今天买了两次一样的商品,你也得分别记下来。这时候用 UNION ALL 就最合适。

操作符 是否去重 性能表现 适用场景
UNION 较低 去重后展示唯一数据
UNION ALL 需要保留全部记录的场景

多表合并与列结构对齐

使用 MySQL UNION 操作符时,列结构对齐是重中之重。我们来看一个常见错误:

-- ❌ 错误示例:列数量不一致
SELECT id, name FROM users
UNION
SELECT id, name, email FROM employees;

这个语句会报错,因为第一个查询返回 2 列,第二个返回 3 列。MySQL 无法“拼接”不同数量的列。

正确的做法是确保每个 SELECT 语句的列数量和类型完全匹配:

-- ✅ 正确示例:列数量和类型一致
SELECT user_id AS id, user_name AS name, 'user' AS type
FROM users
UNION
SELECT employee_id AS id, employee_name AS name, 'employee' AS type
FROM employees;

在这个例子中:

  • 两个 SELECT 都返回 3 列:id、name、type
  • 使用了别名(AS)统一列名,便于后续处理
  • 添加了 type 字段用于标识数据来源,增强可读性

这样即使两个表结构不同,也能通过“统一列结构”实现合并。就像你把不同品牌的快递盒统一贴上“包裹类型”标签,就能分类管理了。

实际应用场景:跨年订单分析

我们来做一个真实的业务场景:某电商公司需要统计 2023 年和 2024 年的全部订单,按金额从高到低排序,并筛选出金额大于 1000 的订单。

-- 查询两年内金额大于 1000 的订单,按金额降序排列
SELECT order_id, customer_name, amount, '2023' AS year
FROM orders_2023
WHERE amount > 1000
UNION ALL
SELECT order_id, customer_name, amount, '2024' AS year
FROM orders_2024
WHERE amount > 1000
ORDER BY amount DESC;

这个查询的逻辑是:

  • 分别从两个年份的表中筛选出金额超过 1000 的订单
  • 用 UNION ALL 合并结果(因为需要保留所有符合条件的记录)
  • 最后统一按金额从高到低排序
  • 通过 AS year 添加年份标识,方便分析

结果将是一张包含两年高价值订单的完整清单。这种写法比分别查询再手动合并要高效得多,也更安全。

高级技巧:与 WHERE、ORDER BY 的配合使用

UNION 操作符可以和其他 SQL 子句灵活组合。但要注意:

  • ORDER BY 只能放在整个查询的最后,不能放在单个 SELECT 里(除非用括号)
  • LIMIT 也必须放在最后
-- ❌ 错误写法:在子查询中使用 ORDER BY
SELECT order_id, amount FROM orders_2023 ORDER BY amount
UNION
SELECT order_id, amount FROM orders_2024 ORDER BY amount;

-- ✅ 正确写法:在最后统一排序
SELECT order_id, amount FROM orders_2023
UNION ALL
SELECT order_id, amount FROM orders_2024
ORDER BY amount DESC
LIMIT 10;

这个例子中:

  • 两个子查询不排序,避免影响合并结果
  • 最后统一按金额降序排列,只取前 10 名
  • 适合做“年度 Top 10 高额订单”报表

另外,你还可以在 UNION 前加 WHERE 条件,过滤掉不需要的数据。比如只关心“北京地区”客户的订单:

SELECT order_id, customer_name, amount
FROM orders_2023
WHERE city = '北京'
UNION ALL
SELECT order_id, customer_name, amount
FROM orders_2024
WHERE city = '北京';

这样就能快速定位特定区域的客户数据,非常实用。

总结与最佳实践

MySQL UNION 操作符是一个强大而灵活的工具,特别适合处理跨表、跨时间的数据合并需求。掌握它,你就能轻松应对复杂的数据整合任务。

在实际使用中,建议遵循以下几点最佳实践:

  1. 确保列结构一致:数量、顺序、数据类型必须匹配,否则会报错
  2. 优先使用 UNION ALL:除非必须去重,否则用 UNION ALL 更快
  3. 合理使用别名:为列起有意义的名字,提升可读性
  4. 在最后统一排序和限制:避免在子查询中使用 ORDER BY 和 LIMIT
  5. 添加来源标识:如 AS yearAS source,便于后期分析

无论你是刚入门的开发者,还是有一定经验的工程师,熟练使用 MySQL UNION 操作符,都能让你的 SQL 查询更高效、更专业。它就像一把“数据拼接刀”,用得好,能帮你快速构建出清晰、完整的数据视图。