TSQL Insert additional rows - tsql

What is the most efficient way to identify and insert rows for the following problem?
Here's my sample data
vId StartDate EndDate Distance
------------------------------------
256 2015-03-04 2015-03-05 365
271 2015-03-04 2015-03-04 86
315 2015-03-05 2015-03-06 254
256 2015-03-07 2015-03-09 150
458 2015-03-10 2015-03-12 141
458 2015-03-15 2015-03-17 85
315 2015-03-15 2015-03-16 76
I want to add additional rows for each vId where the StartDate <> EndDate like follows, so instead of just
315 2015-03-05 2015-03-06 254
256 2015-03-07 2015-03-09 150
I want to show the following
315 2015-03-05 2015-03-06 254
315 2015-03-06 2015-03-06 0
256 2015-03-07 2015-03-09 150
256 2015-03-08 2015-03-09 0
256 2015-03-09 2015-03-09 0
Thanks in advance.

Just a simple insert:
Insert Into Table(vId, StartDate, EndDate, Distance)
Select vId, DateAdd(dd, 1, StartDate), EndDate, 0
From TableName
Where StartDate <> EndDate
If you want just select but not insert then:
Select vId, StartDate, EndDate, Distance
From TableName
Union All
Select vId, DateAdd(dd, 1, StartDate), EndDate, 0
From TableName
Where StartDate <> EndDate
EDIT
This assumes that there are maximum 100 day difference. If you have longer intervals you can add more cross joins to increase possible values:
declare #t table(vId int, StartDate date, EndDate date, Distance int)
insert into #t values
(315, '2015-03-05', '2015-03-06', 254),
(256, '2015-03-07', '2015-03-09', 150)
;with cte as(select row_number() over(order by (select 1)) as rn
from (values(1),(1),(1),(1),(1),(1),(1),(1),(1),(1)) t1(n)
cross join (values(1),(1),(1),(1),(1),(1),(1),(1),(1),(1)) t2(n)
)
select * from #t
union all
select t1.vId, ca.StartDate, t1.EndDate, 0
from #t t1
cross apply(select dateadd(dd, c.rn, StartDate) as StartDate
from cte c
where dateadd(dd, c.rn, t1.StartDate) <= t1.EndDate) as ca
where t1.StartDate <> t1.EndDate
order by vId, StartDate
See fiddle http://sqlfiddle.com/#!6/9eecb/4641

Related

Unable to calculate compound interest in PostgreSQL

I have a table table1 which contains the details of any depositor like
Depositor
Deposit_Amount
Deposit_Date
Maturity_Date
Tenure
Rate
A
25000
2021-08-10
2022-08-10
12
10%
I have another table table2 which contains the interest due date as:
Interest_Due_Date
2021-09-30
2021-12-31
2022-03-31
2022-06-30
2022-08-10
My Code is:
with recursive recur (n, start_bal, days,principle,interest, end_bal) as
(
select sno,deposit_amount,rate,days,deposit_amount * (((rate::decimal(18,2))/100)/365)*days as interest, deposit_amount+(deposit_amount * (((rate::decimal(18,2))/100)/365)*days) as end_bal from (
SELECT
sno, COALESCE(DATE_PART('day', deposit_date::TIMESTAMP - lag(deposit_date::TIMESTAMP) over
(ORDER BY sno ASC rows BETWEEN UNBOUNDED PRECEDING AND CURRENT row)),0) AS
days, deposit_date, deposit_amount, rate
FROM
( SELECT
ROW_NUMBER () OVER (ORDER BY deposit_date) AS sno,
deposit_date,
deposit_amount,
rate
FROM
( SELECT
t1.deposit_date, t1.deposit_amount, t1.rate from table1 t1
UNION ALL
SELECT
t2.Interest_Due_Date AS idate, 0 as depo_amount, 0 as rate
FROM
table2 t2
ORDER BY
deposit_date) dep) calc) b where sno = 1 union all select b.sno, b.end_bal,b.days,b.prin_bal,(coalesce(a.end_bal,0)) * (((b.rate)/100)/365)*b.days as interest_NEW,
coalesce(a.end_bal,0)+ ((a.end_bal) * (((calc.rate)/100)/365)*calc.days) as end_bal_NEW
from b, recur as a
where calc.sno = a.n+1 ) select * from recur
"Every time when i try to execute the query its showing an error 'relation 'b' does not exist"
...
The result table should be
Deposit Amount
Date
Days
Interest
Total Amount
25000
2021-08-10
0
0
25000
0
2021-09-30
51
349.32
25349.32
0
2021-12-31
92
638.94
25988.26
0
2022-03-31
90
640.81
26629.06
0
2022-06-30
91
663.90
27292.97
0
2022-08-10
41
306.58
27599.54

