AVG() OVER 默认计算累积平均而非移动平均,因窗口未固定;需用 ROWS BETWEEN 显式指定固定行数滑动窗口,如 ROWS BETWEEN 4 PRECEDING AND CURRENT ROW 实现5日移动平均。

AVG() OVER 为什么算不出移动平均?
直接写 AVG(price) OVER (ORDER BY date) 得到的是**累积平均**,不是移动平均。它从第一行累加到当前行,窗口大小不断变大,而移动平均要求窗口固定(比如最近5天)。关键在 ROWS BETWEEN 或 RANGE BETWEEN 显式定义滑动范围。
用 ROWS BETWEEN 定义固定长度窗口
这是最常用、最可靠的方式,按物理行数滑动,不依赖时间字段是否连续或重复。例如计算「包含当前行在内的最近5个交易日」的均价:
SELECT
date,
price,
AVG(price) OVER (
ORDER BY date
ROWS BETWEEN 4 PRECEDING AND CURRENT ROW
) AS ma_5
FROM stock_prices;
注意:4 PRECEDING + CURRENT ROW = 5 行;如果想用「前5行不含当前行」,就写 ROWS BETWEEN 5 PRECEDING AND 1 PRECEDING。
- 窗口首行不足时(如第1–3行),
AVG()自动按实际可用行数计算,不会补 NULL 或报错 - 若
date有重复,ROWS仍按排序后的位置计数,不会“跳过”同日期的多条记录 - 性能上,
ROWS比RANGE更快,数据库无需做值去重或区间匹配
RANGE BETWEEN 在时间序列中的陷阱
想按「过去5天」而非「过去5行」计算?可以用 RANGE BETWEEN INTERVAL '4' DAY PRECEDING AND CURRENT ROW,但要注意:
- 仅 PostgreSQL、Oracle、Snowflake 支持
INTERVAL与RANGE组合;MySQL 8.0+ 不支持RANGE时间偏移,SQL Server 完全不支持RANGE时间单位 -
RANGE会合并相同ORDER BY值的行——如果某天有多条记录,它们全被视作“同一位置”,可能让窗口意外扩大 - 遇到缺失日期(如周末无交易),
RANGE不会自动跳过,而是按日历天数拉取,结果可能包含空值或更少有效数据点
如何处理起始段缺失值或对齐问题
移动平均在开头几行天然缺数据,但业务常要求补零、补首值或标记为无效。不要依赖 COALESCE(AVG(...) OVER (...), 0)——因为 AVG() 在单行时返回该行值,不会是 NULL。真正需要判断的是窗口是否满足最小长度:
COUNT(*) OVER










