Рассчитать цену в FIFO SQL

#postgresql #fifo

#postgresql #fifo

Вопрос:

Использование Postgres 11

Используя FIFO, я хотел бы рассчитать цену товаров, взятых из инвентаря, чтобы отслеживать стоимость общего инвентаря.

Набор данных выглядит следующим образом:

 ID  | prodno | amount_purchased | amount_taken | price | created_at
uuid  13976    10                 NULL           130     <timestamp>  
uuid  13976    10                 NULL           150     <timestamp>
uuid  13976    10                 NULL           110     <timestamp>
uuid  13976    10                 NULL           100     <timestamp>
uuid  13976    NULL                 14           ??      <timestamp>
  

Перед вставкой строки с amount_taken мне нужно было бы рассчитать, какова средняя цена каждого из 14 элементов, которая в данном случае была бы 135,71 , но как рассчитать это относительно эффективно?

Моей первоначальной идеей было делегировать строки в две временные таблицы, одну, где amount_taken равно нулю, и другую, где оно не равно нулю, а затем рассчитать все строки вниз, но, учитывая, что эта таблица может стать довольно большой, довольно быстрой (поскольку в большинстве случаев из инвентаря будет взят только 1 элемент), я беспокоюсь, что это было бы достойным решением в краткосрочной перспективе, но замедлилось бы по мере увеличения таблицы. Итак, какое интернет-решение лучше?

Ответ №1:

Учитывая эту настройку:

 CREATE TABLE test (
    id int
    , prodno int 
    , quantity numeric
    , price numeric 
    , created_at timestamp
);
INSERT INTO test VALUES
    (1, 13976, 10,    130, NOW())
    , (2, 13976, 10,  150, NOW() '1 hours')
    , (3, 13976, 10,  110, NOW() '2 hours')
    , (4, 13976, 10,  100, NOW() '3 hours')
    , (5, 13976, -14, NULL, NOW() '4 hours')
    , (6, 13976, -1, NULL, NOW() '5 hours')
    , (7, 13976, -10, NULL, NOW() '6 hours')
    ;
  

затем SQL

 SELECT id, prodno, created_at, qty_sold
    -- 5
    , round((cum_sold_cost - coalesce(lag(cum_sold_cost) over w, 0))/qty_sold, 2) as fifo_price 
    , qty_bought, prev_bought, total_cost
    , prev_total_cost
    , cum_sold_cost
    , coalesce(lag(cum_sold_cost) over w, 0) as prev_cum_sold_cost
FROM (
    SELECT id, tneg.prodno, created_at, qty_sold, tpos.qty_bought, prev_bought, total_cost, prev_total_cost
        -- 4
        , round(prev_total_cost   ((tneg.cum_sold - tpos.prev_bought)/(tpos.qty_bought - tpos.prev_bought))*(total_cost-prev_total_cost), 2) as cum_sold_cost 
    FROM (
      SELECT id, prodno, created_at, -quantity as qty_sold
          , sum(-quantity) over w as cum_sold
      FROM test
      WHERE quantity < 0
      WINDOW w AS (PARTITION BY prodno ORDER BY created_at)
    -- 1
    ) tneg 
    LEFT JOIN (
      SELECT prodno
          , sum(quantity) over w as qty_bought
          , coalesce(sum(quantity) over prevw, 0) as prev_bought
          , quantity * price as cost                              
          , sum(quantity * price) over w as total_cost
          , coalesce(sum(quantity * price) over prevw, 0) as prev_total_cost
      FROM test
      WHERE quantity > 0
      WINDOW w AS (PARTITION BY prodno ORDER BY created_at)
          , prevw AS (PARTITION BY prodno ORDER BY created_at ROWS BETWEEN unbounded preceding AND 1 preceding)
    -- 2
    ) tpos 
    -- 3
    ON tneg.cum_sold BETWEEN tpos.prev_bought AND tpos.qty_bought 
        AND tneg.prodno = tpos.prodno
    ) t
WINDOW w AS (PARTITION BY prodno ORDER BY created_at)
  

дает

 | id | prodno | created_at                 | qty_sold | fifo_price | qty_bought | prev_bought | total_cost | prev_total_cost | cum_sold_cost | prev_cum_sold_cost |
