avg() over需显式定义order by和rows between才能计算滚动平均,直接写avg(profit) over()仅返回全表平均值;必须按时间排序并补全缺失月份,否则结果失真。

AVG() OVER 需要配合 ORDER BY 和 ROWS BETWEEN 才能算滚动平均
直接写 AVG(profit) OVER() 只会返回全表平均值,不是滚动窗口。必须显式定义窗口的排序依据和范围,否则 SQL 引擎无法知道“最近三个月”对应哪些行。
关键点是:时间字段必须可排序(如 order_date 或 report_month),且数据需按时间升序排列;窗口范围不能用“三个月”这种日期单位直接写,得转换成行数或用 RANGE + 时间偏移(但支持度有限)。
- 优先用
ROWS BETWEEN 2 PRECEDING AND CURRENT ROW:适用于等间隔、无缺失的月度汇总数据(比如每月一条记录) - 若原始数据是日粒度且存在多条/空缺,
ROWS会出错——这时得先按月聚合,再对月表应用滚动窗口 - PostgreSQL 支持
RANGE BETWEEN INTERVAL '2 months' PRECEDING AND CURRENT ROW,但 MySQL 8.0+ 不支持INTERVAL在窗口中使用,SQL Server 也不支持
MySQL 8.0+ 实现月度滚动平均的可靠写法
MySQL 不支持基于时间的 RANGE 窗口,所以必须把“最近三个月”落地为“当前行及前两行”,前提是数据已按月严格对齐且无断层。
假设你有一张月度利润表 monthly_profit,含字段 report_month(格式 '2024-01')和 profit:
SELECT
report_month,
profit,
AVG(profit) OVER (
ORDER BY report_month
ROWS BETWEEN 2 PRECEDING AND CURRENT ROW
) AS rolling_avg_3m
FROM monthly_profit
ORDER BY report_month;
注意:report_month 必须是字符串但能字典序正确排序(如 '2024-01' ✅,'2024-1' ❌),否则 ORDER BY 会错乱。
遇到日期不连续或日粒度数据怎么办?
如果原始数据是每日订单,或者某个月没销售导致该月无记录,ROWS BETWEEN 2 PRECEDING 就会把“上上月”错当成“三个月前”,结果失真。
- 先用
GROUP BY YEAR(order_date), MONTH(order_date)汇总出月度利润 - 再用
LEFT JOIN或生成连续月份序列补全空缺(例如用递归 CTE 构造 36 个月),避免窗口跳过空白期 - 补全后,再对完整月表跑
ROWS BETWEEN 2 PRECEDING—— 这才是语义正确的“最近三个月”
漏掉补月这步,滚动平均在业务上就不可信,尤其影响管理层看趋势。
窗口函数性能与索引建议
AVG() OVER 本身不慢,但排序开销大。如果 ORDER BY 字段没索引,大数据量下会触发 filesort,拖慢几倍。
- 务必在排序字段(如
report_month或order_date)建索引 - 避免在窗口函数里套复杂表达式,比如
AVG(CASE WHEN ... THEN profit END)—— 先算好列再进窗口 - 如果只查最近 N 行,加
LIMIT不能优化窗口计算,它是在窗口执行完才截断;真要提速,得靠分区或物化中间结果
滚动平均看似简单,真正上线时,90% 的问题出在数据对齐和索引缺失,而不是函数写法本身。











