滑动窗口在SQL中靠OVER()子句配合ROWS BETWEEN或RANGE BETWEEN实现;ROWS按物理行数,RANGE按排序值(如时间戳),需注意类型、索引及时区影响。

滑动窗口在SQL里靠什么函数实现?
标准SQL没有原生“滑动窗口”语法,但现代数据库基本都支持 OVER() 子句配合 ROWS BETWEEN 或 RANGE BETWEEN 来模拟。关键不是“窗口函数”这个统称,而是具体怎么定义边界——ROWS 按物理行数,RANGE 按排序值(比如时间戳)。
-
ROWS BETWEEN 2 PRECEDING AND CURRENT ROW:固定前2行 + 当前行,不管时间间隔多大 -
RANGE BETWEEN INTERVAL '7 DAYS' PRECEDING AND CURRENT ROW:只对PostgreSQL、MySQL 8.0+、Snowflake等支持时间范围的引擎有效 - 如果用
RANGE但列没索引或类型不匹配(比如用字符串存时间),可能 silently 退化成全表扫描
别默认以为 OVER(ORDER BY event_time) 就自动按时间滑动——没写 BETWEEN 就是累积窗口(从第一行到当前行)。
PostgreSQL里怎么写7天滚动求和?
PostgreSQL支持 RANGE + INTERVAL,但必须确保 event_time 是 TIMESTAMP 类型,且最好有索引:
SELECT
event_time,
SUM(revenue) OVER (
ORDER BY event_time
RANGE BETWEEN INTERVAL '6 DAYS' PRECEDING AND CURRENT ROW
) AS rolling_7d_sum
FROM sales;
- 注意是
'6 DAYS',不是'7 DAYS':因为CURRENT ROW已包含当天,往前推6天凑满7整天 - 如果
event_time有重复值(比如秒级精度但多条记录同秒),RANGE会把所有同时间点的行一起纳入,可能造成统计偏差 - 替代方案:先用
DISTINCT ON (date_trunc('day', event_time))聚合到日粒度再算,避免重复时间点干扰
MySQL 8.0+如何处理非整数天的滑动?
MySQL支持 RANGE,但只接受数字偏移(不能直接写 INTERVAL),所以得把时间转成数值:
SELECT
event_time,
SUM(revenue) OVER (
ORDER BY UNIX_TIMESTAMP(event_time)
RANGE BETWEEN 6 * 86400 PRECEDING AND CURRENT ROW
) AS rolling_7d_sum
FROM sales;
-
UNIX_TIMESTAMP()把时间转成秒数,再用6 * 86400表示6天前(含当天共7天) - 这种写法对时区敏感:如果
event_time是TIMESTAMP类型,UNIX_TIMESTAMP()返回UTC秒数;如果是DATETIME,则按会话时区转换 - 更稳妥的做法是先用
DATE(event_time)截断到日,再用ROW_NUMBER()+ 自连接模拟,虽然慢但逻辑清晰
为什么GROUP BY + 子查询容易出错?
有人用自连接或相关子查询模拟滑动窗口,比如:
SELECT s1.event_time,
(SELECT SUM(s2.revenue)
FROM sales s2
WHERE s2.event_time >= s1.event_time - INTERVAL '7 DAYS'
AND s2.event_time <= s1.event_time) AS rolling_7d_sum
FROM sales s1;
- 这种写法在数据量稍大(比如百万级)时性能极差,MySQL/PG 都难优化
- 时间条件若没索引,会触发全表扫描 × N次
- 更隐蔽的问题:如果某天没数据,结果里就缺那天,而窗口函数默认保留空位(值为NULL),行为不一致
- 真要兼容老版本数据库,优先考虑物化中间表(比如每天跑一次汇总到
daily_rollup表),而不是硬扛实时计算
窗口函数的边界定义和数据类型匹配比语法本身更容易出问题,尤其跨数据库迁移时,RANGE 的语义差异常被忽略。

















