mysql中的union关键字详解

49 阅读3分钟

MySQL 的 UNION 用于合并两个或多个 SELECT 语句的结果集,并自动去除重复行(相当于对结果集执行 DISTINCT)。

1. 基本语法与核心规则

SELECT column1, column2 FROM table1
UNION [ALL | DISTINCT]
SELECT column1, column2 FROM table2;

强制性规则

  • 列数必须相同:两个 SELECT 返回的列数必须一致
  • 数据类型兼容:对应位置的列类型不必完全相同,但必须可隐式转换(如 INT 和 DECIMAL)
  • 列名继承:结果集的列名取自第一个 SELECT 语句
  • ORDER BY 位置:只能放在最后一个 SELECT 之后,且排序依据必须是第一个 SELECT 的列名或别名

2. UNION vs UNION ALL

特性UNION (DISTINCT)UNION ALL
去重自动去重,行唯一保留所有行,包括重复
性能需要临时表+哈希排序去重,较慢直接追加结果,极快
使用场景必须确保唯一性时确定无重复或需要保留重复时

性能建议:除非业务需要去重,否则优先使用 UNION ALL。去重操作需要创建临时表、哈希比较和额外排序,大数据量时性能差距巨大。

3. 实际应用场景

场景 A:合并相似结构表(如分表)

-- 查询 2023 和 2024 年的订单(假设按年分表)
SELECT order_id, amount, created_at FROM orders_2023
UNION ALL
SELECT order_id, amount, created_at FROM orders_2024
ORDER BY created_at DESC
LIMIT 20;

场景 B:多维度统计报表

SELECT 'VIP用户' as user_type, COUNT(*) as cnt FROM users WHERE level >= 8
UNION ALL
SELECT '普通用户', COUNT(*) FROM users WHERE level BETWEEN 1 AND 7
UNION ALL
SELECT '访客', COUNT(*) FROM users WHERE level = 0;

场景 C:数据补全与兜底查询

-- 先查缓存表,若无结果再查主表(常见于配置表)
SELECT config_value FROM config_cache WHERE config_key = 'site_name'
UNION ALL
SELECT config_value FROM config WHERE config_key = 'site_name'
LIMIT 1;

4. 进阶用法

带排序和分页

(SELECT id, name FROM products WHERE category = 'A' ORDER BY price DESC LIMIT 10)
UNION ALL
(SELECT id, name FROM products WHERE category = 'B' ORDER BY price DESC LIMIT 10)
ORDER BY price DESC LIMIT 10;

注意:子查询中的 ORDER BY 必须配合 LIMIT 才生效,否则会被优化器忽略。

混合聚合与明细

SELECT city, sales_amount, NULL as detail FROM sales_summary
UNION ALL
SELECT city, amount, CONCAT(product_name, ':', quantity) FROM sales_detail;

5. 性能优化要点

  1. 索引利用:每个 SELECT 独立使用索引,UNION 本身不走索引
  2. 减少数据集:在子查询中先 WHERE 过滤,避免传递大量数据到 UNION 层
  3. 避免临时表写入UNION DISTINCT 会创建内存临时表(或磁盘临时表,若过大),可通过 SET tmp_table_size 调优
  4. 并行执行:MySQL 8.0.19+ 对非相关子查询的 UNION 可能启用并行查询

6. 常见陷阱

-- ❌ 错误:列数不匹配
SELECT id, name FROM t1
UNION
SELECT id FROM t2;  -- ERROR 1222

-- ❌ 错误:ORDER BY 位置错误
SELECT * FROM t1 ORDER BY id
UNION
SELECT * FROM t2;  -- ERROR 1221

-- ✅ 正确写法
SELECT * FROM t1
UNION
SELECT * FROM t2
ORDER BY id;  -- 使用第一个 SELECT 的列名排序

7. Django ORM 中的 UNION

如果你在使用 Django(看你之前关注过 Django 源码),可以这样使用:

from django.db.models import Q

# QuerySet.union() 默认使用 UNION ALL
qs1 = User.objects.filter(status='active').values('name', 'email')
qs2 = User.objects.filter(is_staff=True).values('name', 'email')

# UNION ALL
combined = qs1.union(qs2, all=True)

# UNION (去重)
combined_distinct = qs1.union(qs2)  # 或 all=False

注意:Django 的 union() 会强制转换为 SQL 的 UNION,且 QuerySet 被求值后返回的是列表而非 QuerySet,无法继续链式过滤。


总结UNION ALL 是合并结果集的高性能首选,UNION (DISTINCT) 仅在需要去重时使用。始终确保子查询列数一致,并在应用层处理复杂的排序逻辑。