rollup适合固定层级带小计/总计的报表汇总,窗口函数适合保留明细并动态添加聚合值;二者不可互换,选错会导致数据重复、逻辑错误或性能下降。

直接说结论:ROLLUP 适合做「固定层级、带小计/总计的报表式汇总」,窗口函数(如 SUM() OVER)适合「保留明细、动态加聚合值」——两者不是替代关系,选错会导致数据重复、逻辑错乱或性能骤降。
ROLLUP 生成的是分组维度的“金字塔”结果集
ROLLUP 是 GROUP BY 的扩展,按指定列顺序逐级上卷,自动补全 NULL 表示“该层全部”。比如 GROUP BY ROLLUP(a, b, c) 实际等价于四个 UNION ALL:(a,b,c) + (a,b) + (a) + ()。
- 必须配合聚合函数(
COUNT、SUM等),否则报错column "xxx" must appear in the GROUP BY clause - NULL 值在
ROLLUP中有语义:它代表“这一维不区分”,不是数据缺失,别用IS NULL过滤掉,否则小计就没了 - 顺序敏感:
ROLLUP(a,b)产生 a 小计和总计;ROLLUP(b,a)则是 b 小计和总计 —— 不可随意调换 - MySQL 8.0+、PostgreSQL、SQL Server 支持;SQLite 和旧版 MySQL 不支持
SELECT COALESCE(region, '总计') AS region, COALESCE(dept, '小计') AS dept, SUM(sales) AS total FROM sales GROUP BY ROLLUP(region, dept);
窗口函数不能替代 ROLLUP,但能解决它的盲区
SUM() OVER 类窗口函数不改变行数,是在每行“旁边加一列计算结果”。它没法生成小计行,但能算出“每个部门在本区域内的占比”“滚动三个月销售额”这类 ROLLUP 根本做不到的事。
- 常见误用:用
SUM(sales) OVER(PARTITION BY region)想代替ROLLUP(region)—— 结果是每行都带区域总和,但没小计行,也没总计行 - 分区键(
PARTITION BY)决定“按什么分组计算”,排序键(ORDER BY)决定是否累计(如ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) - 性能差异大:窗口函数通常比
ROLLUP更耗内存,尤其ORDER BY配合大范围ROWS时,可能触发磁盘临时表 - 注意 NULL 处理:
OVER(PARTITION BY region)中,region 为 NULL 的行会自成一个分区,常被忽略导致统计偏差
SELECT region, dept, sales, SUM(sales) OVER(PARTITION BY region) AS region_total, ROUND(sales * 100.0 / SUM(sales) OVER(PARTITION BY region), 2) AS pct_in_region FROM sales;
什么时候必须用 ROLLUP,什么时候必须用窗口函数
看输出形态和业务意图。如果导出 Excel 要求“每省下面有城市小计、全省总计”,只能用 ROLLUP;如果要做“销售员业绩排名+累计完成率”,只能用窗口函数。
- 需要新增行(小计/总计)→ 选
ROLLUP或CUBE(后者是全组合) - 需要保留原始明细行,且每行附带某种聚合值 → 选窗口函数
- 想在一个查询里既出明细又出小计?可以
UNION ALL拼接:先查明细(带窗口值),再查ROLLUP小计,但要注意字段对齐和类型一致 - Oracle 用户注意:
GROUPING()函数能识别ROLLUP生成的 NULL 是真实数据还是上卷占位符;其他数据库得用GROUPING_ID()或手动判断
真正容易被忽略的点是:ROLLUP 的层级嵌套和窗口函数的 PARTITION BY 逻辑,表面都叫“分组”,但前者改行数、后者不改行数。混用前务必先画出预期结果的行结构——多一行小计,还是多一列数字,决定了整个 SQL 的骨架。










