滑动窗口不能只用 ROWS BETWEEN 因其按物理行数定位,无法响应时间变化;正确做法是用 RANGE BETWEEN INTERVAL 实现动态时间范围,但需注意数据库兼容性、时间字段类型、NULL 处理及性能优化。

滑动窗口为什么不能只用 ROWS BETWEEN
因为 ROWS BETWEEN 按物理行数定位,无法响应时间字段变化——比如订单表按 order_time 排序后,想算“过去7天内销售额”,用 ROWS BETWEEN 6 PRECEDING AND CURRENT ROW 会错:某天没订单就跳过,导致窗口实际跨时长不足7天。
真正动态的滑动窗口必须绑定时间/数值范围,靠 RANGE BETWEEN 或数据库特有语法实现。
-
RANGE BETWEEN要求排序字段是数字或日期类型,且必须是单调的(如ORDER BY order_time) - PostgreSQL 和 Oracle 支持
RANGE BETWEEN INTERVAL '7 days' PRECEDING AND CURRENT ROW - MySQL 8.0+ 不支持
INTERVAL在RANGE中,得用UNBOUNDED PRECEDING配合WHERE子查询模拟(性能差) - ClickHouse 和 Spark SQL 用
ROWS BETWEEN+ORDER BY时间戳,但需确保数据无重复时间点,否则CURRENT ROW可能包含多行
PostgreSQL 中正确写法:用 RANGE + INTERVAL
假设表 sales 有字段 sale_time(timestamptz)和 amount,要计算每条记录“截至当前时刻、过去30天内累计销售额”:
SELECT
sale_time,
amount,
SUM(amount) OVER (
ORDER BY sale_time
RANGE BETWEEN INTERVAL '30 days' PRECEDING AND CURRENT ROW
) AS rolling_30d_sum
FROM sales;注意:RANGE 对时间字段生效的前提是排序字段类型为 DATE / TIMESTAMP;若用字符串存时间(如 '2024-01-01'),会报错或结果异常。
- 如果
sale_time有重复值,CURRENT ROW会把同时间所有行视为一个“组”,一起纳入窗口——这是RANGE的语义,不是 bug - 想排除并列情况?改用
ROWS BETWEEN+ 唯一辅助排序字段,例如ORDER BY sale_time, id -
INTERVAL '30 days'不识别日历月,30天恒为 30 × 24 小时,不处理闰秒或夏令时偏移
MySQL 8.0 替代方案:用变量模拟动态范围
MySQL 不支持 RANGE BETWEEN INTERVAL,硬要用时间滑动窗口,只能放弃窗口函数,改用自连接或变量。更稳的做法是用相关子查询(适合小数据量):
SELECT
s1.sale_time,
s1.amount,
(SELECT SUM(s2.amount)
FROM sales s2
WHERE s2.sale_time >= s1.sale_time - INTERVAL 30 DAY
AND s2.sale_time <= s1.sale_time) AS rolling_30d_sum
FROM sales s1;这个写法在百万级数据上会明显变慢,因为每行都触发一次全表扫描(除非 sale_time 有索引且 selectivity 高)。
- 加复合索引
INDEX(sale_time, amount)能显著提速 - 如果业务允许近似结果,可先按天聚合再用
ROWS BETWEEN 29 PRECEDING AND CURRENT ROW,但丢失日内分布信息 - MySQL 8.0.22+ 支持函数索引,可用
INDEX((sale_time - INTERVAL 30 DAY))优化子查询,但实测效果有限
容易被忽略的边界:NULL 值和排序稳定性
只要 ORDER BY 字段含 NULL,RANGE 窗口行为就不可控:不同数据库对 NULLS FIRST/LAST 的默认处理不同,且 RANGE 把 NULL 视为独立“无穷小”或“无穷大”点,导致窗口突然截断或吞掉大量行。
- 务必在
ORDER BY显式声明NULLS LAST(PostgreSQL)或过滤掉NULL记录 - SQLite 不支持
RANGE,强行用会静默退化成ROWS,别信文档没写清的兼容性说明 - Spark SQL 的
RANGE BETWEEN对timestamp类型支持不一致,Databricks Runtime 11.3+ 才修复了毫秒级精度丢失问题
动态滑动窗口真正的复杂点不在语法,而在你是否清楚当前数据库对 RANGE 的实现深度——它可能只是语法糖,背后仍是嵌套循环。上线前,拿真实时间分布的数据跑 explain,看执行计划里有没有 WindowAgg 下挂 Materialize 节点。

















