MySQL 窗口函数 RANGE frame 怎样确定边界
来源:17golang原创
时间:2026-11-14 18:39:30 437浏览 收藏
窗口函数结果看起来“多算了一行”时,先别急着改聚合表达式。很多时候,真正改变结果的是 window frame:RANGE 按 ORDER BY 的值域找行,而不是按物理行号找行。当前排序值相同的记录会被当作 peers,一起进入当前 frame。
判断 MySQLRANGE边界可以记成一句话:先看当前行的ORDER BY值,再按值的前后范围取行;其中CURRENT ROW表示当前值对应的整组 peers。没有显式 frame 时,带ORDER BY的默认值等价于RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW。
官方文档:https://dev.mysql.com/doc/refman/8.4/en/window-functions-frames.html
ROWS按当前行前后多少行定位,RANGE按排序值的差值定位。RANGE ... CURRENT ROW会包含与当前行ORDER BY值相同的 peers,所以重复值可能让累计结果一次跳两行。- 数值范围使用数值偏移,时间范围使用
INTERVAL;带偏移的 RANGE 需要匹配数值或时间类型的排序表达式。 - 需要稳定、逐行推进的累计时显式写
ROWS;需要“同一业务值同一窗口”时显式写RANGE。
先用两个相同的 score 重现问题
准备一组按部门统计成绩金额的数据。刻意让 score=10 出现两次,这样才能看出“当前行”和“当前值对应的 peers”不是同一个概念。
CREATE TEMPORARY TABLE score_amount (
id INT PRIMARY KEY,
dept VARCHAR(20) NOT NULL,
score INT NOT NULL,
amount INT NOT NULL
);
INSERT INTO score_amount (id, dept, score, amount) VALUES
(1, 'A', 10, 5),
(2, 'A', 10, 7),
(3, 'A', 20, 8),
(4, 'A', 25, 4);
-- 用重复 score 观察 RANGE 和 ROWS 的 frame 成员差别
SELECT
id,
score,
amount,
SUM(amount) OVER (
PARTITION BY dept
ORDER BY score
RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW
) AS range_total,
SUM(amount) OVER (
PARTITION BY dept
ORDER BY score
ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW
) AS rows_total
FROM score_amount
ORDER BY score, id;
按这个数据理解结果,RANGE 的两个 score=10 行都会看到累计值 12,因为它们的当前排序值相同,彼此属于 peers。ROWS 则是一行一行向前扩展,前两行分别是 5 和 12;由于 ORDER BY score 没有为同分行指定唯一顺序,实际逐行顺序不应被当作业务排序依据。

图1:相同 ORDER BY 值在 RANGE 中成组进入 frame,在 ROWS 中按行位置逐步推进。
RANGE 的边界到底由什么决定
可以把 frame 拆成三层来读:PARTITION BY 决定候选数据集,ORDER BY 给当前行一个排序值,RANGE 再围绕这个值计算上下界。它不是在问“当前行前面有几行”,而是在问“哪些行的排序值落在这个值域内”。
| 写法 | 边界含义 | 适合回答的问题 |
|---|---|---|
ROWS ... CURRENT ROW | 当前物理行 | 到第几条记录为止的累计 |
RANGE ... CURRENT ROW | 当前值及其 peers | 到当前分数、价格或日期值为止的累计 |
RANGE 5 PRECEDING | 当前排序值减 5 到当前值 | 数值区间内的滚动统计 |
RANGE INTERVAL 1 DAY PRECEDING | 当前时间向前一天到当前时间 | 按时间值而不是按行数统计 |
例如当前 score=20,写成 RANGE BETWEEN 5 PRECEDING AND CURRENT ROW 时,候选值域是 15 到 20。只要某行的排序值等于 15、16、17、18、19 或 20,它就可能进入当前 frame;这里不要求它在结果集里紧挨着当前行。

