SQL中实现累计求和方法详解(详解窗口函数累计求和与PARTITION BY的作用)

SQL 中累计求和(也叫运行总计、Running Total)的标准做法是窗口函数 SUM() OVER(...):用 ORDER BY 定义累加的时间轴或业务顺序,从第一行加总到当前行,行数不变、明细全留。给 OVER() 加上 PARTITION BY 则把数据切成多个区,在每个区内独立从头累加、跨区不串号。据 OneUptime 的 MySQL 累计求和教程,SUM(amount) OVER (ORDER BY sale_date ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) 是通用写法;据 ufcn.cn 的实战解析,漏掉 PARTITION BY 会让所有分组被当成一张表累加,漏掉 ORDER BY 则累计失去意义。MySQL 8.0+、PostgreSQL、SQL Server、SQLite 3.25+、Oracle 均支持该语法。

一、累计求和在算什么

累计求和是”按某列排序后,从第一条记录一直加到当前这一条”。它和 GROUP BY 根本不同:聚合会折叠行,窗口函数不折叠——每一行都保留,并在行尾多出一列”截至本行的累计值”。

据 SQL Habit 的 SUM 窗口函数文档,SUM(col) OVER() 把整张表当一个窗口,每行都得到同一个总和;一旦加上 ORDER BY,窗口函数就逐行把前面的值收拢进来,变成递增的累计值。

二、基础写法:全表逐行累加

sql

SELECT order_date, amount,
       SUM(amount) OVER (
           ORDER BY order_date
           ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW
       ) AS running_total
FROM sales
ORDER BY order_date;

ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW 把累加范围钉死在”从分区首行到当前行”。据 OneUptime 对比,带 ORDER BY 时数据库默认框架其实是 RANGE 而非 ROWS——当 ORDER BY 列有重复值时,RANGE 会把所有并列行当成同一边界一次性加进去,而 ROWS 保证每行逐次步进。做严格逐行累计,显式写 ROWS 更稳妥。

三、PARTITION BY 的作用:分区分头累加

PARTITION BY 是窗口函数的”分组但不折叠”开关。它把数据按指定列切成多个区,窗口函数在每个区内独立计算、计算起点在每个区第一行重启,跨区互不干扰。据 Iconics 官方窗口函数文档,PARTITION BY 把结果集划分成若干分区,窗口函数分别作用于每个分区,且”对每一个分区计算都会重新开始”。

sql

SELECT customer_id, order_date, amount,
       SUM(amount) OVER (
           PARTITION BY customer_id
           ORDER BY order_date
           ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW
       ) AS cum_per_customer
FROM sales;

上例中,每个客户从第一次下单起独立累计,北京客户的第 1 笔不会加上上海客户的记录。ufcn.cn 特别提醒:漏掉 PARTITION BY 时,OVER(ORDER BY ...) 会把整张表当一个组,导致各分组首尾相接错误累加——这是最常见的 bug。

四、ORDER BY 缺省与陷阱

写法 结果
SUM(col) OVER(ORDER BY k) 按 k 排序,逐行累加(正确累计)
SUM(col) OVER(PARTITION BY g) 只分区不排序,每行得到”本组总和”,不是累计
SUM(col) OVER() 全表一列同值的总额,非累计

据 ufcn.cn 与 CSDN 的实战文章,三条铁律必须守住:一、累计必须带 ORDER BY,否则顺序不确定甚至报错(PostgreSQL 14+ 强制要求);二、漏 PARTITION BY 会变成全表累计;三、ORDER BY 列有重复时要补二级排序键(如 id)保证结果稳定,避免并列行的累计值随机。

五、灵活控制累加范围

ROWS/RANGE 子句可精确圈定”当前行前后的范围”,实现滑动窗口:

sql

-- 最近 3 条(含当前)滚动累计
SUM(amount) OVER (
    PARTITION BY customer_id
    ORDER BY order_date
    ROWS BETWEEN 2 PRECEDING AND CURRENT ROW
) AS rolling_3

据 OneUptime 文档,PRECEDING/FOLLOWING 配 ROWS 表示物理行偏移,RANGE 表示逻辑值范围;UNBOUNDED PRECEDING 只能用在起点。想要”近 N 条”而非”从开头”,就用带数字的 PRECEDING。

六、累计占比:累计求和的高频衍生

把累计值除以分区总额,即得累计百分比,常用于帕累托分析:

sql

SELECT order_date, amount,
       SUM(amount) OVER (ORDER BY order_date) AS cum,
       ROUND(
           100.0 * SUM(amount) OVER (ORDER BY order_date)
                 / SUM(amount) OVER (),
           1
       ) AS pct_of_total
FROM sales;

SQL Habit 指出,空 OVER() 在这里起到”全表总额分母”的作用——一个表达式同时拿到累计与总额,比 GROUP BY 再 JOIN 简洁得多。

七、旧版本兜底写法

MySQL 5.7 及更早不支持窗口函数,需用用户变量或关联子查询(据 OneUptime 旧版教程):

sql

-- MySQL 5.7 用户变量写法
SET @cum := 0;
SELECT order_date, amount, @cum := @cum + amount AS running_total
FROM sales ORDER BY order_date;

关联子查询写法为 SELECT order_date, amount, (SELECT SUM(amount) FROM sales s2 WHERE s2.order_date <= s1.order_date) AS running_total FROM sales s1,但每行重跑一次,性能明显弱于窗口函数。

八、累计求和执行步骤

  1. 明确累加依据列(日期、序号),写进 ORDER BY;有重复值补二级键。
  2. 需要分组独立累计时加 PARTITION BY;全表累计则省略。
  3. 选 ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW 做严格逐行累计。
  4. 需要滚动窗口时用 N PRECEDING 限定近 N 条。
  5. 数据库支持窗口函数直接用 SUM() OVER;旧版回退用户变量或关联子查询。

常见问题(FAQ)

PARTITION BY 和 GROUP BY 冲突吗?
不冲突。GROUP BY 折叠行、每组一行;PARTITION BY 不折叠,只在窗口内分区计算,二者可并存。

为什么累计结果有时几行相同?
ORDER BY 列有并列时默认 RANGE 框架会把并列行一起累加;改成 ROWS 或补唯一排序键即可逐行递增。

只写 SUM() OVER(ORDER BY k) 行吗?
可以,默认即为”首行到当前行”的累计;显式写 ROWS 框架语义更清晰,建议保留。

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 qiqicto@qq.com 举报,一经查实,本站将立刻删除。
赞 (0)
小码农的头像小码农认证作者

相关推荐

返回顶部