使用递归sql计算平均成本(postgres 9.1)

时间:2013-07-17 04:48:25

标签: sql postgresql recursion

我搜索此网站时最接近的是: Inventory Average Cost Calculation in SQL

但不幸的是,使用模型子句是特定于oracle的。

所以,让我们开始。

有两个表:
- 持有库存交易的人,和
- 持有最新库存估价的那个

我正在尝试使用基于特定日期的平均成本核算方法制作库存评估报告。 按照正常方式进行,从开始计算到特定日期,将产生可变响应时间。
想象一下计算五年的数据(以及数千种不同的库存项目)。 这将需要相当长的时间(我的公司不是硅谷等级。意思是,2核心CPU和8 GB RAM) 所以我向后计算它:从最新的(当前的)回溯到特定的日期。

(每个月会计部门都会检查数据,因此计算将永远只处理1个月的数据。 等于一致不变的表现)


我已将表格合并到下面的脚本中

create table test3 ( rn integer, amt numeric, qty integer, oqty integer);
insert into test3 (rn,amt,qty,oqty) values (0,2260038.16765793,8,0);
insert into test3 (rn,amt,qty,oqty) values (1,1647727.2727,3,0);
insert into test3 (rn,amt,qty,oqty) values (2,2489654.75326715,0,1);
insert into test3 (rn,amt,qty,oqty) values (3,2489654.75326715,0,1);
insert into test3 (rn,amt,qty,oqty) values (4,1875443.6364,1,0);
insert into test3 (rn,amt,qty,oqty) values (5,1647727.2727,3,0);
insert into test3 (rn,amt,qty,oqty) values (6,3012987.01302857,0,1);
insert into test3 (rn,amt,qty,oqty) values (7,3012987.01302857,0,1);

select * from test3; (already sorted desc so rn=1 is the newest transaction)

rn  amt        qty  oqty
0   2260038.168 8   0    --> this is the current average
1   1647727.273 3   0
2   2489654.753 0   1
3   2489654.753 0   1
4   1875443.636 1   0
5   1647727.273 3   0
6   3012987.013 0   1
7   3012987.013 0   1


with recursive 
runsum (id,amt,qty,oqty,sqty,avg) as 
    (select data.id, data.amt, data.qty, data.oqty, data.sqty, data.avg
     from (
        select rn as id,amt,qty, oqty, 
        sum(case when rn=0 then qty else 
             case when oqty=0 then qty*-1 
                else oqty end end) over (order by rn) as sqty, lag(amt) over (order by rn) as avg
          from test3 ) data
         ),
trans (id,amt,qty,oqty,sqty,prevavg,avg) as
    (select id,amt,qty,oqty, sqty,avg,avg
     from runsum 
     union
    select runsum.id,trans.amt,trans.qty, trans.oqty, trans.sqty, lag(trans.avg) over (order by 1), 
    case when runsum.sqty=0 then runsum.amt else 
    ((trans.prevavg*(runsum.sqty+trans.qty))-(runsum.amt*trans.qty)+(trans.prevavg*trans.oqty))/(runsum.sqty+trans.oqty)
    end
    from runsum join trans using (id))
select * 
from trans
where prevavg is null and avg is not null
order by id; 

结果应该是这样的

rn  amt        qty   oqty   sum avg
1   1647727.273 3   0   5   2627424.705
2   2489654.753 0   1   6   2627424.705
3   2489654.753 0   1   7   2627424.705
4   1875443.636 1   0   6   2752754.883
5   1647727.273 3   0   3   3857782.493
6   3012987.013 0   1   4   3857782.493
7   3012987.013 0   1   5   3857782.493

但我得到了这个

id  amt        qty   oqty   sqty    avg
1   1647727.273 3   0   5   2627424.705
2   2489654.753 0   1   6   2627424.705
3   2489654.753 0   1   7   2627424.705
5   1647727.273 3   0   3   3607122.137 --> id=4 is missing thus                       
                                                            screwing the calculation 
                                                       and id=6 in turn dissappears tpp
7   3012987.013 0   1   5   3607122.137

我大吃一惊。 错误在哪里?

感谢您的帮助。

EDITED

平均成本计算方法回溯(给定当前平均值计算最后一笔交易平均值,依此类推至第n次交易)

Avg (n) = ((Avg(n-1) * (Cum Qty(n)+In Qty(n))) - (In Amount(n) * In Qty (n)) + (Avg(n-1) * Out Qty(n))/(Cum Qty(n)+Out Amount(n))

回溯交易的累积数量对于in而言是加减去的。 因此,如果当前数量为8,则之前的数量为3的交易,则该交易的累计数量为5。

要计算最后一笔交易的平均值,我们会使用当前平均值来计算该交易。

@ kordirko帮助当前的答案

with recursive 
runsum (id,amt,qty,oqty,sqty,avg) as 
    (select data.id, data.amt, data.qty, data.oqty, data.sqty, data.avg
     from (
        select rn as id,amt,qty, oqty, 
        sum(case when rn=0 then qty else 
             case when oqty=0 then qty*-1 
                else oqty end end) over (order by rn) as sqty, lag(amt) over (order by rn) as avg
          from test3 ) data
         ),
counter (maximum) as
         (select count(rn)
          from test3
         ),
trans (n, id,amt,qty,oqty,sqty,prevavg,avg) as
    (select 0 n, id,amt,qty,oqty, sqty,avg,avg
      from runsum 
     union 
    select trans.n+1, runsum.id,trans.amt,trans.qty, trans.oqty, trans.sqty, 
    lag(trans.avg) over (order by 1), 
    case when runsum.sqty=0 then runsum.amt else 
    ((trans.prevavg*(runsum.sqty+trans.qty))-(runsum.amt*trans.qty)+(trans.prevavg*trans.oqty))/(runsum.sqty+trans.oqty)
    end
    from runsum join trans using (id)
    where trans.n<(select maximum*2 from counter))
select * 
from trans
where prevavg is null and avg is not null
order by id; 

1 个答案:

答案 0 :(得分:1)

这可能不是你问题的“最佳”答案,但在挣扎于这个棘手的问题时,我碰到了 - 只是偶然 - 一些丑陋的解决办法:)。

点击此SQL Fiddle demo

with recursive 
trans (n, id, amt, qty, oqty, sqty, prevavg, avg) as (
    select 0 n, id, amt, qty, oqty, sqty, avg, avg
    from runsum 
    union 
    select trans.n + 1, runsum.id, trans.amt, trans.qty, trans.oqty, trans.sqty, 
           lag(trans.avg) over (order by 1), 
           case when runsum.sqty=0 then runsum.amt 
                 else 
                 ((trans.prevavg *(runsum.sqty+trans.qty))-(runsum.amt*trans.qty)+(trans.prevavg*trans.oqty))/(runsum.sqty+trans.oqty)
           end
    from runsum 
    join trans using (id)
    where trans.n < 20
)
select * 
from trans
where prevavg is null and avg is not null
order by id; 

问题的根源似乎是递归查询中的UNION子句 阅读此链接:http://www.postgresql.org/docs/8.4/static/queries-with.html
他们写道,对于UNION,递归查询在评估递归查询时会丢弃重复的行。