导读:本期聚焦于勇士创作的《SQL 窗口函数如何计算滑动窗口统计?移动平均与累计求和实战详解》,敬请观看详情。滑动窗口统计是数据分析中常见的需求,比如计算近7天移动平均销售额、累计交易金额或者环比增长率。传统做法依赖自关联查询或多次扫描表,数据量大时性能很差。窗口函数配合 ROWS 与 RANGE 子句可以优雅地解决这类问题,一条语句就能完成按分区滚动求和、求平均和排名。本文将围绕窗口函数的语法要点,详细讲解 ROWS 与 RANGE 的区别、帧边界写法,以及移动平均、累计求和、同比环比等典型场景的完整 SQL 示例,并给出索引与分区的性能优化建议,帮助你写出高效可维护的统计查询。

在做数据分析或报表开发时,经常需要回答这样的问题:最近7天的平均销售额是多少?从年初到当前月份累计完成了多少业绩?某个用户的连续登录天数有多长?这类问题统称为滑动窗口统计。如果用传统 SQL 的自关联写法,不仅语句冗长难读,数据量一大还会带来严重的性能问题。窗口函数的出现让这类需求变得非常简洁,尤其是配合 ROWS 和 RANGE 帧控制子句,一条查询就能完成过去需要多层子查询才能实现的效果。本文将系统讲解如何用窗口函数实现各类滑动窗口统计,包括语法原理、典型场景和性能优化。

SQL 窗口函数如何计算滑动窗口统计?移动平均与累计求和实战详解

窗口函数基础与帧的概念

窗口函数的核心思想是:在不折叠行数的前提下,对每一行计算一个与其相关的聚合结果。与 GROUP BY 最大的区别在于,GROUP BY 会把多行合并成一行,而窗口函数保留每一行,只是在旁边附加一列计算结果。基本语法结构如下:

SELECT
    order_date,
    amount,
    SUM(amount) OVER (
        PARTITION BY customer_id   -- 分区:按客户分组
        ORDER BY order_date        -- 排序:确定行的先后顺序
        ROWS BETWEEN 6 PRECEDING AND CURRENT ROW  -- 帧:参与计算的行范围
    ) AS rolling_sum_7d
FROM orders;

这三个组成部分缺一不可:PARTITION BY 决定了在哪个范围内滚动,比如按客户分别统计就靠它;ORDER BY 决定了行的先后顺序,滑动窗口必须依赖一个明确的排序才有意义;而最关键的是帧(Frame)子句,它精确定义了当前行参与计算时要带上哪些相邻的行。

帧子句有两种模式,这是很多初学者容易混淆的地方。ROWS 模式按物理行数计算边界,ROWS BETWEEN 6 PRECEDING AND CURRENT ROW 表示当前行加上前面6行,一共最多7行;而 RANGE 模式按逻辑值计算边界,RANGE BETWEEN INTERVAL '6' DAY PRECEDING AND CURRENT ROW 表示按日期值往前推6天,即使这6天内有很多行也都算进来。两者的区别在数据有重复值时会非常明显,后面会结合例子详细说明。

典型场景一:移动平均与滑动求和

移动平均是滑动窗口最经典的应用,常用于平滑波动趋势、剔除噪声。假设有一张每日销售表 daily_sales,包含日期和销售额两列,计算7日移动平均可以这样写:

SELECT
    sale_date,
    amount,
    AVG(amount) OVER (
        ORDER BY sale_date
        ROWS BETWEEN 6 PRECEDING AND CURRENT ROW
    ) AS avg_7d,
    SUM(amount) OVER (
        ORDER BY sale_date
        ROWS BETWEEN 6 PRECEDING AND CURRENT ROW
    ) AS sum_7d
FROM daily_sales
ORDER BY sale_date;

这里需要注意一个细节:前6天的数据不足7行,窗口函数依然会计算,只是参与平均的行数较少,导致结果偏大或偏小。如果希望只在窗口凑满7行时才输出结果,可以先用 COUNT(*) OVER (...) 数出窗口内的行数,再在外层查询中过滤,或者使用 CASE 条件判断。

如果同一天有多条记录,比如一天内有多笔订单,用 ROWS 就会产生偏差,因为窗口里可能装不满完整的天数。这时应该改用 RANGE 模式:

SELECT
    order_date,
    SUM(amount) OVER (
        ORDER BY order_date
        RANGE BETWEEN INTERVAL '6' DAY PRECEDING AND CURRENT ROW
    ) AS sum_7d
FROM orders;

这种写法在 MySQL 8.0 及以上、PostgreSQL、Oracle 中都支持。RANGE 模式按日期值计算边界,无论一天有多少条记录,窗口始终覆盖最近7个自然日的数据,逻辑上更符合业务直觉。总结一句:行数固定用 ROWS,时间跨度固定用 RANGE。

典型场景二:累计统计与增长率计算

累计求和只需要把帧的上界去掉,只保留起点即可。常见的写法有三种:从分区第一行累计到当前行、从当前行往后累计、或者排除当前行只算之前的行。下面是一个月内累计销售额的例子:

