滚动30天求和必须用range between,不能用rows;range按日历天数计算,要求排序列为日期类型且无重复,mysql 8.0.2+支持range + interval,postgresql/snowflake语法类似,sql server需模拟。

滚动30天求和必须用 RANGE BETWEEN,不能用 ROWS
直接写 ROWS BETWEEN 29 PRECEDING AND CURRENT ROW 算的是“最近30行”,不是“最近30天”——如果某天没销售记录,那行就没了,窗口会漏掉实际日期。真正按日历天数滚动,得靠 RANGE + 时间列排序。
关键前提是:排序字段必须是日期类型(如 DATE 或 datetime),且该列不能有重复值干扰(否则需配合 ORDER BY date, id 消除并列)。
-
RANGE BETWEEN INTERVAL '29' DAY PRECEDING AND CURRENT ROW(PostgreSQL / Snowflake) -
RANGE BETWEEN 29 PRECEDING AND CURRENT ROW(MySQL 8.0+,要求排序列为 DATE 类型) - SQL Server 不支持
RANGE配合INTERVAL,得用自连接或 CTE 模拟
MySQL 8.0 实现滚动30天销售额的最小可行写法
假设表叫 sales,有字段 sale_date(DATE 类型)和 amount(DECIMAL):
SELECT
sale_date,
amount,
SUM(amount) OVER (
ORDER BY sale_date
RANGE BETWEEN INTERVAL '29' DAY PRECEDING AND CURRENT ROW
) AS rolling_30d_sum
FROM sales;
注意:MySQL 8.0.2 起才支持 RANGE + INTERVAL;低于此版本会报错 Window 'w' with RANGE frame requires ORDER BY to be a single column of numeric or temporal type。
- 如果
sale_date是DATETIME,先CAST(sale_date AS DATE)再排序 - 存在多笔同日销售?没问题,
RANGE天然包含所有同日行 - 某天无数据?不影响计算,窗口仍按日历向前推30天
PostgreSQL 中处理时区与边界对齐
若业务按本地时区(如 Asia/Shanghai)统计,直接用 sale_time::DATE 可能跨午夜出错。更稳妥的是用 sale_time AT TIME ZONE 'Asia/Shanghai' 转换后再截断:
SELECT
(sale_time AT TIME ZONE 'Asia/Shanghai')::DATE AS sale_date,
SUM(amount) OVER (
ORDER BY (sale_time AT TIME ZONE 'Asia/Shanghai')::DATE
RANGE BETWEEN '29 days'::INTERVAL PRECEDING AND CURRENT ROW
) AS rolling_30d_sum
FROM sales;
另外,RANGE 的边界是闭区间:当前日当天包含在内,往前推整整29天(即含首尾共30个自然日)。
- 测试时可用
WHERE sale_date >= '2024-01-01' AND sale_date 小范围验证 - 千万避免在
OVER()里写PARTITION BY后再套RANGE—— 分区后每个分区独立计算,但日期不连续,滚动会断裂
SQL Server 用户绕过 RANGE 限制的实操方案
SQL Server 2022 前不支持 RANGE + 时间偏移,只能用 LEFT JOIN 或 CROSS APPLY 模拟:
SELECT s1.sale_date, s1.amount, t.sum_amt AS rolling_30d_sum FROM sales s1 CROSS APPLY ( SELECT SUM(s2.amount) FROM sales s2 WHERE s2.sale_date BETWEEN DATEADD(day, -29, s1.sale_date) AND s1.sale_date ) t(sum_amt);
性能敏感场景务必确保 sale_date 有索引;否则大数据量下会全表扫描。如果数据按日聚合过(每天只有一行),可先建物化视图或临时表预聚合。
- 用
DATEADD而非字符串拼接,避免隐式转换失败 - 注意
BETWEEN包含两端,正好覆盖30天(含当日) - 若原始表含未来日期(如预售单),加
AND s2.sale_date 过滤
RANGE BETWEEN ... INTERVAL——不支持的话,硬套语法只会报错,而不是默默算错。










