移动平均是基于滑动窗口对当前行及邻近行逐行计算的均值,不能用GROUP BY因其会折叠行、丢失顺序;必须用AVG() OVER(ORDER BY ... ROWS BETWEEN ...)配合明确排序和窗口范围。

什么是移动平均,为什么不能用 GROUP BY
移动平均值(Moving Average)不是对整个分组求均值,而是对当前行及前后若干行做滑动窗口计算。用 GROUP BY 会把数据“压扁”,丢失原始行结构,根本没法实现逐行滑动——这是初学者最容易卡住的地方。
必须用窗口函数,核心是定义好 ORDER BY 和 ROWS BETWEEN 的范围。顺序错、范围写反、漏掉排序都会导致结果全乱。
用 AVG() OVER() 写出 3 日移动平均的正确模板
假设有一张销售表 sales,含字段 date 和 amount,要算按日期排序的 3 天移动平均(含当天、前一天、后一天):
SELECT date, amount,
AVG(amount) OVER (
ORDER BY date
ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING
) AS ma3
FROM sales;
关键点:
-
ORDER BY date不可省略,否则窗口无序,结果不可预测 -
ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING表示“当前行 ±1 行”,共 3 行;若要前 3 天(不含当天),写成2 PRECEDING AND CURRENT ROW - 首尾行会自动截断(比如第一天没有“前一天”,就只算自己和后两天),这是标准行为,无需额外处理
日期不连续时怎么避免跳过空天
真实业务中,date 常有缺失(如周末无销售)。直接按 ROWS 算会把“物理行数”当“日历天数”,导致 3 行 ≠ 3 天。这时得改用 RANGE + 时间偏移:
SELECT date, amount,
AVG(amount) OVER (
ORDER BY date
RANGE BETWEEN INTERVAL '2' DAY PRECEDING AND CURRENT ROW
) AS ma3_calendar
FROM sales;
注意:
-
RANGE按实际值(这里是日期值)找范围,INTERVAL '2' DAY表示往前推 2 天,加上当天共 3 日历日 - PostgreSQL 和 Snowflake 支持这种写法;MySQL 8.0+ 也支持,但 SQLite 不支持
RANGE与INTERVAL组合 - 如果用的是字符串日期(如
'2023-01-01'),必须确保类型是DATE或TIMESTAMP,否则RANGE会按字典序比较,出错
性能和 NULL 值的隐性坑
窗口函数本身不慢,但两个细节常被忽略:
- 如果
amount有NULL,AVG()默认跳过它们(符合 SQL 标准),但你可能以为它补 0——实际不会。需要补零得先用COALESCE(amount, 0) - 在大表上加
ORDER BY+ 窗口函数,如果没有date字段的索引,排序会触发磁盘临时文件,查询明显变慢 - 某些数据库(如旧版 Hive)不支持
FOLLOWING,只能用PRECEDING+CURRENT ROW模拟,逻辑要重写
移动平均真正麻烦的从来不是语法,而是搞清你要的是“按行数滑动”还是“按时间跨度滑动”,以及数据里到底有没有空值和断点。

















