GROUPING SETS是SQL标准中用于一次查询实现多维度聚合的语法,解决传统UNION ALL拼接多个GROUP BY导致的代码冗长和多次表扫描低效问题。

GROUPING SETS 是什么,它解决什么问题
当需要在一个查询里同时计算多个分组维度的聚合结果(比如按部门、按城市、按部门+城市、以及总计),传统写法只能用 UNION ALL 拼多个 GROUP BY 查询,既冗长又低效。GROUPING SETS 就是 SQL 标准中专门干这事的语法,它让一次扫描完成多维汇总成为可能。
本质是显式声明一组分组组合,数据库引擎据此生成对应聚合行。不是所有数据库都支持——PostgreSQL 9.5+、SQL Server 2008+、Oracle 9i+、Trino/Presto 支持;MySQL 目前(8.4)仍不支持,别硬套 GROUPING SETS,会报错 ERROR 1064。
怎么写一个带 GROUPING SETS 的基础查询
核心结构是把多个分组元组写在 GROUPING SETS 括号里,每个元组用括号包裹,逗号分隔:
SELECT dept, city, COUNT(*) AS cnt FROM sales GROUP BY GROUPING SETS ( (dept, city), -- 按部门+城市分组 (dept), -- 仅按部门分组 (city), -- 仅按城市分组 () -- 空元组 → 全局总计 );
注意几点:
-
()表示无分组字段,即整张表聚合,不是省略不写;写成GROUP BY ()是等价的,但放进GROUPING SETS更清晰 - 字段顺序不影响结果,但会影响 NULL 占位逻辑(见下一条)
- 每个分组行中,未参与该组分组的字段值为
NULL—— 这是识别汇总层级的关键
如何区分哪一行是哪个汇总层级
GROUPING() 函数是唯一可靠方式。它对指定列返回 1(该列在此行中“被汇总掉”,即未参与分组)或 0(该列有实际值):
SELECT
CASE
WHEN GROUPING(dept)=1 AND GROUPING(city)=1 THEN '总计'
WHEN GROUPING(dept)=1 THEN '城市汇总'
WHEN GROUPING(city)=1 THEN '部门汇总'
ELSE '明细'
END AS level,
COALESCE(dept, 'ALL') AS dept,
COALESCE(city, 'ALL') AS city,
COUNT(*) AS cnt
FROM sales
GROUP BY GROUPING SETS ((dept, city), (dept), (city), ())
ORDER BY level;常见错误:
- 直接用
dept IS NULL判断汇总层 —— 错!原始数据里 dept 本身就可能是 NULL,无法区分是真实 NULL 还是汇总占位 - 漏写
COALESCE或CASE,导致输出里一堆NULL,报表看不懂 - 在
ORDER BY中混用原始字段和GROUPING()表达式,某些数据库(如 SQL Server)要求排序字段必须出现在 SELECT 列表中
GROUPING SETS 和 CUBE / ROLLUP 有什么区别
CUBE(a,b) 等价于 GROUPING SETS((a,b),(a),(b),()),是全组合;ROLLUP(a,b) 等价于 GROUPING SETS((a,b),(a),()),是层次递进。但 GROUPING SETS 更灵活:可以跳过中间层(比如只要 (a,b) 和 (),不要 (a))、可以重复字段、可以混合不同粒度(比如 (region, product) 和 (country) 同时存在)。
性能上,三者底层执行计划往往一致,都是单次扫描 + 多路聚合。但 GROUPING SETS 显式声明意图,可读性更好,也更容易被优化器识别为“确定性多维汇总”——某些场景下比拼接多个 GROUP BY 更易触发物化优化。
实际用的时候,别为了炫技而用 GROUPING SETS。如果只是简单两级汇总,ROLLUP 更简洁;如果要排除某个中间组合(比如不要“仅城市”汇总),才值得上 GROUPING SETS。另外,记得检查目标数据库版本是否真支持——很多线上 MySQL 实例还在 5.7,连语法解析都过不去。

















