SQL中的窗口函数进阶:滑动窗口与帧子句详解

本文涉及的产品
RDS Agent(兼容OpenClaw),2核4GB
RDS MySQL DuckDB 分析主实例,集群系列 4核8GB
PolarDB Agent Flow,2核4GB
简介: 窗口函数是SQL进阶的核心技能,但很多人在使用ROW_NUMBER()、RANK()后就止步了。本文深入讲解窗口函数的帧子句(ROWS/RANGE),实现滑动窗口聚合、移动平均、累计求和等复杂计算。通过真实案例对比ROWS与RANGE的区别,以及使用UNBOUNDED、CURRENT ROW、FOLLOWING的精确定义,帮助DBA和开发人员真正驾驭窗口函数。

大家好,我是小耶,写功课只是为了我踩过的坑,你们别再踩了!

上周讲了窗口函数与子查询、CTE的性能对比,有读者问:窗口函数的帧子句(ROWS/RANGE)到底怎么用?为什么有时候用ROWS有时候用RANGE?今天就把这个坑填上,专门讲讲窗口函数的进阶能力——滑动窗口与帧子句。

先解释两个核心术语

什么是“滑动窗口”?
想象你站在一列数据的长队里,眼前有一个固定宽度的“窗口”,这个窗口每次向右移动一格,每次只统计窗口内的数据。比如计算最近3天的移动平均:第一天看第1-3天,第二天看第2-4天,第三天看第3-5天……窗口在“滑动”。这就是滑动窗口的核心思想:​窗口位置随着当前行移动,每次计算一个范围内的数据​。

什么是“帧子句”?
帧子句就是用来定义这个“窗口范围”的规则。它告诉数据库:当前行的窗口应该从哪里开始、到哪里结束。比如“从当前行的前2行到当前行的后2行”“从分区第一行到当前行”。帧子句是窗口函数实现滑动窗口的关键语法。

窗口函数的核心语法是:函数() 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;无ORDER BY时默认ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING。这一点经常被误解,导致计算结果与预期不符。

二、ROWS vs RANGE 的核心区别

这是最容易踩的坑。用一个比喻帮助你理解:

  • ROWS​:像用“行号”画窗口。窗口按行数严格划分,不管ORDER BY列的值是否相同,每一行都独立计算。类似于“前5个人、后5个人”。
  • RANGE​:像用“值”画窗口。窗口按ORDER BY列的值划分,相同值的数据必须同时出现在窗口内或被排除在外。类似于“所有年龄相同的人放在一起统计”。

用一个具体例子说明。表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_cum
FROM 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
  • ROWS​:严格按行顺序累加,第一行100,第二行100+50=150,每行都变。
  • RANGE​:按sale_date的值分组。2026-01-01的两行属于同一个值,窗口把这两行作为一个整体累计,所以两行的累计值都是150(100+50),直到2026-01-02才增加到350。

实际业务中:

  • 需要​严格逐行计算​(如移动平均、每笔交易独立累计)→ 用ROWS
  • 需要​按逻辑分组聚合​(如按日期统计,同一天的数据应同时计入)→ 用RANGE

三、典型滑动窗口场景

场景1:3日移动平均​(滑动窗口经典案例)

计算每个日期前后各1天(包含当天)的平均销售额。这里的“窗口”就是当前行、前1行、后1行。随着当前行向下移动,窗口也跟着“滑动”。

SELECT sale_date, amount,
  AVG(amount) OVER (ORDER BY sale_date ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING) as moving_avg_3
FROM sales;

注意边界处理:第一行没有1 PRECEDING,窗口只包含当前行和1 FOLLOWING。这就是滑动窗口最常用的形式。

场景2:从当前行到分区末尾的累计

计算每个部门内,按工资从低到高排序,从当前员工到工资最高者的工资总和。

SELECT dept, salary,
  SUM(salary) OVER (PARTITION BY dept ORDER BY salary 
                    ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING) as sum_from_curr
FROM emp;

这里窗口的起点是“当前行”,终点是“分区末尾”,随着当前行下移,窗口越来越小。适合计算“比我工资高的人的总和”等需求。

场景3:排除当前行的滑动窗口

计算当前行之前2行到当前行之后2行,但排除当前行本身。例如分析整体趋势时去掉自身的波动。

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_self
FROM sales;

EXCLUDE CURRENT ROW是SQL标准支持但MySQL尚未实现的语法,PostgreSQL等数据库已支持。如果MySQL需要实现类似效果,可以自行计算总窗口值再减去当前值。

四、ROWS与RANGE在滑动窗口中的选择建议

需求场景 推荐帧类型 原因
时间序列移动平均(按行严格计算) ROWS 不关心时间间隔是否连续,只关心行数
按日期分组统计(同一天数据一起算) RANGE 相同ORDER BY值应属于同一个窗口
财务累计(按交易顺序) ROWS 每笔交易独立,严格逐行累加
滚动窗口(最近7天,不关心行数) RANGE 基于日期的范围,可能某天有多行或没有行

五、实际运用:计算同比环比

假设有每月销售表monthly_sales(year, month, amount)。计算环比(与上月比较):

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_rate
FROM monthly_sales;

LAG/LEAD函数配合帧子句可以更灵活地定义偏移量。计算同比(去年同期)则需要更复杂的窗口定义或自连接。

