You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

PostgreSQL 15中实现累计负值抵扣的自定义聚合函数开发

PostgreSQL 15 实现负值累计抵扣的自定义聚合函数

需求说明

需要实现以下逻辑:

  • 所有负值进行累计,形成待抵扣的负债
  • 遇到正值时用其抵扣累计负债,具体规则:
    • 若正值金额大于累计负债:输出抵扣后的剩余正值,累计负债清零
    • 若正值金额不足以覆盖累计负债:输出0,累计负债更新为抵扣后的剩余负值
    • 若当前是正值且无累计负债:直接输出该正值

测试数据集

SELECT id, value, expected_output, comment
FROM (VALUES 
      (1, 0.89, 0.89, ''), 
      (2, 0.17, 0.17, ''), 
      (3, -0.30, 0, 'accum = -0.30'), 
      (4, -0.36, 0, 'accum = -0.30 - 0.36 = -0.66'), 
      (5, 1.41, 0.75, '0.75 = 1.41 - 0.66; accum = 0'), 
      (6, -0.49, 0, 'accum = -0.49'),
      (7, 0.24, 0, '0, because 0.24 - 0.49 = -0.25 is still negative, so accum = -0.25'), 
      (8, 6.15, 5.9, '5.9 = 6.15 - 0.25; accum = 0'), 
      (9, 6.05, 6.05, '')
) 
AS t(id, value, expected_output, comment);

现有问题分析

你之前写的聚合状态函数只维护了单一的累计值,但我们需要同时跟踪累计负债和当前行的输出结果两个状态,所以仅用单一数值无法满足需求,必须用复合类型来保存状态。

解决方案:自定义聚合函数

1. 创建复合类型存储状态

这个类型用来同时记录累计的负债金额和当前行的输出值:

CREATE TYPE debt_output AS (
    debt numeric,
    output numeric
);

2. 编写状态转换函数

该函数处理每一行的value,根据规则更新累计负债并计算当前行输出:

CREATE OR REPLACE FUNCTION debt_settlement_accum(
    _state debt_output,
    _current numeric
) RETURNS debt_output
LANGUAGE plpgsql
AS $BODY$
BEGIN
    -- 初始化状态(首次处理时累计负债设为0)
    IF _state.debt IS NULL THEN
        _state.debt := 0;
    END IF;

    IF _current < 0 THEN
        -- 当前是负值:累加到负债,输出0
        _state.debt := _state.debt + _current;
        _state.output := 0;
    ELSE
        -- 当前是正值:结算累计负债
        IF _current > abs(_state.debt) THEN
            -- 正值足够抵扣:输出剩余部分,负债清零
            _state.output := _current + _state.debt; -- 等价于 current - abs(debt)
            _state.debt := 0;
        ELSE
            -- 正值不足抵扣:输出0,负债更新为剩余值
            _state.debt := _state.debt + _current;
            _state.output := 0;
        END IF;
    END IF;

    RETURN _state;
END;
$BODY$;

3. 创建聚合函数

基于上面的状态函数创建聚合:

CREATE AGGREGATE debt_settlement(numeric) (
    SFUNC = debt_settlement_accum,
    STYPE = debt_output,
    INITCOND = '(0, 0)'
);

4. 使用窗口函数获取结果

通过窗口函数按id顺序逐行处理,提取输出值:

SELECT
    id,
    value,
    (debt_settlement(value) OVER (ORDER BY id)).output AS calculated_output,
    expected_output,
    comment
FROM (VALUES 
      (1, 0.89, 0.89, ''), 
      (2, 0.17, 0.17, ''), 
      (3, -0.30, 0, 'accum = -0.30'), 
      (4, -0.36, 0, 'accum = -0.30 - 0.36 = -0.66'), 
      (5, 1.41, 0.75, '0.75 = 1.41 - 0.66; accum = 0'), 
      (6, -0.49, 0, 'accum = -0.49'),
      (7, 0.24, 0, '0, because 0.24 - 0.49 = -0.25 is still negative, so accum = -0.25'), 
      (8, 6.15, 5.9, '5.9 = 6.15 - 0.25; accum = 0'), 
      (9, 6.05, 6.05, '')
) AS t(id, value, expected_output, comment);

执行后calculated_output列会和expected_output完全匹配,满足你的需求。

内容的提问来源于stack exchange,提问作者willsbit

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.22 12:44:55