连续移动平均值是对当前行及指定行数范围内的滑动窗口求平均,需配合over()与order by及rows between使用;普通avg()是聚合函数,无窗口语义,不保留原行结构。

什么是连续移动平均值,和普通 AVG() 有什么区别
连续移动平均值(rolling average)不是对整列求平均,而是对当前行及往前 N 行(或前后各 N 行)构成的滑动窗口做平均。它必须依赖 ORDER BY 定义行序,且窗口范围需显式声明;而普通 AVG() 是聚合函数,不带 OVER 就会把整个分组压成一行,没法保留原始行结构。
常见错误是写成 AVG(value) OVER () —— 这等于全表平均,不是“移动”的;或者漏掉 ORDER BY,导致数据库报错(如 PostgreSQL 报 window definition requires an ordering clause)或结果不可预测(MySQL 8.0+ 要求显式排序)。
用 ROWS BETWEEN 实现固定长度滑动窗口
这是最常用、语义最清晰的方式,适用于按时间、ID 或其他单调字段排序的场景:
- 窗口定义必须包含
ORDER BY,例如按date或id -
ROWS BETWEEN 2 PRECEDING AND CURRENT ROW表示:当前行 + 前两行,共 3 行 -
ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING表示:前一行、当前行、后一行(中心对称)
SELECT
date,
value,
AVG(value) OVER (
ORDER BY date
ROWS BETWEEN 2 PRECEDING AND CURRENT ROW
) AS rolling_avg_3
FROM sales;
注意:首两行因缺少前置数据,窗口实际只有 1 或 2 行,AVG() 仍会正常计算(不补零),结果行数与原表一致。
处理时间间隔不均等的数据(如非每日记录)
如果原始数据缺失某些日期(比如周末无交易),用 ROWS 会跳过空档,导致“物理行数”和“业务时间跨度”错位。此时应改用 RANGE,但仅限于支持该语法的数据库(PostgreSQL、SQL Server 支持;MySQL 8.0+ 不支持 RANGE 与数值/日期表达式组合)。
可行替代方案:
- 先用
GENERATE_SERIES(PostgreSQL)或递归 CTE 补全日期维度 - 或改用子查询 + 时间条件关联(性能较差,但兼容性强)
SELECT t1.date, t1.value, (SELECT AVG(t2.value) FROM sales t2 WHERE t2.date BETWEEN t1.date - INTERVAL '2 days' AND t1.date) AS avg_last_3_days FROM sales t1;
这种写法可读性低、无法利用窗口优化,仅作兜底;真正上线应优先确保基础数据按时间密排,或接受 ROWS 的物理窗口语义。
性能与 NULL 值的实际影响
- 窗口函数本身不自动过滤
NULL,AVG() 会跳过 NULL 值参与计算(符合 SQL 标准),但窗口行数仍按 ROWS 规则计入——也就是说,若中间有 NULL,平均基数变小,结果可能意外偏高
- 大表上加
ORDER BY + 窗口函数会触发排序操作,若没在 ORDER BY 字段建索引,性能下降明显
- 部分数据库(如 SQLite)不支持窗口函数,执行会直接报错
no such function: AVG(带 OVER 时)
NULL,AVG() 会跳过 NULL 值参与计算(符合 SQL 标准),但窗口行数仍按 ROWS 规则计入——也就是说,若中间有 NULL,平均基数变小,结果可能意外偏高ORDER BY + 窗口函数会触发排序操作,若没在 ORDER BY 字段建索引,性能下降明显no such function: AVG(带 OVER 时)建议提前检查:
-
SELECT version();确认数据库版本是否支持窗口函数 - 对
ORDER BY字段建立索引,尤其是用于时间序列分析时 - 若业务要求严格“跳过 NULL 并保持窗口大小”,需先用
COALESCE或子查询填充,不能依赖窗口自动对齐
边界情况比想象中多,特别是跨数据库迁移或对接旧系统时,ROWS 和 RANGE 的行为差异、NULL 处理逻辑、以及排序字段的稀疏性,往往才是实际出问题的地方。










