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

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+ 支持,但
ROLLUP在WHERE子句中不能下推过滤,可能扫更多行
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_id、order_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();这些细节不查文档很容易掉进隐式类型转换的坑里。











