grouping sets 是 sql 标准中高效生成多级聚合的原生语法,支持一次扫描输出指定分组组合,需配合 grouping() 或 grouping_id() 区分 null 含义,比 union all 和 with rollup 更灵活、跨库兼容且性能更优。

GROUPING SETS 是 SQL 中最接近 Excel 数据透视表语义的原生能力——它不依赖客户端渲染,也不靠 PIVOT 这类方言语法,而是直接在一次扫描中产出多层级聚合结果。只要你的数据库支持(PostgreSQL 9.5+、SQL Server 2008+、Oracle 9i+、Trino、StarRocks、Doris 等),就该优先用它替代多个 UNION ALL 或应用层拼接。
GROUPING SETS 的本质是显式声明分组组合
它不是“自动推导所有可能维度”,而是让你明确写出要哪几组:哪些字段参与分组、哪些完全不参与(即全表汇总)。这和 WITH CUBE 或 WITH ROLLUP 的隐式逻辑完全不同。
-
GROUPING SETS ((a), (a,b), ())表示:只按a分组、按a和b联合分组、以及全表总计三组 - 每个括号内是一组独立的
GROUP BY子句,顺序无关,但字段重复出现时语义不变 - 结果中未参与当前分组的字段值为
NULL,这是识别汇总行的唯一依据 - 必须配合
GROUPING()函数才能把NULL区分为“真实空值”还是“聚合占位符”
怎么处理 NULL 才不被误读为脏数据?
原始结果里 region = NULL 可能是某条记录本来就没填地区,也可能是按 category 分组时把 region “折叠”掉了。光看 NULL 无法区分。
- 用
GROUPING(region):返回 1 表示该列在此行中未参与分组(即占位NULL),返回 0 表示它是真实值 - 常用写法是
CASE WHEN GROUPING(region) = 1 THEN '总计' ELSE region END AS region_label - 注意:
GROUPING()只接受单个列名,不能写GROUPING(region, category)——那是旧版写法,已废弃;正确是分别调用GROUPING(region)和GROUPING(category)
GROUPING SETS vs WITH ROLLUP:顺序决定一切
WITH ROLLUP 会按 GROUP BY 字段顺序生成递进式小计,比如 GROUP BY a, b, c WITH ROLLUP 会产出 (a,b,c) → (a,b) → (a) → () 四层,但无法跳过中间层(比如不要 (a,b) 只要 (a) 和 ())。
-
GROUPING SETS完全自由:你要 (a)、(c)、() 就只写这三组,不会多扫一遍 (a,c) -
WITH ROLLUP在 MySQL 中仍广泛使用,但它在 PostgreSQL 中不支持;而GROUPING SETS是 SQL:2003 标准,跨库兼容性更好 - 性能上,
GROUPING SETS通常比等价的UNION ALL快 2–5 倍,因为只做一次表扫描 + 一次哈希分组
别漏掉 GROUPING_ID 这个快捷键
当维度超过 3 个时,手写一堆 CASE WHEN GROUPING(a)=0 AND GROUPING(b)=1... 很容易出错。GROUPING_ID(a,b,c) 会把各列的 GROUPING() 结果拼成二进制再转十进制,直接给出唯一编码。
- 比如
GROUPING_ID(region, category, channel)返回0表示三者都参与分组(明细行),返回6(二进制 110)表示只有channel是真实值,其余两个是占位NULL - 这个值可以作为后续
CASE分支的判断依据,比嵌套逻辑清晰得多 - 注意:不同数据库对
GROUPING_ID的参数顺序敏感,务必和GROUPING SETS中字段顺序一致
GROUPING SETS。