Postgres - Update running count whenever row meets a certain condition

I have a table with the following entries in them
id price quantity
1. 10 75
2. 10 75
3. 10 -150
4. 10 75
5. 10 -75
What I need to do is to update each row with a number that is the number of times the running total has been 0. In the above example, the cumulative totals would be
id. cum_total
1. 750
2. 1500
3. 0
4. 750
5. 0
Desired result
id price quantity seq
1. 10 75 1
2. 10 75 1
3. 10 -150 1
4. 10 75 2
5. 10 -75 2
I'm now lost in a spiral of CTEs and window functions and figured I'd ask the experts.
Thanks in advance :-)
Here is one option using analytic functions:
WITH cte AS (
SELECT *, CASE WHEN SUM(price*quantity) OVER (ORDER BY id) = 0 THEN 1 ELSE 0 END AS price_sum
FROM yourTable
),
cte2 AS (
SELECT *, LAG(price_sum, 1, 0) OVER (ORDER BY id) price_sum_lag
FROM cte
)
SELECT id, price, quantity, 1 + SUM(price_sum_lag) OVER (ORDER BY id) cumulative_total
FROM cte2
ORDER BY id;
Demo
You may try running each CTE in succession to see how the logic is working.
With window functions:
SELECT id, price, quantity,
coalesce(
sum(CASE WHEN iszero THEN 1 ELSE 0 END)
OVER (ORDER BY id
ROWS BETWEEN UNBOUNDED PRECEDING AND 1 PRECEDING),
0
) + 1 AS batch
FROM (SELECT id, price, quantity,
sum(price * quantity) OVER (ORDER BY id) = 0 AS iszero
FROM mytable) AS subq;

Group By - Using Absolute Values

