AVG() OVER() 默认计算整个窗口的累积平均,必须用ROWS BETWEEN显式定义滑动窗口范围;如5日移动平均需写ROWS BETWEEN 4 PRECEDING AND CURRENT ROW,并确保ORDER BY字段稳定且无重复。

AVG OVER 为什么不能直接算移动平均?
因为 AVG OVER() 默认是窗口内全部行的平均值,不是你想要的“最近 N 行”滚动平均。必须显式定义 ROWS BETWEEN 或 RANGE BETWEEN 才能控制窗口范围。
常见错误是只写 AVG(value) OVER (ORDER BY date),结果得到的是从第一行到当前行的累积平均,不是移动平均。
- 用
ROWS BETWEEN 2 PRECEDING AND CURRENT ROW表示包含当前行和前两行(共 3 行) - 用
ROWS BETWEEN CURRENT ROW AND 2 FOLLOWING表示当前行加后两行 - 如果
ORDER BY字段有重复值,ROWS比RANGE更稳定,避免意外聚合
怎么写一个 5 日移动平均?
假设表叫 stock_prices,字段为 date 和 close_price,按日期升序计算:
SELECT
date,
close_price,
AVG(close_price) OVER (
ORDER BY date
ROWS BETWEEN 4 PRECEDING AND CURRENT ROW
) AS ma_5
FROM stock_prices;
注意:第 1 行只有 1 个数据点,AVG 仍会返回该值;第 2 行是前 2 行平均……直到第 5 行才真正覆盖满 5 天。这是正常行为,不是 bug。
- 若想跳过不足 5 行的行,加
WHERE或用子查询过滤ma_5 IS NOT NULL AND COUNT(*) OVER (...) >= 5 - PostgreSQL 和 SQL Server 支持
ROWS,MySQL 8.0+ 也支持;SQLite 目前不支持窗口函数 - Oracle 中需确保
date列无空值,否则ORDER BY可能打乱顺序
遇到 NULL 值怎么办?
AVG() 本身会自动忽略 NULL,但窗口框架不会跳过它们——也就是说,如果某天 close_price 是 NULL,它仍占一个“位置”,导致实际参与计算的非空值少于预期。
- 稳妥做法是先用
COALESCE(close_price, 0)或WHERE close_price IS NOT NULL预处理 - 更严谨的替代:用
COUNT()和SUM()手动算,比如SUM(close_price) / COUNT(close_price),效果等价但逻辑更透明 - 某些场景下需要保持原始行数(比如画图),就不能删行,只能接受窗口里含
NULL
性能和排序依赖很关键
窗口函数必须配合 ORDER BY 才有意义,而排序成本随数据量上升明显。如果没建索引,ORDER BY date 可能成为瓶颈。
- 在
date字段上建索引能显著加速,尤其当表很大且只查最近一段时 - 避免在
OVER子句里用表达式排序,如ORDER BY DATE_TRUNC('day', ts),会阻止索引使用 - 如果业务允许近似结果,考虑用物化视图或应用层缓存移动平均,而不是每次查都算
移动平均真正麻烦的从来不是语法,而是时间序列对齐、空值语义、以及窗口边界在业务逻辑里的解释是否一致——这些没法靠 AVG OVER 自动解决。

















