range窗口帧按排序列的值范围(如时间或数值区间)定义窗口,将同值行视为逻辑单元;rows则按物理行号严格计数,逐行滑动。本质区别在于range是值域驱动、rows是行号驱动。

什么是 RANGE 窗口帧,和 ROWS 有什么本质区别
RANGE 是按值范围(value-based)划分窗口边界,不是按行数。它只对 ORDER BY 列生效,且该列必须是可排序的数值或时间类型。比如 ORDER BY order_time 时,RANGE BETWEEN INTERVAL '7 days' PRECEDING AND CURRENT ROW 表示“把当前行时间往前推 7 天内所有行都纳入窗口”,哪怕中间缺数据、有重复时间点,RANGE 也会跨行匹配时间值;而 ROWS 只数物理行数,不管时间是否连续。
常见错误现象:ORDER BY created_at 后用 RANGE BETWEEN INTERVAL '1 hour' PRECEDING AND CURRENT ROW 却返回空结果——因为 created_at 是 TIMESTAMP 类型,但某些数据库(如 PostgreSQL)要求显式 cast 成 INTERVAL 可运算类型,MySQL 8.0+ 则直接支持,SQLite 不支持 INTERVAL 在窗口帧中使用。
- PostgreSQL 必须写成
RANGE BETWEEN INTERVAL '7 days' PRECEDING AND CURRENT ROW,且ORDER BY列需为TIMESTAMP或DATE - MySQL 8.0+ 支持相同语法,但不支持
INTERVAL和非时间列混用(比如对INT列用INTERVAL 100会报错) - SQL Server 完全不支持
INTERVAL,只能用RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW这类静态写法,动态时间窗口得靠子查询或 CTE 模拟
如何在 PostgreSQL 中正确写出 30 天滚动销售额统计
关键不是套语法,而是确认三件事:时间列类型、时区是否统一、是否需要去重聚合。假设表叫 orders,时间字段是 paid_at::TIMESTAMP WITH TIME ZONE,金额是 amount:
SELECT
paid_at,
SUM(amount) OVER (
ORDER BY paid_at
RANGE BETWEEN INTERVAL '30 days' PRECEDING AND CURRENT ROW
) AS rolling_30d_sales
FROM orders
WHERE paid_at IS NOT NULL;
注意点:
- 如果
paid_at有重复值(比如同一秒多笔订单),RANGE会把它们全算进当前窗口,可能造成单秒内突增——这不是 bug,是 RANGE 的设计逻辑 - 若想排除未来时间或测试数据,务必在
WHERE子句里过滤,窗口函数不感知WHERE条件外的数据 - 时区混乱时,
paid_at AT TIME ZONE 'UTC'再ORDER BY更稳妥,避免本地时区夏令时跳变影响窗口跨度
MySQL 8.0 实现动态小时级滑动窗口的限制与绕法
MySQL 支持 RANGE + INTERVAL,但仅限于 DATETIME / TIMESTAMP 列,且不能用变量控制间隔长度(比如不能写 INTERVAL @window_hours HOUR)。所以“用户可配置的 N 小时窗口”必须用预处理语句或应用层拼 SQL。
典型安全写法:
SET @hours = 24;
SET @sql = CONCAT(
'SELECT event_time, COUNT(*) OVER (',
' ORDER BY event_time ',
' RANGE BETWEEN INTERVAL ', @hours, ' HOUR PRECEDING AND CURRENT ROW',
') AS cnt FROM events'
);
PREPARE stmt FROM @sql;
EXECUTE stmt;
容易踩的坑:
- 直接在窗口定义里用
INTERVAL ? HOUR会报错,MySQL 不支持参数化INTERVAL表达式 -
event_time如果是INT存秒级时间戳,必须先转成FROM_UNIXTIME(event_time)才能参与RANGE计算,否则语法不通过 - 没有索引的
ORDER BY列会导致全表排序,大表慎用;建议在event_time上建 B-tree 索引
为什么有些场景必须用 RANGE 而不能用 ROWS
当业务语义依赖“真实时间跨度”而非“记录条数”时,ROWS 就会失效。例如监控系统每 5 分钟上报一次指标,但某次网络故障导致 3 小时没数据——用 ROWS BETWEEN 36 PRECEDING AND CURRENT ROW(即 36×5=180 分钟)会漏掉故障前最后一条正常数据,而 RANGE BETWEEN INTERVAL '3 hours' PRECEDING AND CURRENT ROW 仍能抓到故障前最近的有效点。
另一个典型是金融 K 线聚合:日线要求“过去 24 小时内所有 tick”,不是“最近 1000 笔交易”。这时 RANGE 是唯一符合业务逻辑的选择。
但要注意:RANGE 性能通常比 ROWS 差,尤其在高基数时间列上,数据库要反复做范围查找而不是顺序扫描。如果数据按时间严格递增且无重复,用 ROWS 配合应用层补零更可控。










