窗口函数无法直接实现“最近30天”动态时间窗口计算,因其仅支持固定行偏移或对数字类型可靠的range,而日期类型在多数数据库中不支持稳定range滑动;postgresql虽支持但有类型和重复值限制;通用可靠方案是用相关子查询或join配合日期范围过滤,并需为order_date建立索引。

窗口函数不能直接按“最近30天”动态取数
SQL 窗口函数(如 SUM() OVER)本身不支持基于当前行日期动态滑动时间窗口(比如“往前推30天”),它只能按固定排序+固定行偏移(ROWS BETWEEN)或逻辑范围(RANGE BETWEEN)计算。而日期范围不是等距的,RANGE 在多数数据库里只对数字类型可靠,对 DATE 类型行为不稳定(PostgreSQL 除外,但需配合 ORDER BY date RANGE INTERVAL '30 days' PRECEDING)。
所以直接写 SUM(sales) OVER (ORDER BY order_date RANGE BETWEEN INTERVAL '29 days' PRECEDING AND CURRENT ROW) 在 MySQL、SQL Server 上会报错或结果错误;在 PostgreSQL 虽支持,但要求 order_date 是 DATE 或 TIMESTAMP 类型且无重复值,否则可能漏算。
用自连接或相关子查询更通用可靠
真正兼容各数据库、语义清晰的做法是放弃窗口函数,改用相关子查询或 JOIN:
- 对每条销售记录,查出所有
order_date在[当前行日期 - 30天, 当前行日期]内的销售额求和 - 注意:必须给销售表加
date字段索引(如INDEX idx_order_date (order_date)),否则性能极差 - 示例(标准 SQL,适用于 MySQL 8.0+、PostgreSQL、SQL Server):
SELECT order_date, sales, (SELECT SUM(s2.sales) FROM sales s2 WHERE s2.order_date BETWEEN s1.order_date - INTERVAL '30 days' AND s1.order_date) AS sum_30d FROM sales s1 ORDER BY order_date;
MySQL 中 INTERVAL '30 days' 可写作 DATE_SUB(s1.order_date, INTERVAL 30 DAY);SQL Server 用 DATEADD(day, -30, s1.order_date)。
PostgreSQL 可用 RANGE,但有隐含限制
PostgreSQL 支持 RANGE 配合 INTERVAL,但必须满足两个条件:
-
ORDER BY列必须是DATE或TIMESTAMP(不能是字符串或带时区不一致的类型) - 如果同一天有多笔订单,
RANGE会把它们视为同一位置,导致窗口包含全部同日记录——这反而是合理行为;但若日期有空缺,不会自动补零
正确写法:
SELECT
order_date,
sales,
SUM(sales) OVER (
ORDER BY order_date
RANGE BETWEEN INTERVAL '29 days' PRECEDING AND CURRENT ROW
) AS sum_30d
FROM sales
ORDER BY order_date;
注意是 '29 days',因为 CURRENT ROW 已包含当天,合起来共30天。
别忽略数据去重和聚合粒度
实际业务中,“最近30天销售额”通常指按天聚合后的滚动和,而不是对原始明细行直接窗口计算——否则同一天多笔订单会被重复计入多个窗口,造成放大。
- 先按天汇总:
SELECT order_date, SUM(sales) AS daily_sales FROM sales GROUP BY order_date - 再在这个聚合结果上做滚动求和(用上述任一方法)
- 如果原始表含未来日期或测试数据,务必加
WHERE order_date 过滤,否则影响“最近”的语义
窗口函数容易让人误以为能直接解决时间范围问题,但真正落地时,日期边界、空值、重复日期、时区、聚合层级这些细节,比语法本身更决定结果是否可信。