图2:数值型 RANGE 用当前排序值和偏移量计算上下界,CURRENT ROW 覆盖同值 peers。
CURRENT ROW 为什么不是“这一行”
这是最容易误读的一处。对于 ROWS,CURRENT ROW 确实指当前物理行;对于 RANGE,它指当前行的 peers,也就是按窗口 ORDER BY 判断为同值的行。
因此下面两个 frame 的业务含义不同:
-- RANGE:同一个 score 的记录在同一价值边界内一起计算
SUM(amount) OVER (
PARTITION BY dept
ORDER BY score
RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW
) AS total_by_score,
-- ROWS:每向下移动一条物理记录,frame 才扩大一行
SUM(amount) OVER (
PARTITION BY dept
ORDER BY score
ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW
) AS total_by_row
如果报表要求“同分人员显示同一个累计金额”,使用 RANGE 更接近需求;如果要求“按明细行逐条累加”,就要使用 ROWS,并给 ORDER BY 补充能表达业务先后关系的列,例如唯一编号或明确的创建时间。
默认 frame 可能让结果悄悄变化
窗口函数没有写 frame 时,不能只凭“看起来像累计”来猜。带 ORDER BY 时,MySQL 的默认 frame 等价于:
-- 显式写出带 ORDER BY 时的默认 RANGE 语义 RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW
也就是说,给原查询新增 ORDER BY,可能同时把“整分区聚合”变成“从分区开头累计到当前值”。没有 ORDER BY 时,默认 frame 覆盖整个 partition。为了避免后续维护者误解,报表 SQL 最好显式写出 frame,不把关键业务含义交给默认值。
一个常见的改动是把总额查询改成带排序的窗口查询:
-- 明确要求每个部门都显示整个部门总额,不随 score 递增
SELECT
id,
dept,
score,
SUM(amount) OVER (
PARTITION BY dept
ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING
) AS dept_total
FROM score_amount;
这里故意不写窗口 ORDER BY,因为目标是整分区总额;如果还要按 score 排序,把排序放在查询最外层,不要让显示顺序和聚合 frame 混在一起。
数值范围和时间范围分别怎么写
带偏移量的 RANGE 需要能进行范围计算的排序表达式。数值排序使用非负数值偏移,时间排序使用 INTERVAL。例如下面的数值滚动窗口表示“当前 score 向前 10 分以内”。
-- 数值 RANGE:当前 score 及其之前 10 分内的金额
SELECT
id,
score,
SUM(amount) OVER (
PARTITION BY dept
ORDER BY score
RANGE BETWEEN 10 PRECEDING AND CURRENT ROW
) AS amount_in_score_band
FROM score_amount
ORDER BY score, id;
时间字段的写法则把偏移量写成时间间隔。这里的“前一天”是时间值域,不是结果中前一行;某个时间段即使有很多条记录,也会一起进入 frame。
-- 时间 RANGE:当前时间向前一天的记录都纳入统计
SELECT
event_time,
amount,
SUM(amount) OVER (
PARTITION BY account_id
ORDER BY event_time
RANGE BETWEEN INTERVAL 1 DAY PRECEDING AND CURRENT ROW
) AS rolling_amount
FROM account_events
ORDER BY event_time;
哪些窗口函数真的受 frame 影响
聚合类窗口函数以及 FIRST_VALUE()、LAST_VALUE()、NTH_VALUE() 会读取当前 frame,所以 RANGE 和 ROWS 的选择会直接改变结果。排名和偏移类函数的语义不同,不能因为它们写在 OVER (... frame ...) 里,就认为 frame 一定会限制它们。
| 函数类别 | 排查重点 | 常见误区 |
|---|---|---|
SUM/AVG/MIN/MAX | 当前 frame 包含哪些行 | 把 RANGE 当成前 N 行 |
FIRST_VALUE/LAST_VALUE | frame 末端是否到当前值 | 以为 LAST_VALUE 总能取到整个分区最后一行 |
ROW_NUMBER/RANK/DENSE_RANK | 分区和排序规则 | 用 frame 试图限制排名范围 |
LAG/LEAD | 偏移位置与默认值 | 把偏移行当作 RANGE 值域 |
特别是 LAST_VALUE():默认 frame 只到当前排序值,所以它经常返回当前 frame 的最后值,而不是整个 partition 的最后值。若需求真的是分区末端值,需要显式把 frame 延伸到 UNBOUNDED FOLLOWING。
生产查询上线前的四项检查
- 先问业务单位。需求说的是“前 10 行”,还是“前 10 分钟/前 10 分/同一个分数”?前者通常对应
ROWS,后者通常对应RANGE。 - 列出重复值。检查
ORDER BY列是否会重复,尤其是日期、金额、状态分数。重复值会让 RANGE 一次包含多行。 - 确认类型。数值偏移配数值排序,时间间隔配时间排序;不要把字符串格式的日期直接当作时间范围依据。
- 把 frame 写完整。不要依赖默认 frame;同时确认
CURRENT ROW在当前 frame 单位下是物理行还是 peers。
如果结果出现“同值行相同、下一组突然跳高”,这往往不是重复计算,而是 RANGE 的预期行为。把同一条表达式改写为 ROWS 做对照,通常几分钟就能定位 frame 是否选错。
常见问题
RANGE 和 ROWS 可以互换吗?
不建议。ROWS 面向行位置,RANGE 面向排序值;重复排序值、缺失日期和不规则数据间隔都会让两者产生不同结果。
RANGE CURRENT ROW 会只包含当前 id 吗?
不会。它会包含与当前窗口 ORDER BY 值相同的 peers。若想只推进一条物理记录,使用 ROWS,并提供清晰的唯一排序顺序。
为什么给窗口函数加 ORDER BY 后总额变了?
因为默认 frame 发生了变化:带 ORDER BY 时默认是从分区开始到当前值的 RANGE 累计;不带 ORDER BY 时默认覆盖整个分区。
RANGE 里的 5 PRECEDING 是前五行吗?
不是。它表示当前排序值减去 5 的值域下界;前五行应写 ROWS 语义,并且还要明确行的排序规则。
应该总是显式写 frame 吗?
对于报表、账务累计和滚动指标,建议显式写。这样后续新增排序列或调整查询结构时,frame 的业务含义不会依赖默认规则。
小结
读 MySQL 窗口函数的 RANGE frame,顺序是“先看 partition,再看 ORDER BY 当前值,最后按值域解释边界”。CURRENT ROW 在 RANGE 中代表当前值对应的 peers,这正是重复排序值导致累计结果一起跳变的原因。把“按行”还是“按值”先说清,再显式选择 ROWS 或 RANGE,窗口结果就会变得可解释、可维护。
-
465 收藏
-
Golang · Go教程 | 1个月前 | Go教程 · database/sql · Go database/sql 动态查询 sql.Rows.ColumnTypes ColumnType DatabaseTypeName ScanType122 收藏
-
Golang · Go问答 | 1个月前 | database/sql · Go database/sql Rows Go Rows Scan字段顺序 Go SQL查询列顺序 Go rows.Columns排查 Go rows.Err错误处理359 收藏
-
324 收藏
-
346 收藏
-
- 前端进阶之JavaScript设计模式
- 设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
- 立即学习 543次学习
-
- GO语言核心编程课程
- 本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
- 立即学习 516次学习
-
- 简单聊聊mysql8与网络通信
- 如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
- 立即学习 500次学习
-
- JavaScript正则表达式基础与实战
- 在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
- 立即学习 487次学习
-
- 从零制作响应式网站—Grid布局
- 本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
- 立即学习 485次学习