sum() over 的窗口框架由 rows between 或 range between 定义,默认为 unbounded preceding to current row;必须显式指定 order by,推荐用唯一或带业务意义字段排序,并优先使用 rows 而非 range 以避免重复值导致的范围偏差。

什么是 SUM() OVER 的窗口框架?
SUM() OVER 不是简单求和,它依赖窗口定义(OVER 子句)决定“对哪些行求和”。关键在 ROWS BETWEEN 或 RANGE BETWEEN —— 它们控制当前行的“计算范围”。没显式指定时,默认是 UNBOUNDED PRECEDING TO CURRENT ROW,也就是从第一行累加到当前行。
容易踩的坑:ORDER BY 必须存在,否则窗口无法确定“前几行”或“后几行”,PostgreSQL 和 SQL Server 会报错;MySQL 8.0+ 虽允许缺省,但结果不可靠(逻辑顺序未定义)。
实操建议:
- 始终在
OVER中写明ORDER BY,字段最好是唯一或带业务意义的排序依据(如date、id) - 用
ROWS BETWEEN更安全:它按物理行数截取,不受重复值影响;RANGE在有重复排序值时可能意外扩大范围 - 避免用
UNBOUNDED FOLLOWING做移动平均——它会让窗口包含未来所有行,失去“移动”意义
怎么写 3 日移动平均?
移动平均本质是“以当前行为中心,向前取 N−1 行,共 N 行求均值”。SQL 里没有“向后取”的直接语法,所以通常用 ROWS BETWEEN 2 PRECEDING AND CURRENT ROW 实现 3 日(含当天)平均。
示例(假设表 sales 有 date 和 amount):
SELECT
date,
amount,
ROUND(AVG(amount) OVER (
ORDER BY date
ROWS BETWEEN 2 PRECEDING AND CURRENT ROW
), 2) AS moving_avg_3d
FROM sales;
注意点:
- 第 1 行只有自己,平均值 =
amount;第 2 行是前两行均值;从第 3 行起才真正覆盖 3 天 - 如果想强制从第 3 行开始出值(前两行返回 NULL),加
ROWS BETWEEN 2 PRECEDING AND CURRENT ROW后再套CASE WHEN COUNT(*) OVER (...) -
AVG()比SUM()/COUNT()更稳妥:前者自动忽略 NULL,后者若分母为 0 会报错
累计求和为什么不能只写 SUM(amount) OVER(ORDER BY date)?
可以写,而且就是标准写法——但很多人漏掉关键约束:如果 date 有重复,不同数据库行为不一致。PostgreSQL 和 SQL Server 会把同日期所有行视为“同一组”,在该组内无序,导致累计值跳跃或重复;MySQL 8.0+ 默认用 RANGE 语义,也会把同日期行全纳入当前窗口。
正确做法是打破并列:
- 在
ORDER BY中追加唯一字段,例如ORDER BY date, id或ORDER BY date, ROW_NUMBER() OVER (PARTITION BY date ORDER BY id) - 显式声明
ROWS UNBOUNDED PRECEDING,避免隐式RANGE带来的歧义 - 如果业务上确实允许“同日数据合并后再累计”,那需先
GROUP BY date聚合,再对聚合结果开窗
性能和兼容性要注意什么?
SUM() OVER 是标准 SQL,但实现细节差异大。PostgreSQL 和 SQL Server 支持完整窗口框架;MySQL 8.0+ 支持,但 RANGE 对非数字/日期类型支持弱;SQLite 3.25+ 仅支持简单 ORDER BY + UNBOUNDED PRECEDING,不支持 PRECEDING/FOLLOWING 偏移。
实操建议:
- 生产环境别依赖
RANGE做移动平均——它在时间字段上有精度陷阱(比如TIMESTAMP微秒级重复会导致窗口突然变大) - 大数据量时,
ROWS BETWEEN比RANGE快,因无需排序值去重 - Oracle 用户注意:
ROWS BETWEEN中的数字是“行数”,不是“天数”,别看到2 PRECEDING就以为是“两天前”——它只认物理位置
最常被忽略的其实是排序字段的稳定性。哪怕只是临时查一次,也别让 ORDER BY date 单独出现,尤其当 date 来自 CAST(created_at AS DATE) 这类转换时——重复率极高,窗口行为就失控了。











