贝利信息

SQL 使用窗口函数计算滑动窗口统计

日期:2026-01-26 00:00 / 作者:冰川箭仙
滑动窗口必须显式定义ROWS或RANGE子句;仅ORDER BY默认为RANGE UNBOUNDED PRECEDING TO CURRENT ROW(累积统计),非滑动;需用ROWS BETWEEN n PRECEDING AND CURRENT ROW实现行数滑动;RANGE按值滑动易因重复值扩大窗口;NULL和排序键重复会导致行为异常,应添加唯一字段或ROW_NUMBER()兜底;多窗口函数不共享排序,建议复用WINDOW子句或裁剪数据;时间范围滑动需用自连接等替代方案。

滑动窗口统计必须显式定义 ROWSRANGE 子句

不写 ORDER BY 就用不了滑动窗口,写了 ORDER BY 却没写 ROWS/RANGE,默认是 RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW —— 这不是你想要的“滑动”,而是累积统计。比如想算最近 3 行的平均值,AVG(sales) OVER (ORDER BY date) 实际算的是从第一行到当前行的均值。

正确写法必须明确行数范围:

NULL 值和排序键

重复会导致窗口行为异常

ORDER BY 字段存在 NULL,不同数据库处理方式不同:PostgreSQL 默认把 NULL 排在最前,MySQL 8.0+ 默认排最后,SQL Server 需显式写 NULLS LAST 才可控。一旦排序不可靠,ROWS 的“前 N 行”就失去意义。

更隐蔽的问题是排序键重复:比如按 date 排序,但多条记录同一天。此时 ROWS BETWEEN 1 PRECEDING AND CURRENT ROW 可能跳过部分同天记录(因物理顺序不确定),或把所有同天记录都拉进窗口(RANGE 下)。解决方法只有两个:

性能敏感场景下避免在大结果集上套多层窗口函数

每个窗口函数都会触发一次独立的排序扫描。如果写成 SELECT AVG(x) OVER w, SUM(y) OVER w, COUNT(*) OVER w FROM t WINDOW w AS (ORDER BY ts ROWS BETWEEN 2 PRECEDING AND CURRENT ROW),多数引擎仍会为每个函数重跑一遍窗口逻辑,而非复用一次排序结果。

实际优化建议:

时间范围滑动不能只靠 ORDER BY date,得用 INTERVAL 或自连接模拟

真正按“过去 7 天”而非“过去 7 行”统计时,标准窗口函数无能为力——ROWS 是行数,RANGE 在时间字段上仅支持 UNBOUNDEDCURRENT ROW,不支持 RANGE BETWEEN INTERVAL '7 days' PRECEDING AND CURRENT ROW(除 BigQuery 外,主流 SQL 引擎都不支持该语法)。

可行替代方案:

窗口函数写起来简洁,但滑动逻辑是否真按预期执行,得盯住排序稳定性、NULL 处理、行 vs 值语义,还有数据库版本对 RANGE 的实际支持程度。这些地方一松懈,结果就 quietly 错了。