WITH RECURSIVE 可实现从子部门向上递归聚合至根部门:先用递归CTE展开每个业务记录到根节点的完整路径(初始查sales关联部门,递归JOIN parent_id),再在外层按目标部门ID分组SUM(amount);需防环、建parent_id索引,并避免在递归内聚合。

用 WITH RECURSIVE 从子部门向上聚合到根部门
SQL 本身不支持自动跨层级向上求和,必须显式构造递归路径。核心是先用 WITH RECURSIVE 把每个节点到根节点的完整路径展开,再按根部门(或任意上级)分组聚合。别指望 GROUP BY department_id 直接搞定——它只认当前行的 department_id,不会自动把子部门数据“提上去”。
典型结构:一张部门表 departments(含 id、name、parent_id),一张业务数据表 sales(含 dept_id、amount)。要算每个部门及其所有下级的销售总和,就得反向递归:从叶子节点出发,逐级 JOIN 父节点,直到 parent_id IS NULL。
- 递归 CTE 的初始查询必须选中所有“末端”业务记录(即
sales表的每一行),并关联其直属部门信息 - 递归部分用
JOIN departments ON t.parent_id = d.id向上跳一级,同时保留原始sales.amount和当前d.id(即新上级) - 最终
SELECT按d.id(代表汇总目标部门)分组,SUM(amount)即为该部门含全部下级的总额
MySQL 8.0+ 和 PostgreSQL 的语法差异点
MySQL 要求递归查询中不能出现聚合函数或 DISTINCT,所以必须在递归 CTE 内只做路径展开,聚合必须放在外层查询;PostgreSQL 允许在递归分支里加 GROUP BY,但实际没必要——容易混淆层级归属。
关键区别在初始查询字段顺序和递归引用方式:
- MySQL:递归 CTE 的
SELECT列数、类型、顺序必须严格一致,UNION ALL左右两边都要有dept_id(当前部门)、root_dept_id(最终要汇总到的部门)、amount - PostgreSQL:可直接用
SELECT ... FROM departments d JOIN RECURSIVE cte ON d.id = cte.parent_id,更贴近直觉,但需注意MAX_RECURSION_DEPTH默认值(可通过max_recursion_depth参数调) - 两者都不支持在递归部分对
amount做累计(如running_sum),因为会破坏集合语义——必须靠外层SUM()实现汇总
常见错误:循环引用导致 “Maximum recursion depth exceeded”
部门表里若存在 id=5, parent_id=5 或 id=3 → 4 → 3 这类自环/环状引用,递归会无限进行下去。数据库会在达到深度限制时报错,例如 MySQL 的 ERROR 3636 (HY000): Recursive query aborted after 1001 iterations。
- 上线前务必检查
departments表:运行SELECT * FROM departments WHERE id = parent_id OR parent_id IN (SELECT id FROM departments WHERE parent_id = id)找出自环 - 在递归 CTE 中加入防环字段(如数组或字符串路径),PostgreSQL 可用
NOT d.id = ANY(path),MySQL 只能靠应用层预处理或加LIMIT应急 - 别依赖
parent_id IS NULL作为唯一终止条件——有些系统用0或-1表示根,得统一清理
性能陷阱:没加索引会让递归变全表扫描
递归过程本质是多次嵌套 JOIN,每次都要根据 parent_id 查找父节点。如果 departments(parent_id) 没索引,单次递归可能触发全表扫描,N 层递归就是 N × 表行数,1000 行部门表 + 5 层深度就可能扫几万行。
- 必须确保
departments.parent_id有 B-tree 索引(MySQL/PG 都适用) - 如果经常按“某部门及其下级”查数据,可额外建覆盖索引:
CREATE INDEX idx_dept_parent_id_id ON departments(parent_id, id),让 JOIN 更快命中 - 数据量大时(比如部门超 1w),考虑物化路径(
path字段存 “1/5/23/”)替代递归,虽然写入慢,但读取极快且无深度限制
递归不是银弹——层级超过 20 层或部门关系频繁变动时,CTE 的维护成本和不可预测性会上升。真正稳定的方案往往是在业务写入时同步更新一个 dept_summary 汇总表,而不是每次都现场算。










