计算产品库存的加权平均成本

时间:2014-03-15 16:59:57

标签: sql postgresql recursive-query window-functions postgresql-9.3

我必须计算我的产品库存成本,因此对于每次购买后的每件商品,我都必须重新计算 Weighted Average Cost

我得到了一个视图,它会在每次进/出后为我提供当前产品的库存:

document_type   document_date   product_id  qty_out qty_in  price       row_num stock_balance
SI              01/01/2014      52          0       600     1037.28     1           600
SI              01/01/2014      53          0       300     1357.38     2           300
LC              03/02/2014      53          100     0       1354.16     3           200
LC              03/02/2014      53          150     0       1355.25     4           50
LC              03/02/2014      52          100     0       1035.26     5           500
LC              03/02/2014      52          200     0       1035.04     6           300
LF              03/02/2014      53          0       1040    1356.44     7           1090
LF              03/02/2014      52          0       1560    1045        8           1860
LC              04/02/2014      52          120     0       1039.08     9           1740
LC              04/02/2014      53          100     0       1358.95     10          990
LF              04/02/2014      52          0       600     1038.71     11          2340
LF              04/02/2014      53          0       1040    1363.3      12          2030
LC              05/02/2014      52          100     0       1037.78     13          2240
LF              15/03/2014      53          0       20      1365.87     14          2050
LF              15/03/2014      52          0       50      1054.19     15          2290

我想添加一个如上所示的计算WAC字段:

document_type   document_date   product_id  qty_out qty_in  price           row_num     stock_balance   WAC 
SI              01/01/2014      52          0       600     1 037,28        1           600             1037,28000000000
SI              01/01/2014      53          0       300     1 357,38        2           300             1357,38000000000
LC              03/02/2014      53          100     0       1 354,16        3           200             1357,38000000000
LC              03/02/2014      53          150     0       1 355,25        4           50              1357,38000000000
LC              03/02/2014      52          100     0       1 035,26        5           500             1037,28000000000
LC              03/02/2014      52          200     0       1 035,04        6           300             1037,28000000000
LF              03/02/2014      53          0       1040    1 356,44        7           1090            1356,48311926606 --((1357,38*50)+(1040*1356,44))/(1090)
LF              03/02/2014      52          0       1560    1 045,00        8           1860            1043,75483870968 --((1037,28*300)+(1560*1045))/(1860)
LC              04/02/2014      52          120     0       1 039,08        9           1740            1043,75483870968
LC              04/02/2014      53          100     0       1 358,95        10          990             1356,48311926606
LF              04/02/2014      52          0       600     1 038,71        11          2340            1042,46129032258 --((1043,75483870968*1740)+(600*1038,71))/(2340)
LF              04/02/2014      53          0       1040    1 363,30        12          2030            1359,97000000000 --((1356,48311926606*990)+(1040*1363,3))/(2030)
LC              05/02/2014      52          100     0       1 037,78        13          2240            1042,46129032258
LF              15/03/2014      53          0       20      1 365,87        14          2050            1360,03301857239 --((1359,97551136621*2030)+(20*1365,87))/2050
LF              15/03/2014      52          0       50      1 054,19        15          2290            1042.71737568672 --((1042.46129032258*2240)+(50*1054.19))/2290

每种产品只有一种且只有一种文档类型'SI'(初始库存),与之关联的价格为initial WAC

这是一个SQL Fiddle样本。

如果有人可以帮忙解决这个问题,我无法理解。

编辑:我已经通过在小数点后面显示9个数字来提高精确度来更新计算出的数字。

5 个答案:

答案 0 :(得分:2)

您需要使用递归CTE:

SQLFiddle

with recursive
stock_temp as (
  select 
    *, 
    row_number() over(partition by product_id order by row_num) as rn
  from 
    stock_table 
)

,cte as (
  select 
    document_type, document_date, 
    product_id, qty_out, qty_in, price, 
    row_num, stock_balance, rn, 
    price as wac
  from 
    stock_temp where document_type = 'SI'

  union all

  select 
    sub.document_type, sub.document_date,
    sub.product_id, sub.qty_out,  sub.qty_in, sub.price,
    sub.row_num, sub.stock_balance,  sub.rn,
    case when sub.qty_in = 0 then main.wac else 
    ((sub.stock_balance - sub.qty_in) * main.wac + sub.qty_in * sub.price) 
      / ((sub.stock_balance - sub.qty_in)  + sub.qty_in) end as wac
  from 
    cte as main
    join stock_temp as sub 
      on (main.product_id = sub.product_id and main.rn + 1 = sub.rn)
)

select * from cte

答案 1 :(得分:0)

我在这个移动平均线上花了几个小时!主要是因为Postgresql documentation on window functions根据不可靠的窗口函数first / last / nth_value: This is likely to give unhelpful results for nth_value and particularly last_value.

