grouping()是唯一能区分rollup生成的null与原始null的函数,返回1表示上卷占位符、0表示真实值(含原始null),且仅适用于group by列表中的列;mysql 8.0+才支持,旧版无法安全区分。

GROUPING() 是唯一可靠的方式
ROLLUP 生成的 NULL 和原始数据里的 NULL 在结果集中完全不可见区分,直接用 IS NULL 会把两者混为一谈。只有 GROUPING() 函数能明确告诉你:这个 NULL 是不是 ROLLUP 自动补上的占位符。
它返回整数:0 表示该列是真实值(包括原始 NULL),1 表示该列被上卷、NULL 是聚合占位符。
-
GROUPING()的参数必须是GROUP BY列表中出现的列名,否则报错:ERROR: grouping() can only be used in a GROUP BY clause with ROLLUP, CUBE or GROUPING SETS - 不能对非分组列调用,比如
SELECT GROUPING(status) FROM t GROUP BY dept WITH ROLLUP会失败,因为status没出现在GROUP BY中 - 在 MySQL 中,
GROUPING()仅在 8.0+ 支持;旧版只能用WITH ROLLUP+ 手动识别逻辑,但无法区分原始NULL
别用 IS NULL 做汇总行标记
写 CASE WHEN city IS NULL THEN '合计' ELSE city END 看似简单,实际会把所有原始 city 为空的记录也标成“合计”,报表立刻出错。
正确做法是用 GROUPING() 锁定上卷层级:
SELECT
CASE WHEN GROUPING(city) = 1 THEN '全国汇总'
ELSE COALESCE(city, '[未知城市]')
END AS city_label,
SUM(sales) AS total_sales
FROM sales_data
GROUP BY ROLLUP(city);
-
COALESCE(city, '[未知城市]')保留原始NULL的业务含义 -
GROUPING(city) = 1只匹配 ROLLUP 生成的汇总行,不碰真实数据 - 如果同时按多列
ROLLUP(a, b),GROUPING(b) = 1表示b被上卷(小计),GROUPING(a) = 1才表示总计(a和b都空)
GROUPING_ID() 适合多级分类场景
当 GROUP BY ROLLUP(a, b, c) 产生 4 种聚合层级时,逐个写 GROUPING(a)、GROUPING(b)、GROUPING(c) 太啰嗦。这时可用 GROUPING_ID(a,b,c) 把它们压缩成一个整数编码:
-
GROUPING_ID(a,b,c) = 0→ 明细行(a,b,c全非空) -
GROUPING_ID(a,b,c) = 1→c上卷(a,b有值,c为NULL占位) -
GROUPING_ID(a,b,c) = 3→b,c上卷(a有值,b=c=NULL) -
GROUPING_ID(a,b,c) = 7→ 全上卷(总计行)
本质是二进制拼接:GROUPING_ID(a,b,c) = GROUPING(a)*4 + GROUPING(b)*2 + GROUPING(c)。要不要用它,取决于你是否需要快速归类聚合层级,而不是单点判断。
MySQL 与 PostgreSQL 的语法差异必须注意
MySQL 不支持标准 GROUP BY ROLLUP(...) 写法,只认 GROUP BY ... WITH ROLLUP;而 PostgreSQL、SQL Server、Oracle 支持 ROLLUP() 函数形式。
- MySQL 示例:
SELECT dept, team, SUM(sales) FROM t GROUP BY dept, team WITH ROLLUP - PostgreSQL 示例:
SELECT dept, team, SUM(sales) FROM t GROUP BY ROLLUP(dept, team) - 两者都支持
GROUPING(),但 MySQL 8.0+ 才引入,5.7 及更早版本没有该函数——这意味着你在老 MySQL 里根本没法安全区分 - 索引对 ROLLUP 查询影响明显:复合索引
(dept, team)能加速GROUP BY ROLLUP(dept, team),但反过来ROLLUP(team, dept)就可能走全表扫描
GROUPING(),或者误把 WITH ROLLUP 当成通用标准去套其他数据库。一旦上线,原始 NULL 和汇总 NULL 混在一起,修复成本远高于写对第一行。











