用 RANGE BETWEEN INTERVAL '6 days' PRECEDING AND CURRENT ROW 才真正实现“过去7天”求和,ROWS 6 PRECEDING 仅按行数滑动,易因数据稀疏或密集导致时间范围偏差。

直接说结论:用 ROWS BETWEEN 6 PRECEDING AND CURRENT ROW 是对的,但必须配合 ORDER BY 时间字段(且该字段需唯一或谨慎处理并列),否则结果不可靠。
为什么 ROWS 6 PRECEDING 不等于“过去7天”
这是最常被误解的一点:ROWS 是按物理行数滑动,不是按时间范围。如果某天没销售,对应行就不存在,那 ROWS BETWEEN 6 PRECEDING AND CURRENT ROW 可能只覆盖 5 天甚至更少;反之,如果一天有 10 笔订单,它会把这 10 行全算进窗口,导致单日权重过大。
- ✅ 正确目标:按
order_date向前取≤7天(含当天)的所有记录求和 - ❌
ROWS 6 PRECEDING实际效果:取当前行及往前紧邻的 6 行(不管日期是否连续、是否跨周) - ⚠️ 风险:在数据稀疏或高频写入场景下,结果会系统性偏低或偏高
真正可靠的写法:用 RANGE BETWEEN INTERVAL '6 days' PRECEDING AND CURRENT ROW
PostgreSQL 和 Oracle 支持 RANGE + INTERVAL,这才是语义上“过去7天”的自然表达。MySQL 8.0+ 也支持(需时间类型字段),但注意必须是 DATE 或 DATETIME,不能是字符串。
- 要求
ORDER BY order_date(升序),且order_date为原生日期类型 - 示例:
SELECT order_date, amount, SUM(amount) OVER ( ORDER BY order_date RANGE BETWEEN INTERVAL '6 days' PRECEDING AND CURRENT ROW ) AS sum_7days FROM sales; - 若
order_date含时分秒,建议先CAST(order_date AS DATE)再排序,避免同日多笔被拆到不同窗口
兼容 MySQL 5.7 / SQL Server 等不支持 RANGE INTERVAL 的方案
只能退回到自连接或子查询模拟时间窗口,性能较差但语义明确。核心是显式写出时间条件:
- 用相关子查询(适合小表或加索引后):
SELECT s1.order_date, s1.amount, (SELECT SUM(s2.amount) FROM sales s2 WHERE s2.order_date BETWEEN s1.order_date - INTERVAL 6 DAY AND s1.order_date) AS sum_7days FROM sales s1; - 关键点:
s2.order_date BETWEEN ...必须走order_date索引,否则全表扫描;建议建INDEX(order_date) - SQL Server 可用
DATEADD(day, -6, s1.order_date)替代INTERVAL - 别用
LEFT JOIN+GROUP BY模拟——容易因 NULL 或重复关联放大 sum
最容易被忽略的坑:时区与日期截断
如果你的 order_date 是 TIMESTAMP WITH TIME ZONE 或应用服务器/数据库时区不一致,INTERVAL '6 days' PRECEDING 会按数据库本地时区计算,可能导致跨午夜偏差。
- 统一做法:所有时间字段存 UTC,计算前
AT TIME ZONE 'Asia/Shanghai'(PostgreSQL)或CONVERT_TZ()(MySQL)转为目标时区再截断 - 更稳妥:用
DATE(order_date AT TIME ZONE 'UTC' AT TIME ZONE 'Asia/Shanghai')得到业务日,再按日聚合 - 测试方法:手动查某天(如 '2024-04-01')的窗口结果,对比原始数据里 3月26–4月1号的 sum 是否完全匹配










