ROWS BETWEEN必须配合ORDER BY使用,否则报错或结果不可预测;因SQL标准要求窗口帧依赖确定性排序,数据库无法识别“前一行”;ORDER BY字段需为时间戳或自增ID等确定性列。

ROWS BETWEEN 语法必须配合 ORDER BY 才生效
直接在窗口函数里写 ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING 却没加 ORDER BY,结果会报错或返回不可预测的行序——因为 SQL 标准要求窗口帧(frame)依赖明确的排序逻辑。数据库无法凭空知道“前一行”是谁。
实操建议:
- 窗口定义中
ORDER BY字段必须是确定性排序依据,比如时间戳、自增 ID;用name这类可能重复的字段会导致相同值之间的相对顺序不固定 - 若原始数据无天然顺序列,需先生成序号(如
ROW_NUMBER() OVER (ORDER BY some_col)),再基于该序号做帧计算 - PostgreSQL 和 SQL Server 支持
ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW这类累积形式,但 MySQL 8.0+ 才完整支持ROWS模式
移动平均 = AVG() OVER (...) + ROWS BETWEEN 组合
核心就是把 AVG() 套进带帧的窗口函数里,ROWS BETWEEN 定义参与平均的行范围。例如计算当前行及前后各一行的 3 日移动平均:
SELECT
date,
value,
AVG(value) OVER (
ORDER BY date
ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING
) AS moving_avg_3
FROM sales;
注意点:
- 首尾行会自动截断:第一行只有自己和下一行(2 行参与平均),不是硬补 NULL 或报错
- 如果想强制保持 3 行窗口(不足则返回 NULL),得加
ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING后再配合CASE WHEN COUNT(*) OVER (...) = 3 THEN ... ELSE NULL END - 别误用
RANGE——它按值范围而非行数切片,对日期或数值列容易出意外(比如同一天多条记录会被全算进来)
不同数据库对 ROWS BETWEEN 的兼容性差异
MySQL 5.7 不支持窗口函数,8.0.2 起才支持完整 ROWS 语法;SQLite 3.25+ 支持但不支持 FOLLOWING 关键字(只能用 UNBOUNDED FOLLOWING);Oracle 默认用 RANGE,显式写 ROWS 才按行数算。
实操建议:
- 检查版本:
SELECT VERSION();(MySQL)、SELECT sqlite_version();(SQLite) - 在 PostgreSQL 中可放心用
ROWS BETWEEN 2 PRECEDING AND CURRENT ROW计算 3 期累计均值 - SQL Server 2012+ 支持,但要注意
ORDER BY子句不能含ASC/DESC以外的修饰(比如NULLS LAST不被识别)
性能陷阱:ORDER BY 列没索引时窗口计算极慢
当表有百万行,而 ORDER BY date 对应的 date 列没建索引,数据库每次执行都要全表排序,ROWS BETWEEN 窗口再快也救不回来。
必须做的优化:
- 为
ORDER BY字段建索引,尤其是组合场景:CREATE INDEX idx_sales_date ON sales(date); - 避免在
ORDER BY中用函数,如ORDER BY DATE(created_at)会让索引失效 - 大宽表慎用多个并行窗口函数(如同时算 5 个不同跨度的移动平均),每个多开一个排序通道,I/O 和内存压力陡增
真正卡住的往往不是 ROWS BETWEEN 本身,而是背后那场没索引支撑的排序。

















