ROWS BETWEEN 2 PRECEDING AND CURRENT ROW 表示按 ORDER BY 定义的逻辑顺序取当前行及前两行共3行计算,必须显式指定 ORDER BY(如 order_time, order_id),否则结果不可预测;它依据业务时间而非物理插入顺序,且首两行窗口不足3行属正常现象。

ORDER BY 必须存在,否则 ROWS BETWEEN 无效
窗口函数里用 ROWS BETWEEN 2 PRECEDING AND CURRENT ROW 计算最近3笔,前提是必须明确排序逻辑。没写 ORDER BY 的 OVER() 子句,结果不可预测,很多情况下会返回全表平均而非“最近”。数据库不保证默认顺序,尤其在有并发写入或索引变化时。
实操建议:
- 排序字段优先选业务时间戳,比如
order_time;避免用自增ID,它不一定反映下单先后 - 如果时间有重复,追加唯一字段消歧义,例如
ORDER BY order_time, order_id - MySQL 8.0+、PostgreSQL、SQL Server 都支持该语法;SQLite 3.25+ 支持,但旧版不认
ROWS框架
“最近3笔”指逻辑顺序,不是物理插入顺序
用户常误以为“最新插入的3行”就是最近订单,但实际业务中,订单可能延迟写入、补录或跨时区提交。ROWS BETWEEN 2 PRECEDING AND CURRENT ROW 是按 ORDER BY 定义的行序往前数3行,不是按表里存储位置。
常见错误现象:
- 查出的“最近3笔”金额明显偏高/偏低,其实是按
order_id排序,而ID被跳号或重用 - 同一秒内多笔订单,没加二级排序,导致每次执行结果不一致
- 用了
RANGE BETWEEN却期望行数控制——RANGE按值范围滑动,可能包含0行或5行,无法保证固定3笔
示例:带去重和空值防护的写法
假设表叫 orders,关键字段为 order_id、amount、order_time:
SELECT
order_id,
amount,
AVG(amount) OVER (
ORDER BY order_time, order_id
ROWS BETWEEN 2 PRECEDING AND CURRENT ROW
) AS avg_recent_3
FROM orders
WHERE amount IS NOT NULL;
说明:
-
WHERE amount IS NOT NULL提前过滤,避免AVG()被空值拉低(AVG自动忽略 NULL,但若整段窗口都是 NULL,结果为 NULL) - 排序含
order_id是为防order_time重复时窗口边界漂移 - 第一行只有1笔可算,第二行有2笔,第三行起才稳定为3笔——这是正常行为,不用额外补0
性能注意:大表上慎用未分区的 ORDER BY
如果 orders 有千万级数据且没在 order_time 上建索引,ORDER BY order_time 会触发全表排序,查询变慢甚至超时。
优化路径:
- 确保
order_time字段有 B-tree 索引(升序即可,无需覆盖索引) - 若只查最近N天数据,先用
WHERE order_time >= ...缩小数据集再开窗 - ClickHouse 或 BigQuery 等列存引擎对窗口函数优化较好,但传统 MySQL 在 500 万行以上就需压测验证
真正容易被忽略的是排序键和业务语义的咬合——时间字段是否被业务系统正确赋值、是否统一时区、有没有手动修改痕迹,这些比语法本身更影响结果可信度。

















