导读:本期聚焦于小伙伴创作的《如何用SQL窗口函数实现累计求和、移动平均与分组内排名的组合模式?》,敬请观看详情。做报表分析时常要在一张表里同时算出累计求和、近三行移动平均和每组内的名次,传统写法靠自连接或子查询不仅慢还难维护。窗口函数通过over子句定义计算范围,能在一个查询里并行完成这三种统计。本文说明partition by怎么切分数据组,order by如何决定累计与滑动方向,以及rows between在移动平均里的具体含义。掌握这些组合模式后,复杂指标可以用简短语句表达,数据库执行计划也更优。

在数据分析场景中,我们往往需要在同一份明细数据上同时得到累计求和、移动平均以及分组内的排名结果。SQL窗口函数提供了一套统一的语法,让我们不必依赖多层子查询或自连接,就能在保持原行粒度的同时算出这些衍生指标。

如何用SQL窗口函数实现累计求和、移动平均与分组内排名的组合模式?

窗口函数的基础语法结构

窗口函数的核心在于over()子句,它定义了“函数作用在哪些行上”以及“这些行以什么顺序参与计算”。与聚合函数不同,窗口函数不会把多行压缩成一行,而是为每一行返回一个计算结果。最基本的写法是在聚合函数或排名函数后面加上over(partition by 列1 order by 列2)

其中partition by负责把数据划分成多个独立的组,组与组之间的计算互不影响,相当于先按该列分组再在组内开窗口;order by则决定了窗口内行的排列顺序,累计求和和移动平均都强烈依赖这个顺序。如果省略partition by,则整张表被视为一个组。理解这两点是组合模式的前提。

累计求和的实现模式

累计求和通常使用sum(列) over(partition by 组 order by 排序)。在默认情况下,如果不写窗口范围,数据库会将该组从第一行到当前行作为窗口,这正好符合“累计”的语义。例如,按用户分组、按日期排序,计算每位用户截至当天的消费总额。

下面是一段可运行的 PostgreSQL 风格示例,展示如何在一个查询中同时得到每日金额与累计金额:

select
    user_id,
    order_date,
    amount,
    sum(amount) over (
        partition by user_id
        order by order_date
    ) as cum_amount
from user_orders
order by user_id, order_date;

这段代码里,cum_amount就是每个用户自己的累计消费。由于窗口定义在user_id分区内,不同用户的累计互不影响。如果需要按月份重置累计,只需把partition by改为user_id, date_trunc('month', order_date)即可,非常灵活。

移动平均的组合写法

移动平均与累计求和的区别在于窗口范围不是“从开头到当前”,而是“当前行附近的一个滑动区间”。这时必须显式使用rows between来约束范围。最常见的三天移动平均写法为avg(列) over(order by 时间 rows between 2 preceding and current row)

我们将移动平均与累计求和管理解成同一函数的不同窗口边界设置。以下示例计算产品最近三天的平均销量,并附带累计销量:

select
    product_id,
    sale_date,
    qty,
    sum(qty) over (
        partition by product_id
        order by sale_date
    ) as cum_qty,
    avg(qty) over (
        partition by product_id
        order by sale_date
        rows between 2 preceding and current row
    ) as mov_avg_3
from sales_detail
order by product_id, sale_date;

注意rows between 2 preceding and current row表示包含当前行及之前两行,共三行求平均。若数据不足三行,数据库会自动按实际行数计算,不会报错。这种写法比自连接效率更高,因为排序只需一次。

分组内排名的嵌套使用

排名类函数如row_number()rank()dense_rank()同样依托over(partition by ... order by ...)。它们常用于取出每组前 N 条记录,或标记组内位次。排名函数与累计、移动平均可以共存于同一个 select 列表,因为它们只是各自定义自己的窗口。

假设我们想找出每个用户消费最高的三笔订单,并展示其累计金额与三天移动平均,可以这么写:

select *
from (
    select
        user_id,
        order_date,
        amount,
        sum(amount) over (
            partition by user_id
            order by order_date
        ) as cum_amount,
        avg(amount) over (
            partition by user_id
            order by order_date
            rows between 2 preceding and current row
        ) as mov_avg_3,
        row_number() over (
            partition by user_id
            order by amount desc
        ) as rn
    from user_orders
) t
where rn <= 3
order by user_id, rn;

这里内层查询同时计算了三种指标,外层再用rn <= 3过滤出每组前三。可以看到,分组内排名的order by amount desc与累计求和的order by order_date并不冲突,因为它们分属不同窗口定义。这种组合模式在榜单类和时序类报表中极其常见。

性能与注意事项

虽然窗口函数写法简洁,但仍需注意索引配合。如果partition byorder by的列上有复合索引,数据库可以避免额外排序,显著提升性能。另外,移动平均的rows between不要用成range between,后者按值区间算边界,在重复时间点下会得到意料之外的行数。

最后提醒,在 MySQL 8.0 之前版本不支持窗口函数,需改用变量模拟;而主流的 PostgreSQL、SQL Server、Oracle 均已完整支持上述语法。掌握这些组合模式,能让你用一条 SQL 替代原本几十行的过程代码。

SQL窗口函数累计求和分组内排名修改时间:2026-07-31 20:03:33

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