sql中计算移动平均值应使用avg()配合over()窗口函数与rows between子句,如rows between 1 preceding and 1 following取相邻3行均值;必须显式order by确保行序稳定,禁用range以防重复值导致窗口漂移。

SQL里用AVG()加OVER()窗口函数算移动均值
直接用AVG()配合ORDER BY和ROWS BETWEEN就能算指定范围的移动平均,不需要自连接或子查询。关键在窗口帧定义:想算“当前行及其前后各1行”的均值,就得写ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING。
常见错误是漏掉ORDER BY——没排序的OVER()窗口行为未定义,结果可能错乱;还有人写成RANGE,它按值分组而非物理行,对重复值或空缺序号极不友好。
-
ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING:严格取物理上相邻的3行(含自身) -
ROWS BETWEEN CURRENT ROW AND 2 FOLLOWING:当前+后两行(共3行) - 若排序字段有重复,必须加唯一键(如
id)做二级排序,否则同序号行顺序不确定
计算偏差:用原始值减去AVG() OVER(...)结果
偏差就是原始列值减去对应窗口均值,直接相减即可。注意数据类型隐式转换:如果原始列是INT而窗口AVG()返回DECIMAL,结果会自动转为小数,但不会丢精度。
示例(PostgreSQL/MySQL 8.0+/SQL Server):
SELECT id, value, AVG(value) OVER (ORDER BY id ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING) AS moving_avg, value - AVG(value) OVER (ORDER BY id ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING) AS deviation FROM data;
边界行(首行、末行)的窗口不足3行时,AVG()仍正常工作——只对实际存在的行求均值,比如首行只有自身和下一行,就只算这两行的平均。
不同数据库对ROWS语法的支持差异
MySQL 8.0+、PostgreSQL、SQL Server、Oracle 都支持标准ROWS BETWEEN;但 SQLite 直到 3.28 才支持窗口函数,且不支持FOLLOWING关键字(得用UNBOUNDED FOLLOWING替代,或改用OFFSET模拟);旧版 MySQL(5.7)完全不支持窗口函数,只能用自连接+GROUP BY硬写,性能差且易出错。
- SQLite 3.28+:必须写
ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING,不支持省略ROWS - BigQuery:支持,但
ORDER BY字段必须是确定性表达式(不能是RAND()) - 如果目标库不支持窗口函数,优先考虑应用层计算,而非拼接N次子查询
性能和NULL值处理要提前想清楚
窗口函数本身不慢,但若ORDER BY字段没索引,大表排序开销显著;另外,AVG()默认忽略NULL值——如果某行value是NULL,它不参与均值计算,也不拉低计数,这点和SUM()/COUNT()手动组合行为一致。
- 想把
NULL当0参与计算?先用COALESCE(value, 0)再套窗口 - 想让整行
NULL时偏差也返回NULL?不用额外处理,减法自然得NULL - 千万级表上跑移动均值,确保
ORDER BY字段有索引,否则执行计划可能走全表排序
真正容易被忽略的是排序稳定性——即使你指定了id排序,如果id不是主键或唯一,相同id的多行在不同执行中可能顺序浮动,导致移动窗口覆盖的行不一致。加id, created_at这种复合排序更稳妥。










