SQL 数值函数如何计算累计和与平均值?

来源:HTML教程作者:乙爱丽丝头衔:网络博主
导读:本期聚焦于乙爱丽丝创作的《SQL 数值函数如何计算累计和与平均值?》,敬请观看详情。累计和与移动平均是数据分析里最常见的统计需求,但很多人还在用子查询或者程序循环的方式硬算,效率低还容易出错。其实SQL自带的窗口函数就能优雅地解决这类问题,SUM配合OVER子句可以一行代码算出累计值,AVG搭配ROWS BETWEEN还能实现滑动窗口平均。本文将围绕累计和、移动平均、分组累计这三个核心场景,详细讲解OVER、PARTITION BY、ORDER BY以及帧子句的用法,并对比不同数据库下的写法差异,同时分析常见报错和性能坑点,帮助你真正掌握SQL数值统计函数的进阶技巧。

累计和(Running Total)与平均值是报表统计中最常见的需求,比如电商系统要展示每日销售额的累计曲线,监控系统要展示最近七天的滑动平均负载。传统做法是在应用程序里查出原始数据后用循环累加,或者在SQL里写多层嵌套子查询,数据量一大性能就崩。其实SQL标准从2003年起就引入了窗口函数(Window Function),配合SUM、AVG等数值聚合函数,一条查询语句就能算出累计和与各种口径的平均值,而且数据库引擎会做专门优化,执行效率远高于应用层计算。

SQL 数值函数如何计算累计和与平均值?

窗口函数基础:OVER子句的工作原理

要理解累计和怎么算,先得弄清楚窗口函数和普通聚合函数的区别。普通的SUM配合GROUP BY使用时,会把多行折叠成一行,比如统计每个月的总销售额,结果是每个分组只剩一行。而窗口函数加上OVER子句后,每一行原始数据都保留,同时在旁边附加一个计算结果,这个计算结果的可见范围由窗口定义决定。

OVER子句里最关键的是三个部分:PARTITION BY决定分区,相当于把数据切成多个独立计算的小组;ORDER BY决定累计的方向和顺序;帧子句(Frame Clause)决定从当前行往前后各取多少行参与计算。下面用一个订单表来演示,表结构如下:

CREATE TABLE orders (
    order_date DATE,        -- 下单日期
    region     VARCHAR(20), -- 销售区域
    amount     DECIMAL(10,2) -- 订单金额
);

INSERT INTO orders VALUES
('2024-01-01', '华东', 100.00),
('2024-01-02', '华东', 150.00),
('2024-01-03', '华东', 200.00),
('2024-01-01', '华北', 120.00),
('2024-01-02', '华北', 80.00);

先看一个最简单的普通聚合与窗口函数的对比。GROUP BY写法只能得到每个区域的总额,而窗口函数写法能同时看到每一笔订单和它所属区域的总额,这种特性正是累计计算的基础。

用SUM OVER实现累计和的完整写法

累计和的核心是SUM(amount) OVER (ORDER BY order_date),含义是按照日期排序后,从第一行累计到当前行。数据库在执行时会维护一个不断累加的中间值,每一行输出的都是它之前所有行(含自身)的总和,时间复杂度是O(n),比子查询自关联的O(n²)快了不止一个量级。

SELECT
    order_date,
    amount,
    SUM(amount) OVER (ORDER BY order_date) AS running_total
FROM orders
WHERE region = '华东';

执行结果中,1月1日这一行显示100,1月2日显示250(100+150),1月3日显示450(100+150+200),这就是典型的累计和曲线。如果去掉ORDER BY只写OVER(),窗口就变成整个结果集,每一行都会显示全量总和,这是新手最容易混淆的地方:有没有ORDER BY,窗口的行为完全不同。

还有一个隐藏的坑需要特别注意:如果排序键有重复值,比如同一天有多笔订单,默认的窗口范围是RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW,相同日期的行会被视为一个整体,互相看到对方的值。比如1月2日有两笔订单各100元,这两行的累计和都会显示300而不是一笔200、一笔300。如果想严格按行累计,需要显式改成ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW,用行号而不是排序值来划定边界。

分组累计:PARTITION BY的实战应用

