路径编码查询需谨慎:like匹配祖先须规范路径格式且难走索引;查直接子节点优先用parent_id外键;存在性判断用exists优于in;递归查树必须用with recursive,子查询无法真正递归。

WHERE 子句里的子查询能做父子层级查询,但直接靠它“匹配路径编码”容易掉坑——因为标准 SQL 的嵌套查询本身不维护树结构上下文,IN 或 = 只能做值匹配,不能天然表达“前缀包含”或“层级继承”关系。真要靠路径编码(如 '/1/5/12/')查祖先或后代,得手动拼字符串逻辑,且必须配合函数支持。
路径字段里用 LIKE 做祖先匹配
假设你有一张 category 表,含 id、name、path(存类似 '/1/5/12/' 的字符串),想查 ID=12 的所有祖先节点:
-
path字段必须以斜杠开头和结尾,否则LIKE容易误匹配(比如'/12/'会错中'/112/') - 写法是:
SELECT * FROM category WHERE '/1/5/12/' LIKE CONCAT(path, '%') - 注意:这个查询无法走
path字段的普通 B-Tree 索引,除非用函数索引(MySQL 8.0+ 支持CREATE INDEX idx_path ON category ((SUBSTRING_INDEX(path, '/', -2)))这类表达式索引) - PostgreSQL 用户可考虑
ltree扩展,原生支持路径操作符@>(包含)和(被包含)
用子查询查直接子节点(非递归)
如果只要查某节点的**直接子节点**(即父 ID 明确),根本不需要路径编码,用外键字段更高效:
- 假设表有
parent_id字段,则查 ID=5 的子节点:SELECT * FROM category WHERE parent_id = 5 - 若坚持用路径字段反推(比如没有
parent_id),可这样写子查询:SELECT * FROM category c1 WHERE c1.path LIKE (SELECT CONCAT(path, '%') FROM category c2 WHERE c2.id = 5) AND LENGTH(c1.path) > LENGTH((SELECT path FROM category c3 WHERE c3.id = 5)) - 这个写法性能差:子查询执行多次(取决于优化器是否物化),且
LENGTH+LIKE组合几乎无法索引加速 - 更糟的是,它会把“孙子”也当“子节点”返回,必须额外加层级判断(比如统计斜杠数量),SQL 就开始变味了
EXISTS 比 IN 更适合存在性判断
当你要查“哪些分类下有商品”,且分类表和商品表通过路径关联(例如商品表有个 category_path 字段),别用 IN 套子查询:
- 错误示范:
SELECT * FROM category WHERE path IN (SELECT DISTINCT category_path FROM product)—— 若category_path为 NULL,整行被丢弃;IN对空集返回空结果,不是 false - 推荐用
EXISTS:SELECT * FROM category c WHERE EXISTS (SELECT 1 FROM product p WHERE p.category_path LIKE CONCAT(c.path, '%')) -
EXISTS在找到第一行就短路,比IN先生成完整结果集再比较更省资源 - 仍需注意:这里
LIKE的右值是动态拼接的,多数数据库无法对p.category_path使用索引,除非你建了函数索引或改用前缀字段(如ancestor_ids数组)
真正需要递归时,别硬扛子查询
路径编码本质是扁平化树结构,但“查所有后代”“查到根路径”这类需求,标准嵌套子查询最多模拟 2–3 层,再深就不可维护:
- MySQL 8.0+、PostgreSQL、SQL Server 都支持
WITH RECURSIVE,这才是查树的正解 - 例如查 ID=12 的所有祖先:
WITH RECURSIVE tree AS (SELECT id, parent_id, name, path FROM category WHERE id = 12 UNION ALL SELECT c.id, c.parent_id, c.name, c.path FROM category c INNER JOIN tree t ON c.id = t.parent_id) SELECT * FROM tree - 子查询做不到递归展开,强行多层嵌套(比如三层
IN套娃)不仅难读,还可能触发 32 层限制,且无法动态控制深度 - 如果数据库不支持递归 CTE,优先考虑在应用层分步查,或者加冗余字段(如
root_id、level)预计算
路径编码 + 子查询看似轻量,实则暗藏索引失效、语义模糊、层级失控三重风险。真要查父子关系,先看数据库版本是否支持 WITH RECURSIVE;不支持的话,宁可用两三个简单查询换清晰性和可维护性。











