导读:本期聚焦于阿亮创作的《如何用SQL实现不间断的分组求和?利用SUM OVER实现累计求和》,敬请观看详情。报表里经常要按区域展示每一笔订单金额,同时给出该区域从月初到当前行的累计销售额。用普通 GROUP BY 只能得到区域总合计,无法保留明细行。此时窗口函数 SUM(amount) OVER (PARTITION BY region ORDER BY sales_date) 可以一次性求出不间断分组累计值,避免多次扫描表或使用子查询。它的核心是把数据按分区分成多个窗口,再按排序字段逐行累加。PARTITION BY 控制分组边界,ORDER BY 控制累计顺序,默认窗口帧为分组内从第一行到当前行。实际使用时还要注意排序字段重复、NULL 值以及不同数据库对窗口帧的支持差异。本文通过建表、插入数据和多个查询示例,逐步拆解 SUM OVER 实现分组累计的完整思路,帮助读者在 MySQL 8.0、PostgreSQL、SQL Server 和 Oracle 中直接落地。

用 SQL 做销售报表或对账明细时,常会遇到一个需求:保留每一行原始记录,同时增加一列显示该行所属分组内从开始到当前行的累计值。例如按区域统计订单金额时,既想看华东每一天的单笔金额,又想知道华东截至当天的销售总额。普通的 GROUP BY 聚合会丢失明细行,而利用窗口函数 SUM() OVER 可以一次查询完成分组不间断累计。本文将围绕 SUM OVER 的语法、执行逻辑和常见问题展开,给出可以直接运行的 SQL 示例。

如何用SQL实现不间断的分组求和?利用SUM OVER实现累计求和

下面通过一个订单表 orders 来演示。表结构包含订单编号、区域、销售日期和金额四个字段,需求是按区域分组,同时按日期累计区域销售额。

一、窗口函数与分组累计的关系

窗口函数与 GROUP BY 本质不同。GROUP BY 做的是分组聚合,每个分组只返回一行结果,所有明细被压缩成汇总值。窗口函数则在每一行上计算一个值,这个值可以依赖同一窗口内其他行。以累计求和为例,窗口函数会把分区内的所有行组成一个逻辑窗口,根据 ORDER BY 指定的顺序,将窗口起点到当前行的值累加,从而实现逐行递增的累计效果。

用一个简单对比更容易理解。下面的查询返回每个区域的总销售额,这是传统分组聚合。

SELECT region,
       SUM(amount) AS total_amount
FROM orders
GROUP BY region;

该结果只有一行一个区域,看不到明细。如果想在每一笔订单后追加一列累计金额,就需要窗口函数。窗口函数不会减少结果集行数,它会为原始结果集的每一行计算累计值,这就是累计求和与普通分组求和最明显的区别。

二、用 SUM OVER 实现分组累计

先创建测试数据。

CREATE TABLE orders (
    id INT PRIMARY KEY,
    region VARCHAR(20),
    sales_date DATE,
    amount DECIMAL(10,2)
);

INSERT INTO orders VALUES
(1, '华东', '2024-01-01', 100.00),
(2, '华东', '2024-01-02', 150.00),
(3, '华东', '2024-01-03', 80.00),
(4, '华南', '2024-01-01', 200.00),
(5, '华南', '2024-01-02', 120.00);

接下来使用 SUM(amount) OVER (PARTITION BY region ORDER BY sales_date, id) 计算每个区域的累计销售额。

SELECT 
    id,
    region,
    sales_date,
    amount,
    SUM(amount) OVER (
        PARTITION BY region 
        ORDER BY sales_date, id
    ) AS running_total
FROM orders
ORDER BY region, sales_date, id;

查询结果会显示华东第 1 行累计 100,第 2 行累计 250,第 3 行累计 330;华南第 1 行累计 200,第 2 行累计 320。PARTITION BY region 将数据按区域切成独立窗口,每个区域都从自己的第一行开始累加,不会跨区串数。ORDER BY sales_date 决定按日期先后累加,如果同一天有多条记录,再按 id 排序保证结果稳定。这种写法就是所谓的不间断分组累计,因为分区内每一行都会参与前面所有行的累加,中间不会因为 GROUP BY 丢弃行。

三、理解默认窗口帧和 ROWS 控制

上面的查询没有写窗口帧,数据库默认使用 RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW。意思是窗口范围从分区起点到当前行,按 ORDER BY 的值确定包含哪些行。在日期唯一时,默认行为与累加一致。但如果 ORDER BY 字段存在重复值,RANGE 会把所有相同排序值的行视为同一批次,导致累计结果可能出现多条行都包含同批数据的情况。为了让每一行严格逐行累加,建议显式使用 ROWS 窗口帧。

