必须用 grouping() 函数区分 rollup 汇总行与明细行,因 is null 会混淆业务空值与上卷占位符;grouping() 仅在含 rollup/cube/grouping sets 的 group by 中有效,参数须为分组列,返回 0 或 1 表示是否为占位符。

必须用 GROUPING() 函数区分 ROLLUP 生成的汇总行和原始明细行,仅靠 IS NULL 判断会把业务空值和上卷占位符混为一谈,统计结果必然错乱。
为什么不能用 IS NULL 判断汇总行
ROLLUP 生成的小计/总计行会把被上卷的字段设为 NULL,但表里本来就可能存了合法的 NULL。比如 city 允许为空,WHERE city IS NULL 会同时干掉:部门小计行、全表总计行、所有城市缺失的真实订单——三类数据无法分离。
常见错误现象包括:
-
COALESCE(city, '小计')把所有city IS NULL都标成“小计”,原始空值和汇总行全被污染 - 导出报表后总数对不上,明细行数变少,小计消失
- 按
ORDER BY city排序时,汇总行被挤到开头或结尾,脱离上下文
GROUPING() 的合法使用位置和参数限制
GROUPING() 不是通用判空函数,它只在含 WITH ROLLUP、WITH CUBE 或 GROUPING SETS 的查询中有效。SQL Server 会硬性报错:GROUPING() can only be used in a GROUP BY clause with ROLLUP, CUBE or GROUPING SETS。
参数必须严格满足:
- 只能是
GROUP BY子句中**实际出现的列名或别名**(如GROUP BY region, city WITH ROLLUP→ 可用GROUPING(region)和GROUPING(city)) - 不能是计算列(如
GROUPING(UPPER(city)))、聚合字段(如GROUPING(SUM(amount)))、或未参与分组的字段 - 返回值是
tinyint:0 表示该列为原始分组值(哪怕值本身是NULL),1 表示该列是 ROLLUP 自动生成的占位符
多列 ROLLUP 中如何组合判断层级
对 GROUP BY ROLLUP(dept, team),单看一个 GROUPING() 不够,必须组合判断:
-
GROUPING(dept) = 0 AND GROUPING(team) = 0→ 正常明细行(dept+team都有值) -
GROUPING(dept) = 0 AND GROUPING(team) = 1→dept小计行(team被上卷,占位NULL) -
GROUPING(dept) = 1 AND GROUPING(team) = 1→ 全表总计行(两列都被上卷)
典型写法是用 CASE 美化显示:
SELECT
CASE
WHEN GROUPING(dept) = 1 THEN '总计'
WHEN GROUPING(team) = 1 THEN dept + ' 小计'
ELSE dept
END AS dept_label,
CASE
WHEN GROUPING(team) = 1 THEN NULL
ELSE team
END AS team,
COUNT(*) AS cnt
FROM staff
GROUP BY dept, team WITH ROLLUP
排序和过滤必须基于 GROUPING(),不能用 WHERE
GROUPING() 不能出现在 WHERE 子句中——因为 WHERE 执行早于分组,此时汇总占位符还没生成。想过滤汇总行,要用 HAVING;想控制顺序,要把 GROUPING() 放进 ORDER BY。
例如让小计紧贴所属 dept 组后面、“总计”固定在最底:
- 错误写法:
ORDER BY dept, team(team为NULL的汇总行会被排到最前或最后) - 正确写法:
ORDER BY GROUPING(dept) DESC, dept, GROUPING(team) DESC, team - 只保留明细(不含任何汇总):
HAVING GROUPING(dept) = 0 AND GROUPING(team) = 0
最容易被忽略的是:GROUPING() 返回的是 tinyint,不是布尔值,别在条件里跟字符串或浮点数做隐式转换;也别在 WHERE 里硬写,那会直接报错。











