rollup是对group by的扩展,用于自动生成多级小计和总计;其分组顺序决定层级结构,null是汇总标记需用grouping()区分,混用需谨慎,适用报表多级汇总场景。

ROLLUP 不是用来“替代 GROUP BY”的,而是对 GROUP BY 的扩展——它自动补全多级小计和总计,省掉写一堆 UNION ALL 的麻烦。用错地方或忽略细节,反而会让结果多出 NULL 行、排序混乱、甚至聚合值错误。
ROLLUP 的分组顺序决定结果层级
ROLLUP(a, b, c) 生成的分组是严格从左到右递减的:
- 先按 a, b, c 分组(最细粒度)
- 再按 a, b 分组(c 小计)
- 再按 a 分组(b,c 小计)
- 最后按空分组(a,b,c 总计)
这意味着:ROLLUP(dept, job) 能看到“某部门某岗位 → 某部门总计 → 全部总计”,但 ROLLUP(job, dept) 就变成“某岗位某部门 → 某岗位总计 → 全部总计”,语义完全不同。别只看字段名,盯住括号里的顺序。
NULL 值不是脏数据,是 ROLLUP 的标记位
ROLLUP 产生的汇总行里,未参与当前分组的列会显示为 NULL。比如 GROUP BY ROLLUP(class, student) 中:
- class = 'A', student = 'Alice' → 正常明细
- class = 'A', student = NULL → A 班所有学生的小计
- class = NULL, student = NULL → 全表总计
这些 NULL 不能直接过滤掉,否则就丢了汇总行。需要用 GROUPING() 函数区分真 NULL 和汇总占位符:
SELECT CASE WHEN GROUPING(class) = 1 THEN '全部班级' ELSE class END AS class, CASE WHEN GROUPING(student) = 1 THEN '小计/总计' ELSE student END AS student, SUM(grades) FROM t_student_grades GROUP BY ROLLUP(class, student)
ROLLUP 和普通 GROUP BY 混用容易翻车
写成GROUP BY class, ROLLUP(student, subject) 是合法的,但它等价于:
- GROUP BY class, student, subject
- GROUP BY class, student
- GROUP BY class注意:最外层的 class 是固定分组维度,不会被 ROLLUP “卷起”。这种写法适合“先按某维度锁定,再对其子维度做汇总”,但初学者常误以为它能跨维度叠加,结果发现少了一层总计。
性能比 UNION ALL 好,但别在大表上盲目套用
ROLLUP 底层通常只需一次扫描 + 排序,比多个 UNION ALL 子查询快。但代价是:
- 必须对所有 ROLLUP 列做排序(哪怕加了索引,也可能触发 SORT GROUP BY)
- 如果只想要某一层小计(比如只要部门小计,不要岗位小计),却写了 ROLLUP(dept, job),Oracle 仍会计算全部层级,浪费资源
真正该用 ROLLUP 的场景,是报表导出、BI 后端一次性拉取多级汇总;日常查单条明细或简单聚合,硬套 ROLLUP 反而拖慢响应。
ROLLUP 的核心不是“炫技”,是让层级汇总变得可预测、可维护。最容易被忽略的,其实是 GROUPING() ——没它,你根本分不清哪一行是真数据、哪一行是它帮你算出来的汇总。











