MySQL 8.0 的 AVG() 窗口函数需显式指定 ROWS 范围才能实现移动平均,仅 ORDER BY 会导致全分区平均;ROWS 必须配合确定且无重复的 ORDER BY,否则结果不稳定或报错。

MySQL 8.0 的 AVG() 窗口函数必须搭配 ROWS 才能算移动平均
MySQL 8.0 支持窗口函数,但 AVG() 默认不回溯历史行——你得显式用 ROWS BETWEEN ... AND ... 定义滑动窗口范围,否则就是全分区平均(等价于 AVG() OVER (PARTITION BY ...)),不是移动平均。
常见错误是只写 AVG(x) OVER (ORDER BY t),结果每行都得到整个有序分区的平均值,完全没“移动”起来。
-
ROWS BETWEEN 2 PRECEDING AND CURRENT ROW:当前行 + 前两行,共 3 期移动平均 -
ROWS BETWEEN CURRENT ROW AND 2 FOLLOWING:当前行 + 后两行(适用于预测类平滑) -
ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING:前后各一期,中心对称,常用于降噪
ORDER BY 是 ROWS 范围生效的前提,且必须确定、无重复
没有 ORDER BY,ROWS 子句直接报错:ERROR 3586 (HY000): Window '。更隐蔽的问题是:如果 ORDER BY 字段存在重复值(比如多条记录时间戳相同),MySQL 会任意排序这些行,导致同一组数据每次执行结果不一致,移动平均值漂移。
- 务必用唯一排序键,例如
ORDER BY created_at, id(id补充去重) - 避免仅用
ORDER BY date,尤其当单日有多条记录时 - 若原始数据无天然顺序,可先加
ROW_NUMBER() OVER ()生成序号再排序
计算 5 日移动平均的典型写法与性能注意点
假设表 stock_price 有字段 trade_date(DATE 类型)、close_price(DECIMAL),想按交易日顺序算 5 日收盘价均值:
SELECT
trade_date,
close_price,
AVG(close_price) OVER (
ORDER BY trade_date, id
ROWS BETWEEN 4 PRECEDING AND CURRENT ROW
) AS ma5
FROM stock_price
ORDER BY trade_date;
注意几个实际坑:
- MySQL 不支持
ROWS BETWEEN INTERVAL 4 DAY PRECEDING这种时间偏移写法,必须基于行数,不是天数——如果中间有休市日,4 PRECEDING可能跳过真实 5 个交易日 - 首 4 行结果为
NULL或部分行均值(取决于是否含CURRENT ROW),需用COALESCE(ma5, close_price)填补 - 大表上加
ORDER BY ... ROWS会触发临时文件排序,建议在(trade_date, id)上建联合索引
替代方案:用自连接或子查询模拟移动平均的适用场景
当需要严格按自然日(而非行数)计算、或 MySQL 版本低于 8.0 时,窗口函数不可用,就得绕路。但即使在 8.0,遇到“最近 5 个非空交易日”这类逻辑,ROWS 也无能为力,因为无法跳过休市/缺失数据行。
这时必须用相关子查询:
ROWS











