移动平均是什么
移动平均用窗口函数在「当前行加上前面 N-1 行」上求平均,把日度波动抹平,用来观察趋势。关键是帧的定义:ROWS BETWEEN 4 PRECEDING AND CURRENT ROW 严格按行数取最近 5 行;RANGE 按值范围取,且会把并列值一起纳入。窗口开头的几行数据不足,按已存在的行求平均,不会补 NULL。MySQL 8 与 PostgreSQL 都支持这种写法,列上还需注意窗口里的 ORDER BY 是帧定义的一部分。
基本信息
| 适用平台 | MySQL 8.0+ / PostgreSQL 11+ |
|---|---|
| 默认帧 | 带 ORDER BY 且未写帧子句时是 RANGE UNBOUNDED PRECEDING 到当前行,即累计平均 |
| 移动平均写法 | ROWS BETWEEN 6 PRECEDING AND CURRENT ROW 表示含当前行共 7 行 |
| 与累计平均区别 | 移动平均只看固定长度窗口,累计平均从开头算到当前行 |
| 边界处理 | 开头行窗口不足时按现有行计算,需要「窗口完整才输出」时用 COUNT 判断 |
典型使用场景
- 7 日/30 日均线观察业务趋势,避免单日波动误判
- 平滑转化率、客单价等指标的噪声
- 监控告警去噪:用移动平均替代单点阈值
- 报表里同时给出日值与均线,便于人工判断
- 与环比结合,看趋势是在加速还是回落
基础写法
基础写法
SELECT d, amount,
AVG(amount) OVER (ORDER BY d ROWS BETWEEN 6 PRECEDING AND CURRENT ROW) AS ma7,
AVG(amount) OVER (ORDER BY d) AS cum_avg
FROM daily_stats
ORDER BY d;
# ma7:含当前行共 7 行的平均,开头行按现有行计算
# cum_avg:默认帧是 RANGE UNBOUNDED PRECEDING,等于累计平均按维度分别计算
按维度分别计算
-- 每个门店各自算 7 日移动平均
SELECT shop_id, d, amount,
ROUND(AVG(amount) OVER (PARTITION BY shop_id
ORDER BY d
ROWS BETWEEN 6 PRECEDING AND CURRENT ROW)::numeric, 2) AS ma7,
COUNT(*) OVER (PARTITION BY shop_id
ORDER BY d
ROWS BETWEEN 6 PRECEDING AND CURRENT ROW) AS window_rows # 可用于判断窗口是否完整
FROM daily_stats
ORDER BY shop_id, d;常用帧写法对照
| 帧定义 | 含义 | 典型用途 |
|---|---|---|
| 默认(带 ORDER BY) | RANGE UNBOUNDED PRECEDING 到当前行 | 累计值、累计平均 |
| ROWS BETWEEN 6 PRECEDING AND CURRENT ROW | 固定 7 行 | 7 日移动平均 |
| ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING | 前后各一行共 3 行 | 三点平滑 |
| RANGE BETWEEN INTERVAL '6 days' PRECEDING AND CURRENT ROW | 按时间范围的 7 天 | 日期有缺失时更贴近日历口径 |
日期存在缺失时 ROWS 与 RANGE 结果不同:ROWS 凑够 7 行(可能跨越多天),RANGE 按时间范围取(可能不足 7 行);按日历天定义口径的业务指标应选 RANGE,或先把日期序列补齐。
窗口里的 ORDER BY 是帧定义的一部分,不是输出排序:只写 AVG(x) OVER () 会变成对全分区求平均,每行结果都一样,看起来「算出来了」其实完全不是移动平均。