group by a, b with rollup生成四类行:(a,b)明细、(a,null)一级小计、(null,null)全局总计;必须用grouping()精准识别汇总行,避免与原始null混淆。

WITH ROLLUP 能加总计行,但不是“加一行总数”那么简单——它严格按 GROUP BY 列顺序生成多级小计和全局总计,且必须用 GROUPING() 识别汇总行,否则 NULL 标签会和原始空值混淆。
GROUP BY a, b WITH ROLLUP 生成哪几类行?
结果里会出现四类组合(以 GROUP BY region, city WITH ROLLUP 为例):
-
(region, city):正常明细分组 -
(region, NULL):每个region下所有city的小计(city被上卷) -
(NULL, NULL):全表总计(region和city都被上卷)
注意:GROUPING(region) 在后两行都为 1;GROUPING(city) 只在第二、三行返回 1。靠 IS NULL 判断会把真实 city IS NULL 数据也当成小计,导致重复或误标。
怎么让 NULL 显示成“华东小计”“总计”而不是空值?
必须用 CASE WHEN + GROUPING() 组合判断层级:
SELECT
CASE
WHEN GROUPING(region) = 1 THEN '总计'
WHEN GROUPING(city) = 1 THEN CONCAT(region, ' 小计')
ELSE region
END AS region_label,
COALESCE(city, '') AS city,
SUM(sales) AS total_sales
FROM orders
GROUP BY region, city WITH ROLLUP;
关键点:
- 别用
COALESCE(city, '小计')—— 它无法区分是小计还是原始 NULL -
GROUPING()是 MySQL 8.0+ 原生函数,老版本只能退化用嵌套IF() - 如果只想要最终总计、不要中间小计,
HAVING不管用,得用UNION ALL拼接
为什么 ORDER BY 后总计行跑到最前面?
MySQL 默认把 NULL 排在升序最前,而 WITH ROLLUP 生成的汇总行恰好全是 NULL。直接 ORDER BY region 会让总计行顶在第一行。
正确做法是先按汇总层级排序,再排实际值:
ORDER BY GROUPING(region), GROUPING(city), region, city
等价于:
- 先排非汇总行(
GROUPING() = 0) - 再排
region小计(GROUPING(city) = 1) - 最后排总计(
GROUPING(region) = GROUPING(city) = 1)
这样结构才符合“明细 → 小计 → 总计”的阅读习惯。
真实 NULL 数据会导致总计不准吗?
会。比如 region 字段本身就有 NULL 值,GROUP BY region WITH ROLLUP 会输出:
- ‘East’ 行
- ‘West’ 行
-
NULL行(代表 region 为空的数据) -
NULL总计行(全量和)
此时最后一行的 SUM(sales) 包含了第三行的值,但业务上可能把第三行理解为“未填区域”,又把第四行当“总销售额”,等于把 NULL 数据算了两次。
上线前务必查:SELECT COUNT(*) FROM orders WHERE region IS NULL。若存在真实 NULL,要么清洗数据,要么改用 GROUPING SETS(MySQL 不支持,需换 PostgreSQL/SQL Server)或 UNION ALL 手动拼。
GROUPING() 是唯一可靠依据,其他都是猜。











