rollup是sql中group by的扩展,用于自动生成多级分层汇总(如明细、小计、总计),而group by仅返回单一粒度分组;rollup按列顺序左对齐展开层级,产生null占位的汇总行,需配合grouping()函数准确识别汇总级别。

ROLLUP 是什么,和 GROUP BY 有什么区别?
ROLLUP 是 SQL 标准中用于生成分层汇总的扩展语法,它在 GROUP BY 基础上自动补全多级小计行(包括总计),形成类似“树状展开”的结果。不是所有数据库都默认支持——PostgreSQL 9.5+、SQL Server、Oracle、MySQL 8.0+ 支持;SQLite 和旧版 MySQL 不支持。
关键区别在于:GROUP BY a, b, c 只返回最细粒度分组;而 GROUP BY ROLLUP(a, b, c) 会额外返回:
-
(a, b, c)(明细) -
(a, b, NULL)(c 维度小计) -
(a, NULL, NULL)(b+c 维度小计) -
(NULL, NULL, NULL)(总计)
注意:NULL 在汇总行中是占位符,不代表原始数据为空,这是 ROLLUP 的语义约定。
怎么写一个带层级标签的 ROLLUP 查询?
直接用 ROLLUP 会导致汇总行的列值为 NULL,难以区分是真实空值还是汇总占位符。必须配合 GROUPING() 函数做标记:
SELECT COALESCE(region, '【全部地区】') AS region, COALESCE(product, '【该地区小计】') AS product, SUM(sales) AS total_sales FROM sales_data GROUP BY ROLLUP(region, product) ORDER BY region, product;
但更可靠的方式是用 GROUPING() 判断层级:
SELECT
CASE WHEN GROUPING(region) = 1 THEN '【总计】'
WHEN GROUPING(product) = 1 THEN '【' || region || ' 小计】'
ELSE region END AS region_label,
CASE WHEN GROUPING(product) = 1 THEN NULL
ELSE product END AS product,
SUM(sales) AS total_sales
FROM sales_data
GROUP BY ROLLUP(region, product);
常见错误:在 SELECT 中直接引用未聚合且未出现在 ROLLUP 列表中的字段(如 customer_id),会报错或结果不可靠。
ROLLUP 的顺序为什么不能随便调换?
ROLLUP(a, b, c) 生成的层级是严格左对齐的:从 a→(a,b)→(a,b,c)→总计。它不等价于 ROLLUP(b, a, c) 或 CUBE。
- 如果你按「年→季度→月份」建模,
ROLLUP(year, quarter, month)能出年合计、每季度合计、每月明细; - 但若写成
ROLLUP(month, quarter, year),则只会按月汇总,再把所有月归到“某季度”下(逻辑错乱),因为ROLLUP不推断时间关系,只按字段顺序机械展开。
性能影响:字段越靠前,产生的汇总组合越多。把高基数列(如 order_id)放在 ROLLUP 左侧会导致中间结果暴增,应避免。
替代方案:没有 ROLLUP 怎么办?
MySQL 5.7 或 SQLite 用户无法用 ROLLUP,得手写 UNION ALL 模拟:
SELECT region, product, SUM(sales) FROM sales_data GROUP BY region, product UNION ALL SELECT region, NULL, SUM(sales) FROM sales_data GROUP BY region UNION ALL SELECT NULL, NULL, SUM(sales) FROM sales_data;
但要注意:
- 手动拼接时,各子查询的列数、类型、顺序必须完全一致
-
NULL类型需显式转为对应字段类型(如CAST(NULL AS VARCHAR)),否则某些数据库会拒绝合并 - 缺少
GROUPING()语义,无法自动识别哪行是哪级汇总,得靠额外字段或应用层解析
真正麻烦的不是写法,而是当维度增加到 4–5 个时,UNION ALL 组合数呈指数增长(2ⁿ−1),维护成本陡升。
ROLLUP 的层级语义是隐式的,一旦字段顺序或业务含义理解偏差,结果就 quietly 错——这点最容易被忽略。











