必须显式声明rows between,否则默认range between unbounded preceding and current row;需按行数滑动时须完整写出rows子句,且务必配合partition by和带唯一键的order by,避免数据混算与排序不稳定。

ROWS BETWEEN必须显式声明,否则默认是RANGE
不写ROWS BETWEEN时,数据库(PostgreSQL/MySQL 8.0+/SQL Server)会自动补上RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW,不是你想要的“固定行数滑动”。哪怕只写ORDER BY ts,也触发RANGE逻辑——尤其当ts有重复值,性能可能断崖下跌且无提示。
正确做法是:只要需要按**行数**定义窗口,就必须完整写出ROWS BETWEEN ... AND ...。例如算最近3期(含当前)移动平均,得写ROWS BETWEEN 2 PRECEDING AND CURRENT ROW,而不是依赖隐式行为。
-
CURRENT ROW一定包含当前行,别误以为“PRECEDING”就截止到前一行 - 开头几行帧不足(如第一行前面没有2行),窗口自动收缩,这是正常行为,不是bug
- 想强制等长窗口?得配合
COUNT(*) OVER (...)判断实际行数,再用CASE控制输出
ROWS比RANGE快,但不能乱换
ROWS靠行号跳转,O(1)随机访问;RANGE要逐行比对值边界,最坏O(n²),尤其在ORDER BY列大量重复时(比如日志表的date字段)。执行计划里看到Range字样或双阶段Sort + Window,基本就是RANGE在拖慢查询。
但别见RANGE就删——有些业务逻辑只能靠RANGE实现:
- “过去7天销售额”必须用
RANGE BETWEEN INTERVAL '7 days' PRECEDING AND CURRENT ROW,ROWS没法对应时间跨度 - “价格±50元内竞品均价”也是
RANGE专属场景 - 强行用
ROWS替代,结果会错:高峰期多单挤进同一天,ROWS会漏掉;低峰期又拉进无关数据
PARTITION BY漏写,结果全错
没写PARTITION BY user_id,AVG(amount) OVER (ORDER BY create_time ROWS BETWEEN 2 PRECEDING AND CURRENT ROW)会把所有用户的订单混在一起滑动计算,数值完全失真。窗口函数先切组、再组内滑动,缺PARTITION BY等于放弃分组语义。
实操要点:
- 分组字段必须写全,比如业务上按
user_id和product_type共同分组,少一个就合并了不该合并的组 -
ORDER BY必须带唯一键兜底:ORDER BY create_time, id,避免同时间戳导致行序不确定、结果不可复现 - SQLite需3.25+才支持
ROWS,旧版本直接报错,不是语法写错
窗口内NULL值和行数不足导致结果为NULL
STDDEV_SAMP()要求窗口内至少2个非NULL值,否则返回NULL(分母n−1无定义);AVG()虽跳过NULL,但整帧为空时也返回NULL。这不是bug,是标准行为。
排查重点:
- 检查
ORDER BY列是否含NULL——会导致排序不稳定,同一查询多次运行结果不同 - 确认窗口帧内是否有足够有效数据:用
COUNT(col) OVER (...)看非NULL计数,别只看ROW_NUMBER() - 别用
STDDEV_POP()凑数:它分母为n,单行也返回0,但不符合“样本波动”的统计本意











