SQL GROUPING SETS 的多维聚合与 ROLLUP 等价写法优化

1次阅读

grouping sets 要等价于 rollup(a,b,c),必须显式写出全部前缀组合:(())、(a)、(a,b)、(a,b,c),缺一不可,否则行数和grouping()值均不匹配。

SQL GROUPING SETS 的多维聚合与 ROLLUP 等价写法优化

GROUPING SETS 怎么写才等价于 ROLLUP

直接说结论:GROUPING SETS 要完全模拟 ROLLUP(a, b, c),必须显式列出所有前缀组合:空集、(a)(a,b)(a,b,c)。漏掉任意一个,结果行数和 GROUPING() 值都会对不上。

常见错误是以为 GROUPING SETS ((a,b,c), (a,b), (a)) 就够了——其实还缺 ()(全表聚合),尤其当原始 sql 里有 WHERE 条件时,这个空集组是否出现会直接影响结果一致性。

  • ROLLUP(a,b,c) 固定生成 4 组:`()`, `(a)`, `(a,b)`, `(a,b,c)`
  • GROUPING SETS 不自动补全,少写一组就少一行,且 GROUPING(a) 返回值可能意外为 0
  • postgresql 和 SQL Server 都严格按列表执行;mysql 8.0+ 支持但语法解析更敏感,空括号 () 必须写成 (())

GROUPING() 函数返回值为什么总对不上

GROUPING() 的返回值取决于你当前行匹配的是哪个 GROUPING SETS 子句,不是看字段是否为空。容易误判的点在于:字段值为 NULLGROUPING(col) = 1;前者是数据本身为空,后者是该维度被“折叠”了。

比如在 GROUPING SETS ((a), ()) 中,全表聚合那行 a 显示为 NULL,此时 GROUPING(a) 才是 1;但如果某条原始数据里 a 就是 NULL,而它落在 (a) 这组里,GROUPING(a) 仍是 0。

  • 必须用 GROUPING(a) 判断是否属于聚合维度,不能靠 a IS NULL
  • 多个字段时,GROUPING(a,b) 是位运算结果:GROUPING(a)*2 + GROUPING(b),别手算错
  • spark SQL 和 Trino 对 GROUPING() 行为一致,但 hive 旧版本不支持该函数,需改用 GROUPING__ID

性能差异主要卡在哪儿

语义等价不等于执行等价。ROLLUP 是优化器内置模式,多数引擎会复用一次扫描做多层聚合;而 GROUPING SETS 在某些场景下(尤其是子查询嵌套或带窗口函数时)可能触发多次分组计算。

实测发现:在 PostgreSQL 15 中,简单 select ... GROUPING SETS ((a),(b))ROLLUP(a,b) 多 15%–20% CPU 时间;但在 clickhouse 23.8+ 里两者计划几乎一样——关键看底层是否把 GROUPING SETS 下推到向量化执行层。

  • 避免在 GROUPING SETS 外再套一层 ORDER BYLIMIT,这常导致优化器放弃合并扫描
  • 如果只想要部分组合(比如不要全表汇总),硬写 GROUPING SETS 反而比 ROLLUP 快,因为没冗余计算
  • oracle 12c+ 对两者做了统一优化,但开启 OPTIMIZER_FEATURES_ENABLE 低于 12.1 时,GROUPING SETS 可能退化成 union ALL

数据库移植时最常崩在哪

语法看似标准,实际兼容性断层明显。最痛的是空集写法和字段顺序约束。

比如 GROUPING SETS ((), (a)) 在 PostgreSQL 合法,在 SQL Server 必须写成 GROUPING SETS (GROUPING SETS (), (a));而 BigQuery 根本不支持空集,只能用 UNION ALL SELECT ... WHERE 1=0 模拟。

  • 字段顺序必须和 SELECT 列表严格一致,否则 Presto/Trino 会报 column 'x' not in GROUP BY
  • SQL Server 要求所有 GROUPING SETS 元素字段数相同,哪怕填 NULL 占位也不行;PostgreSQL 允许不同长度
  • sqlite 不支持 GROUPING SETSROLLUP,得用 UNION ALL 手搓,注意去重逻辑是否要加 DISTINCT

真正麻烦的不是语法转换,而是当 GROUPING() 值参与后续 CASE WHEN 分支时,不同引擎对空集的 GROUPING 位掩码解释可能差一位——这种细节查文档都得翻到 release note 里找。

text=ZqhQzanResources