grouping sets 是 group by 的子句而非函数,用于一次查询中指定多个分组组合并返回单一结果集;必须写成 group by grouping sets ((a), (a,b), ()) 形式,不可加括号调用或留空,配合 grouping() 函数可区分占位 null 与真实 null。

GROUPING SETS 不是函数,是 GROUP BY 的子句
很多人在查文档时搜 GROUPING SETS(),以为它是个函数,结果语法报错。实际上 GROUPING SETS 是 GROUP BY 后面的一个关键字结构,必须配合括号和元组使用,不能加括号调用,也不能单独写成 GROUPING SETS()(空括号会报错)。
它的作用是显式声明多组分组维度组合,替代手写多个 UNION ALL 查询,让 SQL 更简洁、执行计划更优。
-
GROUP BY GROUPING SETS ((a), (a,b), ())合法:三组分组 —— 按 a、按 a+b、全表汇总 -
GROUP BY GROUPING SETS (a, b)非法:缺少外层括号和元组结构,会被解析为普通列列表 -
GROUP BY GROUPING SETS()非法:空括号语法不被任何主流数据库支持
用 GROUPING() 函数区分 NULL 是真实值还是汇总占位符
当某列出现在某些 grouping set 中、但不出现在另一些中时,SQL 会在缺失维度的行里填 NULL。但这个 NULL 和业务里真实的 NULL 值无法区分 —— 这正是 GROUPING() 的用途:它对当前行中“因当前 grouping set 未包含该列而补的 NULL”返回 1,否则返回 0。
比如 GROUP BY GROUPING SETS ((region), (product), ()),在 region 行 product 列是补的 NULL,GROUPING(product) 就是 1;而在 product 行 region 列是补的 NULL,GROUPING(region) 就是 1。
- 必须把
GROUPING(col)和col一起选出来,才能做条件判断 - PostgreSQL / SQL Server / Oracle 都支持;MySQL 8.0+ 才支持
GROUPING SETS,且GROUPING()也需 8.0.29+ 版本 - 别直接用
IS NULL判断维度是否“被汇总掉”,它会误判业务真实 NULL
避免 CUBE / ROLLUP 替代陷阱:它们生成的分组不可控
CUBE(a,b) 会固定生成全部 4 种组合:(), (a), (b), (a,b);ROLLUP(a,b) 固定按层级生成:(), (a), (a,b)。但实际报表常需要跳过中间层 —— 比如只要「按部门」和「全公司总计」,不要「按部门+职级」这种粒度。
这时候硬套 ROLLUP 或 CUBE 会导致多余分组,还得用 HAVING 过滤,既影响可读性,也可能干扰优化器选择索引。
- 明确只需要 (dept), () → 直接写
GROUP BY GROUPING SETS ((dept), ()) - 要 (region, product), (region), (product), () →
GROUPING SETS ((region,product), (region), (product), ()) -
ROLLUP(a,b,c)等价于GROUPING SETS ((a,b,c), (a,b), (a), ()),但写法上后者意图更直白
性能与 NULL 处理的实际坑点
GROUPING SETS 在逻辑上等价于多个 UNION ALL,但数据库通常能复用扫描和排序,所以一般比手写 UNION 快。不过有三个容易忽略的细节:
- 所有 grouping set 共享同一套聚合计算(如
SUM(sales)只算一次),但如果聚合表达式里含列引用(如CASE WHEN region='A' THEN sales END),要注意 NULL 行的 region 是补位值,可能意外进入分支 - 如果基表很大且没有覆盖所有 grouping set 维度的复合索引,某些分组可能走不到索引,建议用
EXPLAIN看各 set 对应的执行路径 - Oracle 中
GROUPING()返回的是NUMBER,而 PostgreSQL 返回integer,跨库迁移时若在应用层做类型判断要留心
最麻烦的其实是业务方看到一堆 NULL 行却不知道哪些是汇总占位符 —— 所以只要用了 GROUPING SETS,就别省那几行 GROUPING() 判断,否则后续排查成本远高于写时多敲几个字符。











