SQL入门:集合运算实战指南
在 SQL 数据查询中,集合运算用于对多个数据集进行 “组合、对比、筛选”,核心包括合并(UNION/UNION ALL)、交集(INTERSECT)、差集(EXCEPT/MINUS),而补集需通过 “全量集合 - 目标集合” 的差集实现。这些运算广泛用于多表数据整合(如合并多平台订单)、数据对比(如找出跨表共同用户)等场景。本文从基础概念到实战场景,全面解析五种集合运算的用法、规则及数据库差异。
一、集合运算的前提:数据兼容性
所有集合运算需满足数据集结构完全兼容,否则会报错,核心要求:
- 字段数量相同:两个数据集的返回字段个数必须一致(如 A 返回 2 个字段,B 也需返回 2 个字段);
- 字段类型兼容:对应位置的字段数据类型需可隐式转换(如
INT
与DECIMAL
兼容,VARCHAR
与TEXT
兼容); - 字段顺序一致:对比时按 “第 1 个字段 vs 第 1 个字段”“第 2 个字段 vs 第 2 个字段” 匹配,与字段名无关(建议字段名一致,避免混淆)。
示例:表 A(user_id INT, username VARCHAR
)与表 B(user_id INT, username VARCHAR
)可运算;若表 B 是(username VARCHAR, user_id INT
),需调整字段顺序后再运算。
二、合并运算:UNION 与 UNION ALL(合并多个集合)
合并运算用于将 “多个结构兼容的数据集” 合并为一个结果集,核心区别是是否去重,是多表数据整合的常用工具。
1. UNION:合并并自动去重
(1)核心概念
UNION
用于合并两个或多个数据集,并自动去除重复记录,最终返回 “所有不重复的记录”。可理解为:A UNION B
= 所有在 A 中或在 B 中的记录(无重复)。
(2)标准语法
-- 合并两个数据集,自动去重
SELECT 字段列表 FROM 表A [WHERE 条件A]
UNION
SELECT 字段列表 FROM 表B [WHERE 条件B]
[ORDER BY 排序字段]; -- 可选,对最终结果排序
(3)关键规则
- 自动去重:无论原数据集是否有重复,合并后仅保留唯一记录;
- 字段匹配:仅对比 “对应位置的字段值”,完全相同的记录才视为重复;
- 性能消耗:去重需额外计算(如排序对比),大数据量下效率低于
UNION ALL
。
(4)实例:合并多平台订单
假设有两个平台的订单表:
app_orders
(手机端订单):order_id INT, user_id INT, amount DECIMAL
;web_orders
(网页端订单):order_id INT, user_id INT, amount DECIMAL
。
需求:合并两个平台的订单,去除重复记录(如同一订单被误录到两个表)。
-- 合并并去重
SELECT order_id, user_id, amount FROM app_orders
UNION
SELECT order_id, user_id, amount FROM web_orders
ORDER BY order_id; -- 按订单ID排序
- 结果:若
app_orders
和web_orders
有相同order_id
的记录,仅保留一条。
2. UNION ALL:合并但不去重
(1)核心概念
UNION ALL
用于合并两个或多个数据集,不进行去重,直接保留所有记录(包括重复项)。可理解为:A UNION ALL B
= 所有在 A 中的记录 + 所有在 B 中的记录(保留重复)。
(2)标准语法
-- 合并两个数据集,保留重复
SELECT 字段列表 FROM 表A [WHERE 条件A]
UNION ALL
SELECT 字段列表 FROM 表B [WHERE 条件B]
[ORDER BY 排序字段];
3)关键规则
- 不去重:原数据中的重复记录会全部保留(如 A 中有 2 条相同记录,B 中有 3 条,结果共 5 条);
- 性能优势:无需去重计算,效率远高于
UNION
,是大数据量合并的首选; - 适用场景:明确无重复记录,或需保留重复记录(如统计总订单量)。
(4)实例:统计多平台总订单量
需求:合并两个平台的订单,统计总订单数(需保留所有订单,包括重复录入的)。
-- 合并并保留所有记录,统计总数
SELECT COUNT(*) AS 总订单量
FROM (SELECT order_id FROM app_ordersUNION ALL -- 不去重,确保所有订单被统计SELECT order_id FROM web_orders
) AS all_orders;
- 逻辑:子查询合并所有订单(含重复),外层
COUNT(*)
统计总数量,结果更准确。
3. UNION 与 UNION ALL 的核心对比
对比维度 | UNION | UNION ALL |
去重行为 | 自动去重 | 不去重,保留所有记录 |
性能 | 低(需排序去重) | 高(直接合并,无额外计算) |
结果行数 | ≤ 两个数据集行数之和 | = 两个数据集行数之和 |
适用场景 | 需去重的合并(如整合唯一记录) | 无需去重的合并(如统计总量) |
核心建议:优先使用UNION ALL
(效率高),仅当明确需要去重时才用UNION
。
三、交集运算:INTERSECT(取共同记录)
1. 核心概念
INTERSECT
用于获取 “同时存在于两个数据集的记录”,即 “既在 A 中,又在 B 中” 的记录,可理解为 “逻辑与”。语法:A INTERSECT B
= 所有在 A 且在 B 中的记录(自动去重)。
2. 标准语法
SELECT 字段列表 FROM 表A [WHERE 条件A]
INTERSECT
SELECT 字段列表 FROM 表B [WHERE 条件B]
[ORDER BY 排序字段];
3. 实例:找出跨平台下单的用户
需求:找出 “同时在手机端和网页端下单” 的用户(即两个订单表的共同用户)。
-- 交集:同时在app和web下单的用户
SELECT DISTINCT user_id FROM app_orders
INTERSECT
SELECT DISTINCT user_id FROM web_orders;
- 结果:仅返回在两个表中都存在的
user_id
,自动去重(即使同一用户下多笔订单,也仅出现一次)。
四、差集运算:EXCEPT 与 MINUS(取独有记录)
差集用于获取 “在第一个数据集(A)中,但不在第二个数据集(B)中的记录”,核心是 “逻辑差”,但需注意顺序敏感性(A-B
≠B-A
)。不同数据库对差集的关键字不同:标准 SQL 用EXCEPT
,Oracle 用MINUS
。
1. EXCEPT(标准 SQL)
(1)核心概念
EXCEPT
返回 “在 A 中但不在 B 中的记录”,自动去重。语法:A EXCEPT B
= 所有在 A 中且不在 B 中的记录。
(2)实例:找出 “仅手机端下单的用户”
需求:找出 “在app_orders
中但不在web_orders
中” 的用户(仅手机端活跃用户)。
-- 差集:仅app下单的用户
SELECT DISTINCT user_id FROM app_orders
EXCEPT
SELECT DISTINCT user_id FROM web_orders;
2. MINUS(Oracle 专属)
(1)核心概念
MINUS
是 Oracle 对差集的实现,功能与EXCEPT
完全一致,仅关键字不同。语法:A MINUS B
= 所有在 A 中且不在 B 中的记录(自动去重)。
(2)实例:Oracle 中找出未下单的注册用户
假设有users
(注册用户)和orders
(订单)表,需求:找出 “已注册但未下单” 的用户。
-- Oracle语法:用MINUS实现差集
SELECT user_id FROM users
MINUS
SELECT DISTINCT user_id FROM orders;
3. EXCEPT/MINUS 的关键规则
- 顺序敏感:
A EXCEPT B
返回 A 的独有记录,B EXCEPT A
返回 B 的独有记录,顺序颠倒结果完全不同; - 自动去重:结果会去除重复记录(即使 A 中有重复的独有记录,结果也仅保留一条);
- NULL 处理:
NULL
与NULL
视为不相等(因 NULL 表示 “未知”,无法确定是否相同)。
五、补集运算:全量集合 - 目标集合(间接实现)
标准 SQL 未直接定义 “补集” 运算符,但补集的本质是 “全量集合中除去目标集合的部分”,可通过 “全量集合EXCEPT
目标集合” 间接实现。
1. 核心概念
补集 = 全量集合 - 目标集合,即 “所有符合全量范围,但不符合目标条件” 的记录。前提:需先明确 “全量集合”(如 “所有用户”“所有商品”)。
2. 实现语法
-- 补集 = 全量集合 EXCEPT 目标集合
SELECT 字段列表 FROM 全量表 [WHERE 全量条件] -- 定义全量范围
EXCEPT
SELECT 字段列表 FROM 目标表 [WHERE 目标条件]; -- 定义需排除的范围
3. 实例:找出未实名认证的用户
假设:
- 全量集合:
users
表中所有注册用户; - 目标集合:
user_cert
表中已实名认证的用户。
需求:找出 “所有注册用户中未实名认证” 的用户。
-- 补集:未实名认证用户 = 所有用户 - 已认证用户
SELECT user_id, username FROM users
EXCEPT
SELECT u.user_id, u.username
FROM users u
JOIN user_cert c ON u.user_id = c.user_id
WHERE c.cert_status = '已认证';
六、主流数据库的集合运算兼容性
不同数据库对集合运算的支持程度不同,核心差异如下表,新手需重点关注:
数据库 | UNION | UNION ALL | INTERSECT | EXCEPT | MINUS |
MySQL 8.0+ | √ | √ | √ | √ | × |
MySQL 5.7 及以下 | × | √ | × | × | × |
PostgreSQL | √ | √ | √ | √ | × |
SQL Server | √ | √ | √ | √ | × |
Oracle | √ | √ | √ | × | √ |
避坑指南:
- MySQL 5.7 及以下:无
INTERSECT
/EXCEPT
,需用INNER JOIN
(交集)、LEFT JOIN + IS NULL
(差集)替代; - Oracle:差集用
MINUS
,而非EXCEPT
; - 保留重复记录:若需保留原数据中的重复项(如 “重复的独有记录”),PostgreSQL/SQL Server 可用
EXCEPT ALL
,其他数据库需用JOIN
实现。
七、实战场景:集合运算的综合应用
场景 1:多表数据整合与统计
需求:有 3 个区域的销售表(north_sales
、south_sales
、east_sales
),需合并所有销售数据,统计 “总销售额” 和 “唯一产品数”。
WITH all_sales AS (-- 用UNION ALL合并所有销售记录(保留重复,确保销售额准确)SELECT product_id, amount FROM north_salesUNION ALLSELECT product_id, amount FROM south_salesUNION ALLSELECT product_id, amount FROM east_sales
)
SELECTSUM(amount) AS 总销售额, -- 统计总销售额(保留重复,结果准确)COUNT(DISTINCT product_id) AS 唯一产品数 -- 统计唯一产品(去重)
FROM all_sales;
场景 2:用户行为分析(新增 vs 留存)
需求:已知 “2024 年 1 月注册用户” 和 “2024 年 2 月下单用户”,需分析:
- 1 月注册且 2 月下单的用户(留存用户);
- 1 月注册但 2 月未下单的用户(流失用户)。
-- 定义两个基础数据集
WITH jan_reg_users AS ( -- 1月注册用户(全量)SELECT user_id FROM usersWHERE register_time BETWEEN '2024-01-01' AND '2024-01-31'
),
feb_order_users AS ( -- 2月下单用户(目标)SELECT DISTINCT user_id FROM ordersWHERE create_time BETWEEN '2024-02-01' AND '2024-02-29'
)
-- 1. 留存用户(交集)
SELECT user_id AS 留存用户 FROM jan_reg_users
INTERSECT
SELECT user_id FROM feb_order_users;-- 2. 流失用户(差集)
SELECT user_id AS 流失用户 FROM jan_reg_users
EXCEPT
SELECT user_id FROM feb_order_users;
场景 3:数据清洗(删除无效记录)
需求:orders
表中有部分user_id
在users
表中不存在(无效订单),找出并删除这些无效订单。
-- 步骤1:用差集找出无效订单(orders中无对应user_id的记录)
SELECT order_id FROM orders
EXCEPT
SELECT o.order_id
FROM orders o
JOIN users u ON o.user_id = u.user_id;-- 步骤2:删除无效订单(谨慎操作,建议先查询确认)
DELETE FROM orders
WHERE order_id IN (SELECT order_id FROM ordersEXCEPTSELECT o.order_id FROM orders oJOIN users u ON o.user_id = u.user_id
);
八、常见误区与避坑指南
1. 误区 1:忽略字段兼容性(数量 / 类型 / 顺序)
问题:两个数据集字段数量不同,或类型 / 顺序不匹配,导致运算报错。
-- 错误示例1:字段数量不同(A返回2个字段,B返回1个)
SELECT user_id, username FROM users
UNION
SELECT user_id FROM orders; -- 报错:字段数量不匹配-- 错误示例2:字段类型不兼容(A的amount是INT,B的amount是VARCHAR)
SELECT order_id, amount FROM app_orders
UNION
SELECT order_id, amount FROM web_orders; -- 报错:类型不兼容
解决:确保所有数据集的字段数量、类型、顺序完全一致(可通过CAST
转换类型,调整字段顺序)。
2. 误区 2:滥用 UNION(无需去重时用了 UNION)
问题:明确无重复记录,却用UNION
(而非UNION ALL
),导致性能浪费。
-- 需求:合并两个无重复的区域销售表,统计总金额
-- 错误:用UNION去重(多余计算,效率低)
SELECT amount FROM north_sales
UNION
SELECT amount FROM south_sales;-- 正确:用UNION ALL(无多余计算,效率高)
SELECT amount FROM north_sales
UNION ALL
SELECT amount FROM south_sales;
解决:优先用UNION ALL
,仅当需去重时才用UNION
。
3. 误区 3:混淆差集顺序(A-B vs B-A)
问题:误将B EXCEPT A
当作A EXCEPT B
,导致结果完全相反。
-- 需求:找出“1月注册但2月未下单”的用户(A=1月注册,B=2月下单)
-- 错误:B-A=2月下单但1月未注册的用户(与需求相反)
SELECT user_id FROM feb_order_users
EXCEPT
SELECT user_id FROM jan_reg_users;
解决:明确 “源集合(需保留的基础集合)” 和 “排除集合(需去除的集合)”,按 “源集合 EXCEPT 排除集合” 编写。
4. 误区 4:依赖集合运算的默认排序
问题:部分数据库(如 SQL Server)对集合运算结果默认排序,若业务需固定顺序,未显式加ORDER BY
。
-- 风险:结果顺序可能随数据库版本或数据量变化
SELECT user_id FROM users EXCEPT SELECT user_id FROM orders;-- 安全:显式指定排序,确保结果稳定
SELECT user_id FROM users EXCEPT SELECT user_id FROM orders
ORDER BY user_id;
解决:无论数据库是否默认排序,均显式添加ORDER BY
,避免结果顺序异常。
九、总结
集合运算是 SQL 处理多数据集的核心工具,不同运算的适用场景和核心差异如下:
运算类型 | 关键字 / 实现方式 | 核心作用 | 去重行为 | 适用场景 |
合并 | UNION | 合并并去重 | 自动去重 | 整合唯一记录(如跨表唯一用户) |
合并 | UNION ALL | 合并不去重 | 不去重 | 统计总量、保留重复记录(如多平台总订单) |
交集 | INTERSECT | 取两个集合的共同记录 | 自动去重 | 找出跨表共同数据(如跨平台下单用户) |
差集 | EXCEPT(标准)/MINUS(Oracle) | 取 A 的独有记录(A-B) | 自动去重 | 找出单表独有数据(如未下单的注册用户) |
补集 | 全量集合 EXCEPT 目标集合 | 取全量中除去目标的记录 | 自动去重 | 找出全量中的例外数据(如未认证用户) |
实际开发中,需根据业务需求选择合适的运算:
- 合并数据优先用
UNION ALL
(效率高); - 对比共同数据用
INTERSECT
; - 对比独有数据用
EXCEPT
(或 Oracle 的MINUS
); - 全量例外数据用 “全量集合 - 目标集合” 的补集实现。
同时,需注意数据库兼容性差异,避免语法错误;大数据量场景下,优先用JOIN
替代INTERSECT
/EXCEPT
(效率更高),确保查询性能。