I'm trying to display an accounting report where I show total transactions, voids, the transaction fee, and a total amount for each transaction type.
TransactionType Amount TransactionCount TotalAmount
AgentCredit -$1.00 49 -$49.00
MailFee -$1.25 11 -$13.75
MailFee $1.25 531 $663.75
HardCardFee -$5.00 7 -$35.00
HardCardFee $5.00 239 $1,195.00
QuotaHuntFee -$2.00 1 -$2.00
QuotaHuntFee $2.00 202 $404.00
But what I want to display would look like the following:
TransactionType Amount TransactionCount TotalAmount TotalTrans Voids
AgentCredit -$1.00 49 -$49.00 49 0
MailFee $1.25 520 $650.00 531 11
HardCardFee $5.00 232 $1,160.00 239 7
QuotaHuntFee $2.00 201 $402.00 202 1
Would it be possible to group the transaction types using the absolute value of the Amount and calculate the grand total along with the transaction count & void counts?
This is on SQL Server 2014.
Thanks,
I think this does it
declare #T table (nm varchar(20), prc smallmoney, amt int);
insert into #T values
('AgentCredit', -1.00, 49)
, ('MailFee', -1.25, 11)
, ('MailFee', 1.25, 531)
, ('HardCardFee', -5.00, 7)
, ('HardCardFee', 5.00, 239)
, ('QuotaHuntFee', -2.00, 1)
, ('QuotaHuntFee', 2.00, 202);
with cte as
(
select t.*, (t.prc * t.amt) as net
, count(*) over (partition by t.nm, abs(t.prc)) as cnt
, row_number() over (partition by t.nm, abs(t.prc) order by t.prc) as rn
, lag(t.prc) over (partition by t.nm, abs(t.prc) order by t.prc) as prPrc
, lag(t.amt) over (partition by t.nm, abs(t.prc) order by t.prc) as prAmt
, case when lag(t.prc) over (partition by t.nm, abs(t.prc) order by t.prc) < 0 then t.amt - lag(t.amt) over (partition by t.nm, abs(t.prc) order by t.prc)
else t.amt
end as bal
from #T t
)
select *, ISNULL(t.prAmt, 0) as void
, bal*prc as nnet
from cte t
where t.cnt = 1
or t.rn = 2
order by t.nm, t.prc;
There's a bit of confusion around your results with the data you've provided. HardCardFee has 7 and 23 in the sample you provided, but you want to return 232 for the total?.. MailFee also has some inconsistent math. Also, your 'Voids' returns 0 for the first row; however, it seems as if there are 49?
Perhaps this query could get you started down the right path:
DECLARE #Table TABLE (TransactionType varchar(20), Amount decimal(10,2), TransactionCount int, TotalAmount decimal(10,2))
INSERT #Table
VALUES ('AgentCredit' ,-$1.00 ,49 ,-$49.00 ),
('MailFee' ,-$1.25 ,11 ,-$13.75 ),
('MailFee' ,$1.25 ,531 ,$663.75 ),
('HardCardFee' ,-$5.00 ,7 ,-$35.00 ),
('HardCardFee' ,$5.00 ,23 ,$1195.00 ),
('QuotaHuntFee' ,-$2.00 ,1 ,-$2.00 ),
('QuotaHuntFee' ,$2.00 ,202 ,$404.00 )
;WITH c AS (
SELECT TransactionType, Amount, TransactionCount, TotalAmount,
CASE WHEN t.Amount + ABS(t.Amount) = 0 THEN '-' ELSE '' END +
CAST(t.TransactionCount AS VARCHAR(10)) AS TCount
FROM #Table t
)
SELECT t.TransactionType
,MAX(t.Amount) AS Amount
,SUM(CAST(t.TCount AS INT)) AS TransactionCount
,SUM(t.TotalAmount) AS TotalAmount
,SUM(ABS(t.TransactionCount)) AS TotalTrans
,ABS(MIN(t.TCount)) AS Voids
FROM c t
GROUP BY TransactionType
Again, not sure about some of the values provided.

Drive EndDate of Current Row From StarDate of Next Row

