用ROWS BETWEEN 11 PRECEDING AND CURRENT ROW实现月度滚动年度求和最常用且可靠,前提是数据按年月严格排序、每月仅一条记录;若存在跳月或重复月,需先补全月序列再开窗。

SQL里用ROWS BETWEEN做月度滚动年度求和的关键写法
直接说结论:用 ROWS BETWEEN 11 PRECEDING AND CURRENT ROW 是最常用且可靠的写法,前提是数据按年月严格排序、且每月只有一条记录。如果存在多条记录或缺失月份,结果会出错。
为什么必须用11 PRECEDING而不是12 PRECEDING
滚动“过去12个月”包含当前月本身,一共是12个自然月——但窗口函数的 ROWS 是数行数,不是数月份。假设你按 ORDER BY year_month 排序,每行代表一个完整自然月:
-
ROWS BETWEEN 12 PRECEDING AND CURRENT ROW实际取13行(当前 + 前12行)→ 跨13个月 -
ROWS BETWEEN 11 PRECEDING AND CURRENT ROW才是12行 → 对应连续12个自然月
遇到跳月或重复月怎么办
真实业务数据常有缺失(如2024-02无数据)或重复(同月多笔流水),这时 ROWS 会失效——它只看物理行数,不校验时间连续性。
正确做法是先生成完整月度序列,再左连接原始数据,最后用 RANGE BETWEEN INTERVAL '11' MONTH PRECEDING AND CURRENT ROW(PostgreSQL/Oracle支持)或手动补月后走 ROWS:
SELECT
m.year_month,
SUM(t.amount) AS monthly_amount,
SUM(SUM(t.amount)) OVER (
ORDER BY m.year_month
RANGE BETWEEN INTERVAL '11' MONTH PRECEDING AND CURRENT ROW
) AS rolling_12m_sum
FROM all_months m
LEFT JOIN transactions t ON m.year_month = t.year_month
GROUP BY m.year_month
ORDER BY m.year_month;
MySQL 8.0+ 和 SQL Server 的兼容性注意点
MySQL 8.0+ 不支持 RANGE 配合 INTERVAL,SQL Server 也不支持;二者都只能依赖 ROWS,所以必须确保输入是稠密月序列:
- 先用递归CTE或日历表补全所有目标月份
- 再
LEFT JOIN原始数据,空值补0 - 最后用
SUM(x) OVER (ORDER BY year_month ROWS BETWEEN 11 PRECEDING AND CURRENT ROW) - 别忘了
ORDER BY必须明确,且字段不能有重复值(否则排序不稳定,ROWS行为不可控)
跨月滚动的核心从来不是函数有多炫,而是你敢不敢确认:每一行,真的代表一个不重不漏的自然月。

















