不能在递归CTE内部使用SUM()或GROUP BY,必须先用递归CTE展开完整层级关系(如节点及其所有祖先或子孙),再在外层查询中通过JOIN和GROUP BY进行聚合;否则会报错、结果为空或重复计算。

不能在递归 CTE 内部用 SUM() 或 GROUP BY,必须先展开完整层级关系,再在外层聚合——这是最常报错、结果翻倍或为空的根源。
递归 CTE 里禁止写 SUM() 和 GROUP BY
递归 CTE 的作用是生成扁平化的父子路径(比如每个员工及其所有上级),它本身不支持聚合。数据库引擎在递归过程中无法确定行数边界,GROUP BY 语义不成立。
- 错误写法:
SELECT p.id, p.name, SUM(c.sales) FROM tree c JOIN orgs p ON c.parent_id = p.id GROUP BY p.id—— 递归支中直接聚合,MySQL/PostgreSQL 都会报错或返回空 - 正确逻辑:CTE 只负责“展开”,比如输出
(employee_id, manager_id, level, path);聚合移到外层JOIN后做 - 若强行在递归支里加聚合,可能触发重复计算:同一子节点被多个父级多次引用,
SUM()被累加多次
外层聚合必须用 JOIN 匹配“归属关系”
目标是“每个节点汇总其所有后代数据”,关键不在 CTE 怎么写,而在外层怎么关联业务表。匹配方式取决于 CTE 展开方向:
- 如果 CTE 是向下展开(每个节点列出所有子孙):用
ON t.descendant_id = sales.employee_id,这样一条销售记录会被映射到它本人、其经理、总监等多行,自然实现向上归集 - 如果 CTE 是向上展开(每个节点列出所有祖先):用
ON t.ancestor_id = sales.employee_id,效果一致 - 漏掉索引会断崖式变慢:
sales.employee_id和 CTE 输出的关联字段都应有索引,否则深树下JOIN成全表扫描
GROUPING() 不适用于递归 CTE 场景
GROUPING() 是给 ROLLUP/CUBE 用的,用来区分 NULL 是真实值还是汇总占位符。递归 CTE 汇总不产生这类 NULL 行,所以不需要它。
- 混淆点:有人看到递归结果里有
parent_id IS NULL就想套用GROUPING(parent_id),这是无效的——那只是根节点标识,不是汇总行 - 真正要识别“本级自身”和“含子级汇总”,得靠 CTE 输出的
level字段或自定义标志位(如is_leaf) - 若需分层展示缩进或折叠,应在 CTE 中生成
path或REPEAT(' ', level),而非依赖GROUPING()
最容易被忽略的是:CTE 展开后数据量可能指数级膨胀(比如 10 层组织树,一个基层员工会出现在 10 行里),外层 GROUP BY 前务必确认业务表关联条件足够精确,否则轻则性能骤降,重则内存溢出。

















