当我们需要合并多个SQL查询结果集时,UNION和UNION ALL是最常用的操作符。虽然它们看起来功能相似,但在数据处理方式和性能表现上存在关键差异。
UNION ALL是最基础的结果集合并操作,它会简单地将两个或多个SELECT语句的结果直接堆叠在一起,不做任何去重处理。比如我们从两个分公司数据库查询员工名单:
sql复制SELECT employee_id, name FROM branch1_employees
UNION ALL
SELECT employee_id, name FROM branch2_employees
这个查询会返回branch1和branch2所有员工的记录,如果两个分公司有相同的员工ID和姓名,两条记录都会出现在结果中。
相比之下,UNION会在合并结果集后自动去除重复行。它相当于先执行UNION ALL,再对结果执行DISTINCT操作。使用同样的例子:
sql复制SELECT employee_id, name FROM branch1_employees
UNION
SELECT employee_id, name FROM branch2_employees
这个查询会确保结果中没有完全相同的记录(所有列值都相同的行)。数据库引擎需要额外的工作来识别和消除重复项。
关键区别:UNION ALL保留所有行(包括重复项),UNION自动去除完全重复的行。
从数据库引擎的角度看,UNION和UNION ALL的处理流程完全不同:
UNION ALL的执行过程:
这是一个非常轻量级的操作,因为不需要任何额外的数据处理。
UNION的执行过程:
这个去重过程可能非常消耗资源,特别是当处理大量数据时。数据库通常需要:
根据我的性能测试经验,在百万级数据量下,UNION可能比UNION ALL慢3-5倍。我曾在一个报表系统中将UNION改为UNION ALL,查询时间从12秒降到了3秒。
以下情况优先考虑UNION ALL:
典型案例:合并多个月份的销售数据,即使有少量重复订单也不影响总额统计。
以下情况应该使用UNION:
典型案例:从多个部门数据库合并员工通讯录,确保每人只出现一次。
使用UNION/UNION ALL时,各SELECT语句必须有相同数量的列,且对应列的数据类型必须兼容。数据库会按照第一个查询的列名和类型定义结果集。
常见问题处理:
sql复制SELECT id AS user_id, name FROM users
UNION
SELECT employee_id, full_name FROM employees
-- 结果列名为user_id, name
sql复制SELECT 123 AS number -- 整数
UNION
SELECT '456' AS number -- 字符串
-- 数据库会将字符串转为整数
最佳实践是显式统一类型:
sql复制SELECT CAST(123 AS VARCHAR) AS number
UNION
SELECT '456' AS number
在UNION查询中应用ORDER BY和LIMIT有特殊规则:
sql复制(SELECT name FROM employees ORDER BY join_date LIMIT 10)
UNION
(SELECT name FROM contractors ORDER BY start_date LIMIT 5)
sql复制SELECT name FROM employees
UNION
SELECT name FROM contractors
ORDER BY name LIMIT 20
对于复杂场景,临时表可能比UNION更高效:
sql复制-- 创建临时表存储初始结果
CREATE TEMPORARY TABLE temp_results AS
SELECT * FROM table1 WHERE condition;
-- 追加其他数据
INSERT INTO temp_results
SELECT * FROM table2 WHERE condition;
-- 最后统一处理
SELECT DISTINCT * FROM temp_results; -- 相当于UNION
-- 或
SELECT * FROM temp_results; -- 相当于UNION ALL
这种方法特别适合:
案例1:列数不匹配
sql复制SELECT id, name, email FROM users
UNION
SELECT id, name FROM customers
-- 错误:第二个查询缺少email列
案例2:ORDER BY位置错误
sql复制SELECT name FROM users ORDER BY id
UNION
SELECT name FROM products
-- 错误:ORDER BY不能在UNION的第一个查询中直接使用
案例3:类型不兼容
sql复制SELECT 'ID:' || id AS identifier FROM users
UNION
SELECT id FROM products
-- 错误:字符串与整数类型不匹配
当UNION查询变慢时,检查以下方面:
sql复制EXPLAIN
SELECT * FROM table1
UNION
SELECT * FROM table2
关注:
sql复制SELECT DISTINCT * FROM (
SELECT * FROM table1
UNION ALL
SELECT * FROM table2
) AS combined
UNION查询中使用LIMIT分页时,避免这种低效写法:
sql复制SELECT * FROM (
SELECT * FROM large_table1
UNION
SELECT * FROM large_table2
) AS combined
LIMIT 10 OFFSET 10000
优化方案1:各查询先LIMIT再UNION
sql复制(SELECT * FROM large_table1 LIMIT 10010)
UNION
(SELECT * FROM large_table2 LIMIT 10010)
LIMIT 10 OFFSET 10000
优化方案2:使用游标或条件过滤
sql复制SELECT * FROM (
SELECT * FROM large_table1 WHERE id > last_seen_id
UNION
SELECT * FROM large_table2 WHERE id > last_seen_id
) AS combined
LIMIT 10
虽然SQL标准定义了UNION和UNION ALL,但各数据库的实现有细微差别:
假设我们有一个电商系统,需要合并来自两个数据库的订单数据:
场景1:实时交易看板(使用UNION ALL)
sql复制-- 需要实时显示所有交易,性能优先
SELECT order_id, amount, 'DB1' AS source FROM live_orders_db1
UNION ALL
SELECT order_id, amount, 'DB2' AS source FROM live_orders_db2
ORDER BY order_time DESC
LIMIT 100;
场景2:月度财务报告(使用UNION)
sql复制-- 生成精确的月度报表,需要去重
SELECT order_id, customer_id, amount
FROM january_orders
UNION
SELECT order_id, customer_id, amount
FROM february_orders
WHERE payment_status = 'completed';
场景3:大数据量ETL处理(使用临时表)
sql复制-- 处理千万级历史订单数据
CREATE TABLE temp_combined_orders AS
SELECT * FROM historical_orders_2019;
INSERT INTO temp_combined_orders
SELECT * FROM historical_orders_2020;
-- 添加索引提高后续查询性能
CREATE INDEX idx_order_date ON temp_combined_orders(order_date);
-- 最终去重处理
SELECT DISTINCT * FROM temp_combined_orders
WHERE order_value > 1000;
在这个案例中,我们根据不同的业务需求选择了不同的合并策略,平衡了数据准确性和性能要求。