答案是不完整的。要做的事情:

  • 当qty_in = 0(现在它被清零)时,使WAC保持相同的值
  • WAC不是根据之前的WAC计算的,而是根据“之前的”价格计算的,这就是为什么我计算的WAC与您提供的WAC之间的差异越来越大

SQLFiddle

答案 2 :(得分:0)

这是我用函数做的:

CREATE TYPE stock_table_with_wac AS
   (document_type character varying,
    document_date date,
    product_id bigint,
    qty_out double precision,
    qty_in double precision,
    price double precision,
    row_num bigint,
    stock_balance double precision,
    wac double precision);


CREATE OR REPLACE FUNCTION calculate_wac_value()
  RETURNS SETOF stock_table_with_wac AS
$BODY$
    DECLARE
    r_article stock_table_with_wac%rowtype;--maintain the liste of all products with there wac's value
    r_in_out_article stock_table_with_wac%rowtype;--Each other records
    BEGIN
    --For each products
    FOR r_article IN SELECT *, price FROM stock_table where document_type='SI' order by row_num
        LOOP
        return next r_article; 
        FOR r_in_out_article IN SELECT * FROM stock_table where document_type<>'SI' and product_id=r_article.product_id order by row_num
        LOOP
        --If there is an entry calculate the wac
        if r_in_out_article.qty_in >0 then 
            r_in_out_article.wac:=((r_article.price * (r_in_out_article.stock_balance - r_in_out_article.qty_in)) + (r_in_out_article.qty_in * r_in_out_article.price))/(r_in_out_article.stock_balance);       
            --Update the wac value of the product
            r_article.price:= r_in_out_article.wac;
        else --The waca value still inchanged:      
            r_in_out_article.wac:= r_article.price;
        end if;     
            RETURN NEXT r_in_out_article; -- return current row with caluculated wac if any
        END LOOP;
        return next r_article;
        END LOOP;
        RETURN;
    END
    $BODY$
  LANGUAGE plpgsql VOLATILE
  COST 100
  ROWS 1000;
ALTER FUNCTION calculate_wac_value()
  OWNER TO postgres;

select * from calculate_wac_value();

似乎输出正确。像这样处理是个好主意吗?

答案 3 :(得分:0)

在PostgreSQL的C中已经有一个聚合函数可用,它的计算速度可能比SQL中的任何解决方案快得多:

https://github.com/Kozea/weighted_mean

答案 4 :(得分:-2)

我已经使用了下一篇文章中提供的用于FIFO逻辑的数据。

https://www.red-gate.com/simple-talk/sql/performance/set-based-speed-phreakery-the-fifo-stock-inventory-sql-problem/

在这里,考虑了回报,但是我以平均价格来获取。

这是表结构:

ID  ArticleID   TranDate    TranCode    Items   Price   WACPrice    WACRunningTotal
1   11782   2009-01-01 00:00:41.000 IN  809 256.82  256.82  NULL
2   16967   2009-01-01 00:00:50.000 IN  372 134.44  134.44  NULL
3   13078   2009-01-01 00:01:21.000 IN  532 201.69  201.69  NULL
4   10918   2009-01-01 00:01:34.000 IN  717 348.79  348.79  NULL
5   18871   2009-01-01 00:01:34.000 IN  1045    88.25   88.25   NULL
6   22379   2009-01-01 00:03:01.000 IN  401 326.59  326.59  NULL
7   24049   2009-01-01 00:03:24.000 IN  222 54.54   54.54   NULL
8   12570   2009-01-01 00:03:33.000 IN  731 29.25   29.25   NULL
9   10327   2009-01-01 00:03:33.000 IN  407 222.69  222.69  NULL
10  21548   2009-01-01 00:03:49.000 IN  400 254.05  254.05  NULL
11  15155   2009-01-01 00:03:51.000 IN  719 320.02  320.02  NULL
12  22706   2009-01-01 00:04:00.000 IN  331 25.91   25.91   NULL
13  19126   2009-01-01 00:04:16.000 IN  289 305.47  305.47  NULL
14  21722   2009-01-01 00:04:39.000 IN  434 3.80    3.80    NULL
15  20811   2009-01-01 00:05:57.000 IN  1043    316.57  316.57  NULL
16  21998   2009-01-01 00:06:01.000 IN  1009    15.18   15.18   NULL
17  12928   2009-01-01 00:06:45.000 IN  1122    265.71  265.71  NULL
18  14150   2009-01-01 00:07:36.000 IN  730 148.91  148.91  NULL
19  22307   2009-01-01 00:08:09.000 IN  986 184.38  184.38  NULL
20  17472   2009-01-01 00:08:34.000 IN  1182    62.73   62.73   NULL

请遵循上述文章中的ddl语句。

我使用了Cte,但是对于1000001条记录,它使maxrecursion计数最大化。 因此,我创建了一个程序,该程序由一个项目ID执行,该项目ID可以由另一个过程进行迭代。

我在库存表中添加了两列WACPrice和WACRunningTotal。

请在下面找到我的代码:

    alter proc sp_GetInventoryDetails_ByWAC
