SQL GROUPING SETS / ROLLUP / CUBE 的多维聚合性能与使用场景

1次阅读

grouping sets比rollup/cube更灵活但写法冗长,适用于精确控制聚合维度组合;rollup适合层级汇总,cube易致结果爆炸,须谨慎用于低基数维度;grouping()函数必不可少,用于区分NULL是汇总产生还是原始数据。

SQL GROUPING SETS / ROLLUP / CUBE 的多维聚合性能与使用场景

GROUPING SETS 比 ROLLUP/CUBE 更灵活,但写法稍啰嗦

当你需要精确控制聚合维度组合(比如只要 (a, b)(c) 两组,不要 (a)(a, b, c)),GROUPING SETS 是唯一选择。它本质是显式列出所有想要的分组元组,不推导、不补全。

常见错误是误以为 GROUPING SETS ((a), (b), (a,b)) 等价于 ROLLUP(a,b) —— 实际上 ROLLUP(a,b) 还会多出空集 ()(即全表汇总),而 GROUPING SETS 不会,除非你手动加上 ()

  • GROUPING SETS ((a,b), (c)) → 只产出两组:按 a+b 分组的结果 + 按 c 分组的结果
  • ROLLUP(a,b) → 自动产出 (a,b)(a)()(注意没有单独的 (b)
  • CUBE(a,b) → 产出全部子集:(a,b)(a)(b)()

ROLLUP 适合层级明确的“从细到粗”汇总

典型场景是时间维度(年→季度→月)或组织架构(部门→小组→员工)。ROLLUP 按字段顺序生成前缀组合,天然匹配树状层级关系。

性能上,ROLLUP(a,b,c) 一般比等效的 GROUPING SETS ((a,b,c),(a,b),(a),()) 略快——优化器对 ROLLUP 有更成熟的执行路径,尤其在 postgresql 和 SQL Server 中。但在 sqlite 或某些旧版 mysql(8.0.12+ 才支持)里,两者底层都转成相同计划,差异可忽略。

  • ROLLUP(year, quarter, month) 就能自然得到月度、季度、年度、总计四层
  • 如果把顺序写反成 ROLLUP(month, quarter, year),结果就乱了:它会生成 (month)(month,quarter)(month,quarter,year)(),缺失纯 (quarter) 和纯 (year)
  • MySQL 8.0.12+ 支持,但 ROLLUPWHERE 子句中不能下推过滤,可能扫更多行

CUBE 易导致结果爆炸,务必检查维度基数

CUBE 生成 2ⁿ 个分组,n 是字段数。当 n=5 且每个字段平均有 10 个值时,理论最多产生 32 × 10⁵ 行结果——实际虽受数据稀疏性限制,但依然容易 OOM 或拖慢查询。

错误现象常是查询卡住、返回结果远超预期、或 GROUPING() 列出现大量 1 却不知哪几维被折叠了。用之前先跑 select count(DISTINCT a), COUNT(DISTINCT b), ... 看各维基数。

  • 只对低基数维度用 CUBE:比如 status IN ('active','inactive')region IN ('north','south')
  • 高基数字段(如 user_idorder_id)绝对不能放进 CUBE,哪怕只是测试
  • PostgreSQL 中可用 LIMIT 配合 CUBE 快速验证结构,但注意 LIMIT 在聚合后生效,不减少计算量

GROUPING() 函数是读懂结果的钥匙,别漏掉它

所有 GROUPING SETS/ROLLUP/CUBE 的结果里,被“折叠”的列值是 NULL,但这个 NULL 和真实数据里的 NULL 无法区分——必须靠 GROUPING(col) 返回 1/0 来标记。

最常踩的坑是直接 SELECT col FROM ... GROUP BY CUBE(a,b),看到一 NULL 就懵了。没 GROUPING(),你就永远不知道某行的 NULL 是因为该维被汇总掉了,还是原始数据真为空。

  • 正确姿势:SELECT a, b, GROUPING(a) AS ga, GROUPING(b) AS gb, COUNT(*) FROM t GROUP BY CUBE(a,b)
  • GROUPING(a)=1 AND GROUPING(b)=0 → 这行是按 b 分组的汇总(a 被折叠)
  • 在报表里,常把 GROUPING() 结果映射成字符串,比如 CASE WHEN GROUPING(name)=1 THEN 'All Names' ELSE name END

复杂点在于不同数据库GROUPING() 的 NULL 处理略有差异,比如 oracle 允许 GROUPING_ID() 批量编码,而 SQLite 只支持单列 GROUPING();这些细节不查文档很容易掉进隐式类型转换的坑里。

text=ZqhQzanResources