Have a problem to solve in the SQL Server to generate a Inventory aging report using FIFO Based on SKU & Warehouse. I have attached the schema here.
SKU
TransactionType
WarehouseCode
TransactionDate
Qty
100
IN
WH1
2021-04-30
100
100
IN
WH2
2021-04-30
50
101
IN
WH1
2021-04-30
30
101
IN
WH2
2021-05-01
25
100
OUT
WH2
2021-05-02
30
100
OUT
WH1
2021-05-02
20
100
OUT
WH1
2021-05-04
50
100
OUT
WH2
2021-05-04
20
100
OUT
WH1
2021-05-05
25
100
IN
WH2
2021-05-10
30
100
IN
WH1
2021-05-11
30
101
OUT
WH2
2021-05-12
20
100
OUT
WH1
2021-05-15
30
Based on the above schema structure, i need to develop a inventory aging report based on first in first out (FIFO) and show the remaining qty of each SKU and Warehouse combination and make the previous incoming records remaining quantities as zero.
Expected report format Assuming the report is run on (2021-05-20)
SKU
TransactionType
WarehouseCode
TransactionDate
Qty
Remaining
Aging
100
IN
WH1
2021-04-30
100
0
21
100
IN
WH2
2021-04-30
50
0
21
101
IN
WH1
2021-04-30
30
30
21
101
IN
WH2
2021-05-01
25
5
20
100
IN
WH2
2021-05-10
30
30
11
100
IN
WH1
2021-05-11
50
5
10
Thanks in advance.
You can use window functions to sum up the IN records over time (a cumulative sum), and find out the final total OUT.
Then, if the in_so_far is less than the final_out, you know you have use everything for that line.
The aging just appears to be a datediff() against a constant value?
WITH
cumulative AS
(
SELECT
*,
SUM(CASE WHEN TransactionType = 'IN' THEN Qty ELSE 0 END)
OVER (
PARTITION BY SKU, WarehouseCode
ORDER BY TransactionDate
)
AS qty_in_so_far,
SUM(CASE WHEN TransactionType = 'OUT' THEN Qty ELSE 0 END)
OVER (
PARTITION BY SKU, WarehouseCode
)
AS qty_out_final
FROM
inventory
)
SELECT
*,
CASE
WHEN qty_out_final > qty_in_so_far
THEN 0
WHEN qty_in_so_far - qty_out_final > qty
THEN qty
ELSE qty_in_so_far - qty_out_final
END
AS qty_final,
DATEDIFF(day, TransactionDate, '2021-05-20') + 1 AS aging
FROM
cumulative
WHERE
TransactionType = 'IN'
ORDER BY
TransactionDate,
SKU,
WarehouseCode
Demo... https://dbfiddle.uk/?rdbms=sqlserver_2019&fiddle=b6725edd625001cd874bdb8f37283763
Related
This question already has answers here:
Is there a way to access the "previous row" value in a SELECT statement?
(9 answers)
Closed 7 months ago.
I have a table in SQL Server with sales price data of items on different dates like this:
Item
Date
Price
1
2021-05-01
200
1
2021-06-11
210
1
2021-06-27
225
1
2021-08-01
250
2
2021-02-10
600
2
2021-04-21
650
2
2021-06-17
675
2
2021-07-23
700
I'm creating a table that specifies the start and end date of prices as below:
Item
DateStart
Price
DateEnd
1
2021-05-01
200
2021-06-10
1
2021-06-11
210
2021-06-26
1
2021-06-27
225
2021-07-31
1
2021-08-01
250
Today date
2
2021-02-10
600
2021-04-20
2
2021-04-21
650
2021-06-16
2
2021-06-17
675
2021-07-22
2
2021-07-23
700
Today date
As you can see, the end date is one day less than the next price change date. I also have a calendar table called "DimDates" with one row per day. I had hoped to use joins but it doesn't do what I thought it would do. Any suggestions on how to write the query? I'm using SQL Server 2016.
We can use LEAD() here along with DATEADD():
WITH cte AS (
SELECT *, DATEADD(day, -1, LEAD(Date, 1, GETDATE())
OVER (PARTITION BY Item
ORDER BY Date)) AS LastDate
FROM yourTable
)
SELECT Item, Date AS DateStart, Price, LastDate AS DateEnd
FROM cte
ORDER BY Item, Date;
Demo
Have a problem to solve in the SQL Server to generate a Inventory aging report using FIFO Based on SKU & Warehouse with adjustment support (incoming (+ve) and outgoing (-ve)). I have attached the schema here.
SKU
TransactionType
WarehouseCode
TransactionDate
Qty
100
IN
WH1
2021-04-30
100
100
IN
WH2
2021-04-30
50
101
IN
WH1
2021-04-30
30
101
IN
WH2
2021-05-01
25
100
OUT
WH2
2021-05-02
30
100
OUT
WH1
2021-05-02
20
100
OUT
WH1
2021-05-04
50
100
OUT
WH2
2021-05-04
20
100
OUT
WH1
2021-05-05
25
100
IN
WH2
2021-05-10
30
100
IN
WH1
2021-05-11
30
101
OUT
WH2
2021-05-12
20
100
OUT
WH1
2021-05-15
30
102
IN
WH2
2021-05-15
25
102
OUT
WH2
2021-05-17
2
102
ADJ
WH2
2021-05-18
5
102
ADJ
WH2
2021-05-18
-1
Based on the above schema structure, i need to develop a inventory aging report based on first in first out (FIFO) and show the remaining qty of each SKU and Warehouse combination and make the previous incoming records remaining quantities as zero.
Expected report format Assuming the report is run on (2021-05-20)
SKU
TransactionType
WarehouseCode
TransactionDate
Qty
Remaining
Aging
100
IN
WH1
2021-04-30
100
0
21
100
IN
WH2
2021-04-30
50
0
21
101
IN
WH1
2021-04-30
30
30
21
101
IN
WH2
2021-05-01
25
5
20
100
IN
WH2
2021-05-10
30
30
11
100
IN
WH1
2021-05-11
50
5
10
102
IN
WH2
2021-05-15
25
22
6
102
IN
WH2
2021-05-18
5
5
3
Got the inventory aging report with SQL window function, need assistance adding support for adjustment.
WITH
cumulative AS
(
SELECT
*,
SUM(CASE WHEN TransactionType = 'IN' THEN Qty ELSE 0 END)
OVER (
PARTITION BY SKU, WarehouseCode
ORDER BY TransactionDate
)
AS qty_in_so_far,
SUM(CASE WHEN TransactionType = 'OUT' THEN Qty ELSE 0 END)
OVER (
PARTITION BY SKU, WarehouseCode
)
AS qty_out_final
FROM
inventory
)
SELECT
*,
CASE WHEN qty_out_final > qty_in_so_far THEN 0
WHEN qty_in_so_far - qty_out_final > qty THEN qty
ELSE qty_in_so_far - qty_out_final END AS qty_final,
DATEDIFF(day, TransactionDate, '2021-05-20') + 1 AS aging
FROM
cumulative
WHERE
TransactionType = 'IN'
ORDER BY
TransactionDate,
SKU,
WarehouseCode
SQL Fiddle for sample schema and query - https://dbfiddle.uk/?rdbms=sqlserver_2019&fiddle=de36b2c43d16a0e1e69dd64561422525
Something like this might do it, for the limited data provided.
The first CTE terms tie the 'ADJ' rows to the corresponding prior 'IN' rows, and later simply SUM those group Qtys to derive final 'IN' rows which have the adjustments applied.
Now we use your original logic (with slight corrections) to handle the rest, no longer needing to worry about adjustments.
Limitation: If adjustment rows exist which completely remove more than the prior "IN" transaction, that will require more logic. Unless you need that, the following should be fine.
Also note: Some of your original "Aging" results (in the question) appear wrong, and are not based on the corresponding "IN" row.
The updated fiddle
WITH cte1 AS (
SELECT i.*
, SUM(CASE WHEN TransactionType = 'IN' THEN 1 ELSE 0 END) OVER (PARTITION BY SKU, WarehouseCode ORDER BY TransactionDate) AS grp
FROM inventory AS i
WHERE TransactionType IN ('IN', 'ADJ')
)
, cte2 AS (
SELECT SKU, WarehouseCode, 'IN' AS TransactionType
, MIN(TransactionDate) AS TransactionDate
, SUM(Qty) AS Qty
FROM cte1
GROUP BY WarehouseCode, SKU, grp
UNION
SELECT SKU, WarehouseCode, TransactionType, TransactionDate, Qty
FROM inventory
WHERE TransactionType = 'OUT'
)
, cumulative AS (
SELECT *
, SUM(CASE WHEN TransactionType = 'OUT' THEN Qty ELSE 0 END) OVER (PARTITION BY SKU, WarehouseCode) AS qty_out_final
, SUM(CASE WHEN TransactionType = 'IN' THEN Qty ELSE 0 END) OVER (PARTITION BY SKU, WarehouseCode ORDER BY TransactionDate) AS qty_in_so_far
FROM cte2
)
SELECT SKU, WarehouseCode, TransactionType, TransactionDate
, qty_out_final, qty_in_so_far, Qty
, CASE WHEN qty_out_final >= qty_in_so_far THEN 0
ELSE qty_in_so_far - qty_out_final END AS qty_final
, DATEDIFF(day, TransactionDate, '2021-05-20') + 1 AS aging
FROM cumulative
WHERE TransactionType = 'IN'
ORDER BY TransactionDate, SKU, WarehouseCode
;
The result:
SKU
WarehouseCode
TransactionType
TransactionDate
qty_out_final
qty_in_so_far
Qty
qty_final
aging
100
WH1
IN
2021-04-30
125
100
100
0
21
100
WH2
IN
2021-04-30
50
50
50
0
21
101
WH1
IN
2021-04-30
0
30
30
30
21
101
WH2
IN
2021-05-01
20
25
25
5
20
100
WH2
IN
2021-05-10
50
80
30
30
11
100
WH1
IN
2021-05-11
125
130
30
5
10
102
WH1
IN
2021-05-15
2
29
29
27
6
100
WH1
IN
2021-05-16
125
160
30
35
5
I have this dataset that is structured more or less like the following:
Product
Sales Value
Sales Qty
Sales Date
Period 1 start
Period 1 end
Period 2 start
Period 2 end
XXX
6
2
2021-05-20
2021-05-15
2021-05-21
2021-05-22
2021-06-01
YYY
10
3
2021-05-21
2021-05-15
2021-05-21
2021-05-22
2021-06-01
XXX
3
1
2021-05-23
2021-05-15
2021-05-21
2021-05-22
2021-06-01
XXX
6
2
2021-05-24
2021-05-15
2021-05-21
2021-05-22
2021-06-01
I would like to sum the columns "sales value" and "sales quantity" and create 4 more columns called "Period 1 Sales", "Period 1 Qty", "Period 2 Sales", and "Period 2 Qty".
For example, with the data above, I would get four new columns while rows would be grouped by product:
Product
Period 1 start
Period 1 end
Period 2 start
Period 2 end
Period 1 Sales Value
Period 1 Sales Qty
Period 2 Sales Value
Period 2 Sales Qty
XXX
2021-05-15
2021-05-21
2021-05-22
2021-06-01
6
2
9
3
YYY
2021-05-15
2021-05-21
2021-05-22
2021-06-01
0
0
10
3
I'm using SQL Server and right now I am pretty much stuck.
So far I managed to Cross Join my calendar table and sales table to get the table described in the first matrix.
Can anybody help?
Thanks a lot!
The following provides your desired results, with the exception of your value for YYY where in your sample data the values are in period1 not period 2.
select Product,
Period1start, Period1end, Period1start, Period2end,
Sum(P1sales) Period1Sales, Sum(P1Qty) Period1Qty,
Sum(P2sales) Period2Sales, Sum(P2Qty) Period2Qty
from t
cross apply(values(case when SalesDate between Period1start and Period1end then SalesValue else 0 end) )p1s(P1sales)
cross apply(values(case when SalesDate between Period1start and Period1end then SalesQty else 0 end) )p1q(P1Qty)
cross apply(values(case when SalesDate between Period2start and Period2end then SalesValue else 0 end) )p2s(P2sales)
cross apply(values(case when SalesDate between Period2start and Period2end then SalesQty else 0 end) )p2q(P2Qty)
group by product, Period1start, Period1end, Period1start, Period2end
See example DB<>Fiddle
I have a record of users' purchasing behavior. However, it is long and includes a lot of redundant data. I want to delete orders that purchased and deleted within 5 min
My query so far:
--TABLE 3 COD
select z.user_id,
z.date,
z.actions,
x.name,
x.email,
x.address
sum(z.debit) over (partition by z.seller_id order by z.created_at) -
sum(z.credit) over (partition by z.seller_id order by z.created_at)
as balance
from table_1 z
left join
table_2 x
on z.seller_id = x.uid
order by seller_id, created_at
For simplicity, i got this result
user actions credit debit balance date
1 do_action_A 5000 0 5000 2020-01-01 1:00:00 #no need these 2
1 cancel_A 0 5000 0 2020-01-01 1:03:00 #in result
1 do_action_A 5000 0 5000 2020-01-01 1:10:00
1 do_action_b 3000 0 8000 2020-01-01 1:20:00
1 do_action_c 0 7000 1000 2020-01-01 1:30:00
2 do_action_A 5000 0 5000 2020-01-01 1:00:00
2 do_action_B 3000 0 8000 2020-01-01 1:10:00
We know that users can only cancel their orders within 5 minutes, unfortunately, there is a lot of cancels. I need to make this data table simple and short so as to track and visualize it easily.
Here is my expectataion:
user actions credit debit balance date
1 do_action_A 5000 0 5000 2020-01-01 1:10:00
1 do_action_b 3000 0 8000 2020-01-01 1:20:00
1 do_action_c 0 7000 1000 2020-01-01 1:30:00
2 do_action_A 5000 0 5000 2020-01-01 1:00:00
2 do_action_B 3000 0 8000 2020-01-01 1:10:00
You can try using lead()
select * from
(
select z.user_id,z.date,z.actions,x.name,
x.email,x.address,debtit, credit, balance,
lead(z.actions) over(parition by z.user_id order by z.created_at) as next_action
from table_1 z left join table_2 x
on z.seller_id = x.uid
)A where next_action not like '%cancel%' and actions not like '%cancel%'
I have table balance_detail
sales_period sales_date opening_amt sales_amt payment_amt closing_amt
-----------------------------------------------------------------------------
201501 01-01-2015 210 100 110
201501 02-01-2015 110 300 280 130
201501 03-01-2015 130 50 80
201501 05-01-2015 80 600 670 10
201502 02-02-2015 10 160 100 70
201502 15-02-2015 70 100 170 0
And Want result like this
sales_period opening_amt sales_amt payment_amt closing_amt
-----------------------------------------------------------------------------
201501 80 1110 1110 10
201502 70 260 270 0
One method is to use conditional aggregation along with row_number():
select sales_period,
max(case when seqnum = 1 then opening_amt end) as opening_amt,
sum(sales_amt) as sales_amt,
sum(payment_amt) as payment_amt,
max(case when seqnum = 1 then closing_amt end) as closing_amt
from (select bd.*,
row_number() over (partition by sales_period order sales_date desc) as seqnum
from balance_detail bd
) bd
group by sales_period;