PARTITION BY用于窗口函数,保留原始行数并附加分组计算结果;GROUP BY则压缩行数生成聚合摘要,二者作用层级不同,不可互换。

窗口聚合函数不是用来“替代”GROUP BY的,而是解决它根本干不了的事——比如你既要保留每条原始记录,又要算分组内均值、累计和、排名。硬用GROUP BY做这类事,轻则报错 ERROR 1055,重则结果错、性能崩、改不动。
AVG() OVER(PARTITION BY) 为什么不能直接写成 GROUP BY?
当你需要在员工明细表里加一列「本部门平均薪资」,GROUP BY dept 会把100行压成3行(假设3个部门),而业务要求是“每人一行+带部门均值”。这时候必须用窗口函数:
-
AVG(salary) OVER(PARTITION BY dept)→ 每行都带本部门均值,行数不变 -
AVG(salary) OVER()→ 全表均值,每行都一样,相当于常量列 - 漏掉
PARTITION BY就等于没分组,别以为加了GROUP BY dept就能混着用——SQL标准禁止在SELECT里同时出现非聚合字段和GROUP BY,但窗口函数天然绕过这限制
ROW_NUMBER() OVER(...) 是唯一靠谱的“每组取最新一条”方案
GROUP BY 无法保证 MAX(created_at) 和对应 order_id 来自同一行,PostgreSQL 和 MySQL 8.0+ 都会拒绝这种写法。正确路径是打标再过滤:
- 必须用 CTE 或子查询:窗口函数不能出现在
WHERE中 -
ORDER BY created_at DESC, id DESC是关键——时间相同时靠id保序,避免非确定性结果 - 要严格一行用
ROW_NUMBER();要保留并列项用RANK()或DENSE_RANK() - 示例:
WITH ranked AS ( SELECT *, ROW_NUMBER() OVER(PARTITION BY user_id ORDER BY created_at DESC, id DESC) AS rn FROM orders ) SELECT user_id, order_id, amount FROM ranked WHERE rn = 1;
PARTITION BY 字段没索引,窗口函数比 GROUP BY 还慢
窗口函数不是银弹。如果 PARTITION BY category 只有3个值,但表有千万行,PostgreSQL 得把大量数据拉进内存排序,WindowAgg 节点极易成为瓶颈。
- 执行计划里看到高占比的
WindowAgg或Using filesort,就是信号 - 建联合索引优先按
PARTITION BY字段 +ORDER BY字段,例如CREATE INDEX idx_orders_user_time ON orders(user_id, created_at) INCLUDE(amount); - 加
ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW这类框架时,没索引几乎必然触发磁盘临时文件,1000万行就可能生成数GB临时文件
真正容易被忽略的是:窗口函数执行时机在 GROUP BY 之后,所以它能看到已过滤、已分组的结果,但无法反过来驱动分组逻辑。想靠它“简化GROUP BY”,往往说明需求本身没理清——先确认你要的是“明细+附加统计”,还是“纯汇总结果”。











