rollup按group by列顺序逐级上卷生成树状层级汇总,如group by a,b,c产生(a,b,c)、(a,b,null)、(a,null,null)、(null,null,null)四层;cube则生成所有2ⁿ种组合,易致结果集爆炸;二者均需配合grouping()函数区分汇总null与真实null,并用order by grouping(...)控制汇总行位置。

ROLLUP 和 CUBE 都能生成多级汇总,但它们的汇总逻辑、结果集规模和适用场景完全不同——选错一个,报表就可能漏层、爆炸或误判 NULL。
ROLLUP 严格按 GROUP BY 列顺序逐级上卷
ROLLUP 的层级路径完全由 GROUP BY 子句中列的书写顺序决定,从左到右逐级收口。比如 GROUP BY region, province, city WITH ROLLUP 会固定产出四层:
-
(region, province, city)—— 明细层 -
(region, province, NULL)—— 城市小计(按省归并) -
(region, NULL, NULL)—— 省小计(按大区归并) -
(NULL, NULL, NULL)—— 全量总计
它不会出现 (NULL, province, city) 或 (region, NULL, city) 这类跨级组合——这些在业务上不构成父子关系。常见错误是把高粒度字段(如 product_id)写在前面,导致中间层(如品类小计)直接消失。
CUBE 生成所有 2N 种列组合,极易结果集爆炸
GROUP BY CUBE(a, b, c) 实际等价于同时执行 8 个独立分组:(a,b,c)、(a,b,NULL)、(a,NULL,c)、(NULL,b,c)、(a,NULL,NULL)、(NULL,b,NULL)、(NULL,NULL,c)、(NULL,NULL,NULL)。
这意味着:
- 3 个字段 → 8 行结果
- 5 个字段 → 32 行结果
- 7 个字段 → 128 行结果,且每行都需重新扫描+聚合
宽表慎用:如果其中一列是 user_id(基数百万),CUBE 不仅慢,还可能因中间结果膨胀触发内存溢出。PostgreSQL 不支持原生 WITH CUBE,必须手写 GROUPING SETS 模拟。
不用 GROUPING() 就没法安全识别汇总行中的 NULL
ROLLUP/CUBE 输出的 NULL 是占位符,和原始数据里的真实 NULL 完全混在一起。仅靠 WHERE region IS NULL 会同时删掉“华东区小计”和“region 字段真为 NULL 的脏数据”。
唯一可靠方式是用 GROUPING(region):
- 返回
1→ 该列为汇总生成的空值(可安全展示为“小计”或“总计”) - 返回
0→ 该值来自原始数据(需保留或按业务规则处理)
注意:GROUPING() 在 MySQL 8.0.12+ 才稳定支持;低版本只能用 GROUPING_ID();它不能出现在 WHERE 子句里——只在 SELECT 和 ORDER BY 中有效。
ORDER BY grouping(...) 是控制汇总行位置的唯一可控手段
数据库不保证 ROLLUP/CUBE 结果中汇总行的物理顺序。想让“省份小计”紧贴其下属城市之后,或把“总计”统一放在最后,必须显式排序:
ORDER BY grouping(region) DESC, grouping(province) DESC, region, province
漏掉这步,前端渲染或 Excel 导出时,汇总行可能散落在任意位置——这不是 bug,是标准行为。很多团队花半天调样式,其实缺的只是这一行 ORDER BY。











