移动平均是对当前行及前n行等动态子集求均值,而非整列静态平均;avg()本身不支持滑动窗口,必须配合over()及rows between或range between明确窗口范围,并强制要求order by确保行序。

什么是移动平均,为什么不能直接用 AVG()?
移动平均不是对整列数据求平均,而是对“当前行及前N行”(或前后各N行)的子集求均值。直接写 AVG(column) 会返回整列的静态平均值,完全达不到滑动效果。窗口函数的核心价值就在于它能定义这个动态计算范围——靠 ROWS BETWEEN 或 RANGE BETWEEN 明确边界。
用 ROWS BETWEEN 计算 3 日移动平均(最常用)
假设你有一张按日期排序的销售表 sales,字段为 sale_date 和 amount,要算“包含当天及前两天”的3日平均:
SELECT
sale_date,
amount,
AVG(amount) OVER (
ORDER BY sale_date
ROWS BETWEEN 2 PRECEDING AND CURRENT ROW
) AS moving_avg_3d
FROM sales;
注意点:
-
ORDER BY必须存在,否则窗口无法确定“前两行”是谁 -
ROWS BETWEEN按物理行数计数,适合等间隔时间(如每日数据),不依赖日期差值 - 首两行结果会是
NULL或少于3个值的平均(取决于数据库,默认按实际行数算) - PostgreSQL 和 SQL Server 默认包含
CURRENT ROW;MySQL 8.0+ 同样支持,但旧版不支持窗口函数
RANGE BETWEEN 适合不规则时间间隔
如果数据日期不连续(比如只在工作日有记录),用 ROWS 会导致“跳过周末”,把周一和周四强行连成3日窗口。这时改用 RANGE 更合理:
SELECT
sale_date,
amount,
AVG(amount) OVER (
ORDER BY sale_date
RANGE BETWEEN INTERVAL '2' DAY PRECEDING AND CURRENT ROW
) AS moving_avg_3d_calendar
FROM sales;
关键区别:
-
RANGE按ORDER BY表达式的值做区间判断,不是行号 -
INTERVAL '2' DAY要求sale_date是日期类型,且数据库支持该语法(PostgreSQL、MySQL 8.0+、Snowflake 支持;SQL Server 用DATEADD配合RANGE较麻烦) - 若某天无数据,窗口仍会向前找“时间上最近的2天内”的所有记录,更符合业务语义
常见错误:ORDER BY 写错或漏掉,导致结果不可靠
窗口函数里的 ORDER BY 不仅决定排序,还隐式定义了窗口方向和默认框架。漏写或写错会导致:
- 结果顺序混乱,
CURRENT ROW失去意义 - 某些数据库(如旧版 MySQL)报错:
This function requires an ORDER BY clause - 即使能执行,
ROWS BETWEEN实际按表物理存储顺序算,而非业务时间顺序 - 多个相同
sale_date值时,未加id等二级排序,会导致同一天数据窗口不稳定
稳妥写法是显式加上唯一排序键:ORDER BY sale_date, id。
移动平均真正难的不是语法,而是想清楚“3日”到底指哪3个数据点——按行数、按日历、还是按业务事件数。选错框架,结果就偏了。











