应使用窗口函数而非GROUP BY ROLLUP来保留明细并附加汇总值,SUM() OVER (PARTITION BY ...)可实现不减少行数的前提下按指定列分组计算聚合值。

用 GROUP BY + ROLLUP 会丢明细,别这么干
直接套用 GROUP BY ... WITH ROLLUP 或 GROUPING SETS 会把原始明细行合并掉,只剩汇总行或带 NULL 的伪明细——这不是你想要的“明细+汇总共存”。要保留每条原始记录,同时在每行附上对应分组的聚合值,得靠窗口函数。
SUM() OVER (PARTITION BY ...) 是最常用解法
核心思路:不改变行数,只在每行新增一列汇总值。窗口函数天然支持“按某几列分组计算,但不折叠行”。
- 写法示例:
SELECT order_id, product, amount, SUM(amount) OVER (PARTITION BY order_id) AS order_total FROM sales -
PARTITION BY后字段必须是SELECT中已出现的列(或可推导的表达式),不能是聚合结果 - 如果要跨多级分组(比如先按区域、再按月份汇总),嵌套多个
OVER即可:SUM(amount) OVER (PARTITION BY region) AS region_sum和SUM(amount) OVER (PARTITION BY region, month) AS region_month_sum - 注意:
ORDER BY在窗口定义里会影响累计类函数(如SUM() OVER (... ORDER BY ...)),但对纯分组求和无影响,可省略
遇到 ORDER BY 报错?检查是否混用了聚合和非聚合字段
常见错误:SELECT user_id, name, COUNT(*) OVER (PARTITION BY user_id) FROM users ORDER BY name —— 这条在 PostgreSQL 或 SQL Server 会报错,因为 ORDER BY 引用的 name 不在 GROUP BY 里,也不在窗口函数覆盖范围内。
- 解决办法:要么把
name加进PARTITION BY(如果逻辑允许),要么改用子查询/CTE 先算汇总,再JOIN回明细 - MySQL 8.0+ 对此较宽松,但行为不可移植;PostgreSQL 和 SQL Server 更严格,建议统一按标准写法处理
- 真正需要排序时,把窗口函数结果作为一列,再对外层结果
ORDER BY该列或其它确定性字段
性能敏感时,避免在大表上反复 PARTITION BY 多列组合
每个 OVER 子句都会触发一次分组扫描。1000万行表,写 5 个不同 PARTITION BY 的窗口函数,实际可能扫表 5 次。
- 优先复用相同分区逻辑:比如
SUM(sales) OVER (PARTITION BY dept, year)和AVG(sales) OVER (PARTITION BY dept, year)可以一起计算,引擎通常优化为单次扫描 - 若分区字段无索引,且数据量大,考虑在
dept, year上建复合索引(尤其当该组合常用于过滤+分组) - 实在要多个不同分组,且性能瓶颈明显,拆成多个 CTE 预计算,再用
JOIN合并——虽然 SQL 变长,但执行计划更可控
窗口函数不是银弹,PARTITION BY 字段越多、唯一值越分散,内存和 CPU 开销越明显。上线前用 EXPLAIN 看实际执行计划,别只信“语法跑通了”。

