SELECT
    sale_date,
    amount,
    SUM(amount) OVER (
        PARTITION BY DATE_FORMAT(sale_date, '%Y-%m')
        ORDER BY sale_date
    ) AS month_cum_sum,          -- 默认从分区起点到当前行
    SUM(amount) OVER (
        ORDER BY sale_date
        ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW
    ) AS total_cum_sum,
    SUM(amount) OVER (
        ORDER BY sale_date
        ROWS BETWEEN UNBOUNDED PRECEDING AND 1 PRECEDING
    ) AS cum_before_current      -- 不含当前行的累计值
FROM daily_sales;

当不写帧子句时,带 ORDER BY 的窗口函数默认使用 RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW,也就是从分区第一行累计到当前行(按逻辑值)。第三个窗口用 1 PRECEDING 作为上界排除了当前行,这个技巧在计算环比时特别有用。

结合自关联替代方案,可以计算每日环比增长率:用累计值减去前一天的累计值得到当日增量,或者更直接地用 LAG 函数取前一行:

SELECT
    sale_date,
    amount,
    LAG(amount, 1) OVER (ORDER BY sale_date) AS prev_amount,
    ROUND(
        (amount - LAG(amount, 1) OVER (ORDER BY sale_date))
        / LAG(amount, 1) OVER (ORDER BY sale_date) * 100, 2
    ) AS growth_rate_pct
FROM daily_sales;

LAG 和 LEAD 虽然不是聚合型窗口函数,但它们与滑动统计配合得非常紧密。LAG 负责取前面的行,LEAD 负责取后面的行,比如判断连续登录天数、计算用户行为间隔,都可以用它们完成,避免了以前自关联的写法。

性能优化与常见坑

窗口函数虽然好用,但性能并非没有代价。数据库需要对窗口列进行排序,如果 PARTITION BY 和 ORDER BY 涉及的列上有合适的索引,排序开销可以大幅降低。比如按 customer_id 加 order_date 建复合索引,窗口函数就能利用索引避免额外的排序步骤。对于超大规模数据,还可以先按分区键做预处理或分批计算。

其次是帧模式带来的性能差异。ROWS 模式通常比 RANGE 模式快,因为 ROWS 只需要数行数,而 RANGE 需要比较值并处理重复值分组。在不影响业务正确性的前提下优先用 ROWS。另外,一个查询中多个窗口函数如果排序方式相同,尽量写在一个 OVER 子句定义里复用,很多数据库支持 WINDOW 子句来命名和复用窗口定义:

SELECT
    sale_date,
    amount,
    SUM(amount) OVER w AS cum_sum,
    AVG(amount) OVER w AS cum_avg,
    COUNT(*)   OVER w AS cum_cnt
FROM daily_sales
WINDOW w AS (ORDER BY sale_date ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW);

最后提醒几个常见坑:第一,MySQL 8.0 之前不支持窗口函数,老版本需要用变量模拟,升级才是正解;第二,滑动窗口统计对 NULL 值要小心,AVG 会自动忽略 NULL,如果希望 NULL 按 0 参与,需要先用 COALESCE 处理;第三,RANGE 模式在部分数据库(如 SQL Server)中不支持日期间隔写法,需要折算成数值或改用 ROWS。掌握这些细节后,滑动窗口统计的各类需求基本都能用一条清晰高效 SQL 解决。

窗口函数滑动窗口移动平均修改时间:2026-08-31 17:49:07

免责声明:​ 已尽一切努力确保本网站所含信息的准确性。网站内容多为原创整理与精心编撰,观点力求客观中立。本站旨在免费分享,内容仅供个人学习、研究或参考使用。若引用了第三方作品,版权归原作者所有。如内容涉及您的权益,请联系我们处理。
内容垂直聚焦
专注技术核心技术栏目,确保每篇文章深度聚焦于实用技能。从代码技巧到架构设计,为用户提供无干扰的纯技术知识沉淀,精准满足专业提升需求。
知识结构清晰
覆盖从开发到部署的全链路。AI、前端、编程、数据库、服务器、建站、系统层层递进,构建清晰学习路径,帮助用户系统化掌握开发与运维所需的核心技术。
深度技术解析
拒绝泛泛而谈,深入技术细节与实践难点。无论是数据库优化还是服务器配置,均结合真实场景与代码示例进行剖析,致力于提供可直接应用于工作的解决方案。
专业领域覆盖
精准对应开发生命周期。从前端界面到后端编程,从数据库操作到服务器运维,形成完整闭环,一站式满足全栈工程师和运维人员的技术需求。
即学即用高效
内容强调实操性,步骤清晰、代码完整。用户可根据教程直接复现和应用于自身项目,显著缩短从学习到实践的距离,快速解决开发中的具体问题。
持续更新保障
专注既定技术方向进行长期、稳定的内容输出。确保各栏目技术文章持续更新迭代,紧跟主流技术发展趋势,为用户提供经久不衰的学习价值。