滚动窗口该用 ROWS 还是 RANGE,取决于你真正想统计的是“固定行数”还是“固定值域”:ROWS 严格按物理行号滑动,如“最近 3 笔订单”;RANGE 按排序列值范围匹配,如“过去 7 天所有订单”,重复值易致窗口膨胀。

直接说结论:滚动窗口该用 ROWS 还是 RANGE,取决于你真正想统计的是“固定行数”还是“固定值域”。选错会导致结果跳变、膨胀、甚至查不到数据。
ROWS BETWEEN 是怎么数行的?
ROWS 完全按物理顺序数行号,不看值是否重复,也不管时间是否连续。比如 ROWS BETWEEN 2 PRECEDING AND CURRENT ROW 就是“当前行 + 前面紧挨着的 2 行”,一共 3 行,不多不少。
- 常见错误现象:第 1 行累计和是 100,第 2 行是 180,第 3 行突然变成 250 —— 这不是 bug,是滑动窗口在逐行推进的真实表现
- ORDER BY 列必须稳定:如果用了
RANDOM()或NOW(),同一行在不同查询中可能被纳入/排除窗口 - 适用场景:最近 3 笔订单总金额、过去 5 天日活用户数之和(前提是每天只有一条记录)
- 性能友好:数据库只需游标偏移,有索引时基本是 O(1) 查找
RANGE BETWEEN 为什么常多算?
RANGE 不数行,而是以当前行的排序列值为锚点,向左右扩展一个逻辑值范围。比如 RANGE BETWEEN INTERVAL '7' DAY PRECEDING AND CURRENT ROW,会把所有 sale_date >= 当前行.sale_date - INTERVAL '7' DAY 的行全拉进来。
- 常见错误现象:某天有 50 笔交易,
RANGE窗口就把这 50 行全包了;而等效的ROWS BETWEEN 7 PRECEDING AND CURRENT ROW最多只取 8 行 - 仅支持可比较类型:DATE、INT、NUMERIC 可用;VARCHAR、BOOLEAN 不支持
INTERVAL形式 - 重复值是最大陷阱:按小时分桶的时间戳若大量重复,
RANGE会把整组同值行一次性全吞进去 - 必须带
ORDER BY:漏写会直接报错,如 PostgreSQL 提示ERROR: RANGE is only supported with ORDER BY
ORDER BY 有重复值时,ROWS 和 RANGE 的结果为啥差这么多?
核心就一句话:ROWS 把每行当独立个体,RANGE 把同值的所有行当一个逻辑单元。
- 示例数据(按
score升序):id=1,score=80;id=2..4,score=85;id=5,score=90 - 用
SUM(score) OVER (ORDER BY score ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW):结果是 80 → 165 → 250 → 335 → 425(严格累加) - 用
RANGE版本:第 2–4 行都看到“从最小值到 85”的全部行,结果全是 335(因为前三行score=85被视为同一逻辑点) - 业务含义差异极大:“截至当前分数的累计销量”适合
RANGE;“截至当前第 N 名学生的总分”必须用ROWS
生产环境上线前必须验证的三件事
别只看语法对不对,重点看数据行为是否符合预期。
- 先跑
SELECT COUNT(*) FROM table GROUP BY order_col,检查排序字段重复程度;重复率高就慎用RANGE - 用真实数据量执行
EXPLAIN ANALYZE,对比ROWS和RANGE的实际扫描行数与执行时间 - 确认排序字段是否有索引:无索引时
RANGE在大数据集上可能比等效ROWS慢 3–5 倍
真正难的不是写对语法,而是判断业务逻辑到底依赖“位置”还是“值域”——比如“过去 N 笔订单”是位置问题,用 ROWS;“过去 N 天内所有订单”是值域问题,用 RANGE 更安全。

















