grouping sets 是 oracle 的分组语法,用于单查询生成多粒度汇总结果,解决重复扫描表和 sql 冗余问题;它需紧跟 group by,配合 grouping() 或 grouping_id() 区分 null 含义。

GROUPING SETS 是什么,它解决什么问题
Oracle 的 GROUPING SETS 不是函数,而是一种分组语法,用来在一个查询中同时计算多个不同粒度的汇总结果。它替代了过去靠多个 UNION ALL 拼接 GROUP BY 查询的做法,避免重复扫描表、减少 SQL 冗余,也更利于维护。
常见错误是把它当成函数调用,比如写成 GROUPING_SETS(...) —— 实际上它必须紧跟在 GROUP BY 后面,作为分组列表的一部分。
使用场景很典型:要同时查「按部门+岗位汇总」、「按部门汇总」、「按岗位汇总」、「全部汇总」,传统写法要 4 个子查询 union,而 GROUPING SETS 一行搞定。
注意:必须配合 GROUPING() 或 GROUPING_ID() 函数才能区分哪些字段是当前分组维度(否则 NULL 分不清是数据本身为空,还是被聚合掉的占位符)。
怎么写 GROUPING SETS 查询,参数怎么组织
GROUPING SETS 接收一个元组列表,每个元组是一组分组字段,用括号包裹,逗号分隔。最外层用括号包住整个列表。
例如:
GROUP BY GROUPING SETS ( (deptno, job), (deptno), (job), () )
-
(deptno, job):部门+岗位两级分组 -
(deptno):只按部门汇总(job 列值为 NULL,需用GROUPING(job) = 1标识) -
(job):只按岗位汇总 -
():空括号表示全表汇总(所有分组字段都为 NULL)
容易踩的坑:
- 括号层级不能错:外层是
GROUPING SETS (...),内层每个分组维度是(a,b)或(a),不是GROUPING SETS (a,b), GROUPING SETS (a) - 字段名必须来自 SELECT 列表或 GROUP BY 子句,不能出现未分组又非聚合的列
- 如果用了
ROLLUP或CUBE,它们和GROUPING SETS是互斥的写法,不能混用
如何识别每行的汇总层级:GROUPING() 和 GROUPING_ID()
GROUPING() 是单字段判断函数,返回 1 表示该字段在当前分组中被“折叠”(即不参与分组,仅作占位),返回 0 表示实际参与了分组。
比如对 deptno 和 job 做 GROUPING SETS ((deptno, job), (deptno), ()):
- 当前行为部门+岗位汇总 →
GROUPING(deptno)=0,GROUPING(job)=0 - 当前行为部门汇总 →
GROUPING(deptno)=0,GROUPING(job)=1 - 当前行为全汇总 →
GROUPING(deptno)=1,GROUPING(job)=1
GROUPING_ID(deptno, job) 把多个 GROUPING() 结果当二进制位拼起来转十进制,更紧凑。上面三种情况对应值分别是 0、1、3。
实际写报表时,常用 CASE WHEN GROUPING_ID(deptno, job) = 0 THEN '明细' ... END 来标注层级,比堆一堆 GROUPING() 更清晰。
性能和兼容性要注意什么
GROUPING SETS 在 Oracle 9i 及以后支持,但早期版本(如 9iR2)对复杂嵌套或大表性能不够稳定;11g 起优化明显,执行计划里通常显示为 “HASH GROUP BY” 或 “SORT GROUP BY”,和普通 GROUP BY 差异不大。
关键点:
- 它不会自动加索引,仍依赖底层字段的统计信息和索引设计
- 如果分组组合太多(比如
GROUPING SETS ((a,b,c), (a,b), (a,c), (b,c), (a), (b), (c), ())),会导致中间结果膨胀,比拆成几个简单查询还慢 -
WHERE条件尽量下推——写在GROUP BY外层的过滤,Oracle 一般能优化进聚合前,但复杂子查询或函数条件可能失效
多级汇总逻辑越复杂,GROUPING_ID() 的位运算表达就越重要;漏判 GROUPING() 状态,很容易把“部门汇总行”的 job=NULL 当成真实数据,导致统计翻倍。











