移动平均必须用ROWS BETWEEN而非RANGE BETWEEN,因ROWS严格按物理行数滑动确保“最近N行”准确平均,而RANGE按值分组遇重复或缺失值会导致窗口边界漂移、结果不可控。

移动平均为什么必须用 ROWS BETWEEN,而不是 RANGE BETWEEN
因为移动平均依赖「物理行位置」而非「值大小」:比如过去3天的均价,必须取当前行向上数3行,哪怕某天没数据(空行),也不能跳到更早的非空行去凑数。RANGE BETWEEN 会按排序键值聚合相同/相近值,导致窗口边界漂移——尤其在时间字段有重复、缺失或非连续时,结果完全不可控。
常见错误现象:AVG(price) OVER (ORDER BY date RANGE BETWEEN INTERVAL '2 days' PRECEDING AND CURRENT ROW) 在日期不连续时漏掉实际该包含的行,或意外拉入远超3天的数据。
-
ROWS BETWEEN按实际行号计数,稳定可靠 - 排序字段(如
date)必须明确指定,否则窗口无序,ROWS失效 - 若原始数据存在多条同日期记录,需先去重或用
ROW_NUMBER()辅助锚定
ROWS BETWEEN 的三种典型写法与适用场景
核心是控制窗口左边界:从哪一行开始算,右边界固定为 CURRENT ROW 最常用。
- 前N日(含当天):
ROWS BETWEEN N-1 PRECEDING AND CURRENT ROW→ 例如3日均值写ROWS BETWEEN 2 PRECEDING AND CURRENT ROW - 前N日(不含当天):
ROWS BETWEEN N PRECEDING AND 1 PRECEDING→ 适合做“昨日3日均值”类滞后指标 - 中心对称窗口(如5日均值,当前日在正中):
ROWS BETWEEN 2 PRECEDING AND 2 FOLLOWING→ 注意首尾2行会因边界截断而结果为NULL
性能提示:窗口越宽,内存占用越高;ROWS 本身不触发排序开销,但 ORDER BY 子句若未命中索引,可能引发临时排序。
处理开头几行 NULL 结果的实用方案
窗口不足时(如第1行无法往前取2行),AVG() 默认返回 NULL。这不是bug,是SQL标准行为,但业务常需降级填充。
- 用
COALESCE(AVG(x) OVER (...), x):首行直接取自身值(简单粗暴) - 用
AVG(x) OVER (ORDER BY t ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW)替代:变成长期累积均值(仅适用于趋势平滑,非移动) - 真正严格的移动平均降级(如前2行不够就取前2行均值):需配合
COUNT(*)窗口判断,例如AVG(x) FILTER (WHERE row_num >= 1) OVER (...)(PostgreSQL)或用子查询构造动态窗口(通用但低效)
注意:FILTER 不是所有数据库支持;MySQL 8.0+ 需用 AVG(CASE WHEN ... THEN x END) 模拟。
MySQL 8.0 / PostgreSQL / SQL Server 写法差异速查
语法骨架一致,但细节易踩坑:
- MySQL 8.0:支持完整
ROWS BETWEEN,但PRECEDING必须是字面量整数,不能是变量或列名 - PostgreSQL:支持
UNBOUNDED和负偏移(如-2 PRECEDING),也支持FILTER子句优化计算 - SQL Server:
ROWS BETWEEN行为一致,但若排序字段含NULL,需显式写ORDER BY col ASC NULLS LAST(2022+),旧版默认NULLS FIRST可能打乱顺序
跨库移植时,最安全写法是避免 NULL 排序值 + 所有偏移量用正整数字面量 + 不依赖 FILTER。
边界情况比想象中多:排序字段重复、含 NULL、数据稀疏、窗口宽度动态变化……这些地方一动就错,得实测每种组合。

















