#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 |
-
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 |
-
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 |
-
Мы сопоставляем строки в
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 |
-
Доля
((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
. -
Как только вы получите
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, вы получите два объединения в одной строке. Но также спасибо вам за этот ответ, он мне очень помог.