AVG()配合ROWS BETWEEN可直接实现移动平均,无需子查询;需ORDER BY保证顺序,日期重复时加唯一字段;ROWS按行偏移,RANGE按值偏移;NULL值自动忽略,边界行默认返回NULL。

用 AVG() 窗口函数直接算移动平均,别写子查询偏移
绝大多数场景下,AVG() 配合 ROWS BETWEEN 就能干净实现移动平均,根本不需要嵌套子查询模拟偏移。子查询写法不仅性能差(尤其数据量稍大时反复扫描)、可读性低,还容易在 ORDER BY 不唯一时结果错乱。
正确做法是明确指定窗口范围:
-
AVG(sales) OVER (ORDER BY date ROWS BETWEEN 2 PRECEDING AND CURRENT ROW):计算包含当前行及前两行的3期移动平均 - 必须带
ORDER BY,否则窗口无序,PRECEDING/FOLLOWING失效 - 若日期有重复,需额外加唯一排序字段(如
id),否则同日期多行可能被随机打乱顺序
子查询偏移真要写,得用 LIMIT + OFFSET 模拟(仅限 PostgreSQL / MySQL 8.0+)
某些老版本数据库不支持窗口函数,或业务逻辑强制要求逐行查前N条——这时才考虑子查询。但要注意:OFFSET 在子查询里不能直接引用外层字段,必须用关联条件“拉平”范围。
例如计算每行往前推3天的平均销量(非固定行数,而是按时间窗口):
SELECT t1.date,
(SELECT AVG(t2.sales)
FROM sales t2
WHERE t2.date BETWEEN t1.date - INTERVAL '3 days' AND t1.date) AS ma_3d
FROM sales t1
ORDER BY t1.date;- 这个写法依赖
date字段连续且无缺失;若有空缺日期,结果会包含更早的数据,实际不是“最近3天”而是“日期区间内所有数据” - MySQL 5.7 或 SQLite 不支持
INTERVAL,得用DATE_SUB(t1.date, INTERVAL 3 DAY)或strftime替换 - 每行触发一次子查询,1万行 = 1万次全表扫描,务必给
date加索引
ROWS 和 RANGE 的区别直接影响移动平均语义
窗口定义里用 ROWS 是按物理行偏移,用 RANGE 是按排序值偏移——这对移动平均很关键。
-
ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING:严格取前后各1行,共3行,不管这些行的date是否连续 -
RANGE BETWEEN INTERVAL '1 day' PRECEDING AND CURRENT ROW:取date落在当前行日期前1天到当天的所有行,可能1行,也可能10行 - 金融场景常用
ROWS(固定周期数),运营分析有时用RANGE(固定时间跨度),选错会导致平均值分母突变
NULL 值和边界行处理常被忽略
移动平均在开头几行天然缺数据,窗口函数默认返回 NULL,但业务上可能需要补0、向前填充,或直接跳过。
- 开头第1行用
ROWS BETWEEN 2 PRECEDING AND CURRENT ROW,实际只有1个值参与计算,AVG()结果就是它自己,不是NULL - 但如果某行
sales是NULL,它会被AVG()自动忽略(即不计入分母),这点和SUM()/COUNT()分开算不同 - 想强制排除
NULL行再算窗口,得先用WHERE sales IS NOT NULL过滤,否则窗口内混入NULL会影响有效行数判断
实际用的时候,先确认数据库版本是否支持窗口函数;支持就死磕 ROWS BETWEEN,不支持再考虑子查询——但得接受性能代价和逻辑歧义。时间维度的移动平均,永远比行数维度更容易出偏差。

















