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

不能直接替代,但能解决 GROUP BY 做不了的事:保留原始行的同时叠加分组聚合值。该用 GROUP BY 时别硬套窗口函数,该用窗口函数时漏写 PARTITION BY 就等于白写。
什么时候必须用窗口函数,而不是 GROUP BY
核心判断就一条:结果里还得看到每条原始记录(比如每个订单、每个员工、每笔日志),同时又要附带分组统计值(如“该用户总金额”“本部门平均薪资”)。这时候 GROUP BY 必然丢行,窗口函数是唯一干净解法。
- 想查所有订单,同时显示“该客户累计消费额” → 用
SUM(amount) OVER (PARTITION BY customer_id) - 要列出全部学生成绩,并标出“班级最高分” → 用
MAX(score) OVER (PARTITION BY class) - 需取每个用户的最新一笔订单完整字段(order_id、amount、created_at)→ 必须用
ROW_NUMBER() OVER (PARTITION BY user_id ORDER BY created_at DESC)打标后嵌套过滤 - GROUP BY 在这种场景下要么报错(
ERROR 1055),要么返回不可控的非聚合字段值(MySQL 5.7+ 默认拒绝)
AVG/SUM/COUNT 加 OVER 的写法陷阱
AVG(salary) OVER (PARTITION BY dept) 看似简单,但几个细节一错就全错:
-
PARTITION BY是必须项,漏写就变成全表计算,每行都填同一个数,毫无分组意义 - 不加
ORDER BY时,SUM()和AVG()返回整组总和/均值;加了ORDER BY就默认触发累计计算(如SUM() OVER (PARTITION BY x ORDER BY t)是“截至当前行”的累加) -
COUNT(*) OVER (PARTITION BY x)等价于“每组多少行”,比GROUP BY x+COUNT(*)多保留了所有原始字段,无需 JOIN - 多个聚合可并列写:
COUNT(*) OVER (PARTITION BY u), AVG(v) OVER (PARTITION BY u), MAX(w) OVER (PARTITION BY u),主流引擎(PostgreSQL/MySQL 8.0+/SQL Server)会复用一次分区扫描,不是各自扫三遍
为什么不能在 WHERE 里直接用 ROW_NUMBER()
窗口函数执行阶段晚于 WHERE,所以 WHERE rn = 1 这种写法一定报错或无结果。必须套一层:
WITH ranked AS ( SELECT *, ROW_NUMBER() OVER (PARTITION BY user_id ORDER BY created_at DESC) AS rn FROM orders ) SELECT order_id, amount, created_at FROM ranked WHERE rn = 1;
- 别用子查询代替 CTE——CTE 更易读、优化器更友好(尤其 PostgreSQL)
- 如果业务允许并列(同时间多单),改用
RANK()或DENSE_RANK(),否则ROW_NUMBER()可能把并列第一的其他行筛掉 -
ORDER BY字段含NULL时行为不稳定:PostgreSQL 要加NULLS LAST,MySQL 得写成ORDER BY created_at IS NULL, created_at DESC
性能比语法更容易翻车
窗口函数慢,往往不是因为写法,而是因为没管索引和数据分布:
-
PARTITION BY字段区分度低(比如只有 3 个分类值,但表有千万行),WindowAgg 阶段极易成为瓶颈 -
PARTITION BY a ORDER BY b的组合,最佳索引是(a, b)联合索引,单列索引无效 - 先
WHERE status = 'active'再套窗口,比窗口里塞条件快得多——窗口函数不跳过被过滤的行,它照算 - EXPLAIN ANALYZE 中看到高占比的
WindowAgg或Using filesort,基本就是信号:该加索引,或该提前过滤
最常被忽略的一点:窗口函数输出的是表达式结果,不是真实列,没法直接建索引,也没法在后续 WHERE 中引用——想筛“部门均薪 > 15000 的员工”,必须再套一层子查询。











