ROLLUP按GROUP BY字段从左到右逐级归并生成树状层级,如ROLLUP(a,b,c)产生(a,b,c)、(a,b)、(a)、()四层;需用GROUPING()函数区分汇总行与真实NULL,避免混淆;其单向递归特性区别于CUBE的全组合与GROUPING SETS的自定义组合。

ROLLUP 生成的分组层级怎么理解
SQL 的 ROLLUP 不是简单叠加多个 GROUP BY,而是按括号内字段顺序构造一棵“层级树”:最右字段为最细粒度,逐级向左收拢,最后补一行全 NULL 的总计。比如 GROUP BY ROLLUP(a, b, c) 实际等价于:GROUP BY a,b,c(明细)、GROUP BY a,b(c 小计)、GROUP BY a(b+c 小计)、GROUP BY ()(全表总计)。注意:顺序不能颠倒,ROLLUP(b,a,c) 的层级结构就完全不同。
如何识别小计行和总计行
ROLLUP 本身不标记哪行是小计,得靠 GROUPING() 函数判断。它对当前分组中为 NULL 的维度返回 1,否则为 0。例如字段 region 在某行是 NULL,且该行属于 ROLLUP(region, product) 的 region 小计层,则 GROUPING(region) 返回 1。常用写法是用 CASE WHEN GROUPING(region) = 1 THEN 'ALL_REGIONS' ELSE region END 替换 NULL 值,避免和真实 NULL 混淆。
容易踩的坑:
- 直接 SELECT region, product, SUM(sales) 会把小计行的 region 显示为 NULL,业务方看不懂;
- 忘记给所有参与 ROLLUP 的字段都套 GROUPING() 判断,导致部分小计标签没替换;
- 把 GROUPING(region) 和 region IS NULL 混用——真实数据里 region 可能真为 NULL,仅靠 IS NULL 无法区分。
ROLLUP 和 CUBE、GROUPING SETS 的关键区别
三者都扩展分组能力,但行为不同:
- ROLLUP(a,b,c) 只生成 (a,b,c)、(a,b)、(a)、() 四组,是单向递归收拢;
- CUBE(a,b,c) 生成全部 2³=8 种组合(包括 (b,c)、(a,c) 等交叉小计),结果集更大,易爆炸;
- GROUPING SETS((a,b), (a), ()) 手动指定要哪些分组,最灵活,也最明确,适合只想要特定小计组合的场景。
性能上,ROLLUP 通常比 CUBE 快,因为扫描和聚合路径更线性;但若只需要 (a,b) 和 () 两组,硬写 ROLLUP(a,b) 会多算 (a) 这一组,不如直接用 GROUPING SETS 干净。
MySQL 8.0+ 和 PostgreSQL 的语法兼容性注意点
ROLLUP 在 MySQL 8.0+ 和 PostgreSQL 9.5+ 都原生支持,但细节有差异:
- MySQL 要求 ROLLUP 必须紧跟 GROUP BY 后,不支持在 GROUPING SETS 里嵌套;
- PostgreSQL 允许 GROUP BY a, ROLLUP(b,c) 这种混合写法(a 固定,b/c 做 ROLLUP),MySQL 不支持;
- 两者都支持 GROUPING(),但 PostgreSQL 还提供 GROUPING_ID() 返回位掩码整数,方便批量判断多字段组合状态。
示例(通用写法):
SELECT<br> CASE WHEN GROUPING(region) = 1 THEN 'TOTAL' ELSE region END AS region,<br> CASE WHEN GROUPING(product) = 1 THEN 'ALL_PRODUCTS' ELSE product END AS product,<br> SUM(amount) AS total<br>FROM sales<br>GROUP BY ROLLUP(region, product);
实际跑之前,先确认执行计划里是否走了索引——如果 region 和 product 上没有联合索引,ROLLUP 可能触发全表排序,大数据量时延迟明显。

















