rollup中null表示“全部”而非缺失,按字段顺序生成从细到粗的层级聚合;cube枚举所有组合,结果行数呈指数增长;mysql 8.0+支持rollup,旧版需union all模拟;排序须用grouping()函数区分层级。

ROLLUP 生成层级汇总时,NULL 值代表“全部”而非缺失
SQL 中 ROLLUP 不是简单加总,而是按分组字段顺序构造“从细到粗”的层级聚合。比如 GROUP BY ROLLUP(a, b, c) 会产出 (a,b,c)、(a,b,NULL)、(a,NULL,NULL)、(NULL,NULL,NULL) 四层结果——这里的 NULL 是 SQL 标准定义的占位符,表示该层级“不区分”,即“所有 b”“所有 a”“全部数据”。很多人误以为是数据丢失或计算错误,其实只要用 COALESCE 或 CASE 显式替换就能看清语义:
SELECT COALESCE(a, '总计') AS region, COALESCE(b, '小计') AS dept, SUM(sales) AS total FROM sales GROUP BY ROLLUP(a, b);
注意:ROLLUP 的字段顺序直接影响层级结构,调换 a 和 b 位置,小计逻辑就完全变了。
CUBE 与 ROLLUP 的聚合组合差异直接决定结果行数
CUBE(a, b, c) 会枚举所有子集组合(2³=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);而 ROLLUP(a, b, c) 只按前缀展开(4 种)。这意味着:
-
CUBE更适合探索性分析,比如想同时看“按部门+年份”“按部门”“按年份”“全部”四类视角 -
ROLLUP更贴近报表需求,比如“部门→小组→员工”这种天然父子关系 - 字段越多,
CUBE结果膨胀越剧烈,3 字段 8 行,4 字段就 16 行,容易拖慢查询和前端渲染
别硬套 CUBE 图省事——它不是“更全的 ROLLUP”,而是不同建模意图。
MySQL 8.0+ 支持 ROLLUP,但旧版本必须手写 UNION ALL 模拟
MySQL 5.7 及更早版本不支持 ROLLUP(哪怕写了也不报错,但行为不符合预期);部分云厂商的兼容模式可能默认禁用。验证方式很简单:
SELECT a, b, COUNT(*) FROM t GROUP BY a, b WITH ROLLUP;
如果返回结果里没有 NULL 行,或者行数明显少于理论值,基本就是被降级执行了。此时只能手动拼:
- 基础分组:
SELECT a, b, COUNT(*) FROM t GROUP BY a, b - 第一层小计:
SELECT a, NULL, COUNT(*) FROM t GROUP BY a - 总计:
SELECT NULL, NULL, COUNT(*) FROM t - 用
UNION ALL合并,注意字段类型对齐(比如NULL要显式转成CHAR或INT)
手写成本高、易出错,升级 MySQL 版本仍是首选方案。
ORDER BY 不能直接依赖 ROLLUP 输出顺序,需用 GROUPING() 函数控制
ROLLUP 产生的 NULL 行在 ORDER BY 里默认排最前,但你没法靠字段值排序来区分“这是 a 的小计”还是“这是总计”。真正可靠的判断依据是 GROUPING() 函数:
-
GROUPING(a)返回 1 表示当前行中a是ROLLUP生成的汇总值(即NULL占位),0 表示真实数据 - 可组合使用:
ORDER BY GROUPING(a), GROUPING(b), a, b,确保“明细→a 小计→总计”自然下拉 - PostgreSQL 和 SQL Server 支持
GROUPING SETS,但 MySQL 目前只认GROUPING()配合ROLLUP
漏掉 GROUPING(),报表里小计和明细混在一起,业务方根本分不清哪行是哪层聚合。










