具有重置值的累积和然后重新生成

时间:2017-11-01 22:55:49

标签: sql postgresql sum window-functions

我有以下数据:

lbid | lbdate     | lbtype        | lbamount
-----+------------+---------------+---------
   1 | 2017-11-01 | Add Plafon    |     20
   2 | 2017-11-02 | Use Balance   |      5
   3 | 2017-11-03 | Add Balance   |      1
   4 | 2017-11-04 | Reduce Plafon |     10
   5 | 2017-11-06 | Use Balance   |      8
   6 | 2017-11-07 | Add Balance   |      2
   7 | 2017-11-08 | Reduce Plafon |      5
   8 | 2017-11-10 | Add Plafon    |     10
   9 | 2017-11-11 | Use Balance   |      1
  10 | 2017-11-12 | Reduce Plafon |      5

基本上我期望的最终结果如下:

lbid | lbdate     | lbtype        | lbamount  | sumplafon | sumbalance
-----+------------+---------------+-----------+-----------+-------------
   1 | 2017-11-01 | Add Plafon    |     20    |       20  |        20
   2 | 2017-11-02 | Use Balance   |      5    |       20  |        15
   3 | 2017-11-03 | Add Balance   |      1    |       20  |        16
   4 | 2017-11-04 | Reduce Plafon |     10    |       10  |        10
   5 | 2017-11-06 | Use Balance   |      8    |       10  |         2
   6 | 2017-11-07 | Add Balance   |      2    |       10  |         4
   7 | 2017-11-08 | Reduce Plafon |      5    |        5  |         4
   8 | 2017-11-10 | Add Plafon    |     10    |       15  |        14
   9 | 2017-11-11 | Use Balance   |      1    |       15  |        15
  10 | 2017-11-12 | Reduce Plafon |      5    |       10  |        10

sumplafon是所有lbamount的总和,其中lbtype是Add Balance(正数)和Reduce Balance(负数,以减去sumplafon)。

我已经这样做了。

sum(
    case
        when "lbtype" = 'Add Plafon' then "lbamount"
        when "lbtype" = 'Reduce Plafon' then -1 * "lbamount"
        else 0
    end
) over (order by "lbdate") sumplafon

并且总和是所有lbamount的总和,其中lbtype是Add Plafon(postive),Use Balance(postive),Use Balance(negative),但每次lbtype Reduce Plafon被找到时,sumbalance将被重置为sumplafon,如果sumbalance大于sumplafon。

例如lbid 4,其中lbtype是Reduce Plafon,总和是16并且它大于sumplafon 10,因此需要将sumbalance重置为其10的sumplafon,然后再次继续sumbalance的累积和。

我尝试通过首先在cte中准备这个小组,并计算这样的数量。

count(
   case when "lbtype" = 'Reduce Plafon' then 1 else null end
) over (order by "lbdate") countplafon

然后在第二个cte中我通过第一个cte中的countplafon使用分区求和,如下所示:

sum(
    case
        when "lbtype" = 'Add Plafon' or "lbtype" = 'Add Balance' then "lbamount"
        when "lbtype" = 'Use Balance' then -1 * "lbamount"
        else 0
    end
) over (partition by "countplafon" order by "lbdate") sumbalance

但结果只是从开始重置平衡,因为它使用了countplafon组。

lbid | lbdate     | lbtype        | lbamount  | countplafon |sumplafon  | sumbalance
-----+------------+---------------+-----------+-----------+-------------|-----------
   1 | 2017-11-01 | Add Plafon    |     20    |           0 |       20  |        20
   2 | 2017-11-02 | Use Balance   |      5    |           0 |       20  |        15
   3 | 2017-11-03 | Add Balance   |      1    |           0 |       20  |        16
   4 | 2017-11-04 | Reduce Plafon |     10    |           1 |       20  |         0
   5 | 2017-11-06 | Use Balance   |      8    |           1 |       20  |        -8
   6 | 2017-11-07 | Add Balance   |      2    |           1 |       20  |        -6
   7 | 2017-11-08 | Reduce Plafon |      5    |           2 |       20  |         0
   8 | 2017-11-10 | Add Plafon    |     10    |           2 |       20  |        10
   9 | 2017-11-11 | Use Balance   |      1    |           2 |       20  |         9
  10 | 2017-11-12 | Reduce Plafon |      5    |           3 |       20  |         0

这是sqlfiddle

这是sql。

with
    cte_runningnumbers1
    as (
        select
            "lbid",
            "lbdate",
            "lbtype",
            "lbamount",
            count(
                case when "lbtype" = 'Reduce Plafon' then 1 else null end
            ) over (order by "lbdate") countplafon,
            sum(
                case
                    when "lbtype" = 'Add Plafon' then "lbamount"
                    when "lbtype" = 'Reduce Plafon' then -1 * "lbamount"
                    else 0
                end
            ) over (order by "lbdate") sumplafon
        from    "lb"
    ),
    cte_runningnumbers2 as (
        select
            *,
            sum(
                case
                    when "lbtype" = 'Add Plafon' or "lbtype" = 'Add Balance' then "lbamount"
                    when "lbtype" = 'Use Balance' then -1 * "lbamount"
                    else 0
                end
            ) over (partition by "countplafon" order by "lbdate") sumbalance
        from    "cte_runningnumbers1"
    )
select  *
from    cte_runningnumbers2

我正在关注此SO question,但我仍然对如何解决问题感到困惑。

我需要做的最后一步是将它添加到之前的平衡或sumplafon(如果sumbalance大于sumplafon),但我不知道该怎么做。任何人都可以帮助我吗?

1 个答案:

答案 0 :(得分:1)

创建custom aggregate function.将逻辑置于状态转换函数中:

create or replace function lb_agg_fun(sumbalance numeric, lbtype text, lbamount numeric)
returns numeric language sql as $$
    select case
        when lbtype in ('Add Plafon', 'Add Balance') then sumbalance + lbamount
        when lbtype = 'Use Balance' then sumbalance - lbamount
        else case
            when lbamount < sumbalance then lbamount
            else sumbalance
        end
    end;
$$;

Create an aggregate:

create aggregate lb_agg(text, numeric) (
    sfunc = lb_agg_fun,
    stype = numeric,
    initcond = 0
);

并使用它:

select *, lb_agg(lbtype, lbamount) over (order by lbdate) as sumbalance
from lb;

 lbid |   lbdate   |    lbtype     | lbamount | sumbalance 
------+------------+---------------+----------+------------
    1 | 2017-11-01 | Add Plafon    |       20 |         20
    2 | 2017-11-02 | Use Balance   |        5 |         15
    3 | 2017-11-03 | Add Balance   |        1 |         16
    4 | 2017-11-04 | Reduce Plafon |       10 |         10
    5 | 2017-11-06 | Use Balance   |        8 |          2
    6 | 2017-11-07 | Add Balance   |        2 |          4
    7 | 2017-11-08 | Reduce Plafon |        5 |          4
    8 | 2017-11-10 | Add Plafon    |       10 |         14
    9 | 2017-11-11 | Use Balance   |        1 |         13
   10 | 2017-11-12 | Reduce Plafon |        5 |          5
(10 rows)