窗口函数中的帧子句(ROWS/RANGE)是实现滑动窗口分析的核心语法,本文将深入解析其使用场景与选择策略,帮助开发者掌握这一高阶SQL技巧。

先解释两个核心术语
什么是“滑动窗口”?
数据队列中的固定宽度窗口会随着当前行移动,每次只统计窗口范围内的数据。例如计算3日移动平均时,窗口会从第1-3天滑动到第2-4天。这种动态范围的计算方式正是滑动窗口的本质特征。
什么是“帧子句”?
帧子句作为定义窗口范围的关键语法,明确指定了窗口的起始和结束位置。在函数() OVER (PARTITION BY ... ORDER BY ... 帧子句)结构中,帧子句决定了窗口函数是进行简单分组还是实现复杂滑动分析。
完整语法结构如下:
ROWS | RANGE BETWEEN 起点 AND 终点
起止点支持五种定义方式:
UNBOUNDED PRECEDING:分区首行n PRECEDING:前n行CURRENT ROW:当前行n FOLLOWING:后n行UNBOUNDED FOLLOWING:分区末行需特别注意:当存在ORDER BY时默认使用RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW,否则默认ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING,这个差异常导致计算结果与预期不符。
两者的本质差异在于窗口划分依据:
通过sales表示例可清晰展示差异:
| sale_date | amount |
|---|---|
| 2026-01-01 | 100 |
| 2026-01-01 | 50 |
| 2026-01-02 | 200 |
| 2026-01-03 | 150 |
执行以下查询:
SELECT sale_date, amount, SUM(amount) OVER (ORDER BY sale_date ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) as rows_cum, SUM(amount) OVER (ORDER BY sale_date RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) as range_cumFROM sales;
结果对比:
| sale_date | amount | rows_cum | range_cum |
|---|---|---|---|
| 2026-01-01 | 100 | 100 | 150 |
| 2026-01-01 | 50 | 150 | 150 |
| 2026-01-02 | 200 | 350 | 350 |
| 2026-01-03 | 150 | 500 | 500 |
业务场景选择建议:
场景1:3日移动平均
计算包含当前行及前后各1天的平均值:
SELECT sale_date, amount, AVG(amount) OVER (ORDER BY sale_date ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING) as moving_avg_3FROM sales;
场景2:从当前行到分区末尾的累计
计算部门内从当前员工到最高工资者的总和:
SELECT dept, salary, SUM(salary) OVER (PARTITION BY dept ORDER BY salary ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING) as sum_from_currFROM emp;
场景3:排除当前行的滑动窗口
PostgreSQL等数据库支持以下语法:
SELECT sale_date, amount, AVG(amount) OVER (ORDER BY sale_date ROWS BETWEEN 2 PRECEDING AND 2 FOLLOWING EXCLUDE CURRENT ROW) as moving_avg_exclude_selfFROM sales;
| 需求场景 | 推荐帧类型 | 原因 |
|---|---|---|
| 时间序列移动平均(按行严格计算) | ROWS | 不关心时间间隔是否连续,只关心行数 |
| 按日期分组统计(同一天数据一起算) | RANGE | 相同ORDER BY值应属于同一个窗口 |
| 财务累计(按交易顺序) | ROWS | 每笔交易独立,严格逐行累加 |
| 滚动窗口(最近7天,不关心行数) | RANGE | 基于日期的范围,可能某天有多行或没有行 |
计算月度环比增长率:
SELECT year, month, amount, LAG(amount, 1) OVER (ORDER BY year, month) as prev_amount, (amount - LAG(amount, 1) OVER (ORDER BY year, month)) / LAG(amount, 1) OVER (ORDER BY year, month) as growth_rateFROM monthly_sales;
掌握帧子句的ROWS与RANGE选择策略,能显著提升SQL处理复杂分析任务的能力,避免不必要的自连接操作,使查询既简洁又高效。