I want to implement FIFO Stock model in my inventory.
So I face some problems but before staring query i describe my physical structure of the table.
create table purchase_mas
(
stock_id number
pur_date date,
product_code number,
product_name varchar2(10),
stock_qty number,
stock_rate number(7,2)
);
create table sale_mas
(
sale_id number
sale_date date,
product_code number,
product_name varchar2(10),
sale_qty number,
sale_rate number(7,2)
);
insert into purchase_mas values (1,'01-Jan-17',1,'A',10,110);
insert into purchase_mas values (2,'02-Jan-17',1,'A',20,120);
insert into purchase_mas values (3,'02-Jan-17',2,'B',5,60);
insert into purchase_mas values (4,'03-Jan-17',1,'A',12,70);
insert into purchase_mas values (5,'05-Jan-17',3,'C',30,130);
insert into purchase_mas values (6,'06-Jan-17',2,'B',12,90);
commit;
insert into sale_mas values (11,'03-Jan-17',1,'A',4,130);
insert into sale_mas values (12,'03-Jan-17',1,'A',5,140);
insert into sale_mas values (13,'04-Jan-17',1,'A',7,160);
commit;
I want to like following sale report.
but here following column is just for understanding purpose.
actual_sal_qty, stock_id, remaing_stock, actual_stk_qty, actual_stk_qty_after_sale.
sale_id sale_date product_code product_name qty rate actual_sal_qty stock_id remaing_stock actual_stk_qty actual_stk_qty_after_sale stock_rate
11 03-Jan-17 1 A 4 130 4 1 6 30 26 110
12 03-Jan-17 1 A 5 140 5 1 1 26 21 110
13 04-Jan-17 1 A 7 160 1 1 0 21 14 110
13 04-Jan-17 1 A 7 160 6 2 14 21 14 120
Basic FIFO Stock Model how work?
My Inventory Start date is 01-Jan-17 product A have 30 Qty.
Suppose I Sale qty 4 of product A with 130$ on 03-Jan-17.
When I run sale report i want to know my stock rate so Sale Id 11 is able to get qty from stock id 1. and
Stock Id 1 left 6 Qty and Stock id 2 left 20 Qty and stock rate is 110.
When I sale qty 5 of same product A to other client on same day,
Here Stock id 1 is able to give 5 qty.
When i see sale report, stock rate is 110.
When I purchase product A qty 12 on 03-jan-17 but does not effect on sale report,
When I sale qty 7 of same product A on 04-jan-17,
Here Stock id 1 is able to give only 1 qty.
and stock id 2 is able to give 6 qty
When i see sale report, stock rate is different so stock rate is (( 110 * 1) + ( 120 * 6))/7 here 7 is sale qty .
Stock rate is 118.58.
How it is possible using plsql or cursor programming or select query.
So, you want to apportion sales to stock purchases in a FIFO manner? I.e. sales always consume the oldest stock you have first?
If so, you can do something like this:
- First, calculate the running totals of stock and sales AND the total up to the previous row for each. You can find the prior total with:
rows between unbounded preceding and 1 preceding
In the window clause of the running sum. Then join the stock and sales data joined on product code. Also filter:
- The current stock total >= the previous sale total AND
- The current sale total >= the previous stock total
with stock as (
select p.*,
nvl(sum(stock_qty) over (
partition by product_code
order by pur_date, stock_id
rows between unbounded preceding and 1 preceding
), 0) prev_run_tot,
nvl(sum(stock_qty) over (
partition by product_code
order by pur_date, stock_id
rows between unbounded preceding and current row
), stock_qty) run_tot
from purchase_mas p
), sales as (
select s.*,
sum(sale_qty) over (
partition by product_code
order by sale_date, sale_id
rows between unbounded preceding and current row
) run_tot,
nvl(sum(sale_qty) over (
partition by product_code
order by sale_date, sale_id
rows between unbounded preceding and 1 preceding
), 0) prev_run_tot
from sale_mas s
)
select pur_date, sale_date, stock_qty, sale_qty,
sa.prev_run_tot sale_prev, sa.run_tot sale_tot,
st.prev_run_tot stock_prev, st.run_tot stock_tot
from stock st
join sales sa
on st.product_code = sa.product_code
and sa.run_tot >= st.prev_run_tot
and st.run_tot >= sa.prev_run_tot
order by pur_date, sale_date, sale_id;
PUR_DATE SALE_DATE STOCK_QTY SALE_QTY SALE_PREV SALE_TOT STOCK_PREV STOCK_TOT
01-JAN-0017 00:00:00 03-JAN-0017 00:00:00 10 4 0 4 0 10
01-JAN-0017 00:00:00 03-JAN-0017 00:00:00 10 5 4 9 0 10
01-JAN-0017 00:00:00 04-JAN-0017 00:00:00 10 7 9 16 0 10
02-JAN-0017 00:00:00 04-JAN-0017 00:00:00 20 7 9 16 10 30
Then to assign the correct quantity to each row, return the smallest of:
- The sale quantity
- The stock quantity
- The overall sale total - the prev stock total
- The overall stock total - the prev sale total
To cover for the case where the current sales match the current stock total, also filter where the result of this calculation are > 0.
with stock as (
select p.*,
nvl(sum(stock_qty) over (
partition by product_code
order by pur_date, stock_id
rows between unbounded preceding and 1 preceding
), 0) prev_run_tot,
nvl(sum(stock_qty) over (
partition by product_code
order by pur_date, stock_id
rows between unbounded preceding and current row
), stock_qty) run_tot
from purchase_mas p
), sales as (
select s.*,
sum(sale_qty) over (
partition by product_code
order by sale_date, sale_id
rows between unbounded preceding and current row
) run_tot,
nvl(sum(sale_qty) over (
partition by product_code
order by sale_date, sale_id
rows between unbounded preceding and 1 preceding
), 0) prev_run_tot
from sale_mas s
)
select pur_date, sale_date, stock_qty, sale_qty,
sa.prev_run_tot, sa.run_tot, st.prev_run_tot, st.run_tot,
least (
sale_qty,
stock_qty,
sa.run_tot - st.prev_run_tot,
st.run_tot - sa.prev_run_tot
) qty
from stock st
join sales sa
on st.product_code = sa.product_code
and sa.run_tot >= st.prev_run_tot
and st.run_tot >= sa.prev_run_tot
and least (
sale_qty,
stock_qty,
sa.run_tot - st.prev_run_tot,
st.run_tot - sa.prev_run_tot
) > 0
order by pur_date, sale_date, sale_id;
PUR_DATE SALE_DATE STOCK_QTY SALE_QTY PREV_RUN_TOT RUN_TOT PREV_RUN_TOT RUN_TOT QTY
01-JAN-0017 00:00:00 03-JAN-0017 00:00:00 10 4 0 4 0 10 4
01-JAN-0017 00:00:00 03-JAN-0017 00:00:00 10 5 4 9 0 10 5
01-JAN-0017 00:00:00 04-JAN-0017 00:00:00 10 7 9 16 0 10 1
02-JAN-0017 00:00:00 04-JAN-0017 00:00:00 20 7 9 16 10 30 6
Note: this assumes that you can always assign sales to the next available stock, even if it arrives after the sale and/or you can only make sales if there is stock available. i.e. you don't have the situation where you have 10 stock units available, but sell 20. Or you can, but you assign the 10 left over from the sale to the next stock purchase, even if that arrives weeks later...
HT to Kim Berg Hansen for his FIFO analytic articles, which provided the groundwork for this answer:
http://www.kibeha.dk/2012/11/analytic-fifo-multiplied-part-1.html