ROLLUP生成的是按GROUP BY列顺序自底向上逐层卷起的聚合链,必须配合GROUPING()函数识别小计行,否则NULL值会与真实数据混淆;列顺序决定汇总层级路径,须按粗→细排列,且仅生成n+1种组合,不支持跨级汇总。

GROUPING()函数才能正确识别和标记每一级小计行,否则NULL值会和真实数据混淆。
GROUP BY列顺序决定汇总层级路径
ROLLUP严格按你写的列顺序从左到右“卷起”,不是按业务逻辑自动理解。比如要生成“年级 → 班级 → 地区”三级结构,必须写GROUP BY GradeName, ClassName, Area WITH ROLLUP。
常见错误是把细粒度字段放前面,例如GROUP BY Area, ClassName, GradeName,结果只会出地区级小计、空班级、空年级三层,丢失“每个年级下所有班级的小计”这个关键层级。
- 正确顺序:粗→细,如部门、小组、员工;年、季度、月;大区、城市、门店
- ROLLUP只生成 n+1 行组合:(a,b,c)、(a,b,NULL)、(a,NULL,NULL)、(NULL,NULL,NULL),不会出现(a,NULL,c)这类跨级组合
- 如果需要任意维度交叉(比如“华东区+笔记本销量”或“2025年+华东区”),该用
WITH CUBE,但结果集会指数膨胀
用GROUPING()区分NULL是占位符还是真数据
ROLLUP生成的汇总行会在被“卷起”的列上填NULL,但这和原始数据里真实的NULL无法靠IS NULL判断。必须用GROUPING(column_name)——返回1表示这行是该列的汇总行,0表示正常分组值。
比如GROUP BY GradeName, ClassName WITH ROLLUP后:
-
GROUPING(GradeName) = 1→ 这是全校总计行(年级和班级都被卷起) -
GROUPING(GradeName) = 0 AND GROUPING(ClassName) = 1→ 这是各年级小计行(班级被卷起,年级保留) -
GROUPING(ClassName) = 0→ 所有明细行(含真实ClassName为NULL的数据)
别直接用ISNULL(GradeName, '合计'),否则会把原始表中年级为空的学生也标成“合计”。
用HAVING过滤掉不需要的汇总层级
ROLLUP默认输出全部层级,但报表常只要明细 + 年级小计 + 总计,不要“班级小计”。这时不能靠WHERE(它在聚合前执行),得用HAVING在聚合后筛选。
例如只保留年级小计和总计:
SELECT CASE WHEN GROUPING(GradeName) = 1 THEN '总计' ELSE GradeName END AS 年级, SUM(CASE WHEN Sex = 1 THEN 1 ELSE 0 END) AS 男生数 FROM Students GROUP BY GradeName, ClassName WITH ROLLUP HAVING GROUPING(GradeName) = 1 OR GROUPING(ClassName) = 0
关键点:
-
HAVING GROUPING(ClassName) = 0→ 保留所有“班级不为空”的行(即明细 + 年级小计) -
HAVING GROUPING(GradeName) = 1→ 强制包含总计行 - 注意
HAVING里不能引用非聚合列(如StudentName),只能用GROUPING()或聚合结果
排序时GROUPING()比原始字段更可靠
ROLLUP生成的汇总行混在明细中间,靠ORDER BY GradeName会把“合计”排到最前(因NULL最小)。想让“年级小计”紧贴对应年级下方、“总计”固定在最后,得用GROUPING()控制顺序:
ORDER BY GROUPING(GradeName), GradeName, GROUPING(ClassName), ClassName
这样:
-
GROUPING(GradeName) = 0(明细和年级小计)排前面 -
GROUPING(GradeName) = 1(总计)排最后 - 同级内再按
GradeName自然序排列
真正容易被忽略的是:ROLLUP的层级是静态绑定在GROUP BY顺序上的,改一个字段位置,整个汇总结构就崩了;而GROUPING()是唯一能安全解码这些NULL的钥匙——没它,所有美化显示和过滤都是空中楼阁。

















