ROLLUP是GROUP BY的扩展子句,用于自动生成多级小计和总计行,按字段顺序从右向左逐级升维;普通GROUP BY仅返回明细分组结果,而WITH ROLLUP额外增加(a,NULL)、(NULL,NULL)等层级。

ROLLUP 是什么,它和 GROUP BY 有什么区别
ROLLUP 是 GROUP BY 的扩展语法,用于在分组统计基础上自动生成多级小计和总计行。它不是独立语句,而是写在 GROUP BY 后面的修饰子句,比如 GROUP BY a, b WITH ROLLUP。
关键区别在于:普通 GROUP BY a, b 只返回每个 (a,b) 组的聚合结果;而 WITH ROLLUP 会额外补上 (a, NULL)(按 a 小计)、(NULL, NULL)(总计)这两层——顺序严格按字段列表从右往左逐级“升维”。
常见错误现象:GROUP BY a, b WITH ROLLUP 返回了 NULL 值却不知道来源;或者误以为能控制小计层级顺序,其实顺序由字段书写顺序决定。
使用场景包括:销售报表按地区+门店统计销售额,并要同时看到各地区汇总、全部总和;订单分析按年+月分组,还需年度小计和最终总计。
如何识别和处理 ROLLUP 生成的 NULL 行
ROLLUP 产生的小计/总计行中,低优先级字段会被置为 NULL。但这个 NULL 是占位符,不是真实数据缺失——直接 WHERE col IS NULL 会误删真实数据行。
正确做法是用 GROUPING() 函数判断:它对当前行中因 ROLLUP 生成的 NULL 返回 1,对真实数据的 NULL 或非 NULL 值都返回 0。
SELECT IF(GROUPING(region), '总计', region) AS region, IF(GROUPING(store), '小计', store) AS store, SUM(sales) AS total FROM sales_data GROUP BY region, store WITH ROLLUP;
注意:
-
GROUPING()参数必须是GROUP BY中出现的列名 - 多列
ROLLUP时,GROUPING(a)=1 AND GROUPING(b)=0表示这是 a 级小计(b 已被 rollup 掉) - MySQL 5.7+ 支持该函数;旧版本只能靠
IS NULL+ 业务逻辑推断,风险高
ROLLUP 和 CUBE、GROUPING SETS 的取舍
ROLLUP(a,b,c) 生成的分组组合是固定的:(a,b,c) → (a,b,NULL) → (a,NULL,NULL) → (NULL,NULL,NULL),共 4 层,呈树状层级关系。
CUBE(a,b,c) 则穷举所有排列组合:(a,b,c)、(a,b,NULL)、(a,NULL,c)、(a,NULL,NULL)、(NULL,b,c)……共 8 种,适合需要任意维度交叉分析的场景,但结果行数指数增长,性能压力大。
GROUPING SETS(MySQL 不支持,PostgreSQL / SQL Server 支持)更灵活,可显式指定要哪些组合,比如只想要 (a,b) 和 (a) 两层,避免多余小计。
所以:
- 只需逐级向上汇总 → 用
ROLLUP - 需要所有维度任意组合 → 考虑
CUBE,但先评估数据量 - MySQL 用户别想
GROUPING SETS,要么用UNION ALL拼多个GROUP BY,要么换引擎
ORDER BY 和 ROLLUP 结果顺序容易被忽略的细节
ROLLUP 生成的行顺序不保证和你期望的报表结构一致。例如 GROUP BY year, month WITH ROLLUP 会先出 2023-01、2023-02…再出 2023-NULL、NULL-NULL,但如果加了 ORDER BY year DESC, month DESC,小计行可能被插到中间,破坏层级视觉逻辑。
解决办法:
- 不依赖默认顺序,显式用
ORDER BY GROUPING(year), year DESC, GROUPING(month), month DESC -
GROUPING()值越小(0)表示越细粒度,越大(1)表示越粗粒度,这样能确保明细在前、小计次之、总计最后 - 若数据库不支持
GROUPING()在ORDER BY中使用(如某些旧版 MySQL),就用IF构造排序键,例如ORDER BY IF(GROUPING(year), 2, IF(GROUPING(month), 1, 0)), year DESC, month DESC
真正麻烦的是嵌套层级深、又要求前端直接渲染的报表——这时候小计行的标识、缩进、折叠逻辑得靠应用层配合,光靠 SQL 很难干净解决。

















