两个集合运算符
INTERSECT 取两个查询都有的行,EXCEPT 取前一个查询有、后一个查询没有的行,两者默认都去重,属于 SQL 标准集合运算。MySQL 从 8.0.31 才开始支持这两个关键字,更早的版本要用 JOIN 与 NOT EXISTS 模拟;PostgreSQL 一直支持,Oracle 里的差集写作 MINUS。
基本信息
| 适用版本 | MySQL 8.0.31+ 支持;PostgreSQL 各版本都支持 |
|---|---|
| 去重行为 | 默认去重,保留重复要写 INTERSECT ALL / EXCEPT ALL |
| 比较口径 | 按整行比较,列个数、顺序与类型必须一致 |
| 运算优先级 | MySQL 中 INTERSECT 高于 UNION 与 EXCEPT,可用括号调整 |
| Oracle 兼容 | 差集写作 MINUS |
典型使用场景
- 求两个用户集合的公共成员
- 从活跃用户里剔除黑名单
- 对比测试库与生产库的数据差异
- 两次统计结果的口径对齐检查
- 替代多层嵌套的 NOT IN 与 EXISTS
标准写法
标准写法
-- 交集:两张表都有的 id
SELECT id FROM members
INTERSECT
SELECT id FROM paid_users;
-- 差集:members 里有而黑名单里没有的 id
SELECT id FROM members
EXCEPT
SELECT id FROM blacklist; -- 标准与 PG 写法;Oracle 叫 MINUS
-- 需要保留重复行时
SELECT id FROM members
EXCEPT ALL
SELECT id FROM blacklist;兼容写法
兼容写法
-- 交集用 INNER JOIN 模拟
SELECT m.id FROM members m
JOIN paid_users p ON p.id = m.id;
-- 差集用 NOT EXISTS 模拟,右表含 NULL 也不会像 NOT IN 那样整段变空
SELECT m.id FROM members m
WHERE NOT EXISTS (SELECT 1 FROM blacklist b WHERE b.id = m.id);写法与等价替代
| 写法 | 语义 | 等价替代 |
|---|---|---|
| INTERSECT | 两边都有的行,去重 | INNER JOIN 或 IN 子查询 |
| EXCEPT | 左边有、右边没有的行,去重 | NOT EXISTS |
| INTERSECT ALL | 两边都有的行,保留重复 | 需计数比较,不易用单条 JOIN 表达 |
| EXCEPT ALL | 差集且保留重复 | 同上,需要按行计数 |
| MINUS | Oracle 的差集,语义同 EXCEPT | 迁移时逐一改写 |
这两个运算符按整行比较,参与比较的列顺序与类型要严格对齐,否则差异列表会出现大量伪差异;只关心部分列时就显式 SELECT 出那些列,不要在末尾用 SELECT *。
旧版本 MySQL 用 NOT EXISTS 模拟差集比 NOT IN 更稳妥:右表含 NULL 时 NOT IN 的结果可能整段为空,NOT EXISTS 不受影响。
集合运算的先后顺序会改变结果:先取差集再求交集,与先求交集再取差集得到的行并不相同,复杂口径要用括号把运算顺序写清楚。