Inventory Average Cost Calculation in SQL

前端 未结 2 1634
别那么骄傲
别那么骄傲 2020-12-09 12:31

I want to compute inventory costs using average value, and I\'m somewhat stuck here...

Consider a simple transaction table tr: (ids are autoincrement, n

相关标签:
2条回答
  • 2020-12-09 12:59

    There is a problem with Richard's model clause query. It is doing 1000 iterations without an UNTIL clause. After four iterations the end result is achieved already. The next 996 iterations consume CPU power, but do nothing.

    Here you can see that the query is done processing after 4 iterations with the current data set:

    SQL> select order_id
      2       , volume
      3       , price
      4       , total_vol
      5       , total_costs
      6       , unit_costs
      7    from ( select order_id
      8                , volume
      9                , price
     10                , volume total_vol
     11                , 0.0 total_costs
     12                , 0.0 unit_costs
     13                , row_number() over (order by order_id) rn
     14             from costs
     15            order by order_id
     16         )
     17   model
     18         dimension by (order_id)
     19         measures (volume, price, total_vol, total_costs, unit_costs)
     20         rules iterate (4)
     21         ( total_vol[any] = volume[cv()] + nvl(total_vol[cv()-1],0.0)
     22         , total_costs[any]
     23           = case SIGN(volume[cv()])
     24             when -1 then total_vol[cv()] * nvl(unit_costs[cv()-1],0.0)
     25             else volume[cv()] * price[cv()] + nvl(total_costs[cv()-1],0.0)
     26             end
     27         , unit_costs[any] = total_costs[cv()] / total_vol[cv()]
     28         )
     29   order by order_id
     30  /
    
      ORDER_ID     VOLUME      PRICE  TOTAL_VOL TOTAL_COSTS UNIT_COSTS
    ---------- ---------- ---------- ---------- ----------- ----------
             1       1000        100       1000      100000        100
             2       -500        110        500       50000        100
             3       1500         80       2000      170000         85
             4       -100        150       1900      161500         85
             5       -600        110       1300      110500         85
             6        700        105       2000      184000         92
    
    6 rows selected.
    

    It needs 4 iterations and not 6, because automatic order is used, and each iteration tries to adjust all 6 rows.

    You are far more performant if you use just as many iterations as there are rows and each iteration adjusts just one row. You can also skip the subquery and then the final query becomes:

    SQL> select order_id
      2       , volume
      3       , price
      4       , total_vol
      5       , total_costs
      6       , unit_costs
      7    from costs
      8   model
      9         dimension by (row_number() over (order by order_id) rn)
     10         measures (order_id, volume, price, type, 0 total_vol, 0 total_costs, 0 unit_costs)
     11         rules iterate (1000) until (order_id[iteration_number+2] is null)
     12         ( total_vol[iteration_number+1]
     13           = nvl(total_vol[iteration_number],0) + volume[iteration_number+1]
     14         , total_costs[iteration_number+1]
     15           = case type[iteration_number+1]
     16             when 'B' then volume[iteration_number+1] * price[iteration_number+1] + nvl(total_costs[iteration_number],0)
     17             when 'S' then total_vol[iteration_number+1] * nvl(unit_costs[iteration_number],0)
     18             end
     19         , unit_costs[iteration_number+1]
     20           = total_costs[iteration_number+1] / total_vol[iteration_number+1]
     21         )
     22   order by order_id
     23  /
    
      ORDER_ID     VOLUME      PRICE  TOTAL_VOL TOTAL_COSTS UNIT_COSTS
    ---------- ---------- ---------- ---------- ----------- ----------
             1       1000        100       1000      100000        100
             2       -500        110        500       50000        100
             3       1500         80       2000      170000         85
             4       -100        150       1900      161500         85
             5       -600        110       1300      110500         85
             6        700        105       2000      184000         92
    
    6 rows selected.
    

    Hope this helps.

    Regards,
    Rob.

    EDIT Some proof to backup my claim:

    SQL> create procedure p1 (p_number_of_iterations in number)
      2  is
      3  begin
      4    for x in 1 .. p_number_of_iterations
      5    loop
      6      for r in
      7      ( select order_id
      8             , volume
      9             , price
     10             , total_vol
     11             , total_costs
     12             , unit_costs
     13          from ( select order_id
     14                      , volume
     15                      , price
     16                      , volume total_vol
     17                      , 0.0 total_costs
     18                      , 0.0 unit_costs
     19                      , row_number() over (order by order_id) rn
     20                   from costs
     21                  order by order_id
     22               )
     23         model
     24               dimension by (order_id)
     25               measures (volume, price, total_vol, total_costs, unit_costs)
     26               rules iterate (4)
     27               ( total_vol[any] = volume[cv()] + nvl(total_vol[cv()-1],0.0)
     28               , total_costs[any]
     29                 = case SIGN(volume[cv()])
     30                   when -1 then total_vol[cv()] * nvl(unit_costs[cv()-1],0.0)
     31                   else volume[cv()] * price[cv()] + nvl(total_costs[cv()-1],0.0)
     32                   end
     33               , unit_costs[any] = total_costs[cv()] / total_vol[cv()]
     34               )
     35         order by order_id
     36      )
     37      loop
     38        null;
     39      end loop;
     40    end loop;
     41  end p1;
     42  /
    
    Procedure created.
    
    SQL> create procedure p2 (p_number_of_iterations in number)
      2  is
      3  begin
      4    for x in 1 .. p_number_of_iterations
      5    loop
      6      for r in
      7      ( select order_id
      8             , volume
      9             , price
     10             , total_vol
     11             , total_costs
     12             , unit_costs
     13          from costs
     14         model
     15               dimension by (row_number() over (order by order_id) rn)
     16               measures (order_id, volume, price, type, 0 total_vol, 0 total_costs, 0 unit_costs)
     17               rules iterate (1000) until (order_id[iteration_number+2] is null)
     18               ( total_vol[iteration_number+1]
     19                 = nvl(total_vol[iteration_number],0) + volume[iteration_number+1]
     20               , total_costs[iteration_number+1]
     21                 = case type[iteration_number+1]
     22                   when 'B' then volume[iteration_number+1] * price[iteration_number+1] + nvl(total_costs[iteration_number],0)
     23                   when 'S' then total_vol[iteration_number+1] * nvl(unit_costs[iteration_number],0)
     24                   end
     25               , unit_costs[iteration_number+1]
     26                 = total_costs[iteration_number+1] / total_vol[iteration_number+1]
     27               )
     28         order by order_id
     29      )
     30      loop
     31        null;
     32      end loop;
     33    end loop;
     34  end p2;
     35  /
    
    Procedure created.
    
    SQL> set timing on
    SQL> exec p1(1000)
    
    PL/SQL procedure successfully completed.
    
    Elapsed: 00:00:01.32
    SQL> exec p2(1000)
    
    PL/SQL procedure successfully completed.
    
    Elapsed: 00:00:00.45
    SQL> exec p1(1000)
    
    PL/SQL procedure successfully completed.
    
    Elapsed: 00:00:01.28
    SQL> exec p2(1000)
    
    PL/SQL procedure successfully completed.
    
    Elapsed: 00:00:00.43
    
    0 讨论(0)
  • 2020-12-09 13:07

    You can use the MODEL clause to do this recursive calculation

    Create sample table and insert data

    create table costs (order_id int, volume int, price numeric(16,4), type char(1));
    
    insert into costs (order_id, volume, price) values (1,1000,100);
    insert into costs (order_id, volume, price) values (2,-500,110);
    insert into costs (order_id, volume, price) values (3,1500,80);
    insert into costs (order_id, volume, price) values (4,-100,150);
    insert into costs (order_id, volume, price) values (5,-600,110);
    insert into costs (order_id, volume, price) values (6,700,105);
    

    The query (EDITED changing rules iterate(1000) to rules automatic order implements the MODEL clause as it is intended to function, i.e. top to bottom sequentially. It also took the query from 0.44s to 0.01s!)

    select order_id, volume, price, total_vol, total_costs, unit_costs
        from (select order_id, volume, price,
                     volume total_vol,
                     0.0 total_costs,
                     0.0 unit_costs,
                     row_number() over (order by order_id) rn
              from costs order by order_id)
       model
             dimension by (order_id)
             measures (volume, price, total_vol, total_costs, unit_costs)
             rules automatic order -- iterate(1000)
             ( total_vol[any] = volume[cv()] + nvl(total_vol[cv()-1],0.0),
               total_costs[any] =
                        case SIGN(volume[cv()])
                        when -1 then total_vol[cv()] * nvl(unit_costs[cv()-1],0.0)
                        else volume[cv()] * price[cv()] + nvl(total_costs[cv()-1],0.0)
                        end,
               unit_costs[any] = total_costs[cv()] / total_vol[cv()]
             )
       order by order_id
    

    Output

    ORDER_ID VOLUME     PRICE      TOTAL_VOL   TOTAL_COSTS   UNIT_COSTS
    1        1000       100        1000        100000        100
    2        -500       110        500          50000        100
    3        1500        80        2000        170000        85
    4        -100       150        1900        161500        85
    5        -600       110        1300        110500        85
    6        700        105        2000        184000        92
    

    This site has a good tutorial on the MODEL clause

    • http://www.sqlsnippets.com/en/topic-11663.html


    The EXCEL sheet for the data above would look like this, with the formula extended downwards

        A         B       C      D          E                         F
     ---------------------------------------------------------------------------
    1|  order_id  volume  price  total_vol  total_costs               unit_costs
    2|                                   0                         0           0
    3|  1           1000    100  =C4+E3     =IF(C4<0,G3*E4,F3+C4*D4)  =F4/E4
    4|  2           -500    110  =C5+E4     =IF(C5<0,G4*E5,F4+C5*D5)  =F5/E5
    5|  3           1500     80  =C6+E5     =IF(C6<0,G5*E6,F5+C6*D6)  =F6/E6
    6|  4           -100    150  =C7+E6     =IF(C7<0,G6*E7,F6+C7*D7)  =F7/E7
    7|  5           -600    110  =C8+E7     =IF(C8<0,G7*E8,F7+C8*D8)  =F8/E8
    8|  6           700     105  =C9+E8     =IF(C9<0,G8*E9,F8+C9*D9)  =F9/E9
    
    0 讨论(0)
提交回复
热议问题