GROUP BY不能替代PARTITION BY,因为前者压缩行数丢失明细,后者在保留每行原始数据基础上分组附加计算结果,如部门排名或平均薪资。

为什么不能用 GROUP BY 替代 PARTITION BY?
GROUP BY 会压缩行数,丢失原始明细;PARTITION BY 只分组不压缩,每行仍保留,同时可附加计算结果。ROW_NUMBER()、SUM() OVER() 都依赖这个特性。
- 写
GROUP BY department→ 输出只有几行,看不到每个员工的原始记录 - 写
PARTITION BY department→ 输出仍是全员行数,但每行都带上了“本部门排名”或“部门平均薪资” - ORDER BY 在窗口内必须存在才影响
ROW_NUMBER()或累计类函数行为;缺它,SUM() OVER(PARTITION BY x)就等价于全组静态聚合,失去时序/顺序敏感性
ROWS BETWEEN 为什么经常被忽略却关键?
默认帧(frame)是 ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW,仅对含 ORDER BY 的窗口生效。没显式写,就容易误以为 SUM(sales) OVER(PARTITION BY city) 是滚动求和——其实它是“城市总销售额”重复填满每一行。
- 要移动平均:必须写
AVG(sales) OVER(ORDER BY date ROWS BETWEEN 2 PRECEDING AND CURRENT ROW) - 要首行到当前行累计:显式写
ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW更安全,避免隐式行为差异 - MySQL 8.0 和 PostgreSQL 对默认帧处理一致,但 SQLite(需启用扩展)可能不支持完整 frame 子句
LAG/LEAD 为什么比自连接更稳?
用 LAG(sales, 1) OVER(PARTITION BY store ORDER BY month) 直接取上月值,比 JOIN sales s1 ON s1.store = s2.store AND s1.month = DATE_SUB(s2.month, INTERVAL 1 MONTH) 少了匹配失败风险、NULL 处理负担和执行计划不确定性。
- 自连接在日期不连续时会漏行;
LAG始终返回前一行(或 NULL),语义确定 - 没有 JOIN 条件错误或笛卡尔积隐患
- 执行计划更扁平,PostgreSQL 和 MySQL 8.0+ 都能将其优化为流式扫描,不建临时表
OVER(),而是判断什么时候该加 ORDER BY、什么时候必须限定 ROWS 范围、以及是否该用 LAG 而非 JOIN —— 这些边界稍不注意,结果就静默错。

