-- sp_GetInventoryDetails_ByWAC 10017
@ArticleId int
as
begin

-- select * from #stocktemp order by trandate 
--select *, 0 as WACRunningTotal into #StockTemp from stock where articleid=10000 order by TranDate;
;WITH y AS 
(
  SELECT articleid,TranDate,trancode, items, rn = ROW_NUMBER() OVER (ORDER BY TranDate)
    FROM stock where ArticleID =@ArticleId
), x AS
(
    SELECT articleid, TranDate,trancode, rn, items, rt = items
      FROM y
      WHERE rn = 1
    UNION ALL
    SELECT y.articleid,y.TranDate,y.trancode, y.rn, y.items,case when y.TranCode='OUT' then x.rt - y.items else x.rt+y.Items end
      FROM x INNER JOIN y
      ON y.rn = x.rn + 1
)

update st set st.WACRunningTotal=x.rt
from stock st
inner join x on x.ArticleID=st.ArticleID and x.TranDate=st.TranDate and x.TranCode=st.TranCode and isnull(st.WACRunningTotal,0)=0

OPTION (MAXRECURSION 0);




;WITH    StockCTE
          AS (SELECT    articleid,
                        items,
                        WACRunningTotal,
                        WACPrice,
                        trandate,
                        ROW_NUMBER() OVER (PARTITION BY articleid ORDER BY trandate) RowNum
              FROM      stock where ArticleID =@ArticleId),

/* CleanStock - A recursive CTE. This runs down the list of values for each stock, checking the Price column, if it is null it gets the previous non NULL value.*/
        CleanStock
          AS (SELECT    articleid,
                        items,
                        WACRunningTotal,
                        ISNULL(WACPrice ,0) WACPrice ,/* Ensure we start with no NULL values for each stock */
                        trandate,
                        RowNum
              FROM      StockCTE cur
              WHERE     RowNum = 1
              UNION ALL
              SELECT    Curr.articleid,
                        curr.items,
                        Curr.WACRunningTotal,
                        case when Curr.WACPrice=0 then  prev.WACPrice else ((curr.WACPrice*curr.Items)+(prev.WACRunningTotal*prev.WACPrice))/curr.WACRunningTotal end as WACPrice,
                        Curr.trandate,
                        Curr.RowNum
              FROM      StockCTE curr
              INNER JOIN CleanStock prev ON curr.articleid = prev.articleid
                                           AND curr.RowNum = prev.RowNum + 1)

/* Update the base table using the result set from the recursive CTE */
    UPDATE trg
    SET trg.WACPrice = src.WACPrice
    FROM    stock trg
    INNER JOIN CleanStock src ON trg.articleid = src.articleid
                                AND trg.trandate = src.trandate and trg.ArticleID=@ArticleId 

/* Display the results */
SELECT * FROM stock where ArticleID=@ArticleId order by TranDate

--drop table stock



end

这将产生结果

StockID ArticleID   TranDate    TranCode    Items   Price   WACPrice    WACRunningTotal
20119   10017   2009-01-06 10:37:40.000 IN  1088    27.91   27.91   1088
69802   10017   2009-01-20 00:40:44.000 OUT 39  0.00    27.91   1049
71338   10017   2009-01-20 10:42:25.000 OUT 967 0.00    27.91   82
91638   10017   2009-01-25 21:54:14.000 OUT 75  0.00    27.91   7
130881  10017   2009-02-05 14:38:34.000 IN  1061    312.18  310.3168    1068
174059  10017   2009-02-17 09:00:34.000 OUT 779 0.00    310.3168    289
188516  10017   2009-02-21 06:46:01.000 OUT 264 0.00    310.3168    25
192423  10017   2009-02-22 08:53:40.000 RET 8   0.00    310.3168    33
228070  10017   2009-03-04 02:24:26.000 OUT 30  0.00    310.3168    3
235671  10017   2009-03-06 03:59:49.000 IN  750 9.78    10.9773 753
247309  10017   2009-03-09 08:36:20.000 OUT 44  0.00    10.9773 709
249207  10017   2009-03-09 21:38:26.000 IN  256 155.09  49.2082 965
253665  10017   2009-03-11 02:42:14.000 OUT 379 0.00    49.2082 586
254121  10017   2009-03-11 05:59:00.000 OUT 527 0.00    49.2082 59
263772  10017   2009-03-13 20:46:09.000 OUT 11  0.00    49.2082 48
271272  10017   2009-03-15 22:09:04.000 OUT 34  0.00    49.2082 14
273709  10017   2009-03-16 13:51:34.000 OUT 1   0.00    49.2082 13
274065  10017   2009-03-16 16:13:02.000 OUT 5   0.00    49.2082 8
275679  10017   2009-03-17 02:38:20.000 IN  1165    217.19  216.0443    1173
280661  10017   2009-03-18 10:59:20.000 OUT 1053    0.00    216.0443    120

我现在正在寻找COGS。