在 SQL 查询中,当需要将多个独立查询的结果集合并为一个统一结果时,就需要用到 “组合查询”(也称为 “合并查询”)。组合查询的核心价值在于能高效整合多组符合不同条件的数据,避免编写重复的查询逻辑。本文将围绕组合查询的核心技术 —— UNION 操作符,从基础创建、使用规则、重复行处理到结果排序,结合实例详细讲解其用法与应用场景。
组合查询是指将两个或多个 SELECT 语句的结果集合并成一个单一结果集的查询方式。这些 SELECT 语句通常是针对同一结构的数据源(如同一张表的不同条件数据,或结构一致的多张表),最终合并的结果集将拥有统一的列结构。
组合查询常见应用场景:
合并同一表中不同条件的数据(如查询 “销售部” 和 “技术部” 的员工信息)。
整合结构一致的多张表数据(如合并 2023 年和 2024 年的订单表数据)。
补充查询结果(如将 “特殊用户” 数据与 “普通用户” 数据合并展示)。
在 SQL 中实现组合查询的核心是 UNION 操作符,它用于连接多个 SELECT 语句并合并其结果。此外还有 UNION ALL 变体,用于保留重复行。
组合查询的基本语法格式如下:
SELECT 列1, 列2, ... FROM 表1 [WHERE 条件1] UNION [ALL] SELECT 列1, 列2, ... FROM 表2 [WHERE 条件2] UNION [ALL] SELECT 列1, 列2, ... FROM 表3 [WHERE 条件3];
注意:
(1)组合查询要求多个 SELECT 语句返回的列数必须相同,且对应列的数据类型需兼容(如 INT 与 DECIMAL 兼容,VARCHAR 与 TEXT 兼容)。
(2)UNION 将默认去除合并结果集中的重复行(相当于执行了 DISTINCT)。
(3)UNION ALL 将保留所有结果行,包括重复行,性能优于 UNION(无需去重计算)。
实例 1:最简单的示例,方便了解 UNION 和 UNION ALL 的区别
-- 使用 union,去重 > select 'hello' as value union select 'hello' as value value| -----+ hello| 1 row(s) fetched. -- 使用 union all,不去重 > select 'hello' as value union all select 'hello' as value value| -----+ hello| hello| 2 row(s) fetched.
实例 2:合并员工数据(去重),下面将查询“年龄小于30岁”和“年龄大于25岁”的所有员工信息,去除重复记录
> select * from user where age < 30 union select * from user where age > 25 user_id|name|gender|age|phone |email |address |create_time |update_time | -------+----+------+---+-----------+---------------------+---------------+-------------------+-------------------+ 1|张三 |男 | 25|13800138000|zhangsan@example.com |北京市朝阳区建国路88号 |2025-01-28 22:14:00|2026-01-06 22:56:28| 3|王五 |男 | 28|13800138002|wangwu@example.com |广州市天河区天河路385号 |2025-01-13 07:24:00|2026-01-06 22:56:28| 4|赵六 |女 | 22|13800138003|zhaoliu@example.com |深圳市南山区科技园10号 |2025-01-22 21:22:00|2026-01-06 22:56:28| 6|周八 |女 | 27|13800138005|zhouba@example.com |成都市武侯区武侯祠大街231号|2025-01-27 22:56:00|2026-01-06 22:56:28| 8|郑十 |女 | 29|13800138007|zhengshi@example.com |武汉市武昌区中南路99号 |2025-01-16 03:39:00|2026-01-06 22:56:28| 9|钱十一 |男 | 24|13800138008|qianshiyi@example.com|西安市雁塔区雁塔路15号 |2025-01-25 03:36:00|2026-01-06 22:56:28| 2|李四 |女 | 30|13800138001|lisi@example.com |上海市浦东新区张江路100号 |2025-01-14 05:13:00|2026-01-06 22:56:28| 5|孙七 |男 | 35|13800138004| |杭州市西湖区西湖路58号 |2025-01-13 12:36:00|2026-01-06 22:56:28| 7|吴九 | | 32|13800138006|wujiu@example.com |重庆市渝中区解放碑路18号 |2025-01-08 04:53:00|2026-01-06 22:56:28| 10|孙十二 |女 | 31|13800138009| |南京市玄武区玄武湖路78号 |2025-01-16 07:02:00|2026-01-06 22:56:28| 10 row(s) fetched.
实例 3:合并员工数据(不去重),下面将查询“年龄小于30岁”和“年龄大于25岁”的所有员工信息,保留重复记录
> select * from user where age < 30 union all select * from user where age > 25 user_id|name|gender|age|phone |email |address |create_time |update_time | -------+----+------+---+-----------+---------------------+---------------+-------------------+-------------------+ 1|张三 |男 | 25|13800138000|zhangsan@example.com |北京市朝阳区建国路88号 |2025-01-28 22:14:00|2026-01-06 22:56:28| 3|王五 |男 | 28|13800138002|wangwu@example.com |广州市天河区天河路385号 |2025-01-13 07:24:00|2026-01-06 22:56:28| 4|赵六 |女 | 22|13800138003|zhaoliu@example.com |深圳市南山区科技园10号 |2025-01-22 21:22:00|2026-01-06 22:56:28| 6|周八 |女 | 27|13800138005|zhouba@example.com |成都市武侯区武侯祠大街231号|2025-01-27 22:56:00|2026-01-06 22:56:28| 8|郑十 |女 | 29|13800138007|zhengshi@example.com |武汉市武昌区中南路99号 |2025-01-16 03:39:00|2026-01-06 22:56:28| 9|钱十一 |男 | 24|13800138008|qianshiyi@example.com|西安市雁塔区雁塔路15号 |2025-01-25 03:36:00|2026-01-06 22:56:28| 2|李四 |女 | 30|13800138001|lisi@example.com |上海市浦东新区张江路100号 |2025-01-14 05:13:00|2026-01-06 22:56:28| 3|王五 |男 | 28|13800138002|wangwu@example.com |广州市天河区天河路385号 |2025-01-13 07:24:00|2026-01-06 22:56:28| 5|孙七 |男 | 35|13800138004| |杭州市西湖区西湖路58号 |2025-01-13 12:36:00|2026-01-06 22:56:28| 6|周八 |女 | 27|13800138005|zhouba@example.com |成都市武侯区武侯祠大街231号|2025-01-27 22:56:00|2026-01-06 22:56:28| 7|吴九 | | 32|13800138006|wujiu@example.com |重庆市渝中区解放碑路18号 |2025-01-08 04:53:00|2026-01-06 22:56:28| 8|郑十 |女 | 29|13800138007|zhengshi@example.com |武汉市武昌区中南路99号 |2025-01-16 03:39:00|2026-01-06 22:56:28| 10|孙十二 |女 | 31|13800138009| |南京市玄武区玄武湖路78号 |2025-01-16 07:02:00|2026-01-06 22:56:28| 13 row(s) fetched.
实例 4:合并同一表中不同条件的数据,查询 user 表中“年龄小于25岁”和“年龄大于30岁”的员工信息:
-- 同一表的两个条件查询,用UNION合并 > select * from user where age < 25 union select * from user where age > 30 user_id|name|gender|age|phone |email |address |create_time |update_time | -------+----+------+---+-----------+---------------------+-------------+-------------------+-------------------+ 4|赵六 |女 | 22|13800138003|zhaoliu@example.com |深圳市南山区科技园10号 |2025-01-22 21:22:00|2026-01-06 22:56:28| 9|钱十一 |男 | 24|13800138008|qianshiyi@example.com|西安市雁塔区雁塔路15号 |2025-01-25 03:36:00|2026-01-06 22:56:28| 5|孙七 |男 | 35|13800138004| |杭州市西湖区西湖路58号 |2025-01-13 12:36:00|2026-01-06 22:56:28| 7|吴九 | | 32|13800138006|wujiu@example.com |重庆市渝中区解放碑路18号|2025-01-08 04:53:00|2026-01-06 22:56:28| 10|孙十二 |女 | 31|13800138009| |南京市玄武区玄武湖路78号|2025-01-16 07:02:00|2026-01-06 22:56:28| 5 row(s) fetched.
使用UNION 组合查询时,必须遵守以下规则,否则会报错:
每个 SELECT 语句返回的列数必须相同。例如,第一个 SELECT 查询3列,后续所有 SELECT 也必须查询3列,不能多列或少列。例如:
> select user_id, name, gender from user union select user_id, name from user SQL 错误 [1222] [21000]: The used SELECT statements have a different number of columns SQL 错误 [1222] [21000]: 使用的 SELECT 语句的列数不一致
(2)对应列数据类型需兼容
多个 SELECT 语句中,对应位置的列的数据类型必须兼容(可隐式转换),不能完全不匹配。例如:INT 与 DECIMAL 兼容,VARCHAR 与 TEXT 兼容,但 INT 与 VARCHAR 通常不兼容(部分数据库可能允许,但不推荐)。
示例:下面是在 MySQL 中尝试,没有报错,能正常查询,但是不推荐这么做。
> select user_id, name, age from user where age<25 union select user_id, age, name from user where age>25 and age<30 user_id|name|age| -------+----+---+ 4|赵六 |22 | 9|钱十一 |24 | 3|28 |王五 | 6|27 |周八 | 8|29 |郑十 | 5 row(s) fetched. > select user_id, name, age, create_time from user where age<25 union select user_id, age, create_time as times, name from user where age>25 and age<30 user_id|name|age |create_time | -------+----+-------------------+-------------------+ 4|赵六 |22 |2025-01-12 18:42:00| 9|钱十一 |24 |2025-01-26 03:42:00| 3|28 |2025-01-17 11:38:00|王五 | 6|27 |2025-01-12 21:04:00|周八 | 8|29 |2025-01-30 16:00:00|郑十 | 5 row(s) fetched.
(3)列名由第一个 SELECT 决定
合并后的结果集的列名,由第一个 SELECT 语句中的列名决定,后续 SELECT 的列名无论是什么,都会被第一个的列名覆盖。因此,建议让所有 SELECT 的对应列名一致,提升可读性。例如:
> select user_id, name, age, create_time from user where age<25 union select user_id as id, name as username, age, create_time from user where age>25 and age<30 user_id|name|age|create_time | -------+----+---+-------------------+ 4|赵六 | 22|2025-01-12 18:42:00| 9|钱十一 | 24|2025-01-26 03:42:00| 3|王五 | 28|2025-01-17 11:38:00| 6|周八 | 27|2025-01-12 21:04:00| 8|郑十 | 29|2025-01-30 16:00:00| 5 row(s) fetched.
上面 SQL 中,第二个 SELECT 将 user_id 和 name 设置别名分别为 id 和 username,但是被第一个 SELECT 覆盖了。
(4)可以对单个SELECT使用WHERE、LIMIT等子句
可以为每个 SELECT 语句单独添加 WHERE(过滤条件)、LIMIT(限制行数)等子句,实现对单个结果集的精准控制。例如:
> select user_id, name, age, create_time from user where age<25 union select user_id, name, age, create_time from user where age>25 and age<30 user_id|name|age|create_time | -------+----+---+-------------------+ 4|赵六 | 22|2025-01-12 18:42:00| 9|钱十一 | 24|2025-01-26 03:42:00| 3|王五 | 28|2025-01-17 11:38:00| 6|周八 | 27|2025-01-12 21:04:00| 8|郑十 | 29|2025-01-30 16:00:00| 5 row(s) fetched. -- 错误写法 > select user_id, name, age, create_time from user where age<25 limit 2 union select user_id, name, age, create_time from user where age>25 and age<30 limit 2 SQL 错误 [1064] [42000]: You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'union select user_id, name, age, create_time from user where age>25 and age<30 ' at line 2 -- 正确写法 > (select user_id, name, age, create_time from user where age<25 limit 1,2) union (select user_id, name, age, create_time from user where age>25 and age<30 limit 1,2) user_id|name|age|create_time | -------+----+---+-------------------+ 9|钱十一 | 24|2025-01-26 03:42:00| 6|周八 | 27|2025-01-12 21:04:00| 8|郑十 | 29|2025-01-30 16:00:00| 3 row(s) fetched.
注意,MySQL 中,LIMIT 不能直接跟在单个 UNION 子查询的末尾(除非给子查询加括号),因为 UNION 会将多个查询结果合并,数据库无法直接识别分散的 LIMIT 归属。正确的写法是给每个子查询加上括号,明确 LIMIT 作用于单个子查询,而非整个联合结果。
重复行的处理是组合查询的关键场景,核心通过 UNION 和 UNION ALL 区分,具体差异如下表:
操作符 | 重复行处理 | 性能 | 适用场景 |
UNION | 自动去除重复行(DISTINCT) | 较差(需额外执行去重计算) | 需合并无重复数据的场景(如合并不同时期的不重复记录) |
UNION ALL | 保留所有重复行 | 较好(无需去重,直接合并) | 需保留完整数据的场景(如统计总条数、分析重复数据) |
注意:请优先使用 UNION ALL(除非必须去重)。由于 UNION 需要额外进行去重计算,在数据量较大时,性能会明显低于 UNION ALL。因此,若业务场景无需去重(如合并的两个结果集本身就无重复),或需要保留重复行,请使用 UNION ALL,提升查询效率。
组合查询的结果集是可以进行排序的,但需注意:排序操作必须放在最后一个 SELECT 语句之后,且排序规则作用于整个合并后的结果集,不能对单个 SELECT 的结果集单独排序。
语法如下:
SELECT 列1, 列2 FROM 表1 UNION [ALL] SELECT 列1, 列2 FROM 表2 ORDER BY 排序列 [ASC/DESC]; -- 排序放在最后,作用于整个结果集
在 MySQL 中,可以对单个查询进行排序,但是排序没有任何效果,如下:
> (select user_id, name, age, create_time from user where age<25 order by user_id desc) union (select user_id, name, age, create_time from user where age>25 and age<30 order by user_id asc) user_id|name|age|create_time | -------+----+---+-------------------+ 4|赵六 | 22|2025-01-12 18:42:00| 9|钱十一 | 24|2025-01-26 03:42:00| 3|王五 | 28|2025-01-17 11:38:00| 6|周八 | 27|2025-01-12 21:04:00| 8|郑十 | 29|2025-01-30 16:00:00| 5 row(s) fetched.
示例1:按年龄降序排序合并结果
> select user_id, name, age, create_time from user where age<25 union select user_id, name, age, create_time from user where age>25 and age<30 order by age desc user_id|name|age|create_time | -------+----+---+-------------------+ 8|郑十 | 29|2025-01-30 16:00:00| 3|王五 | 28|2025-01-17 11:38:00| 6|周八 | 27|2025-01-12 21:04:00| 9|钱十一 | 24|2025-01-26 03:42:00| 4|赵六 | 22|2025-01-12 18:42:00| 5 row(s) fetched.
示例2:按多列排序,先按年龄升序,再按创建时间降序排序
> select user_id, name, age, create_time from user where age<25 union select user_id, name, age, create_time from user where age>25 and age<30 order by age asc, create_time desc user_id|name|age|create_time | -------+----+---+-------------------+ 4|赵六 | 22|2025-01-12 18:42:00| 9|钱十一 | 24|2025-01-26 03:42:00| 6|周八 | 27|2025-01-12 21:04:00| 3|王五 | 28|2025-01-17 11:38:00| 8|郑十 | 29|2025-01-30 16:00:00| 5 row(s) fetched.
示例3:使用列别名排序。若合并后的列名较长,可在第一个 SELECT 中给列起别名,排序时使用别名
> select user_id, name, age as user_age, create_time from user where age<25 union select user_id, name, age, create_time from user where age>25 and age<30 order by user_age asc user_id|name|user_age|create_time | -------+----+--------+-------------------+ 4|赵六 | 22|2025-01-12 18:42:00| 9|钱十一 | 24|2025-01-26 03:42:00| 6|周八 | 27|2025-01-12 21:04:00| 3|王五 | 28|2025-01-17 11:38:00| 8|郑十 | 29|2025-01-30 16:00:00| 5 row(s) fetched.
很多初学者会混淆 “外部联结(LEFT/RIGHT JOIN)” 和 “组合查询(UNION)”,两者的核心区别如下:
外部联结:基于关联键 “横向拼接” 数据(一行数据包含多张表的列);
组合查询:将多个结果集 “纵向叠加” 数据(结果集行数增加,列数不变)。
下面将从本质上区分这两个操作。
外部联结是横向拼接数据,基于关联字段(如主键 / 外键)将多个表的列合并到同一行。常用于获取多个表中 “相关联” 的数据,同时保留其中一个或两个表中没有匹配的行(如左外连接保留左表所有行,右外连接保留右表,全外连接保留两者)。
外部联结最终结果的列数是参与联结的表的列数之和(去重后),行数取决于匹配情况 + 未匹配的行。
示例:联结商品和商品分类表,查询商品信息和商品分类信息
> select p.product_id, p.name, p.price, p.stock, pc.name as 'category_name' from product p join product_category pc on p.category_id=pc.category_id where p.price > 5000 product_id|name |price |stock|category_name| ----------+-------------+--------+-----+-------------+ 2|华为Mate60 Pro | 6999.00| 800|手机 | 3|苹果iPhone 15 | 7999.00| 1200|手机 | 10|三星S24 Ultra | 8999.00| 600|手机 | 13|联想拯救者Y9000P | 8999.00| 300|电脑 | 14|苹果MacBook Pro|12999.00| 180|电脑 | 5 row(s) fetched.
组合查询的本质是纵向拼接数据,将多个查询结果的行合并到同一结果集中,要求列数、列类型一致。主要用于合并多个 “结构相同” 的查询结果(比如从不同表 / 不同条件查同类型数据)。
组合查询最终结果的列数和单个查询的列数一致,行数是多个查询结果的行数之和(Union 会去重,Union All 保留重复)。
示例:使用组合查询将用户年龄小于 25 岁,用户年龄大于等于 30 且小于 40 岁 的查询结果进行合并
> select user_id,name,gender,age from user where age<25 union select user_id,name,gender,age from user where age>=30 and age<40 user_id|name|gender|age| -------+----+------+---+ 4|赵六 |女 | 22| 9|钱十一 |男 | 24| 2|李四 |女 | 30| 5|孙七 |男 | 35| 7|吴九 | | 32| 10|孙十二 |女 | 31| 6 row(s) fetched.