avg() over() 默认返回每行相同的全局均值,无法实现移动平均;必须显式配合order by和rows between(如2 preceding and current row)定义滑动窗口范围,且rows比range更稳定可靠。

AVG OVER 为什么算不出移动平均?
直接写 AVG(column) OVER () 默认是整表聚合,不是移动窗口——它会返回每行都一样的全局均值。要算移动平均,必须显式定义窗口范围,否则结果毫无意义。
关键在 ROWS BETWEEN 或 RANGE BETWEEN 子句。多数场景用 ROWS 更可控,因为它是按物理行数滑动,不受重复值或空值干扰。
- 想算“最近3天”的移动平均:用
ROWS BETWEEN 2 PRECEDING AND CURRENT ROW(含当前行共3行) - 想算“过去7天(含当天)”:需先按日期排序,再用
ROWS BETWEEN 6 PRECEDING AND CURRENT ROW - 若时间字段有缺失(比如某天没数据),
ROWS仍按行数算,RANGE可能跳过空档但实现复杂、性能差,一般不推荐
ORDER BY 是强制要求,且顺序决定计算方向
OVER 中没有 ORDER BY,窗口函数行为未定义(PostgreSQL 报错,MySQL 8.0+ 允许但结果不可靠)。移动平均必须明确“按什么排序”,否则“最近”无从谈起。
常见错误是只按 id 排序——如果 id 不严格对应时间顺序(比如软删除后重用 ID),移动平均就乱了。务必用时间戳字段,例如:
AVG(sales) OVER (ORDER BY order_date ROWS BETWEEN 6 PRECEDING AND CURRENT ROW)
- 升序
ORDER BY order_date→ 计算“到当前行为止的前N天” - 降序
ORDER BY order_date DESC→ 计算“从当前行开始往后N天”,通常不符合业务直觉 - 多列排序(如
ORDER BY order_date, created_at)可解决同日期多记录的不确定性
处理 NULL 和边界行的实际表现
AVG() 自动忽略 NULL 值参与计算,但窗口大小不变。例如 ROWS BETWEEN 2 PRECEDING AND CURRENT ROW 总是取最多3行,若其中1行为 NULL,则按剩余2个非空值平均。
首几行必然缺前置数据:第1行只有自己,第2行最多2行……窗口自动截断,不会报错,但你要意识到早期结果基于更少样本。
- 第1行:等价于
AVG(col)单值,结果就是该行值本身 - 第2行:若
ROWS BETWEEN 1 PRECEDING AND CURRENT ROW,则平均第1、2行(跳过 NULL) - 想补全首N行(比如填0或前向填充),需额外用
CASE WHEN ROW_NUMBER() OVER (...) 判断
性能和兼容性要注意这些点
窗口函数在 MySQL 8.0+、PostgreSQL 8.4+、SQL Server 2005+、Oracle 8i+ 都支持,但 SQLite 直到 3.25.0(2018年)才支持,旧版本会语法错误。
大数据量下,ORDER BY 字段必须有索引,否则排序开销极大;移动窗口越宽(比如 90 PRECEDING),内存占用越高。
- 避免在
WHERE之前用窗口函数过滤(如WHERE avg_sales > 100)——窗口函数在WHERE后执行,必须用子查询或 CTE - PostgreSQL 支持
frame_exclusion(如EXCLUDE CURRENT ROW),MySQL 不支持,跨数据库迁移时需注意 - 如果源数据已按时间分区(如每天一张表),优先考虑用
UNION ALL+ 普通AVG()聚合,比大窗口更稳
移动平均真正难的不是语法,而是确认“窗口对齐的是业务时间还是入库时间”,以及如何应对数据延迟到达——这两点几乎每次上线都会出问题。










