SQL 查询数据:组合查询

在 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 核心规则

使用UNION 组合查询时,必须遵守以下规则,否则会报错:

(1)列数必须一致

每个 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 和 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)”,两者的核心区别如下:

  • 外部联结:基于关联键 “横向拼接” 数据(一行数据包含多张表的列);

  • 组合查询:将多个结果集 “纵向叠加” 数据(结果集行数增加,列数不变)。

下面将从本质上区分这两个操作。

外部联结(Outer Join)

外部联结是横向拼接数据,基于关联字段(如主键 / 外键)将多个表的列合并到同一行。常用于获取多个表中 “相关联” 的数据,同时保留其中一个或两个表中没有匹配的行(如左外连接保留左表所有行,右外连接保留右表,全外连接保留两者)。

外部联结最终结果的列数是参与联结的表的列数之和(去重后),行数取决于匹配情况 + 未匹配的行。

示例:联结商品和商品分类表,查询商品信息和商品分类信息

> 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)

组合查询的本质是纵向拼接数据,将多个查询结果的行合并到同一结果集中,要求列数、列类型一致。主要用于合并多个 “结构相同” 的查询结果(比如从不同表 / 不同条件查同类型数据)。

组合查询最终结果的列数和单个查询的列数一致,行数是多个查询结果的行数之和(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.

  

说说我的看法
全部评论(
没有评论
关于
本网站专注于 Java、数据库(MySQL、Oracle)、Linux、软件架构及大数据等多领域技术知识分享。涵盖丰富的原创与精选技术文章,助力技术传播与交流。无论是技术新手渴望入门,还是资深开发者寻求进阶,这里都能为您提供深度见解与实用经验,让复杂编码变得轻松易懂,携手共赴技术提升新高度。如有侵权,请来信告知:hxstrive@outlook.com
其他应用
公众号