不能直接用with recursive,因mysql 5.7等旧版本不支持,且路径字段需字符串匹配模拟层级;应改用自连接+like前缀匹配(如c2.path like concat(c1.path, '%')),并统一路径格式、添加深度校验防误匹配。

为什么不能直接用 WITH RECURSIVE 就完事?
很多用户一看到“递归统计”就立刻查 WITH RECURSIVE,但实际中常卡在路径解析这步:比如字段存的是 /1/5/12/ 这种字符串路径,数据库没开递归支持(如 MySQL 5.7),或业务要求兼容旧版本。这时候硬上 CTE 反而报错 ERROR 1142 (42000): WITH RECURSIVE is not allowed in this context,得换路子。
核心思路是:不依赖递归语法,改用自连接 + 字符串匹配模拟层级关系。
-
parent_id字段缺失或不可靠时,path是唯一可信的层级依据 - MySQL 5.7+、PostgreSQL 9.6+、SQL Server 2016+ 都能用
LIKE或正则做路径前缀匹配 - 注意路径分隔符必须统一(推荐用
/,避免\引发转义问题)
用 LIKE 匹配父路径并统计子节点数量
假设表 categories 有字段 id、name、path(值如 /1/、/1/5/、/1/5/12/),要统计每个节点下有多少子孙(含自己):
SELECT c1.id, c1.name, c1.path, COUNT(c2.id) AS descendant_count FROM categories c1 LEFT JOIN categories c2 ON c2.path LIKE CONCAT(c1.path, '%') GROUP BY c1.id, c1.name, c1.path;
关键点:
-
CONCAT(c1.path, '%')确保匹配所有以该路径开头的子孙,比如/1/能命中/1/5/和/1/5/12/ - 必须用
LEFT JOIN,否则根节点(如/1/)若无子节点会直接被过滤掉 - 如果路径末尾不带斜杠(如存成
/1),需改用c2.path = c1.path OR c2.path LIKE CONCAT(c1.path, '/%')
排除自身、只算严格子节点的写法
上面的统计包含节点自己,若只要“子节点数量”(不含自身),得加条件过滤:
SELECT c1.id, c1.name, c1.path, COUNT(c2.id) AS children_count FROM categories c1 LEFT JOIN categories c2 ON c2.path LIKE CONCAT(c1.path, '%') AND c2.path != c1.path -- 排除自己 GROUP BY c1.id, c1.name, c1.path;
常见陷阱:
- 漏掉
c2.path != c1.path→ 每个节点至少算 1(自己),结果全错 - 路径格式不规范:如
/1/5和/1/5/并存,会导致/1/5错误匹配/1/50/(因为'/1/5%' LIKE '/1/5/'成立)→ 务必统一结尾加斜杠 - 索引失效:
LIKE以通配符开头(如'%1/5/')无法走索引;但CONCAT(c1.path, '%')是前缀匹配,只要path字段有 B-tree 索引就能加速
路径深度不确定时,如何避免跨级误匹配?
当路径结构松散(如允许 /1/5/12/ 和 /1/50/12/ 共存),单纯用 LIKE 可能导致 /1/5/ 错把 /1/50/12/ 当作子节点(因为 '/1/50/12/' LIKE '/1/5/%' 为真)。这时得靠路径段数约束:
SELECT
c1.id,
c1.name,
COUNT(c2.id) AS safe_children_count
FROM categories c1
LEFT JOIN categories c2
ON c2.path LIKE CONCAT(c1.path, '%')
AND (LENGTH(c2.path) - LENGTH(REPLACE(c2.path, '/', '')))
> (LENGTH(c1.path) - LENGTH(REPLACE(c1.path, '/', '')))
GROUP BY c1.id, c1.name;
说明:
-
LENGTH(path) - LENGTH(REPLACE(path, '/', ''))算出路径中/的个数,即深度(/1/深度为 2,/1/5/为 3) - 要求子节点深度严格大于父节点,排除同级或父级干扰
- 性能代价:每行都算两次字符串长度,大数据量时建议提前把深度存为冗余字段
depth,然后直接c2.depth > c1.depth
真正麻烦的不是写法,而是路径数据本身是否干净——如果已有数据混着 /1、/1/、/1//5/ 几种格式,先清洗再统计,比硬写 SQL 更重要。










