贝利信息

SQL 窗口函数 ROWS 与 RANGE 的选择

日期:2026-01-24 00:00 / 作者:舞夢輝影
ROWS按物理行数切片,RANGE按排序键值范围切片;前者严格计数,后者受重复值影响大,且必须有ORDER BY和可比较类型字段。

ROWS 和 RANGE 的语义差异必须看懂

窗口函数里 ROWSRANGE 看似只差一个字,实际行为完全不同:前者按物理行数切片,后者按排序键值范围切片。比如对时间序列用 RANGE BETWEEN INTERVAL '7 days' PRECEDING AND CURRENT ROW,它会把所有落在最近 7 天内的记录都拉进来,哪怕中间有空缺日期;而 ROWS BETWEEN 7 PRECEDING AND CURRENT ROW 只取紧挨着的 7 行,不管这些行对应的时间跨度有多大。

ORDER BY 缺失时 RANGE 会报错

RANGE 要求必须有 ORDER BY 子句,且排序字段必须是可比较的数值或日期类型。如果漏写 ORDER BY,PostgreSQL 报 ERROR: RANGE is only supported with ORDER BY,SQL Server 直接不支持 RANGE(只认 ROWS)。MySQL 8.0+ 支持 RANGE,但若 ORDER BY 字段是字符串或布尔型,也会拒绝执行。

重复值场景下 RANGE 容易“吞掉”多行

ORDER BY 字段存在大量重复值(比如状态码、分类 ID),RANGE 会把所有相同值的行视为同一“点”,导致窗口实际包含远超预期的行数。例如按 status 排序后写 RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW,只要前面有 100 行 status = 1,当前这行就会带上全部 100 行——而 ROWS 只会严格按行号计数。

性能差异取决于索引和数据分布

ROWS 窗口基本靠顺序扫描+游标偏移,只要

ORDER BY 字段有索引,性能很稳;RANGE 需要对每个窗口做值区间查找,重复值越多、范围越宽,CPU 和临时内存开销越大。PostgreSQL 中 RANGE 窗口在大数据集上可能比等效 ROWS 慢 3–5 倍,尤其当 ORDER BY 字段无索引时。

真正难的不是语法,而是判断当前业务逻辑到底依赖“位置”还是“值域”——比如“过去 N 笔订单”是位置问题,用 ROWS;“过去 N 天内所有订单”是值域问题,用 RANGE 更安全。但一旦排序字段有大量重复,RANGE 就可能悄悄改变语义,这点容易被忽略。