Calculating the Weighted Average Cost of products stock

前端 未结 5 1546
甜味超标
甜味超标 2020-12-28 23:12

I have to calculate my products stock cost, so for every product after each buy, i have to recalculate the Weighted Average Cost.

I got a view thats

相关标签:
5条回答
  • 2020-12-28 23:45

    Here what i did using function:

    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();
    

    It seems to have a correct output. Is it a good idea to process like this?

    0 讨论(0)
  • 2020-12-28 23:57

    I have spent several hours on this moving average! Mainly because of the not reliable window function first/last/nth_value, according to Postgresql documentation on window functions: This is likely to give unhelpful results for nth_value and particularly last_value.

    The answer is incomplete. Things to do:

    • make WAC stay the same value when qty_in = 0 (now it is zeroed out)
    • WAC is not calculated based on previous WAC but on "previous" price, that's why there are increasing differences between WAC calculated by me and presented by You

    SQLFiddle

    0 讨论(0)
  • 2020-12-29 00:00

    You need to use recursive 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
    
    0 讨论(0)
  • 2020-12-29 00:02

    There already is a aggregate function in C for PostgreSQL available, and it will probably calculate much faster than any solution in SQL:

    https://github.com/Kozea/weighted_mean

    0 讨论(0)
  • 2020-12-29 00:07

    I have used the data provided in the following article which was meant for FIFO logic.

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

    Here, the returns are considered, but I take them at average price.

    Here is the table structure:

    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
    

    please follow the ddl statement in the above mentioned post.

    I have use Cte, but for 1000001 records, it maxes out the maxrecursion count. So I created a procedure to execute by one item id which can be iterated by another procedure.

    I have added two columns to the stock table, WACPrice and WACRunningTotal.

    please find my code below:

        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
    

    This will yield result as

    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
    

    I am now working to find the COGS.

    0 讨论(0)
提交回复
热议问题