|---- -------- ---------------------------- ---------- ------------ ------------ ------------- ------------ ----------------- --------------- --------------------|
|  5 |  13976 | 2019-03-07 21:07:13.267218 |       14 |     135.71 |         20 |          10 |       2800 |            1300 |       1900.00 |                  0 |
|  6 |  13976 | 2019-03-07 22:07:13.267218 |        1 |     150.00 |         20 |          10 |       2800 |            1300 |       2050.00 |            1900.00 |
|  7 |  13976 | 2019-03-07 23:07:13.267218 |       10 |     130.00 |         30 |          20 |       3900 |            2800 |       3350.00 |            2050.00 |
  

  1. tneg содержит информацию о проданных количествах

     | id | prodno | created_at                 | qty_sold | cum_sold |
    |---- -------- ---------------------------- ---------- ----------|
    |  5 |  13976 | 2019-03-07 21:07:13.267218 |       14 |       14 |
    |  6 |  13976 | 2019-03-07 22:07:13.267218 |        1 |       15 |
    |  7 |  13976 | 2019-03-07 23:07:13.267218 |       10 |       25 |
      
  2. tpos содержит информацию о закупленных количествах

     | prodno | qty_bought | prev_bought | cost | total_cost | prev_total_cost |
    |-------- ------------ ------------- ------ ------------ -----------------|
    |  13976 |         10 |           0 | 1300 |       1300 |               0 |
    |  13976 |         20 |          10 | 1500 |       2800 |            1300 |
    |  13976 |         30 |          20 | 1100 |       3900 |            2800 |
    |  13976 |         40 |          30 | 1000 |       4900 |            3900 |
      
  3. Мы сопоставляем строки в tneg со строками в tpos при условии, что cum_sold находится между qty_bought и prev_bought .
    cum_sold — совокупная сумма проданных, qty_bought — совокупная сумма купленных и prev_bought — предыдущее значение qty_bought .

     | id | prodno | created_at                 | qty_sold | cum_sold | qty_bought | prev_bought | total_cost | prev_total_cost | cum_sold_cost |
    |---- -------- ---------------------------- ---------- ---------- ------------ ------------- ------------ ----------------- ---------------|
    |  5 |  13976 | 2019-03-07 21:07:13.267218 |       14 |       14 |         20 |          10 |       2800 |            1300 |       1900.00 |
    |  6 |  13976 | 2019-03-07 22:07:13.267218 |        1 |       15 |         20 |          10 |       2800 |            1300 |       2050.00 |
    |  7 |  13976 | 2019-03-07 23:07:13.267218 |       10 |       25 |         30 |          20 |       3900 |            2800 |       3350.00 |
      
  4. Доля

     ((tneg.cum_sold - tpos.prev_bought)/(tpos.qty_bought - tpos.prev_bought)) as frac
      

    измеряет, какое расстояние cum_sold лежит между qty_bought и prev_bought . Мы используем эту дробь для вычисления
    cum_sold_cost совокупная стоимость, связанная с покупкой cum_sold товаров.
    cum_sold_cost лежит frac расстояние между prev_total_cost и total_cost .

  5. Как только вы получите cum_sold_cost , у вас будет все необходимое для вычисления предельных цен за единицу по FIFO. Для каждой строки tneg разница между cum_sold_cost и ее предыдущим значением является стоимостью qty_sold . Цена FIFO — это просто соотношение этой стоимости и qty_sold .

Комментарии:

1. Спасибо за очень полный ответ. Судя по тому, как вы структурируете запрос, кажется, что вы могли бы решить будущие проблемы, с которыми я бы тоже столкнулся (фрагменты кода, безусловно, могут быть повторно использованы для других запросов)

2. Если я не ошибаюсь, неправильно использовать BETWEEN при объединении tneg и tpo. Это должно быть tneg.cum_sold > tpos.prev_bought И tneg.cum_sold <= tpos. qty_bought. Например, если вы измените количество в строке с идентификатором 6 на 6, вы получите два объединения в одной строке. Но также спасибо вам за этот ответ, он мне очень помог.