窗口函数不能替代GROUP BY,因二者作用层级不同:GROUP BY压缩行数(输出≤输入),OVER保留原始行结构叠加计算(输出=输入);需压缩用GROUP BY,需每行附加统计值才用OVER。

不能替代,但能解决 GROUP BY 做不了的事。
窗口函数和 GROUP BY 不是“谁替代谁”的关系,而是分工明确:GROUP BY 用来压缩行数、产出聚合结果;窗口函数用来保留每一行原始数据,同时附加分组计算值。想把 100 行订单压成 10 行用户汇总?必须用 GROUP BY。想查这 100 行每笔订单,还顺手标出“该用户总消费额”“这是第几单”“比上一单多多少”?只能靠窗口函数。
GROUP BY 报错或结果错乱,其实是你在强求它干窗口函数的活
常见错误现象:SELECT user_id, order_id, SUM(amount) FROM orders GROUP BY user_id 在 MySQL 8.0+ 或 PostgreSQL 中直接报错(ERROR 1055),因为 order_id 既没出现在 GROUP BY 子句里,也不是聚合表达式。
硬加 MAX(order_id) 虽能过语法检查,但语义错位——你拿到的可能是张三的 order_id,对应却是李四的 SUM(amount)。这不是数据不准,是逻辑坍塌。
- GROUP BY 的输出行数 ≤ 输入行数,且每行只代表一个分组
- 窗口函数输出行数 = 输入行数,每行仍带着自己的
order_id、created_at等原始字段 - 想保留明细 + 附带统计值,别拼 GROUP BY + JOIN,直接写
SUM(amount) OVER (PARTITION BY user_id)
PARTITION BY 是窗口函数的“分组开关”,漏写就全表广播
PARTITION BY 不是可选修饰,它是定义窗口范围的必要项。漏写,COUNT(*) OVER () 就变成全表计数,每行都填同一个数,失去分组意义。
对比这两个写法:
COUNT(*) OVER (PARTITION BY user_id) → 每个用户有多少单,每行显示对应用户的总数
COUNT(*) OVER () → 全表共多少单,每行都显示这个固定值
-
PARTITION BY定义“和谁一起算”,类似 GROUP BY 的分组字段 - 不加
ORDER BY时,SUM()/AVG()/COUNT()返回整个分区的聚合结果 - 加了
ORDER BY(如SUM(amount) OVER (PARTITION BY user_id ORDER BY created_at)),默认按ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW累计,不是总数
ROW_NUMBER() 过滤必须套子查询,WHERE 里直接写会报错
想取每个用户的最新一笔订单完整字段?别写 WHERE ROW_NUMBER() OVER (PARTITION BY user_id ORDER BY created_at DESC) = 1 —— 这在任何主流 SQL 引擎里都报语法错误。
原因很实在:窗口函数执行阶段晚于 WHERE,SQL 引擎还没算出 ROW_NUMBER(),WHERE 就已经要筛选了。
- 正确姿势是先用 CTE 或子查询打标:
WITH ranked AS (SELECT *, ROW_NUMBER() OVER (PARTITION BY user_id ORDER BY created_at DESC) AS rn FROM orders) - 再在外层过滤:
SELECT * FROM ranked WHERE rn = 1 - PostgreSQL 对
ROW_NUMBER() OVER (PARTITION BY x)不加ORDER BY直接报错;MySQL 可能跑通,但结果不可复现(依赖存储顺序)
多个窗口函数可能拖慢查询,别无脑堆砌
看着简洁:SELECT *, SUM(a) OVER (PARTITION BY x), AVG(b) OVER (PARTITION BY y), RANK() OVER (PARTITION BY z ORDER BY c) —— 但优化器很可能为每个 PARTITION BY 单独做一次排序或哈希分组。
尤其在 PostgreSQL 和 SQL Server 中,这种写法容易触发多次全量扫描,性能反不如拆成几个小查询或预计算中间表。
- 如果多个窗口函数共享同一
PARTITION BY和ORDER BY,引擎通常能复用排序结果 - 混用不同
PARTITION BY字段(比如一个按user_id,一个按product_id),基本等于告诉数据库:“请分别扫三遍表” - 对大表,优先考虑是否真需要实时计算——有些统计值(如用户总单数)完全可以提前物化到宽表中
真正容易被忽略的,是执行阶段的不可见约束:窗口函数在 SELECT 阶段才计算,看不见 WHERE 已经过滤掉的行,也拿不到 GROUP BY 后聚合出来的中间结果(除非显式嵌套)。写的时候图方便,查出来数据不对,再回头补 CTE 或重写逻辑,成本远高于一开始想清楚“我要压缩行,还是贴标签”。

