真实业务里很少只算一条累计线,更多时候要按区域、按用户、按商品分类分别累计。这时PARTITION BY就派上用场了,它相当于把窗口函数执行多次,每个分区内部独立累计,互不干扰。写法上只需要在OVER里先写分区再写排序:

SELECT
    region,
    order_date,
    amount,
    SUM(amount) OVER (
        PARTITION BY region
        ORDER BY order_date
        ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW
    ) AS region_running_total
FROM orders;

这条语句会为华东和华北各自生成一条累计线:华东区的行只累计华东区的数据,华北区同理。生成多区域对比报表时,这种写法一条查询就能搞定,不需要在应用层按区域分组后循环计算。

分区的另一个高频场景是计算占比。先用窗口函数算出总额,再用当前行金额除以总额,就能得到每笔订单在全量中的占比,而不需要先查一次总额再回表关联:

SELECT
    order_date,
    amount,
    amount / SUM(amount) OVER () AS pct_of_total
FROM orders;

滑动平均与帧子句的精确控制

累计平均是把当前行之前的所有数据都算进去,但很多场景需要的是滑动平均(也叫移动平均),比如股票的5日均线、服务器的近7天平均响应时间。这就轮到帧子句出场了。帧子句的语法是ROWS BETWEEN 上边界 AND 下边界,边界可以是UNBOUNDED PRECEDING(分区第一行)、N PRECEDING(前N行)、CURRENT ROW(当前行)、N FOLLOWING(后N行)、UNBOUNDED FOLLOWING(分区最后一行)。

计算最近3天的滑动平均,写法如下:

SELECT
    order_date,
    amount,
    AVG(amount) OVER (
        ORDER BY order_date
        ROWS BETWEEN 2 PRECEDING AND CURRENT ROW
    ) AS moving_avg_3
FROM orders
WHERE region = '华东';

2 PRECEDING AND CURRENT ROW表示窗口覆盖前两行加当前行,共3行,这就是标准的3日滑动窗口。前两行因为不足3条数据,会按实际行数平均,比如第一行的滑动平均就等于它自身的值。如果希望不足窗口大小时返回NULL,可以额外判断行号:CASE WHEN ROW_NUMBER() OVER (ORDER BY order_date) >= 3 THEN ... END

顺带一提,ROWSRANGE是两种不同的帧模式:ROWS按物理行数计算边界,RANGE按排序值计算,相同排序值的行会被一起纳入窗口。做滑动平均时绝大多数情况应该用ROWS,语义更精确可控。

不同数据库的兼容性与性能注意事项

窗口函数在主流数据库中的支持情况总体良好:MySQL从8.0版本开始支持,PostgreSQL、SQL Server、Oracle、SQLite(3.25以上)都支持完整语法。老版本的MySQL 5.x不支持窗口函数,只能通过用户变量@total := @total + amount这种黑科技模拟累计和,可读性差且在优化器改写下容易出错,建议升级或改用应用层计算。

性能方面有几点经验值得参考。第一,窗口函数要求结果集先按分区和排序键排列,索引设计应尽量与PARTITION BYORDER BY的字段顺序保持一致,比如上例中建立(region, order_date)的联合索引,可以避免额外的排序操作。第二,窗口函数无法直接配合WHERE过滤计算结果,因为窗口计算发生在WHERE之后,如果想筛选累计和大于某个阈值的行,需要用子查询或CTE包一层再过滤。

WITH totals AS (
    SELECT
        order_date,
        amount,
        SUM(amount) OVER (ORDER BY order_date) AS running_total
    FROM orders
    WHERE region = '华东'
)
SELECT * FROM totals
WHERE running_total >= 300;  -- 窗口函数结果必须在外层过滤

第三,避免在一个查询里堆砌大量不同的窗口定义,每个不同的OVER子句都可能触发一次单独的排序,多个窗口尽量统一排序键,让数据库复用同一次排序结果。掌握这些细节后,累计和、移动平均、分组占比这类统计需求都可以在SQL层一步到位,既省去了应用层的循环代码,也让统计口径集中在数据库中,维护起来更加清晰。

SQL数值函数累计和窗口函数修改时间:2026-09-15 07:16:37

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