下面的 SQL 显式声明 ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW,表示从分区第一行到当前行逐行累计,不受重复排序值影响。

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

如果想计算最近 N 条记录的滚动合计,可以把 UNBOUNDED PRECEDING 换成 N PRECEDING。例如按区域和日期排序后,只累计当前行及前两行,可以写 ROWS BETWEEN 2 PRECEDING AND CURRENT ROW。这种窗口帧控制在库存明细、移动平均等场景中非常实用。

四、不分组累计和多字段分区

有时需求不是按分组累计,而是对整个结果集按时间累计。此时可以省略 PARTITION BY,只保留 ORDER BY。例如计算全公司每天销售总额的累计值。

SELECT 
    sales_date,
    SUM(amount) AS day_total,
    SUM(SUM(amount)) OVER (
        ORDER BY sales_date
        ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW
    ) AS running_total
FROM orders
GROUP BY sales_date
ORDER BY sales_date;

这段代码先按日期汇总每天销售额,再用窗口函数对日汇总做累计。注意内层 SUM(amount) 是聚合,外层 SUM(...) OVER 是对聚合结果再计算累计,这种嵌套写法很常见。也可以在多字段分区,比如按区域和月份累计,只需在 PARTITION BY 后列出多个字段。

SELECT 
    region,
    DATE_FORMAT(sales_date, '%Y-%m') AS sales_month,
    sales_date,
    amount,
    SUM(amount) OVER (
        PARTITION BY region, DATE_FORMAT(sales_date, '%Y-%m')
        ORDER BY sales_date
        ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW
    ) AS month_running_total
FROM orders
ORDER BY region, sales_month, sales_date;

DATE_FORMAT 是 MySQL 的函数,其他数据库可以使用 TO_CHAR 或 FORMAT。关键是 PARTITION BY 后的多个字段共同决定窗口边界,每个区域每个月都会重新开始累计,非常适合月度指标分析。

五、NULL 值与排序细节

累计求和遇到 NULL 金额会产生两个问题:一是 SUM 会忽略 NULL,但它仍会占一行;二是如果累计列本身有 NULL,外层逻辑可能出现断档。最好在数据准备阶段用 COALESCE 把 NULL 转成 0,例如 SUM(COALESCE(amount, 0)) OVER (...)。这样累计值不会因为某行缺失金额而失真。

排序字段重复是另一个容易忽略的坑。假设只按 sales_date 排序,而同一天有多条记录,RANGE 默认会把同一天的所有记录放在同一窗口位置,这会导致这些行计算出相同的累计值,而不是按插入顺序逐行累加。解决办法是在 ORDER BY 中加入唯一键,例如 ORDER BY sales_date, id,并显式使用 ROWS 窗口帧。这样即使日期相同,也能按照 id 的顺序严格逐行累计。

不同数据库对窗口函数的支持有所不同。MySQL 从 8.0 开始完整支持 SUM() OVER、ROWS 和 RANGE;PostgreSQL、SQL Server、Oracle 很早就支持。使用时需要确认数据库版本,尤其是老版本 MySQL 不支持窗口函数,只能通过变量或自连接模拟累计。

六、性能优化建议

窗口函数虽然写起来方便,但执行时需要对分区和排序字段进行处理。如果表数据量很大,最好在 PARTITION BY 和 ORDER BY 的字段上建立联合索引。例如本示例可以在 region、sales_date、id 上建立索引,减少排序开销。数据库执行计划中通常会看到 WindowAgg 节点,排序操作是最大的成本来源。

另一个优化点是避免在窗口函数内部重复计算复杂表达式。例如 DATE_FORMAT(sales_date, '%Y-%m') 出现在 PARTITION BY 中,如果表达式不基于索引,可能导致全表扫描。可以先生成月份字段,再在外层查询中使用窗口函数。对于只需要累计前几行的场景,使用 ROWS BETWEEN N PRECEDING AND CURRENT ROW 通常比默认 RANGE 更高效,因为不需要处理同值分组。

总之,利用 SUM() OVER 实现分组累计求和,核心是 PARTITION BY 划定边界、ORDER BY 决定顺序、ROWS 控制窗口帧。掌握这三个部分后,就能在不丢失明细行的情况下完成各种累计指标计算,避免重复扫描表或复杂的自连接。

SQL累计求和SUM OVER分组求和修改时间:2026-09-20 09:08:10

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