SQL 窗口函数 ROWS 与 RANGE 的选择

13次阅读

ROWS按物理行数切片,RANGE按排序键值范围切片;前者严格计数,后者受重复值影响大,且必须有ORDER BY和可比较类型字段。

SQL 窗口函数 ROWS 与 RANGE 的选择

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 BYpostgresqlError: RANGE is only supported with ORDER BY,SQL Server 直接不支持 RANGE(只认 ROWS)。mysql 8.0+ 支持 RANGE,但若 ORDER BY 字段是字符串布尔型,也会拒绝执行。

  • 数值类字段(INTDECIMALtimestamp)可用 RANGE
  • 字符串字段(VARCHAR)在多数引擎中不支持 RANGE 边界计算
  • ORDER BY 就别想用 RANGE,连语法检查都过不去

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

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

  • 业务上需要“截至当前值”的聚合(如累计销量按价格分段),RANGE 更自然
  • 需要稳定可控的窗口大小(如移动平均固定看前 5 天销售),优先选 ROWS + 显式日期补全
  • RANGE 前先 select count(*) GROUP BY order_col 检查重复程度

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

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

  • 高频查询且排序字段已建索引 → ROWSRANGE 差距不大
  • 窗口范围动态(如 INTERVAL '30 days')且数据稀疏 → RANGE 实际扫描行数可能远少于 ROWS
  • 生产环境上线前务必用真实数据量 EXPLaiN ANALYZE 对比两者执行计划

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

text=ZqhQzanResources