rollup是group by的扩展修饰符,用于自动生成多级小计和总计行;本质区别在于group by仅返回单一粒度分组,而group by rollup(a,b,c)按(a,b,c)→(a,b)→(a)→()逐级上卷生成四层结果,null为占位符而非缺失值,需用grouping()函数精准识别汇总层级。

ROLLUP 是什么,它和 GROUP BY 有什么本质区别?
ROLLUP 不是独立语句,而是 GROUP BY 的扩展修饰符,用于在分组结果中自动添加多级小计行(subtotal)和总计行(grand total)。关键区别在于:GROUP BY a, b, c 只产出一个粒度的分组;而 GROUP BY ROLLUP(a, b, c) 会按 (a,b,c) → (a,b) → (a) → () 的顺序逐级上卷,生成四层结果。
常见错误是把它当成“自动画报表”的黑盒——实际它只负责生成带 NULL 占位符的聚合行,后续展示逻辑(比如把 NULL 显示为“合计”)得自己处理。
使用场景集中在财务、销售、BI 报表这类需要“钻取式汇总”的需求,比如:按部门→团队→员工统计销售额,同时要看到每个团队小计、部门总计、全公司总计。
怎么写一个能用的 ROLLUP 查询?注意 NULL 和排序问题
ROLLUP 生成的上卷行会在对应列填入 NULL,但这个 NULL 是占位符,不是真实数据缺失。如果不加处理,报表里会出现一堆 NULL,容易误导。
SELECT COALESCE(dept, '【部门总计】') AS dept, COALESCE(team, '【团队小计】') AS team, COALESCE(emp_name, '【员工汇总】') AS emp_name, SUM(sales) AS total_sales FROM sales_data GROUP BY ROLLUP(dept, team, emp_name) ORDER BY dept, team, emp_name;
注意三点:
-
COALESCE替换NULL是必须的,否则无法区分“真实为空的员工名”和“上卷占位符” -
ORDER BY必须和ROLLUP列顺序一致,否则小计行可能被插到中间,破坏层级感 - 如果某列本身允许
NULL,仅靠COALESCE不够,得加辅助字段标识层级,比如GROUPING(dept)返回 1 表示该行是上卷行
GROUPING() 函数为什么不能省?它解决什么真实问题?
当原始数据里就有 NULL 值(比如某个员工尚未分配团队),仅靠 COALESCE(team, '小计') 会把真实 NULL 和上卷占位符混为一谈。这时必须用 GROUPING() 来精确识别。
GROUPING(dept) 在 dept 是上卷行时返回 1,否则返回 0;支持多列组合判断,比如 GROUPING(dept, team) = 2 表示 dept 和 team 都是上卷(即只按 emp_name 分组那层)。
典型用法:
- 用
CASE WHEN GROUPING(dept) = 1 THEN '总计' ELSE dept END安全替换 - 在 WHERE 中过滤掉某级上卷行:
HAVING GROUPING(team) = 0(只保留有具体 team 的行) - 排序时让上卷行排在末尾:
ORDER BY GROUPING(dept), dept
ROLLUP 和 CUBE、GROUPING SETS 性能差异大吗?
ROLLUP(a,b,c) 生成 4 组分组(a,b,c / a,b / a / 空);CUBE(a,b,c) 生成 8 组(所有排列组合);GROUPING SETS((a,b,c),(a,b),(a)) 则只生成你明确列出的三组。
性能上:行数越多,聚合计算和内存占用越高。尤其在大数据量 + 多列 ROLLUP 时,执行计划可能退化成多次扫描。建议:
- 优先用
GROUPING SETS替代ROLLUP,如果只需要部分层级 - 避免对高基数列(如用户 ID)做
ROLLUP - 在 PostgreSQL 中,
ROLLUP可能触发哈希聚合优化;但在 MySQL 8.0+ 才支持,旧版只能模拟(用UNION ALL)
真正麻烦的是嵌套逻辑——比如想对“部门小计”再按年份做二次上卷,ROLLUP 本身不支持嵌套,得用子查询或 CTE 拆开处理。这点很容易在设计初期被忽略。










