rollup 是 group by 的扩展子句,用于生成层级小计和总计;group by 仅返回明细分组,而 rollup 自动追加 (a,b)、(a) 和 () 等汇总行,并需用 grouping() 区分 null 含义。

ROLLUP 是什么,它和 GROUP BY 有什么区别
ROLLUP 不是独立语句,而是 GROUP BY 的扩展子句,用于在分组聚合结果中自动添加层级小计和总计。它按括号内列的从左到右顺序构建层级:最右边的列先聚合,然后逐级向左“卷起”(roll up),直到全表总计。
关键区别在于:GROUP BY a, b, c 只返回 (a,b,c) 组合的明细聚合;而 GROUP BY ROLLUP(a, b, c) 会额外生成 (a,b)、(a) 和 ()(空组,即总计)三类汇总行。
注意:Oracle 默认对 ROLLUP 结果中的空值不做特殊标记,容易误判哪一行是小计——这点后面会重点提醒。
怎么写一个带小计和总计的 ROLLUP 查询
基本语法就是把 ROLLUP 直接加在 GROUP BY 后面,括号里填维度列:
SELECT dept, job, SUM(sal) AS total_sal FROM emp GROUP BY ROLLUP(dept, job);
这个查询会输出:
- 每个
dept+job组合的小计(如 'SALES' + 'CLERK') - 每个
dept的小计(job列为NULL) - 全表总计(
dept和job都为NULL)
常见错误:
- 把
ROLLUP(dept, job)写成ROLLUP(dept), ROLLUP(job)→ 语法报错ORA-00933: SQL command not properly ended - 在
SELECT列表中引用未出现在ROLLUP中的非聚合列 → 报错ORA-00979: not a GROUP BY expression
如何区分 NULL 是真实数据还是 ROLLUP 生成的占位符
这是最容易踩坑的地方:Oracle 用 NULL 表示“该维度未参与聚合”,但原始数据里也可能真有 NULL 值。光看 dept IS NULL 无法判断这是部门小计还是全表总计。
推荐用 GROUPING() 函数辅助识别:
SELECT CASE WHEN GROUPING(dept) = 1 THEN 'TOTAL' ELSE dept END AS dept_label, CASE WHEN GROUPING(job) = 1 THEN 'SUBTOTAL' ELSE job END AS job_label, SUM(sal) AS total_sal FROM emp GROUP BY ROLLUP(dept, job);
GROUPING(dept) 返回 1 表示当前行中 dept 是 ROLLUP 生成的汇总层级(即“此处无具体 dept 值”),返回 0 表示该行有实际 dept 值。
多个维度时,GROUPING_ID(dept, job) 还能一键返回二进制标识(比如 3 表示两个都为 1,即总计行),但日常用单列 GROUPING() 更直观。
ROLLUP 和 CUBE、GROUPING SETS 性能与语义差异
ROLLUP(a,b,c) 生成的是树状层级聚合:(a,b,c) → (a,b) → (a) → ()CUBE(a,b,c) 生成所有组合:包括 (a,c)、(b,c) 等交叉小计,结果行数明显更多,执行计划通常更重GROUPING SETS((a,b), (a), ()) 是显式枚举,语义最清晰,也最容易控制输出范围
实际选型建议:
- 要按业务自然层级出报表(如 地区→城市→门店),用
ROLLUP - 要支持任意维度下钻/上卷分析,考虑
CUBE,但务必加 WHERE 或物化视图缓存 - 对性能敏感或只需特定几组小计,直接写
GROUPING SETS,避免多余计算
ROLLUP 本身不改变执行计划结构,但聚合行数增加会影响排序和内存使用,大数据量时注意 PGA 限制。