六、注意事项与性能建议

  • 帧子句只对​聚合窗口函数​(SUM、AVG、COUNT、MIN、MAX)有意义;排名函数(ROW_NUMBER、RANK等)和偏移函数(LAG、LEAD)忽略帧子句,始终基于整个分区。
  • RANGE模式要求ORDER BY列是数值或日期类型,且通常会产生比ROWS更多的内存消耗,因为需要识别“相同值”的组边界。
  • 超大窗口滑动时(如UNBOUNDED PRECEDING),相当于全分区扫描,性能开销大。可考虑使用索引和物化视图预计算。

七、总结

窗口函数的高级能力——帧子句,是实现复杂滑动分析的关键。区分ROWS与RANGE、正确设置边界,能写出更简洁高效的SQL,避免使用自连接或游标。掌握这些技巧,是SQL从“能写”到“会优化”的重要一步。

小耶在手,SQL 不愁

还有什么想了解的,欢迎留言!小耶一定知无不言言无不尽……我们下次见~

相关文章
|
1月前
|
关系型数据库 MySQL 测试技术
JOIN、IN、EXISTS谁最快?实测三种写法性能差异与执行计划深度剖析
本文用MySQL 8.0实测拆解`IN`/`EXISTS`/`JOIN`子查询性能:从执行计划、半连接优化、临时表开销等底层原理出发,结合10万+100万数据实测(`EXISTS`最快95ms),给出三条选型铁律——告别盲从“最佳实践”,只选最适配业务与数据的写法!
|
1月前
|
SQL 关系型数据库 MySQL
批量操作性能飙升:从30秒到1秒的三种实战方法
业务系统中经常需要批量导入或更新大量数据(如Excel上传、定时同步)。许多开发人员采用循环单条执行的方式,导致1万条数据耗时30秒以上,严重影响用户体验。本文从数据库IO、事务开销、锁竞争三个角度分析单条操作的性能瓶颈,并给出三种优化方案:批量INSERT、LOAD DATA文件导入、批量UPDATE用临时表。每种方案均附实测数据对比与适用场景说明,帮助读者在1万\~100万行级别批量操作中选择最优策略。
|
1月前
|
SQL 运维 关系型数据库
DBA必备技能:MySQL误删恢复完全指南(全量备份+binlog回放)
本文详解误删数据(如`DELETE FROM orders`)后的紧急恢复三步法:查Binlog→临时库回放→差异导回,并附4条血泪预防措施。不讲段子,只教能救命的操作!
|
2月前
|
SQL 数据库
多表关联查询入门:LEFT JOIN、INNER JOIN一文搞懂|转行学DB第6天
本文通俗易懂地讲解了数据库多表查询的三种JOIN操作:INNER JOIN(内连接)只返回两表匹配的数据,适用于查询交集数据;LEFT JOIN(左连接)保留左表所有记录并匹配右表数据,适用于查询主表完整信息;RIGHT JOIN(右连接)则保留右表所有记录。
|
8天前
|
人工智能 运维 安全
主流AI Agent框架对比:Hermes Agent与OpenClaw核心差异与选型指南及部署教程
随着AI智能体技术全面落地,各类开源AI Agent开发框架层出不穷,其中 **Hermes Agent** 与 **OpenClaw** 凭借成熟的架构、丰富的功能、活跃的社区生态,成为2026年个人开发者、初创团队与中小企业最常用的两大主流框架。二者均支持大模型对接、工具调用、自动化任务、多轮会话等核心智能体能力,能够帮助开发者快速搭建生产级AI智能体应用。
221 1
|
8天前
|
人工智能 数据挖掘 BI
本体论 vs 语义层:两种 AI 业务语义底座的区别、场景与建设路径
本体论和语义层并不是互斥关系,也不是简单的“谁替代谁”。本体论表达了企业 AI 的高阶目标,语义层提供了多数企业更容易落地的起点。
|
8天前
|
人工智能 自然语言处理 API
阿里云计算巢一键部署DeepSeek-TUI: DeepSeek 版的 Claude Code 终端AI编程实操教程
在AI编程工具飞速迭代的当下,终端类智能编码助手凭借轻量化、无依赖、高效流畅的特性,深受开发者青睐。DeepSeek-TUI作为对标Claude Code的终端原生编程智能体,基于DeepSeek V4系列模型深度打造,能够直接在命令行中完成代码生成、项目搭建、脚本编写、漏洞排查、网页检索、Git管理等全流程开发工作,凭借强大的上下文能力与完备工具链,迅速在开发者社区走红。
239 2
|
8天前
|
人工智能 自然语言处理 API
阿里云Qwen Cloud正式发布 面向Agent时代全新云服务全解析
随着AI智能体技术迎来爆发式增长,智能体已经不再是辅助工具,逐步演变成为云服务消费的核心主体。传统云平台以人类用户为中心设计的界面交互、操作逻辑,已经难以适配AI Agent无规律弹性调用、短时任务瞬时启停、自动化流程高频运行的全新需求。面对全球市场AI算力需求持续攀升,智能体带动模型调用量与云资源消耗呈指数级增长的行业现状,阿里云于5月26日在新加坡面向全球市场正式重磅推出Qwen Cloud全新云服务平台。
361 1
|
8天前
|
人工智能 自然语言处理 算法
|
8天前
|
人工智能 运维 机器人
RAG、Agentic RAG 和 AI Memory 到底有什么区别?
它们合在一起,回答的是同一个问题:AI 怎么从“回答当前问题”,变成“参与长期工作”。RAG 让它会查资料;Agentic RAG 让它更会查资料;Memory 让它能带着过去的上下文继续工作。当这三者组合起来,AI 才更像一个长期协作的助手,而不只是一个每次都要重新介绍背景的聊天窗口。
146 0
RAG、Agentic RAG 和 AI Memory 到底有什么区别?