滚动30天销量和需用窗口函数range between interval 29 day preceding and current row按日期值范围滑动计算,确保每个日期包含自身及往前29天的销量总和;mysql 8.0+/postgresql支持,老版本需用子查询或join兜底,且必须为sale_date建索引以避免全表扫描。

用 DATE_SUB 和窗口函数实现滚动求和
MySQL 8.0+ 或 PostgreSQL 可直接用窗口函数加时间过滤,核心是把“最近30天”转化为按日期排序、向前取30行的累计逻辑。但注意:滚动30天 ≠ 最近30个自然日销量总和,而是每个日期都算它往前推30天(含当天)的销量和。
典型写法:
SELECT
sale_date,
SUM(sales_amount) OVER (
ORDER BY sale_date
RANGE BETWEEN INTERVAL 29 DAY PRECEDING AND CURRENT ROW
) AS rolling_30d_sales
FROM sales
WHERE sale_date >= DATE_SUB(CURDATE(), INTERVAL 29 DAY);
-
RANGE BETWEEN INTERVAL 29 DAY PRECEDING AND CURRENT ROW是关键,它按日期值范围滑动,不是按行数;如果某天没数据,会自动跳过,不影响跨度 - 必须配合
ORDER BY sale_date,否则窗口无法按时间对齐 - MySQL 中
RANGE对DATE类型支持有限,部分版本只认ROWS;若报错,改用自连接或子查询兜底
兼容老版本 MySQL(5.7)的替代方案
没有窗口函数时,得靠相关子查询或 JOIN 模拟滚动。性能差但稳定,适合数据量不大的表。
常见写法:
SELECT t1.sale_date, (SELECT SUM(t2.sales_amount) FROM sales t2 WHERE t2.sale_date BETWEEN DATE_SUB(t1.sale_date, INTERVAL 29 DAY) AND t1.sale_date) AS rolling_30d_sales FROM sales t1 WHERE t1.sale_date >= '2024-01-01';
- 子查询里用
DATE_SUB(t1.sale_date, INTERVAL 29 DAY)动态算起点,确保每天都是独立的30天窗口 - 务必给
sale_date加索引,否则每次子查询都全表扫描,几万行就明显卡顿 - 如果销售表有重复日期(比如多笔订单同一天),
SUM没问题,但别误用COUNT(*)当销量——那是订单数,不是金额
日期边界容易漏掉的坑
“最近30天”在业务中常被理解为“过去30个日历日”,但 SQL 计算时容易错算成29天或31天。
-
INTERVAL 30 DAY是从当前日往前推30整天,包含起止日共31天;要严格30天(含当天),得用INTERVAL 29 DAY - 如果用
BETWEEN a AND b,MySQL 默认闭区间,a和b都包含;但若字段带时间(如datetime),需用+ <code>>=并手动加23:59:59,否则最后一天可能丢数据 - 跨月计算时(比如1月31日往前推29天),
DATE_SUB自动处理,不用自己判断大小月
为什么不能直接用 GROUP BY DATE_SUB(sale_date, INTERVAL DAYOFYEAR(sale_date) DAY)?
有人想先截断日期再分组聚合,但这算的是“每月累计”或“每周累计”,不是滚动窗口。滚动的核心是每个输出行对应一个独立的时间切片,而不是归并。
- 滚动销量必须保留原始粒度(通常是日级),不能先
GROUP BY再算——那样丢失了中间日期的动态性 - 如果源表只有月度汇总,那就没法算真正的滚动30天,只能近似用月均值×30,但误差不可控
- 真正要压测性能时,优先考虑物化滚动结果到一张每日快照表,用定时任务更新,避免每次查都实时计算
RANGE 和 ROWS 的行为完全不同。











