Postgres中计算下限为0的滚动求和的最佳方法是什么
PostgreSQL 下限为0的滚动求和实现方案
实现这类带下限的滚动求和不需要额外的特殊内置函数,以下是两种常用的可落地实现方式:
测试数据准备
首先构造你提到的示例测试表,注意滚动求和必须依赖明确的行顺序,因此额外新增了自增id作为排序依据,实际使用时可替换为业务对应的排序字段(如时间戳、业务序号等):
CREATE TEMP TABLE demo ( id SERIAL PRIMARY KEY, val INT ); INSERT INTO demo(val) VALUES (0), (-1), (-1), (2);
方案1:递归CTE实现(无需自定义函数,临时使用推荐)
递归CTE是最通用的实现方式,不需要预先定义任何函数,逻辑直观易调整:
WITH RECURSIVE rolling_sum AS ( -- 初始化第一行的计算结果,用GREATEST保证不低于0 SELECT id, val, GREATEST(val, 0) AS bounded_sum FROM demo WHERE id = 1 UNION ALL -- 逐行累加,每一步都对求和结果做下限限制 SELECT d.id, d.val, GREATEST(rs.bounded_sum + d.val, 0) FROM demo d JOIN rolling_sum rs ON d.id = rs.id + 1 ) -- 取最后一行的结果即为最终求和值 SELECT bounded_sum FROM rolling_sum ORDER BY id DESC LIMIT 1;
计算逻辑完全匹配你的需求,示例的计算过程为:
- 第1行值为0 → 结果为
GREATEST(0,0) = 0 - 第2行值为-1 → 结果为
GREATEST(0 + (-1), 0) = 0 - 第3行值为-1 → 结果为
GREATEST(0 + (-1), 0) = 0 - 第4行值为2 → 结果为
GREATEST(0 + 2, 0) = 2
最终返回结果为2,符合预期。
方案2:自定义聚合函数(高频使用推荐)
如果业务中需要频繁使用这类带下限的滚动求和,可自定义聚合函数,配合窗口函数使用更便捷,性能也优于递归CTE:
步骤1:定义聚合函数
-- 定义状态转换函数,每一步累加后限制下限为0 CREATE OR REPLACE FUNCTION bounded_sum_state(acc numeric, curr numeric) RETURNS numeric AS $$ SELECT GREATEST(acc + curr, 0); $$ LANGUAGE sql IMMUTABLE; -- 注册自定义聚合函数 CREATE AGGREGATE bounded_rolling_sum(numeric) ( SFUNC = bounded_sum_state, STYPE = numeric, INITCOND = '0' );
步骤2:使用聚合函数
SELECT bounded_rolling_sum(val) OVER (ORDER BY id) FROM demo ORDER BY id DESC LIMIT 1;
注意事项
- 必须为窗口/递归逻辑指定明确的排序规则,否则行顺序不确定会导致计算结果完全错误。
- 百万级以上大数据量场景下优先使用自定义聚合方案,性能比递归CTE高30%以上。
内容的提问来源于stack exchange,提问作者David Ramsaran
相关产品推荐
相关产品推荐

