vault backup: 2026-05-21 12:20:54

This commit is contained in:
hhs
2026-05-21 12:20:54 +08:00
parent 2e84683d9c
commit 531d4b7d8c
8 changed files with 867 additions and 89 deletions
@@ -1,5 +1,5 @@
---
tags: [MySQL, UNION, UNION ALL, 集合运算]
tags: [MySQL, UNION, UNION ALL, INTERSECT, EXCEPT, 集合运算]
create time: 2026-05-16 00:00
---
@@ -47,6 +47,114 @@ flowchart LR
> [!TIP] 首选 UNION ALL
> 如果你能通过业务逻辑保证各部分结果不重复(比如按日期区间划分),**一律用 UNION ALL**。UNION 的去重操作需要 Sort/Dedup 阶段,在大结果集上是昂贵操作。
## INTERSECT 与 EXCEPT(MySQL 8.0.22+)
MySQL 8.0.22 开始支持标准 SQL 的 `INTERSECT`(交集)和 `EXCEPT`(差集),补齐了集合运算的最后一块拼图。
### 基本语法
```sql
-- INTERSECT:取两个查询的交集(两边都有的行)
SELECT city FROM customers_china
INTERSECT
SELECT city FROM customers_japan;
-- 结果:只返回同时存在于两个结果集中的城市
-- EXCEPT:取差集(左边有、右边没有的行)
SELECT city FROM customers_china
EXCEPT
SELECT city FROM customers_japan;
-- 结果:返回在中国有但日本没有的城市
```
> [!QUESTION] INTERSECT vs IN / EXISTS?
> 两者在语义上等价,但 `INTERSECT` 更直观地表达了"取交集"的意图。实际执行中,Optimizer 通常会将 INTERSECT 转换为 Semi Join 或 Anti Join——因此性能差异不大,选择哪种取决于**可读性**。
### INTERSECT / EXCEPT vs UNION 行为对比
```mermaid
flowchart LR
A["查询 A: {1,2,3}"] --> OP
B["查询 B: {2,3,4}"] --> OP
OP --> R1["UNION: {1,2,3,4}"]
OP --> R2["INTERSECT: {2,3}"]
OP --> R3["EXCEPT: {1}"]
style R1 fill:#74C0FC,color:#000
style R2 fill:#00D866,color:#fff
style R3 fill:#FF9F43,color:#000
```
| 操作 | 语义 | 去重 | 对应集合论 |
|------|------|------|-----------|
| `UNION` | A ∪ B | ✅ 默认去重 | 并集 |
| `UNION ALL` | A ∪ B(含重复) | ❌ | 多重集并 |
| `INTERSECT` | A ∩ B | ✅ 默认去重 | 交集 |
| `EXCEPT` | A − B | ✅ 默认去重 | 差集 |
> [!NOTE] 默认行为:去重
> INTERSECT 和 EXCEPT 默认都会去重(等价于 DISTINCT)。如果想去重保留所有行,目前 **MySQL 不支持 `INTERSECT ALL` / `EXCEPT ALL`**,需要通过 UNION ALL + 计数的方式模拟。
### 实用场景
```sql
-- 场景一:找出"既买了 A 又买了 B"的用户(交集)
SELECT user_id FROM orders WHERE product_id = 'A'
INTERSECT
SELECT user_id FROM orders WHERE product_id = 'B';
-- 等价的 EXISTS 写法(对比可读性)
SELECT DISTINCT o1.user_id
FROM orders o1
WHERE o1.product_id = 'A'
AND EXISTS (SELECT 1 FROM orders o2 WHERE o2.user_id = o1.user_id AND o2.product_id = 'B');
-- 场景二:找出"注册了但从未下单"的用户(差集)
SELECT id FROM users
EXCEPT
SELECT DISTINCT user_id FROM orders;
-- 等价的 NOT EXISTS 写法
SELECT u.id FROM users u
WHERE NOT EXISTS (SELECT 1 FROM orders o WHERE o.user_id = u.id);
```
> [!TIP] 何时选 INTERSECT/EXCEPT?
> - **逻辑简单、需要取交集/差集**:`INTERSECT` / `EXCEPT` 语法最清晰
> - **需要返回关联表的字段**:用 `EXISTS` / `NOT EXISTS` 或 JOIN,因为 INTERSECT/EXCEPT 只能操作整行或指定列
> - **需要高性能**:大表场景下 EXPLAIN 确认执行计划——Optimizer 通常会转换为 Semi/Anti Join,性能与手写子查询一致
### 优先级与括号
当混合使用多个集合运算时,默认**从左到右**执行。需要用括号改变优先级:
```sql
-- 先 UNION 再 INTERSECT(左到右)
SELECT id FROM t1
UNION
SELECT id FROM t2
INTERSECT
SELECT id FROM t3;
-- 实际含义:t1 UNION (t2 INTERSECT t3) ← 错误!
-- 实际执行:(t1 UNION t2) INTERSECT t3 ← 从左到右
-- ✅ 用括号明确意图
(SELECT id FROM t1 UNION SELECT id FROM t2)
INTERSECT
SELECT id FROM t3;
-- 也可以用 ORDER BY + LIMIT 修饰每个子句(需括号)
(SELECT id FROM t1 ORDER BY id LIMIT 10)
INTERSECT
(SELECT id FROM t2 ORDER BY id LIMIT 5);
```
> [!WARNING] 注意括号的必要性
> 当 INTERSECT/EXCEPT 与 ORDER BY/LIMIT 混用时,不加括号可能产生歧义。**养成给每个子查询加括号的习惯**,避免优先级意外。
---
## 实战场景
### 场景一:多表同构合并
@@ -348,6 +456,7 @@ flowchart TD
| 主题 | 一句话 |
|------|--------|
| UNION ALL vs UNION | 能用 ALL 就绝不用 UNION — 去重的临时表+文件排序代价远超想象 |
| INTERSECT / EXCEPT | 8.0.22+ 原生支持交集与差集,语义比 IN/EXISTS 更直观,Optimizer 会转为 Semi/Anti Join |
| ORDER BY/LIMIT 作用域 | 只在 UNION 最后一个 SELECT 生效;跨表排序必须外层包装子查询 |
| 字段匹配规则 | 列数必须相同,类型应尽量兼容 — 列名取自第一个 SELECT |
| Feed 流/多源合并 | UNION ALL 的经典场景,一次 DB 往返搞定多数据源混合 |