grouping sets 是多组独立分组的一次性声明,非多级分组语法糖;必须用外层括号包裹元组如 ((a), (b), ()), 空括号表示全表总计,需配合 grouping() 函数区分占位符 null 与真实 null。

GROUPING SETS 不是“多级分组”的语法糖,它是「多组独立分组」的一次性声明——你要的不是层级递进(如 ROLLUP),而是明确指定哪几套字段组合该被分别聚合。
GROUPING SETS 语法必须写对括号层级,否则直接报错
常见错误是把 GROUPING SETS (dept, region) 当成合法写法,其实它根本无法解析。SQL Server、PostgreSQL、Oracle 都要求:外层一对括号包裹所有元组,每个元组再用小括号包住字段列表。
-
GROUP BY GROUPING SETS ((dept), (region), (dept, region), ())✅ 正确:四组独立分组 -
GROUP BY GROUPING SETS (dept, region)❌ 报错:Incorrect syntax near ','(SQL Server)或 ERROR 42601(PostgreSQL) -
GROUP BY GROUPING SETS (dept), (region)❌ 缺少外层括号,解析失败 - 空括号
()表示全表总计,不能省略,也不能写成(NULL)或( )(带空格)
NULL 值到底是数据还是占位符?只靠 IS NULL 会漏判
执行 GROUPING SETS ((dept), (region)) 后,结果里会出现大量 dept = NULL 或 region = NULL。这些 NULL 是数据库自动填入的占位符,不代表原始数据为空。如果你用 WHERE dept IS NULL 过滤,会同时删掉真实为 NULL 的部门记录,以及所有按 region 分组的行(因为它们的 dept 被折叠了)。
- 必须搭配
GROUPING(dept):返回 1 表示该行中dept是占位符,0 表示是真实分组值 - 多列判断要分开调用:
GROUPING(dept) = 1 AND GROUPING(region) = 0→ 这行是按region分组的汇总 - 全表总计行满足:
GROUPING(dept) = 1 AND GROUPING(region) = 1 - 别用
COALESCE(dept, 'All')直接替换——先用GROUPING()判断,再做 CASE 映射才安全
GROUPING SETS 不支持嵌套,子查询只能做清洗,不能塞逻辑进去
你不能写 GROUPING SETS ((a), (SELECT b FROM t2 WHERE t2.id = t1.b_id)),也不允许在括号里放函数调用如 (DATE_TRUNC('month', order_time))。所有参与分组的列,必须在子查询中预先计算好、起好别名,并原样输出。
- 子查询必须显式别名,如
FROM (SELECT COALESCE(dept, '[Unknown]') AS dept, ... FROM raw) AS t - 时间维度需提前截断:
DATE_TRUNC('month', order_time) AS order_month,不能在外层 GROUP BY 中计算 - JOIN 和窗口函数建议放在子查询里完成;若用 CTE,PostgreSQL 12+ 可加
MATERIALIZED避免重复执行 - 任何试图“动态生成 grouping set 列表”的做法(比如拼字符串再 EXEC)都不属于标准 SQL,且不可移植
三列以上组合时性能会断崖下降,得提前评估扫描代价
写 GROUPING SETS ((a), (b), (c), (a,b), (a,c), (b,c), (a,b,c), ()) 看似全面,实际会让优化器生成 8 个独立聚合路径。主流数据库不会复用中间哈希表,而是对每组都重扫数据或重建索引结构。
- 用执行计划检查是否有多个
Hash Aggregate或Sort Aggregate节点——有,说明物理扫描没省 - 索引应优先覆盖最宽组合,例如上例建
INDEX ON t(a, b, c),比三个单列索引更有效 - 当维度数 ≥ 4 或 grouping set 组合数 > 8 时,不如拆成两段查询:先算宽口径(如
(a,b,c)),再用应用层聚合出其余维度 - Hive/Trino 对 GROUPING SETS 优化较好,但 MySQL 仍不支持(截至 8.4),强行写会触发
ERROR 1064 (42000)
真正容易被忽略的是:GROUPING SETS 输出的每一行,本质都是新生成的聚合记录,它不保留原始明细的任何上下文。如果你需要在汇总行旁附带明细统计(比如“华北区共 127 笔订单,其中 32 笔超 5 万元”),就得用子查询预计算再 JOIN,而不是指望 GROUPING SETS 自己“嵌套”出层次。











