最直接的移动平均写法是AVG()配合ROWS BETWEEN窗口子句,需严格依赖ORDER BY确保物理行序稳定,如ROWS BETWEEN 2 PRECEDING AND CURRENT ROW计算含当前行共3行的均值,且必须避免排序列重复或缺失导致结果错位。

AVG() 窗口函数 + ROWS BETWEEN 是最直接的移动平均写法
SQL 标准里没有 MOVING_AVG 这种函数,但用 AVG() 配合窗口定义就能精准控制“往前看几行、往后看几行”,实现任意长度的移动平均。关键不是套公式,而是理解 ROWS BETWEEN 的偏移逻辑——它按物理行序(不是时间值)计算,所以必须先用 ORDER BY 明确排序依据,否则结果不可靠。
为什么 ORDER BY 在 OVER() 里不能省略
没 ORDER BY 的窗口帧是未定义行为:不同数据库可能返回随机顺序下的平均值,甚至报错。尤其当原始数据没主键或时间字段无索引时,ROWS BETWEEN 会按存储顺序取行,和业务意义完全脱节。
-
OVER (ORDER BY created_at)—— 按时间戳升序,适合时序分析 -
OVER (ORDER BY id)—— 仅当id严格递增且代表插入顺序才安全 - 避免
OVER ()或OVER (PARTITION BY ...)单独使用,不带ORDER BY就无法定义“移动”方向
ROWS BETWEEN 的边界写法容易搞反
常见错误是把“过去3天”写成 ROWS BETWEEN 3 PRECEDING AND CURRENT ROW,其实这是包含当前行共4行;而“最近3行(含当前)”才是 ROWS BETWEEN 2 PRECEDING AND CURRENT ROW。记住:数字指“往前/后跳几行”,不是“总共几行”。
- 计算当前行及前2行的均值(共3行):
ROWS BETWEEN 2 PRECEDING AND CURRENT ROW - 计算前1行、当前行、后1行(共3行):
ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING - 想排除当前行只算前N行?不行——
CURRENT ROW是下界最小单位,只能用N-1 PRECEDING+CURRENT ROW再减去当前值,不推荐
NULL 值和数据稀疏场景要主动处理
如果某天没数据,ROWS BETWEEN 不会自动跳过空缺,而是按行号硬取——可能导致平均值基于错误的时间跨度。例如按 id 排序,中间缺了 id=5,那 id=6 的“前2行”会取到 id=3,4,跳过本该存在的 id=5。
- 时序数据优先用时间字段
ORDER BY event_time,再配合RANGE BETWEEN INTERVAL '2 days' PRECEDING AND CURRENT ROW(PostgreSQL/Oracle 支持,MySQL 8.0+ 有限支持) - 必须用
ROWS时,先用GENERATE_SERIES(PostgreSQL)或日期维表补全缺失日期,再左连接原始数据 -
AVG()本身会自动忽略NULL,但如果整段窗口都是NULL,结果就是NULL,需用COALESCE(AVG(...), 0)显式兜底
移动平均真正的坑不在语法,而在“行”的定义是否匹配业务语义——时间不连续、排序字段重复、分区边界切割不当,都会让 ROWS BETWEEN 算出看似正确实则错位的结果。

















