正确写法是 rows between 3 preceding and 3 following,表示以当前行为中心、前后各3行共7行的滑动窗口;需搭配确定性 order by(如 order by ts, id),且窗口大小动态收缩、不跨分区、不补空。

ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING 是错的,别抄
想让窗口包含“前3行 + 当前行 + 后3行”,共7行,不能写成 ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING(那是前后各1行,共3行)。必须明确写出数字:用 ROWS BETWEEN 3 PRECEDING AND 3 FOLLOWING。
这个表达式严格按 ORDER BY 定义的物理行序数行——第1行前面没有3行,那窗口就只含它自己和后面3行;末尾几行同理。SQL Server 不会补空或报错,窗口大小是动态收缩的。
- 必须搭配确定性
ORDER BY,比如ORDER BY order_time, id;只写ORDER BY order_time且该字段有重复值时,行序不稳定,同一查询多次执行可能返回不同结果 -
RANGE BETWEEN 3 PRECEDING AND 3 FOLLOWING是无效写法 ——RANGE不接受数字偏移,只接受UNBOUNDED或与排序列值比较(如RANGE BETWEEN INTERVAL '3' DAY PRECEDING AND CURRENT ROW),在 SQL Server 中直接报错 - 如果分区(
PARTITION BY)后某组不足7行,窗口自动截断,不会跨分区拉数据
为什么不能用 LIMIT 或 TOP 实现滑动窗口
LIMIT 和 TOP 是对**整个结果集**做行数裁剪,不是窗口函数里的局部范围控制。它们作用在最终输出层,无法为每一行动态计算一个“含前后3行”的聚合值。
例如你写 SELECT TOP 100 * FROM sales ORDER BY ts,只是取前100条记录;但你要的是每条记录都带一个“它前后3条的平均销量”,这只能靠 AVG() OVER (…) 这类窗口函数完成。
-
TOP可以用在子查询里模拟“取最近N条”,但无法支撑逐行滑动逻辑 -
OFFSET-FETCH用于分页,和窗口范围完全无关,混用会导致语法错误或语义错乱 - 试图用 CTE +
ROW_NUMBER()手动关联前后行,代码爆炸且性能差,远不如原生ROWS BETWEEN
实际写法示例:7行中心滑动平均
假设表 sensor_data 有字段 ts(时间戳)、value(数值),要为每条记录计算以它为中心、前后各3行的平均值:
SELECT
ts,
value,
AVG(value) OVER (
ORDER BY ts, id
ROWS BETWEEN 3 PRECEDING AND 3 FOLLOWING
) AS avg_7day
FROM sensor_data;
注意这里用了 ts, id 联合排序:即使 ts 相同,id 能保证行序唯一。如果只用 ts,而某秒内插入多条数据,SQL Server 可能任意打乱这些行的物理顺序,导致同一 ts 下不同执行得到不同窗口成员。
- 窗口函数不改变原始行数,输出行数 = 输入行数
- 聚合结果为
NULL仅当窗口内所有value都为NULL;单个NULL会被AVG忽略(按非空值计数) - 若需强制7行满窗才计算,得额外加条件过滤:
HAVING COUNT(value) = 7不适用(窗口函数不能进HAVING),应改用CASE WHEN COUNT(*) OVER (...) = 7 THEN ... END
容易被忽略的性能点:排序字段的选择直接影响执行计划
窗口函数的 ORDER BY 列如果有对应索引,SQL Server 很可能复用索引顺序避免额外排序;但如果排序字段是表达式(如 DATEADD(day, 1, ts))或未建索引,就会触发 Sort 算子,百万级数据上可能吃掉80%以上执行时间。
- 优先选高选择性、已建索引的列,比如主键
id或带索引的created_at - 避免在
ORDER BY里用函数或计算列,除非你确认该列上有计算列索引 - 分区(
PARTITION BY)能显著降低单个窗口的扫描量,但要注意分区键分布是否倾斜——比如按user_id分区,而某个大V用户占了50%数据,那他的窗口计算仍会很慢