Can some one please help me with how to create end date from start date.
Products referred to a company for testing while the product with the company they carry out multiple tests on different dates and record the test date to establish the product condition i.e. (outcomeID).
I need to establish the StartDate which is the testDate and EndDate which is the start date of the next row. But if multiple consecutive tests resulted in the same OutcomeID I need to return only one row with the StartDate of the first test and the end date of the last test. In another word if the outcomeID did not change over a few consecutive tests.
Here is my data set
DECLARE #ProductTests TABLE
(
RequestID int not null,
ProductID int not null,
TestID int not null,
TestDate datetime null,
OutcomeID int
)
insert into #ProductTests
(RequestID ,ProductID ,TestID ,TestDate ,OutcomeID )
select 1,2,22,'2005-01-21',10
union all
select 1,2,42,'2007-03-17',10
union all
select 1,2,45,'2010-12-25',10
union all
select 1,2,325,'2011-01-14',13
union all
select 1,2,895,'2011-08-10',15
union all
select 1,2,111,'2011-12-23',15
union all
select 1,2,636,'2012-05-02',10
union all
select 1,2,554,'2012-11-08',17
--select *from #producttests
RequestID ProductID TestID TestDate OutcomeID
1 2 22 2005-01-21 10
1 2 42 2007-03-17 10
1 2 45 2010-12-25 10
1 2 325 2011-01-14 13
1 2 895 2011-08-10 15
1 2 111 2011-12-23 15
1 2 636 2012-05-02 10
1 2 554 2012-11-08 17
And this is what I need to achieve.
RequestID ProductID StartDate EndDate OutcomeID
1 2 2005-01-21 2011-01-14 10
1 2 2011-01-14 2011-08-10 13
1 2 2011-08-10 2012-05-02 15
1 2 2012-05-02 2012-11-08 10
1 2 2012-11-08 NULL 17
As you see from the dataset the first three tests (22, 42, and 45) all resulted in OutcomeID 10 so in my result I only need start date of test 22 and end date of test 45 which is the start date of test 325.As you see in test 636 outcomeID has gone back to 10 from 15 so it needs to be returned too.
--This is what I have managed to achieve at the moment using the following script
select T1.RequestID,T1.ProductID,T1.TestDate AS StartDate
,MIN(T2.TestDate) AS EndDate ,T1.OutcomeID
from #producttests T1
left join #ProductTests T2 ON T1.RequestID=T2.RequestID
and T1.ProductID=T2.ProductID and T2.TestDate>T1.TestDate
group by T1.RequestID,T1.ProductID ,T1.OutcomeID,T1.TestDate
order by T1.TestDate
Result:
RequestID ProductID StartDate EndDate OutcomeID
1 2 2005-01-21 2007-03-17 10
1 2 2007-03-17 2010-12-25 10
1 2 2010-12-25 2011-01-14 10
1 2 2011-01-14 2011-08-10 13
1 2 2011-08-10 2011-12-23 15
1 2 2011-12-23 2012-05-02 15
1 2 2012-05-02 2012-11-08 10
1 2 2012-11-08 NULL 17
nov 7 but still not answered
so here is my solution
not soo pretty but works
my hint is read about windowing , ranking and aggregate functions like row_number, rank , avg, sum etc.
those are essential when you want to write raports , and becoming quite powerfull in sql server 2012
i have also used CTE (common table expression) but it can be written as subquery or temporary table
;with cte ( ida, requestid, productid, testid, testdate, outcomeid) as
(
-- select rows where the outcome id is changing
select b.* from
(select ROW_NUMBER() over( partition by requestid, productid order by testDate) as id, * from #ProductTests)a
right outer join
(select ROW_NUMBER() over(partition by requestid, productid order by testDate) as id, * from #ProductTests) b
on a.requestID = b.requestID and a.productID = b.productID and a.id +1 = b.id
where 1=1
--or a.id = 1
and a.outcomeid <> b.outcomeid or b.outcomeid is null or a.id is null
)
select --*
a.RequestID,a.ProductID,a.TestDate AS StartDate ,MIN(b.TestDate) AS EndDate ,a.OutcomeID
from cte a left join cte b on a.requestid = b.requestid and a.productid = b.productid and a.testdate < b.testdate
group by a.RequestID,a.ProductID ,a.OutcomeID,a.TestDate
order by StartDate
Actually, there seem to be two problems in your question. One is how to group sequential (based on specific criteria) rows containing the same value. The other is the one actually spelled out in your title, i.e. how to use the next row's StartDate as the current row's EndDate.
Personally, I would solve these two problems in the order I mentioned them, so I would first address the grouping problem. One way to group the data properly in this case would be to use double ranking like this:
WITH partitioned AS (
SELECT
*,
grp = ROW_NUMBER() OVER (PARTITION BY RequestID, ProductID ORDER BY TestDate)
- ROW_NUMBER() OVER (PARTITION BY RequestID, ProductID, OutcomeID ORDER BY TestDate)
FROM #ProductTests
)
, grouped AS (
SELECT
RequestID,
ProductID,
StartDate = MIN(TestDate),
OutcomeID
FROM partitioned
GROUP BY
RequestID,
ProductID,
OutcomeID,
grp
)
SELECT *
FROM grouped
;
This should give you the following output for your data sample:
RequestID ProductID StartDate OutcomeID
--------- --------- ---------- ---------
1 2 2005-01-21 10
1 2 2011-01-14 13
1 2 2011-08-10 15
1 2 2012-05-02 10
1 2 2012-11-08 17
Obviously, one thing is still missing, and it's EndDate, and now is the right time to care about it. Use ROW_NUMBER() once again, to rank the result set of the grouped CTE, then use the rankings in the join condition when joining the result set with itself (using an outer join):
WITH partitioned AS (
SELECT
*,
grp = ROW_NUMBER() OVER (PARTITION BY RequestID, ProductID ORDER BY TestDate)
- ROW_NUMBER() OVER (PARTITION BY RequestID, ProductID, OutcomeID ORDER BY TestDate)
FROM #ProductTests
)
, grouped AS (
SELECT
RequestID,
ProductID,
StartDate = MIN(TestDate),
OutcomeID,
rnk = ROW_NUMBER() OVER (PARTITION BY RequestID, ProductID ORDER BY MIN(TestDate))
FROM partitioned
GROUP BY
RequestID,
ProductID,
OutcomeID,
grp
)
SELECT
g1.RequestID,
g1.ProductID,
g1.StartDate,
g2.StartDate AS EndDate,
g1.OutcomeID
FROM grouped g1
LEFT JOIN grouped g2
ON g1.RequestID = g2.RequestID
AND g1.ProductID = g2.ProductID
AND g1.rnk = g2.rnk - 1
;
You can try this query at SQL Fiddle to verify that it returns the output you are after.

SQL Query to calculate remaining running balances based on a given conditions

I have a stock transaction table like this:
StockID Item TransDate TranType BatchNo Qty Price
10001 ABC 01-Apr-2012 IN 71001000 200 750.0
10002 ABC 02-Apr-2012 OUT 100
10003 ABC 03-Apr-2012 IN 71001001 50 700.0
10004 ABC 04-Apr-2012 IN 71001002 75 800.0
10005 ABC 10-Apr-2012 OUT 125
10006 XYZ 05-Apr-2012 IN 71001003 150 350.0
10007 XYZ 05-Apr-2012 OUT 120
10008 XYZ 15-Apr-2012 OUT 10
10009 XYZ 20-Apr-2012 IN 71001004 90 340.0
10010 PQR 06-Apr-2012 IN 71001005 50 510.0
10011 PQR 15-Apr-2012 IN 71001006 60 505.0
10012 MNO 01-Apr-2012 IN 71001007 76 410.0
10013 MNO 11-Apr-2012 OUT 76
Each of my IN transactions has price associated to it and a batch number (lot number). Now I would like to calculate the remaining quantity by First In First Out (FIFO) rule, meaning the first in should be adjusted with first out. After adjusting the quantities the remaining balances are to be calculated against each IN transaction for the same item as shown below:
StockID Item TransDate TranType BatchNo Qty Price RemainingQty
10001 ABC 01-Apr-2012 IN 71001000 200 750.0 0
10002 ABC 02-Apr-2012 OUT 100
10003 ABC 03-Apr-2012 IN 71001001 50 700.0 25
10004 ABC 04-Apr-2012 IN 71001002 75 800.0 75
10005 ABC 10-Apr-2012 OUT 125
10006 XYZ 05-Apr-2012 IN 71001003 150 350.0 20
10007 XYZ 05-Apr-2012 OUT 120
10008 XYZ 15-Apr-2012 OUT 10
10009 XYZ 20-Apr-2012 IN 71001004 90 340.0 90
10010 PQR 06-Apr-2012 IN 71001005 50 510.0 50
10011 PQR 15-Apr-2012 IN 71001006 60 505.0 60
10012 MNO 01-Apr-2012 IN 71001007 76 410.0 0
10013 MNO 11-Apr-2012 OUT 76
As we can see from the above table for item ABC, after adjusting (125 + 100) OUT qty against the IN qty (100 + 50 + 75) using FIFO the quantity remaining for the batch 71001000 is 0, 71001001 is 25 and for batch 71001002 is 75. From the remaining quantity the value can be derived.
Please help me to achieve this using any of the methods (either cursor based or CTE or JOINS, etc)
Thanks in advance for the help.
One of the users of StockOverflow suggested this answer:
SELECT 10001 as stockid,'ABC' as item,'01-Apr-2012' as transdate,'IN' as trantype, 71001000 as batchno, 200 as qty, 750.0 as price INTO #sample
UNION ALL SELECT 10002 ,'ABC','02-Apr-2012','OUT', NULL ,100,NULL
UNION ALL SELECT 10003 ,'ABC','03-Apr-2012','IN', 71001001, 50 , 700.0
UNION ALL SELECT 10004 ,'ABC','04-Apr-2012','IN', 71001002, 75 , 800.0
UNION ALL SELECT 10005 ,'ABC','10-Apr-2012','OUT', NULL ,125,NULL
UNION ALL SELECT 10006 ,'XYZ','05-Apr-2012','IN', 71001003, 150 , 350.0
UNION ALL SELECT 10007 ,'XYZ','05-Apr-2012','OUT', NULL , 120 ,NULL
UNION ALL SELECT 10008 ,'XYZ','15-Apr-2012','OUT', NULL , 10 ,NULL
UNION ALL SELECT 10009 ,'XYZ','20-Apr-2012','IN', 71001004, 90 , 340.0
UNION ALL SELECT 10010 ,'PQR','06-Apr-2012','IN', 71001005, 50 , 510.0
UNION ALL SELECT 10011 ,'PQR','15-Apr-2012','IN', 71001006, 60 , 505.0
UNION ALL SELECT 10012 ,'MNO','01-Apr-2012','IN', 71001007, 76 , 410.0
UNION ALL SELECT 10013 ,'MNO','11-Apr-2012','OUT', NULL ,76 ,NULL
;WITH remaining AS
(
SELECT *,
CASE
WHEN trantype = 'IN' THEN 1
ELSE -1
END * qty AS stock_shift,
ROW_NUMBER() OVER(PARTITION BY item ORDER BY transdate) AS row,
CASE
WHEN trantype = 'OUT' THEN NULL
ELSE ROW_NUMBER()OVER(PARTITION BY item, CASE WHEN trantype = 'IN' THEN 0 ELSE 1 END ORDER BY transdate)
END AS in_row,
SUM(CASE WHEN trantype = 'OUT' THEN qty END) OVER(PARTITION BY item) AS total_out
FROM #sample
)
,remaining2 AS
(
SELECT r1.item,
r1.stockid,
MAX(r1.transdate) AS transdate,
MAX(r1.trantype) AS trantype,
MAX(r1.batchno) AS batchno,
MAX(r1.qty) AS qty,
MAX(r1.price) AS price,
MAX(r1.total_out) AS total_out,
MAX(r1.in_row) AS in_row,
CASE
WHEN MAX(r1.trantype) = 'OUT' THEN NULL
WHEN SUM(CASE WHEN r1.trantype = 'IN' THEN r2.qty ELSE 0 END) - MAX(r1.total_out) < 0 THEN SUM(CASE WHEN r1.trantype = 'IN' THEN r2.qty ELSE 0 END)
- MAX(r1.total_out)
ELSE 0
END AS running_in
FROM remaining r1
LEFT OUTER JOIN remaining r2
ON r2.row <= r1.row
AND r2.item = r1.item
GROUP BY
r1.item,
r1.stockid
)
SELECT r2.item,
r2.stockid,
MAX(r2.transdate) AS transdate,
MAX(r2.trantype) AS trantype,
MAX(r2.batchno) AS batchno,
MAX(r2.qty) AS qty,
MAX(r2.price) AS price,
MAX(CASE WHEN r2.trantype = 'OUT' THEN NULL ELSE ISNULL(r2.qty + r3.running_in, 0) END) AS remaining_stock
FROM remaining2 r2
LEFT OUTER JOIN remaining2 r3
ON r2.in_row - 1 = r3.in_row
AND r2.item = r3.item
GROUP BY
r2.item,
r2.stockid
This sql is having a problem and the result is attached here The records for which the value are not matching are indicated in yellow color. Kindly help to solve the problem.
I think this should do the trick?
SELECT 10001 as stockid,'ABC' as item,'01-Apr-2012' as transdate,'IN' as trantype, 71001000 as batchno, 200 as qty, 750.0 as price INTO #sample
UNION ALL SELECT 10002 ,'ABC','02-Apr-2012','OUT', NULL ,100,NULL
UNION ALL SELECT 10003 ,'ABC','03-Apr-2012','IN', 71001001, 50 , 700.0
UNION ALL SELECT 10004 ,'ABC','04-Apr-2012','IN', 71001002, 75 , 800.0
UNION ALL SELECT 10005 ,'ABC','10-Apr-2012','OUT', NULL ,125,NULL
UNION ALL SELECT 10006 ,'XYZ','05-Apr-2012','IN', 71001003, 150 , 350.0
UNION ALL SELECT 10007 ,'XYZ','05-Apr-2012','OUT', NULL , 120 ,NULL
UNION ALL SELECT 10008 ,'XYZ','15-Apr-2012','OUT', NULL , 10 ,NULL
UNION ALL SELECT 10009 ,'XYZ','20-Apr-2012','IN', 71001004, 90 , 340.0
UNION ALL SELECT 10010 ,'PQR','06-Apr-2012','IN', 71001005, 50 , 510.0
UNION ALL SELECT 10011 ,'PQR','15-Apr-2012','IN', 71001006, 60 , 505.0
UNION ALL SELECT 10012 ,'MNO','01-Apr-2012','IN', 71001007, 76 , 410.0
UNION ALL SELECT 10013,'MNO','11-Apr-2012','OUT', NULL ,76 ,NULL
;with remaining_stock as
(
SELECT *
,CASE WHEN trantype = 'IN' THEN 1 ELSE -1 END * qty AS stock_shift
,row_number() OVER (PARTITION BY item ORDER BY transdate) as row
,CASE WHEN trantype = 'OUT' THEN NULL ELSE
row_number()OVER (PARTITION BY item,CASE WHEN trantype = 'IN' THEN 0 ELSE 1 END ORDER BY transdate) END as in_row
,CASE WHEN trantype = 'IN' THEN NULL ELSE
row_number()OVER (PARTITION BY item,CASE WHEN trantype = 'OUT' THEN 0 ELSE 1 END ORDER BY transdate) END as out_row
,ISNULL(SUM(CASE WHEN trantype = 'OUT' THEN qty END) OVER (PARTITION BY item),0) AS total_out
,ISNULL(SUM(CASE WHEN trantype = 'IN' THEN qty END) OVER (PARTITION BY item),0) AS total_in
FROM #sample
)
,remaining_stock2 AS
(
SELECT
r1.item
,r1.stockid
,MAX(r1.transdate) as transdate
,MAX(r1.trantype) as trantype
,MAX(r1.batchno) as batchno
,MAX(r1.qty) as qty
,MAX(r1.price) as price
,MAX(r1.total_in) as total_in
,MAX(r1.total_out) as total_out
,SUM(r2.qty) as running_in
FROM remaining_stock r1
LEFT OUTER JOIN remaining_stock r2 on r2.in_row <= r1.in_row
AND r2.item = r1.item
GROUP BY
r1.item
,r1.stockid
)
SELECT
item
,stockid
,transdate
,trantype
,batchno
,qty
,price
,CASE WHEN trantype = 'OUT' THEN NULL
WHEN total_out >= running_in THEN 0
WHEN (running_in - total_out) < qty THEN (running_in - total_out)
WHEN (running_in - total_out) >= qty THEN qty
END as remaining_stocks
FROM remaining_stock2
Your question isn't very clear to me on how the FIFO logic is to be applied. I'm going to assume that you want to associate each IN record against the next OUT record if one exists. To achieve this you need to join the table on itself like the following
select
t1.BatchNo,
isnull(t1.Qty,0) as 'IN Qty',
isnull(t2.Qty,0) as 'OUT Qty',
isnull(t1.Qty,0) - isnull(t2.Qty,0) as 'Remaining Qty'
from
tbl_test t1
left join tbl_test t2
on t2.StockID = (t1.StockID + 1)
and t2.TranType = 'OUT'
where
t1.TranType = 'IN'
The results will show you the following for the first 5 records for ABC from your question.
BatchNo | IN Qty | OUT Qty | Remaining Qty
71001000 | 200 | 100 | 100
71001001 | 50 | 0 | 50
71001002 | 75 | 125 | -50
The left join works on the assumption that the StockID for each IN record is always one less number than the associated OUT record. I personally think your data model needs improving.
OUT records should have a BatchNo assigned or a reference to the
StockID of the associated IN record
add a timestamp field for sequential ordering
add a DateTime field for handling IN/OUT occuring on same day