基于同一列计算结果的累计和
Cumulative sum based on same column calculated result
我有以下 table,我正在尝试为其计算 运行 余额和剩余价值,但剩余价值是先前计算的行的函数,因此:
date PR amount total balance remaining_value
----------------------------------------------------------
'2020-1-1' 1 1.0 100.0 1.0 100 -- 100 (inital total)
'2020-1-2' 1 2.0 220.0 3.0 320 -- 100 (previous row) + 220
'2020-1-3' 1 -1.5 -172.5 1.5 160 -- 320 - 160 (see explanation 1)
'2020-1-4' 1 3.0 270.0 4.5 430 -- 160 + 270
'2020-1-5' 1 1.0 85.0 5.5 515 -- 430 + 85
'2020-1-6' 1 2.0 202.0 7.5 717 -- 575 + 202
'2020-1-7' 1 -4.0 -463.0 3.5 334.6 -- 717 - 382.4 (see explanation 2)
'2020-1-8' 1 -0.5 -55.0 3.0 ...
'2020-1-9' 1 2.0 214.0 5.0
'2020-1-1' 2 1.0 100 1.0 100 -- different PR: start new running total
逻辑如下:
对于正数行,剩余的值只是remaining_value
列中前一行的值+该行total
列中的值。
对于负数行,它变得更灵活:
解释 1: 我们从 320
(前一行余额)开始,然后从中删除 1.5/3.0
(当前行金额的绝对值除以前一行余额),我们将它乘以前一行 remaining_value
,即 320
。计算得出:
320 - (1.5/3 * 320) = 160
解释二:同上逻辑。 717 - (4/7.5 * 717) = 717 - 382.4
4/7.5
这里表示当前行的绝对金额除以上一行的余额。
我尝试了 window 函数 sum()
但没有得到想要的结果。有没有办法在 PostgreSQL 中完成此操作而不必诉诸循环?
额外的复杂性: 有多个产品由 PR(产品 ID)、1、2 等标识。每个产品都需要自己的 运行 总数和计算。
你可以create a custom aggregate function:
CREATE OR REPLACE FUNCTION f_special_running_sum (_state numeric, _total numeric, _amount numeric, _prev_balance numeric)
RETURNS numeric
LANGUAGE sql IMMUTABLE AS
'SELECT CASE WHEN _amount > 0 THEN _state + _total
ELSE _state * (1 + _amount / _prev_balance) END';
CREATE OR REPLACE AGGREGATE special_running_sum (_total numeric, _amount numeric, _prev_balance numeric) (
sfunc = f_special_running_sum
, stype = numeric
, initcond = '0'
);
CASE
表达式进行拆分:如果金额为正,只需添加总计,否则应用您的(简化)公式:
320 * (1 + -1.5 / 3.0)
而不是 320 - (1.5/3 * 320)
,即:
_state * (1 + _amount / _prev_balance)
函数和聚合参数名称仅供参考。
那么您的查询可以如下所示:
SELECT *
, special_running_sum(total, amount, prev_balance) OVER (PARTITION BY pr ORDER BY date)
FROM (
SELECT pr, date, amount, total
, lag(balance, 1, '1') OVER (PARTITION BY pr ORDER BY date) AS prev_balance
FROM tbl
) t;
db<>fiddle here
我们需要一个子查询来应用第一个 window 函数 lag()
并将之前的余额提取到当前行 (prev_balance
)。如果没有前一行以避免 NULL
值,我默认为 1
。
注意事项:
如果第一行的总数为负,则结果未定义。我的聚合函数默认为 0
.
您没有声明数据类型,也没有声明关于精度的要求。我假设 numeric
并以最大精度为目标。 numeric
的计算是精确的。但是您的公式会产生分数小数。如果没有四舍五入,经过几次除法后会有 很多 的小数位,并且计算会在 性能 上迅速下降。您必须在精度和性能之间做出妥协。例如,对 double precision
执行相同的操作具有恒定的性能。
相关:
我有以下 table,我正在尝试为其计算 运行 余额和剩余价值,但剩余价值是先前计算的行的函数,因此:
date PR amount total balance remaining_value
----------------------------------------------------------
'2020-1-1' 1 1.0 100.0 1.0 100 -- 100 (inital total)
'2020-1-2' 1 2.0 220.0 3.0 320 -- 100 (previous row) + 220
'2020-1-3' 1 -1.5 -172.5 1.5 160 -- 320 - 160 (see explanation 1)
'2020-1-4' 1 3.0 270.0 4.5 430 -- 160 + 270
'2020-1-5' 1 1.0 85.0 5.5 515 -- 430 + 85
'2020-1-6' 1 2.0 202.0 7.5 717 -- 575 + 202
'2020-1-7' 1 -4.0 -463.0 3.5 334.6 -- 717 - 382.4 (see explanation 2)
'2020-1-8' 1 -0.5 -55.0 3.0 ...
'2020-1-9' 1 2.0 214.0 5.0
'2020-1-1' 2 1.0 100 1.0 100 -- different PR: start new running total
逻辑如下:
对于正数行,剩余的值只是
remaining_value
列中前一行的值+该行total
列中的值。对于负数行,它变得更灵活:
解释 1: 我们从 320
(前一行余额)开始,然后从中删除 1.5/3.0
(当前行金额的绝对值除以前一行余额),我们将它乘以前一行 remaining_value
,即 320
。计算得出:
320 - (1.5/3 * 320) = 160
解释二:同上逻辑。 717 - (4/7.5 * 717) = 717 - 382.4
4/7.5
这里表示当前行的绝对金额除以上一行的余额。
我尝试了 window 函数 sum()
但没有得到想要的结果。有没有办法在 PostgreSQL 中完成此操作而不必诉诸循环?
额外的复杂性: 有多个产品由 PR(产品 ID)、1、2 等标识。每个产品都需要自己的 运行 总数和计算。
你可以create a custom aggregate function:
CREATE OR REPLACE FUNCTION f_special_running_sum (_state numeric, _total numeric, _amount numeric, _prev_balance numeric)
RETURNS numeric
LANGUAGE sql IMMUTABLE AS
'SELECT CASE WHEN _amount > 0 THEN _state + _total
ELSE _state * (1 + _amount / _prev_balance) END';
CREATE OR REPLACE AGGREGATE special_running_sum (_total numeric, _amount numeric, _prev_balance numeric) (
sfunc = f_special_running_sum
, stype = numeric
, initcond = '0'
);
CASE
表达式进行拆分:如果金额为正,只需添加总计,否则应用您的(简化)公式:
320 * (1 + -1.5 / 3.0)
而不是 320 - (1.5/3 * 320)
,即:
_state * (1 + _amount / _prev_balance)
函数和聚合参数名称仅供参考。
那么您的查询可以如下所示:
SELECT *
, special_running_sum(total, amount, prev_balance) OVER (PARTITION BY pr ORDER BY date)
FROM (
SELECT pr, date, amount, total
, lag(balance, 1, '1') OVER (PARTITION BY pr ORDER BY date) AS prev_balance
FROM tbl
) t;
db<>fiddle here
我们需要一个子查询来应用第一个 window 函数 lag()
并将之前的余额提取到当前行 (prev_balance
)。如果没有前一行以避免 NULL
值,我默认为 1
。
注意事项:
如果第一行的总数为负,则结果未定义。我的聚合函数默认为
0
.您没有声明数据类型,也没有声明关于精度的要求。我假设
numeric
并以最大精度为目标。numeric
的计算是精确的。但是您的公式会产生分数小数。如果没有四舍五入,经过几次除法后会有 很多 的小数位,并且计算会在 性能 上迅速下降。您必须在精度和性能之间做出妥协。例如,对double precision
执行相同的操作具有恒定的性能。
相关: