vault backup: 2026-05-21 12:20:54
This commit is contained in:
@@ -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 往返搞定多数据源混合 |
|
||||
|
||||
Reference in New Issue
Block a user