ROLLUP必须按业务层级从左到右书写,因为其仅依GROUP BY字段顺序逐级上卷:dept→(dept,team)→(dept,NULL,NULL)→(NULL,NULL,NULL),顺序错则层级错乱、小计缺失。

直接用 GROUP BY ... WITH ROLLUP,但顺序写错、排序乱套、NULL分不清,结果就不是树形而是“乱坟岗”。
GROUP BY dept, team, emp WITH ROLLUP 的顺序为什么不能调换?
ROLLUP 不是算所有组合,它只按 GROUP BY 字段从左到右逐级上卷:先明细(dept, team, emp),再 team 小计(dept, team, NULL),再 dept 小计(dept, NULL, NULL),最后总计(NULL, NULL, NULL)。顺序一反,比如写成 GROUP BY emp, team, dept WITH ROLLUP,生成的就是“每个员工 → 所有员工在空团队 → 所有员工在空部门”,完全丢失业务层级逻辑。
- 真实场景要的是“部门 → 部门内各团队 → 团队内各员工”,所以必须把最粗粒度字段(如
dept)放最左 - 字段顺序 = 业务层级顺序,不是数据表字段顺序,也不是字母顺序
- SQL Server 不接受
GROUP BY ROLLUP(dept, team, emp)这种写法(那是 PostgreSQL/MySQL 8.0+ 的语法),必须用WITH ROLLUP后缀
ORDER BY 怎么排才不会打乱树状结构?
很多人加 ORDER BY team, dept 或 ORDER BY emp DESC,结果 NULL 行全飘到中间或末尾,根本看不出哪行是部门小计、哪行是总计。ROLLUP 的层级依赖字段前缀顺序,排序也得跟着走。
- 只允许用
GROUP BY开头的连续前缀字段排序,例如ORDER BY dept, team—— 这样 (dept=A, team=X)、(dept=A, team=NULL)、(dept=A, NULL, NULL) 会自然聚在一起 - 禁止出现
ORDER BY team, emp或ORDER BY dept DESC, team,会切断层级视觉连贯性 - 如果非要按数值大小排明细(比如员工编号升序),只能在外层再套一层
SELECT ... ORDER BY,但必须保留内层 ROLLUP 的原始排序逻辑
怎么区分是真实 NULL 还是 ROLLUP 占位 NULL?
比如 region 列本身允许 NULL,那仅靠 region IS NULL 无法判断这是“华东区小计”还是“地区未知的明细”。必须用 GROUPING() 函数。
-
GROUPING(region)返回1表示该行是因 ROLLUP 上卷导致region为空,返回0表示原始值就是 NULL 或有值 - 常配合
CASE WHEN GROUPING(region) = 1 THEN '总计' ELSE ISNULL(region, '未知') END做可读性处理 - 注意:
GROUPING()只对出现在GROUP BY中的列有效,对计算列或别名无效
SQL Server 的 ROLLUP 和递归 CTE 是两回事,别混着用
ROLLUP 是固定维度的横向逐级归并,不依赖 parent_id;递归 CTE 是纵向树形展开,靠父子关系动态生成路径。想查“某部门下所有子部门+员工的销售总和”,用 ROLLUP 没用——它压根不知道谁是谁的子节点。
- ROLLUP 适合报表类固定层级:年→月→日、大区→省→市、部门→团队→员工
- 递归 CTE 适合组织架构、商品类目、成本中心这类深度不确定、需路径匹配的场景
- 两者性能特征不同:ROLLUP 是单次聚合,快;递归 CTE 在深树+大数据量下容易慢,
cost_centers.code LIKE t.path || '%'这类前缀匹配必须有索引支撑
最容易被忽略的是:ROLLUP 生成的每一层都是“完整切片”,它不会补全缺失组合(比如某部门下没团队,就不会有该部门的“团队小计”行)。真要强制补全,得先构造骨架再 LEFT JOIN,这不是 ROLLUP 能解决的事。

















