GROUPING SETS 是 SQL 标准中用于一次性生成多个指定分组聚合结果的语法,必须在需同时统计多维组合(如部门、城市、部门+城市、总计)且要求高效维护与优化执行计划时使用。

GROUPING SETS 是什么,什么时候必须用它
GROUPING SETS 是 SQL 标准中用于一次性生成多个分组聚合结果的语法,本质是把多个 GROUP BY 子句的结果合并成一张表。它不是“可有可无的语法糖”,当你需要同时统计「按部门」「按城市」「按部门+城市」「总计」这四类汇总时,不用 GROUPING SETS 就得写 4 个 SELECT 加 UNION ALL,维护成本高、执行计划难优化。
常见错误是把它当成 ROLLUP 或 CUBE 的替代品去硬套——但三者语义不同:ROLLUP(a,b) 固定生成 (a,b)、(a)、() 三层;CUBE(a,b) 生成全部组合;而 GROUPING SETS 完全由你显式指定,更精确、更可控。
怎么写一个合法且高效的 GROUPING SETS 查询
核心是把所有想汇总的维度组合写进括号里,每个组合本身是一组圆括号,整体用 GROUPING SETS 包裹:
SELECT COALESCE(dept, '总计') AS dept, COALESCE(city, '总计') AS city, COUNT(*) AS cnt, SUM(salary) AS total_salary FROM employees GROUP BY GROUPING SETS ( (dept, city), (dept), (city), () );
注意几点:
-
GROUPING SETS后面不能跟普通列,所有输出列必须在SELECT中做兼容处理(比如用COALESCE填充空值) - 每个子集括号内可以为空
(),表示全表聚合(即总计行) - 不支持嵌套或动态生成,括号里的组合必须静态写出
- 如果某列在某个 grouping set 中未出现,查询中读取该列会返回
NULL,这是正常行为,不是数据问题
GROUPING() 函数用来区分 NULL 是分组缺失还是原始数据
当原始数据里就有 NULL 值(比如某些员工 city 字段本身就是 NULL),和因分组缺失产生的 NULL(比如 () 这个 grouping set 导致 dept 和 city 都为 NULL)就容易混淆。
这时必须用 GROUPING() 函数判断:
SELECT CASE WHEN GROUPING(dept) = 1 THEN '总计' ELSE dept END AS dept, CASE WHEN GROUPING(city) = 1 THEN '总计' ELSE city END AS city, COUNT(*) AS cnt FROM employees GROUP BY GROUPING SETS ((dept), (city), ());
GROUPING(dept) 返回 1 表示当前行的 dept 值是因分组缺失而补的 NULL;返回 0 表示这个 NULL 来自原始数据。不加这层判断,报表标题可能把真实空城市和总计行混在一起。
PostgreSQL / SQL Server / Oracle 兼容性差异
- PostgreSQL 9.5+、SQL Server 2008+、Oracle 12c+ 都支持标准
GROUPING SETS,语法一致
- MySQL 至今不支持(截至 8.4),强行写会报错
ERROR 1064,只能用 UNION ALL 模拟
- 在 SQL Server 中,
GROUPING_ID() 可以替代多个 GROUPING() 调用,压缩判别逻辑
- PostgreSQL 对空 grouping set
() 处理最严格,如果 WHERE 条件过滤太狠导致没数据,那一整行总计也会消失,需检查是否有数据满足全表聚合条件
GROUPING SETS,语法一致 ERROR 1064,只能用 UNION ALL 模拟 GROUPING_ID() 可以替代多个 GROUPING() 调用,压缩判别逻辑 () 处理最严格,如果 WHERE 条件过滤太狠导致没数据,那一整行总计也会消失,需检查是否有数据满足全表聚合条件 真正麻烦的不是语法,而是设计阶段就要想清楚:哪些维度组合业务上真需要?漏掉一个,报表就得返工;多写一个,性能可能翻倍下降——尤其当基表几千万行时,每个 grouping set 都是一次独立扫描或哈希聚合。










