group by + rollup可直接生成多级小计与总计,按列从左到右逐级上卷,产生(a,b,c)→(a,b,null)→(a,null,null)→(null,null,null)四层结果,null表示该维被聚合,须用grouping()函数精准识别汇总行。

用 GROUP BY + ROLLUP 实现分组小计和总计
直接加 ROLLUP 就能出小计和总计,不用写两个查询再合并。它会在常规分组结果末尾追加若干汇总行,最后一行就是全表总计。
比如按部门和岗位统计人数,GROUP BY dept, role WITH ROLLUP 会生成:每组(dept+role)、每个部门小计(dept+NULL)、全表总计(NULL+NULL)三层数据。
-
ROLLUP的列顺序很重要:最右列先聚合,逐步向左扩展,GROUP BY a, b, c WITH ROLLUP会产生 (a,b,c) → (a,b,NULL) → (a,NULL,NULL) → (NULL,NULL,NULL) 四层 - MySQL 5.7+ 和 PostgreSQL 9.5+ 支持标准语法
GROUP BY ... WITH ROLLUP;SQL Server 用GROUP BY ... WITH ROLLUP;Oracle 需用GROUPING SETS - NULL 值在汇总行中表示“该维度未限定”,可用
GROUPING()函数识别(如GROUPING(dept) = 1表示此行 dept 是小计/总计位)
用 GROUPING() 区分真实 NULL 和汇总占位符
原始数据里可能真有 NULL 值,而 ROLLUP 又用 NULL 表示汇总层级,不加判断会误读。必须靠 GROUPING() 函数来分辨。
例如:当 GROUPING(role) = 1,说明这行是部门小计(role 被聚合掉了),不是因为某人岗位真为 NULL。
-
GROUPING()返回 1 表示当前列参与了上层聚合,0 表示该列有实际值 - 常配合
CASE WHEN给汇总行打标签:CASE WHEN GROUPING(role) = 1 THEN '小计' ELSE role END - MySQL 8.0+、PostgreSQL、SQL Server 都支持
GROUPING();老版本 MySQL 只能靠IS NULL加业务规则硬判,风险高
ROLLUP 和 CUBE 的关键区别在哪
ROLLUP 是单向层级聚合(从细到粗),CUBE 是全组合聚合(所有维度任意组合),结果集大小差异极大。
比如 GROUP BY a, b, c WITH CUBE 会产出 2³=8 种组合;而 WITH ROLLUP 只产 4 种(含总计)。数据量大时,CUBE 容易拖慢查询甚至 OOM。
- 需要“部门小计”“岗位小计”“部门×岗位交叉小计”才用
CUBE;只要求逐级汇总(如地区→城市→门店),选ROLLUP - PostgreSQL 和 SQL Server 支持
CUBE;MySQL 目前不支持,得用GROUPING SETS模拟(8.0+) -
GROUPING SETS ((a),(b),(a,b),())等价于CUBE(a,b),但写法更啰嗦,也更可控
性能与可读性兼顾的实用写法
别把 ROLLUP 当万能锤——字段多、数据量大、又没索引时,执行计划容易退化成全表扫描。优先在分组字段上建联合索引,并限制输出行数做验证。
- 加
ORDER BY让汇总行位置可预期(如ORDER BY dept, role),否则 MySQL 可能打乱顺序 - 避免在
ROLLUP查询里套复杂子查询或窗口函数,部分数据库优化器不友好 - 如果只要总计+一个维度小计(比如总人数+各部门人数),用
UNION ALL两查反而更稳、更易调试
真正难的不是写出 ROLLUP,而是确认哪些 NULL 是业务数据、哪些是聚合占位符,以及评估多维聚合对响应时间的影响。这两点漏掉一个,报表就容易出错。










