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
@@ -136,6 +136,80 @@ SELECT a.name, b.name FROM table_a CROSS JOIN table_b;
-- 结果 = |a| × |b| 行
```
## SELF JOIN(自连接)
自连接是**同一张表与自身进行 JOIN**——表没有变,只是用两个不同的别名把它"当成两张表"来用。这是处理**层次结构**和**行间比较**的经典手法。
### 场景一:组织架构树(上下级关系)
```sql
-- 找出每个员工及其直属上级的名字
SELECT e.name AS employee, m.name AS manager
FROM employees e
LEFT JOIN employees m ON e.manager_id = m.id;
-- 结果:
-- | employee | manager |
-- | Alice | NULL | ← CEO,无上级
-- | Bob | Alice |
-- | Carol | Alice |
-- | David | Bob |
```
> [!NOTE] SELF JOIN vs 递归 CTE 的选择
> - **只查一层关系**(直接上级/下级):SELF JOIN 简洁高效
> - **需要遍历整棵树**(所有层级的汇报线):必须用递归 CTE——详见 [[hhs/MySQL/02-SQL核心/10-子查询与派生表]]
### 场景二:查找同一组内的相邻行
```sql
-- 找出连续两天都有订单的用户(日环比分析)
SELECT DISTINCT a.user_id
FROM orders a
INNER JOIN orders b
ON a.user_id = b.user_id
AND DATEDIFF(b.created_at, a.created_at) = 1
WHERE DATE(a.created_at) = '2026-05-20';
```
### 场景三:去重——保留每组最新的一条
```sql
-- 删除同一用户的历史记录,只保留最新一条
DELETE old FROM orders old
INNER JOIN orders new
ON old.user_id = new.user_id
AND old.created_at < new.created_at;
-- old 是"要删的旧记录",new 是"用来比较的新记录"
```
```mermaid
flowchart LR
subgraph "orders 表(别名 old)"
O1["user=1, 05-18"]
O2["user=1, 05-19"]
O3["user=1, 05-20"]
end
subgraph "orders 表(别名 new)"
N1["user=1, 05-18"]
N2["user=1, 05-19"]
N3["user=1, 05-20"]
end
O1 -->|"old.created_at < new.created_at"| N2
O1 --> N3
O2 --> N3
O3 -.->|"无更早匹配"| N3
style O1 fill:#EE5A24,color:#fff
style O2 fill:#EE5A24,color:#fff
style O3 fill:#00D866,color:#fff
```
> [!TIP] 自连接的性能注意事项
> - 自连接本质是同一张表扫描两次,如果表很大且没有索引,代价等同于两张大表的 BNLJ
> - **务必确保 JOIN 条件列有索引**(如 `manager_id`、`user_id + created_at`)
> - 如果只是做"每组取最新一条",`ROW_NUMBER()` 窗口函数通常比自连接 DELETE 更安全(详见 [[hhs/MySQL/02-SQL核心/08-DQL SELECT 全解析]])
## JOIN 执行算法
MySQL InnoDB 在执行 JOIN 时,**本质上只有两种核心策略**:被驱动表有索引时用 **INLJ**(Index Nested-Loop Join),没有索引时退化到 **BNLJ**(Block Nested-Loop Join)。而 INLJ 内部又根据索引是否为唯一键进一步细分。