WITH ROLLUP可直接在GROUP BY后添加生成小计和总计行,按字段从右到左逐级上卷,最后一行为全量合计;需用GROUPING()函数精准识别汇总行,避免与真实NULL混淆。

MySQL 8.0+ 用 WITH ROLLUP 实现合计行最简单
直接在 GROUP BY 后加 WITH ROLLUP 就能生成小计和总计行,不需要写 UNION 或子查询。它按分组字段从右到左逐级“上卷”,最后一行就是全量合计。
常见错误是把 WITH ROLLUP 放在 HAVING 后面,或者误以为它只加一行——实际上每级分组都会多出一行 NULL 值标记的汇总行。
-
SELECT region, product, SUM(sales) FROM orders GROUP BY region, product WITH ROLLUP会产出:各 (region, product) 组 → 各 region 小计(product 为NULL)→ 全局合计(region 和 product 都为NULL) - 用
IFNULL(region, '总计')或CASE WHEN product IS NULL AND region IS NOT NULL THEN '小计' WHEN region IS NULL THEN '总计' ELSE region END可读性更好 -
WITH ROLLUP仅支持 MySQL 8.0+ 和部分旧版(5.7 可用但行为略有差异),PostgreSQL/SQL Server 不兼容
PostgreSQL 和 SQL Server 用 GROUPING SETS 替代
GROUPING SETS 是标准 SQL 方案,明确指定要哪些组合维度,比 ROLLUP 更可控,也更易理解。
容易忽略的是:必须配合 GROUPING() 函数识别哪一列被“折叠”了,否则 NULL 值无法区分是真实数据还是汇总占位符。
-
SELECT region, product, SUM(sales) FROM orders GROUP BY GROUPING SETS ((region, product), (region), ())—— 分别对应明细、按 region 小计、全局合计 -
SELECT COALESCE(region, '总计') AS region, COALESCE(product, '') AS product, SUM(sales) FROM ...不安全,因为真实region可能就是NULL;应改用GROUPING(region)判断 - SQL Server 中
GROUPING SETS自带GROUPING_ID(),PostgreSQL 需手动用多个GROUPING()表达式拼
所有数据库通用:UNION ALL 手动拼合计行(慎用)
看似通用,实则隐患最多:排序错乱、类型隐式转换失败、NULL 处理不一致。尤其当聚合字段含 TEXT 或 JSON 类型时,UNION ALL 很可能报错 incompatible types。
如果非得用,务必保证两部分 SELECT 的列数、顺序、类型完全一致,并显式 cast。
- 先查明细:
SELECT region, product, SUM(sales) FROM orders GROUP BY region, product - 再查合计:
SELECT NULL::TEXT, NULL::TEXT, SUM(sales) FROM orders(PostgreSQL 示例;MySQL 用CAST(NULL AS CHAR)) - 最后
ORDER BY必须加,否则合计行位置不可控;建议用ORDER BY region NULLS LAST, product NULLS LAST
合计行里的 COUNT(*) 和 COUNT(字段) 差别很大
这是最容易踩的坑:合计行中 COUNT(*) 统计的是当前分组下的行数(含 NULL),而 COUNT(col) 会跳过该分组内 col 为 NULL 的记录——但在 WITH ROLLUP 或 GROUPING SETS 生成的汇总行里,col 本身就被设为 NULL,导致 COUNT(col) 返回 0。
- 想统计原始数据总条数?用
COUNT(*)或窗口函数COUNT(*) OVER() - 想统计某字段非空值总数?不能依赖汇总行里的
COUNT(col),得单独查或用条件聚合:SUM(CASE WHEN col IS NOT NULL THEN 1 ELSE 0 END) - Oracle 用户注意:
COUNT(*)在ROLLUP合计行里行为和其他库一致,但STATS_XXX类函数可能不适用
NULL 是数据缺失还是汇总标记,以及聚合函数在不同层级的语义变化。没加 GROUPING() 或没处理好类型,跑出来的结果看着像对,其实逻辑已偏移。

















