GROUPING SETS 与 CUBE 是什么
一条查询同时产出多个分组口径,是报表里的常见诉求:按城市、按状态、以及全表合计一次拿全。PostgreSQL 用 GROUPING SETS 显式列出需要的组合,用 CUBE 生成全部列组合,用 ROLLUP 生成逐级汇总;MySQL 8.0 只支持 ROLLUP,其余口径要靠 UNION ALL 拼装。
基本信息
| PostgreSQL | GROUPING SETS、CUBE、ROLLUP 三种分组修饰符,且可嵌套书写 |
|---|---|
| MySQL 8.0 | 仅支持 GROUP BY ... WITH ROLLUP,无 GROUPING SETS 与 CUBE |
| 合计行识别 | 用 GROUPING() 函数判断某列在当前行是否参与分组 |
| 结果形态 | 单个结果集,各口径的行混在一起,靠标识列与排序区分 |
| 成本 | 每个分组组合都要各自扫描与聚合一次 |
三种口径对照
| 写法 | 产生的分组组合 | 典型用途 |
|---|---|---|
| GROUPING SETS ((a), (b), ()) | 只产出显式列出的三个组合 | 口径固定、组合数不多的报表 |
| CUBE (a, b) | 全组合:(a,b)、(a)、(b)、() | 两三个维度的任意交叉汇总 |
| ROLLUP (a, b) | 层级组合:(a,b)、(a)、() | 省到市、年到月这类上下级汇总 |
| GROUP BY () | 只有一个总计组 | 单独取整表合计,等价于不分组 |
典型使用场景
- 固定口径报表:按城市、按状态、整表合计三个数字一次查出
- 交叉分析:两个维度全组合汇总,省掉手写多条语句
- 层级汇总:省到市、年到月的逐级小计与总计
- 核对校验:合计行可直接与明细行加总做一致性检查
- 减少往返:一次查询代替多次分组查询,应用层不再拼接结果
- 供 BI 落地:单个结果集即可导成一张带口径标识的宽表
PostgreSQL 三种写法
PostgreSQL 三种写法
SELECT city, status, COUNT(*) AS cnt # 三个口径一次输出
FROM users
GROUP BY GROUPING SETS ((city), (status), ());
SELECT city, status, COUNT(*) AS cnt # CUBE:两列的全部组合
FROM users
GROUP BY CUBE (city, status)
ORDER BY city NULLS LAST, status NULLS LAST; # 让合计行排在结果末尾
SELECT city, status, COUNT(*) AS cnt, # 用 GROUPING 区分合计行
GROUPING(city) AS g_city, GROUPING(status) AS g_status
FROM users GROUP BY ROLLUP (city, status)
ORDER BY g_city, g_status; # 先明细行、再小计、最后总计结果逐行读法
| g_city / g_status | 含义 | 如何使用 |
|---|---|---|
| 0 / 0 | 两列都参与分组,是最细的明细行 | 正常业务数据行 |
| 1 / 0 | 按 status 汇总,city 位显示 NULL | 该 NULL 是占位符,不是城市为空 |
| 0 / 1 | 按 city 汇总,status 位显示 NULL | 同上,需要按口径过滤 |
| 1 / 1 | 总计行,两个维度都不参与分组 | 整表合计,常用于算占比 |
多口径结果里的 NULL 有两重含义:列本身为空,或者该列在当前口径下不参与分组。两者必须靠 GROUPING() 区分,否则合计行会被下游当成空值行处理,汇总数字会翻倍或串口径。
MySQL 8.0 没有 GROUPING SETS 与 CUBE,常见替代是用 UNION ALL 拼多条分组语句;此时要保证各分支的列数与类型一致,并自行补一个口径标识列,方便下游区分小计与总计。
分组组合越多,扫描与聚合的代价越高:CUBE 的组合数随列数按 2 的幂增长。大表上先想清楚真正需要的口径,能用 GROUPING SETS 枚举就不要用 CUBE 全开。