跳转到主内容
websoft网络软件专家 - 深耕网络技术,打造实用软件!

SQL如何实现同一张表的层级查询_利用Self Join处理父子结构

Self Join是同一张表与自身关联,适合查父子关系因父/子节点共存于一表;需用别名区分,建parent_id索引,深度查询依赖数据库递归CTE。 什么是Self Join,为什么它适合查父子关系 Self Join 是指一张表和自己做 JOIN,本质是把同一张表当作两个逻辑上不同的表(比如
t1
和
t2
)来关联。在父子结构中,父节点和子节点都存在同一张表里(如
id
和
parent_id
),必须用 Self Join 才能分别拿到“父行”和“子行”。 不推荐用子查询或应用层递归拼接,因为: 子查询嵌套深了性能断崖式下降 应用层遍历无法利用数据库索引,IO 成倍增加 缺少层级控制(比如只查 3 层)时逻辑易出错 怎么写基础的两层父子查询(Parent → Child) 核心是让子表的
parent_id
关联父表的
id
:
SELECT p.name AS parent_name, c.name AS child_name FROM categories p JOIN categories c ON c.parent_id = p.id;
注意点: 别名
p
和
c
必须明确,否则字段歧义(如
id
到底是父还是子?) 如果要包含根节点(
parent_id IS NULL
),改用
LEFT JOIN
,但
c.*
字段会为 NULL 索引一定要建在
parent_id
上,否则 JOIN 全表扫描 如何查任意深度的层级(比如祖→父→子→孙) 标准 SQL 不支持递归,但主流数据库提供了扩展: PostgreSQL:用
WITH RECURSIVE
MySQL 8.0+:同样支持
WITH RECURSIVE
SQL Server:用
CTE
+
UNION ALL
SQLite:支持
WITH RECURSIVE
(3.8.3+) 以 PostgreSQL 为例,查从根到第 3 层的所有路径:
WITH RECURSIVE tree AS ( SELECT id, name, parent_id, 1 AS level FROM categories WHERE parent_id IS NULL -- 根节点 UNION ALL SELECT c.id, c.name, c.parent_id, t.level + 1 FROM categories c JOIN tree t ON c.parent_id = t.id WHERE t.level < 3 -- 控制最大深度 ) SELECT * FROM tree;
容易踩的坑: 忘记加终止条件(如
t.level < 3
),导致无限循环报错 递归分支没限制,数据有环(A→B→A)时直接崩溃,上线前务必检查
parent_id
是否合法 MySQL 8.0 以下不支持递归 CTE,只能靠应用层或临时表模拟 查某节点的所有祖先或所有后代,哪个更高效 查所有 后代 (向下遍历)通常更快: 可以用单次递归 CTE 完成,索引走
parent_id
即可 大多数业务场景(如展示分类树、权限继承)都需向下展开 查所有 祖先 (向上遍历)难点在于: 每次 JOIN 都要回溯一层,深度越大 JOIN 越多 若没有对
id
建主键索引,性能恶化明显 更稳妥的做法是:在写入时冗余存储路径(如
path = '/1/5/12/'
),查祖先就用
LIKE '/1/%'
实际选型要看更新频次——路径冗余适合读多写少,递归 CTE 适合写频繁但层级浅(≤5)的场景。 递归不是银弹,深度超过 10 层时,数据库执行计划可能退化,这时候该考虑把树结构缓存到 Redis 或改用闭包表(Closure Table)模型。

相关文章