窗口函数不能直接处理层级关系,必须先用递归cte展开树结构并在递归支内完成聚合,且partition by字段顺序须严格匹配业务层级。

窗口函数本身不处理层级关系,它只在已有行集上做计算;要实现层次结构汇总,必须先用递归CTE或自连接把树状关系展开成平面行,再用SUM() OVER()这类函数按路径聚合。
递归CTE必须在内部完成子树求和,不能靠外部GROUP BY
常见错误是写完递归CTE后直接GROUP BY parent_id来汇总子节点——SQL Server会报错“recursive query does not have the form non-recursive-term UNION ALL recursive-term”,因为递归支未包含聚合逻辑。
- 锚点(非递归部分)只取叶子节点或基础明细,不加
GROUP BY - 递归支里用
JOIN关联上一层结果,并对子节点值做COALESCE(SUM(c.value), 0) - 聚合必须在递归支内完成,例如:
SELECT p.id, p.parent_id, COALESCE(SUM(c.value), 0) + p.base_value AS subtree_sum FROM parent p LEFT JOIN cte c ON c.parent_id = p.id GROUP BY p.id, p.parent_id, p.base_value - 深度超限需加
OPTION (MAXRECURSION n),默认100层,无限递归用OPTION (MAXRECURSION 0)
用PARTITION BY模拟层级路径汇总时,字段顺序决定业务含义
比如组织树有region→dept→team三级,想算每个部门下所有团队的总和,不能写PARTITION BY team, dept——这会让SUM() OVER()按团队分组再算部门,逻辑全乱。
- 正确做法是先构造出带完整路径的列,如
CONCAT(region, '/', dept)或用递归CTE生成root_id、path字段 - 再用
SUM(value) OVER(PARTITION BY root_id)或PARTITION BY path_level_2做聚合 - 若路径列含斜杠,注意
ORDER BY可能影响RANGE帧行为,建议显式用ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING - 避免在
PARTITION BY中混入高粒度字段(如employee_id),否则分区过细,汇总失效
GROUPING()和窗口函数混用时,执行顺序决定能否识别汇总行
窗口函数在SQL Server逻辑处理顺序中晚于GROUP BY但早于ORDER BY;而GROUPING SETS或WITH ROLLUP产生的汇总行是在GROUP BY阶段生成的。所以你可以在SELECT里同时用GROUPING(dept)和SUM(sales) OVER(PARTITION BY dept),但后者不会自动跳过NULL汇总行——它照样把dept = NULL当成一个分区来算。
- 真实NULL值和ROLLUP占位符都为
NULL,仅靠IS NULL无法区分,必须用GROUPING(dept) = 1 - 若需对汇总行单独计算,得在外层再套一层查询,用
CASE WHEN GROUPING(dept) = 1 THEN ... ELSE ... END -
SUM() OVER()无法替代ROLLUP的层级归并能力,两者定位不同:前者是行级增强,后者是分组结构生成 - 混合使用时,性能开销叠加,大表慎用,优先考虑物化中间结果(如临时表存递归树)
真正卡住人的地方不是语法,而是递归CTE的聚合必须内嵌、以及PARTITION BY字段顺序和业务层级必须严格对齐——这两点一旦错,结果看起来像对,实则小计值全是错的,而且很难一眼看出来。










