Rewrite SQL without cursor for Data Warehouse - sql

I have SQL code (main core) below that works fine on SQL Server. How to rewrite it for T-SQL Azure SQL DW, which doesn't like cursor? I didn't find appropriate example and can't do it myself.
CREATE PROCEDURE calc_balance
CREATE TABLE output_table (Amount FLOAT, ValueStart FLOAT, ValueStop FLOAT);
DECLARE cursor_1 CURSOR FOR
SELECT Criteria, Amount, ValueStart, ValueStop
FROM Input_table
OPEN cursor_1
WHILE #fetchstatus = 0
BEGIN
FETCH NEXT FROM cursor_1 INTO #Criteria, #Amount, #ValueStart, #ValueStop;
#ValueStart1 = #balance;
#ValueStop1 = #ValueStart1 + #Amount;
IF (#Criteria = 1) AND (#balance> -100)
BEGIN
#Amount = 0;
#ValueStop1 = #ValueStart;
END;
#balance = #balance + #Amount + #ValueStart;
INSERT INTO output_table
VALUES (#Amount, #ValueStart1, #ValueStop1);
END;

You may want to try this.
with cte as (
select 0 as ctr, 0 as balance, Criteria, Amount, ValueStart, ValueStop, (select count(1) from Input_table) as ct from Input_table
union all
select ctr +1, balance + case when Criteria = 1 and balance >-100 then 0 else Amount end + ValueStart
, Criteria, Amount, balance
, case when Criteria = 1 and balance >-100 then balance + Amount else ValueStart end
, ct
from cte
where ctr < ct
)
Insert into output_table VALUES
(select Amount,ValueStart, ValueStop from cte where ctr>0)

Related

Churn Rate From Contract Start & End Dates

I am trying to calculate the churn rate of customer and all I have to play with is contract start & end dates.
When the date year is 9999 it just means they are still a customer and I would like to consider account only who have a tenure of more than 3 months (as in have been with the company for 3 or more months).
CREATE TABLE #temp
(
ACCOUNT varchar(20)NOT NULL
,CONTRACT_START_DATE DATETIME NOT NULL
,CONTRACT_END_DATE DATETIME NOT NULL
)
;
INSERT INTO #temp
VALUES('64074558792','20160729','20170805');
INSERT INTO #temp
VALUES('54654654664','20160810','20170110');
INSERT INTO #temp
VALUES('21454654764','20160112','20160812');
INSERT INTO #temp
VALUES('21654765134','20160101','20161231');
INSERT INTO #temp
VALUES('13214868794','20160811','99991231');
INSERT INTO #temp
VALUES ('88321546894','20160427','20160627');
SELECT *,
CASE WHEN CONTRACT_END_DATE <> '99991231' THEN DATEDIFF(DAY, CONTRACT_START_DATE,CONTRACT_END_DATE) ELSE null END AS TENURE
FROM #temp
The below will work for a single reporting year. There's a few things it seems like you're trying to get:
-Number of customers who stay longer than x months
-% of customers that stay for more than x months
-% of customers lost during the year
DECLARE #records TABLE
(
ACCOUNT varchar(20)NOT NULL
,CONTRACT_START_DATE DATETIME NOT NULL
,CONTRACT_END_DATE DATETIME NOT NULL
);
DECLARE #ReportingYear INT = 2016;
DECLARE #MinTenureMonths INT = 3;
INSERT INTO #records
VALUES('64074558792','20160729','20170805'),
('54654654664','20160810','20170110'),
('21454654764','20160112','20160812'),
('21654765134','20151011','20161231'),--I changed this one
('13214868794','20160811','99991231'),
('88321546894','20160427','20160627');
SELECT *,
[CustomerGained] = IIF(YEAR(CONTRACT_START_DATE) = #ReportingYear,1,0),
[CustomerLost] = IIF(YEAR(CONTRACT_END_DATE) = #ReportingYear,1,0) ,
[ExistingCustomer] = IIF(CONTRACT_START_DATE < DATEFROMPARTS(#ReportingYear,1,1)
AND CONTRACT_END_DATE > DATEFROMPARTS(#ReportingYear,12,31), 1, 0),
[CurrentCustomer] = IIF(CONTRACT_END_DATE = '99991231 00:00:00.000',1,0),
[MinTenureExceeded] = IIF(DATEDIFF(MONTH, CONTRACT_START_DATE, CONTRACT_END_DATE) >= #MinTenureMonths,1,0)
FROM #records;
With recordCTE AS
(
SELECT
[CustomersGained] = SUM(IIF(YEAR(CONTRACT_START_DATE) = #ReportingYear,1,0)),
[CustomersLost] = SUM(IIF(YEAR(CONTRACT_END_DATE) = #ReportingYear,1,0)),
[MinTenureExceeded] = SUM(IIF(DATEDIFF(MONTH, CONTRACT_START_DATE, CONTRACT_END_DATE) >= #MinTenureMonths,1,0)),
[ExistingCustomers] = SUM(IIF(CONTRACT_START_DATE < DATEFROMPARTS(#ReportingYear,1,1)
AND CONTRACT_END_DATE > DATEFROMPARTS(#ReportingYear,12,31), 1, 0)),
[CurrentCustomer] = SUM(IIF(CONTRACT_END_DATE = '99991231 00:00:00.000',1,0)),
[TotalCustomers] = COUNT(1)
FROM #records
)
SELECT c.CustomersGained,
c.CustomersLost,
c.MinTenureExceeded,
c.ExistingCustomers,
[% Lost] = CAST(((c.CustomersLost * 1.0) / c.TotalCustomers) * 100 AS DECIMAL(14,2)),
[% Gained] = CAST(((c.CustomersGained * 1.0) / c.TotalCustomers) * 100 AS DECIMAL(14,2))
FROM recordCTE c;
You'll need to decide whether you're just looking for a single reporting year, a reporting period, or in total for everything. If you want everything, just do all the calcs using CurrentCustomers instead.

While Loop SAP B1 SQL stored procedure for blocking

I have an issue with my stored procedure SAP B1.
What I'm trying to do here is storing the sum of the quantity, group by the manufacturing order and insert it into the temp table. Then use a while loop to go thru each ID to compare with the user table [#FGTRACKING] and block if
temp.quantity > sum(quantity) in [#FGTracking].
However this is not working, the transaction still passed the stored procedure block. I suspect there is something wrong with my syntax.
IF #transaction_type IN ('A') AND #object_type = '67'
BEGIN
declare #top as int
declare #temp table (id int,quantity int NOT NULL, monum int NOT NULL)
insert into #temp (id, quantity,monum)
select row_number() over (order by (select NULL)), sum(quantity) as quantity, u_shipment_line as monum
from wtr1 t1
where t1.docentry = #list_of_cols_val_tab_del
group by u_shipment_line
set #top = 1
WHILE #top <= (select count(monum) from #temp)
BEGIN
IF EXISTS (select t100.monum from #temp t100
where t100.quantity > (select sum(t111.u_transfer)
from [#FGTRACKING] t111 where t111.u_mo_num = t100.monum
group by t111.u_mo_num) and t100.id = #top)
BEGIN
SELECT #Error = 666, #error_message = 'Over-transfer'
END
ELSE
set #top = #top + 1
END
END
It looks like you're only incrementing your iterator (#top) when you don't encounter your error condition, so if your error condition triggers, you're stuck in an infinite loop.
Get rid of your "else" and always increment #top, or alternatively break out of your while loop when you hit your error condition.
...
ELSE -- Get rid of this else
set #top = #top + 1
...

SQL Server - loop through table and update based on count

I have a SQL Server database. I need to loop through a table to get the count of each value in the column 'RevID'. Each value should only be in the table a certain number of times - for example 125 times. If the count of the value is greater than 125 or less than 125, I need to update the column to ensure all values in the RevID (are over 25 different values) is within the same range of 125 (ok to be a few numbers off)
For example, the count of RevID = "A2" is = 45 and the count of RevID = 'B2' is = 165 then I need to update RevID so the 45 count increases and the 165 decreases until they are within the 125 range.
This is what I have so far:
DECLARE #i INT = 1,
#RevCnt INT = SELECT RevId, COUNT(RevId) FROM MyTable group by RevId
WHILE(#RevCnt >= 50)
BEGIN
UPDATE MyTable
SET RevID= (SELECT COUNT(RevID) FROM MyTable)
WHERE RevID < 50)
#i = #i + 1
END
I have also played around with a cursor and instead of trigger. Any idea on how to achieve this? Thanks for any input.
Okay I cam back to this because I found it interesting even though clearly there are some business rules/discussion that you and I and others are not seeing. anyway, if you want to evenly and distribute arbitrarily there are a few ways you could do it by building recursive Common Table Expressions [CTE] or by building temp tables and more. Anyway here is a way that I decided to give it a try, I did utilize 1 temp table because sql was throwing in a little inconsistency with the main logic table as a cte about every 10th time but the temp table seems to have cleared that up. Anyway, this will evenly spread RevId arbitrarily and randomly assigning any remainder (# of Records / # of RevIds) to one of the RevIds. This script also doesn't rely on having a UniqueID or anything it works dynamically over row numbers it creates..... here you go just subtract out test data etc and you have what you more than likely want. Though rebuilding the table/values would probably be easier.
--Build Some Test Data
DECLARE #Table AS TABLE (RevId VARCHAR(10))
DECLARE #C AS INT = 1
WHILE #C <= 400
BEGIN
IF #C <= 200
BEGIN
INSERT INTO #Table (RevId) VALUES ('A1')
END
IF #c <= 170
BEGIN
INSERT INTO #Table (RevId) VALUES ('B2')
END
IF #c <= 100
BEGIN
INSERT INTO #Table (RevId) VALUES ('C3')
END
IF #c <= 400
BEGIN
INSERT INTO #Table (RevId) VALUES ('D4')
END
IF #c <= 1
BEGIN
INSERT INTO #Table (RevId) VALUES ('E5')
END
SET #C = #C+ 1
END
--save starting counts of test data to temp table to compare with later
IF OBJECT_ID('tempdb..#StartingCounts') IS NOT NULL
BEGIN
DROP TABLE #StartingCounts
END
SELECT
RevId
,COUNT(*) as Occurences
INTO #StartingCounts
FROM
#Table
GROUP BY
RevId
ORDER BY
RevId
/************************ This is the main method **********************************/
--clear temp table that is the main processing logic
IF OBJECT_ID('tempdb..#RowNumsToChange') IS NOT NULL
BEGIN
DROP TABLE #RowNumsToChange
END
--figure out how many records there are and how many there should be for each RevId
;WITH cteTargetNumbers AS (
SELECT
RevId
--,COUNT(*) as RevIdCount
--,SUM(COUNT(*)) OVER (PARTITION BY 1) / COUNT(*) OVER (PARTITION BY 1) +
--CASE
--WHEN ROW_NUMBER() OVER (PARTITION BY 1 ORDER BY NEWID()) <=
--SUM(COUNT(*)) OVER (PARTITION BY 1) % COUNT(*) OVER (PARTITION BY 1)
--THEN 1
--ELSE 0
--END as TargetNumOfRecords
,SUM(COUNT(*)) OVER (PARTITION BY 1) / COUNT(*) OVER (PARTITION BY 1) +
CASE
WHEN ROW_NUMBER() OVER (PARTITION BY 1 ORDER BY NEWID()) <=
SUM(COUNT(*)) OVER (PARTITION BY 1) % COUNT(*) OVER (PARTITION BY 1)
THEN 1
ELSE 0
END - COUNT(*) AS NumRecordsToUpdate
FROM
#Table
GROUP BY
RevId
)
, cteEndRowNumsToChange AS (
SELECT *
,SUM(CASE WHEN NumRecordsToUpdate > 1 THEN NumRecordsToUpdate ELSE 0 END)
OVER (PARTITION BY 1 ORDER BY RevId) AS ChangeEndRowNum
FROM
cteTargetNumbers
)
SELECT
*
,LAG(ChangeEndRowNum,1,0) OVER (PARTITION BY 1 ORDER BY RevId) as ChangeStartRowNum
INTO #RowNumsToChange
FROM
cteEndRowNumsToChange
;WITH cteOriginalTableRowNum AS (
SELECT
RevId
,ROW_NUMBER() OVER (PARTITION BY RevId ORDER BY (SELECT 0)) as RowNumByRevId
FROM
#Table t
)
, cteRecordsAllowedToChange AS (
SELECT
o.RevId
,o.RowNumByRevId
,ROW_NUMBER() OVER (PARTITION BY 1 ORDER BY (SELECT 0)) as ChangeRowNum
FROM
cteOriginalTableRowNum o
INNER JOIN #RowNumsToChange t
ON o.RevId = t.RevId
AND t.NumRecordsToUpdate < 0
AND o.RowNumByRevId <= ABS(t.NumRecordsToUpdate)
)
UPDATE o
SET RevId = u.RevId
FROM
cteOriginalTableRowNum o
INNER JOIN cteRecordsAllowedToChange c
ON o.RevId = c.RevId
AND o.RowNumByRevId = c.RowNumByRevId
INNER JOIN #RowNumsToChange u
ON c.ChangeRowNum > u.ChangeStartRowNum
AND c.ChangeRowNum <= u.ChangeEndRowNum
AND u.NumRecordsToUpdate > 0
IF OBJECT_ID('tempdb..#RowNumsToChange') IS NOT NULL
BEGIN
DROP TABLE #RowNumsToChange
END
/***************************** End of Main Method *******************************/
-- Compare the results and clean up
;WITH ctePostUpdateResults AS (
SELECT
RevId
,COUNT(*) as AfterChangeOccurences
FROM
#Table
GROUP BY
RevId
)
SELECT *
FROM
#StartingCounts s
INNER JOIN ctePostUpdateResults r
ON s.RevId = r.RevId
ORDER BY
s.RevId
IF OBJECT_ID('tempdb..#StartingCounts') IS NOT NULL
BEGIN
DROP TABLE #StartingCounts
END
Since you've given no rules for how you'd like the balance to operate we're left to speculate. Here's an approach that would find the most overrepresented value and then find an underrepresented value that can take on the entire overage.
I have no idea how optimal this is and it will probably run in an infinite loop without more logic.
declare #balance int = 125;
declare #cnt_over int;
declare #cnt_under int;
declare #revID_overrepresented varchar(32);
declare #revID_underrepresented varchar(32);
declare #rowcount int = 1;
while #rowcount > 0
begin
select top 1 #revID_overrepresented = RevID, #cnt_over = count(*)
from T
group by RevID
having count(*) > #balance
order by count(*) desc
select top 1 #revID_underrepresented = RevID, #cnt_under = count(*)
from T
group by RevID
having count(*) < #balance - #cnt_over
order by count(*) desc
update top #cnt_over - #balance T
set RevId = #revID_underrepresented
where RevId = #revID_overrepresented;
set #rowcount = ##rowcount;
end
The problem is I don't even know what you mean by balance...You say it needs to be evenly represented but it seems like you want it to be 125. 125 is not "even", it is just 125.
I can't tell what you are trying to do, but I'm guessing this is not really an SQL problem. But you can use SQL to help. Here is some helpful SQL for you. You can use this in your language of choice to solve the problem.
Find the rev values and their counts:
SELECT RevID, COUNT(*)
FROM MyTable
GROUP BY MyTable
Update #X rows (with RevID of value #RevID) to a new value #NewValue
UPDATE TOP #X FROM MyTable
SET RevID = #NewValue
WHERE RevID = #RevID
Using these two queries you should be able to apply your business rules (which you never specified) in a loop or whatever to change the data.

Incremental Group BY

How I can achieve incremental grouping in query ?
I need to group by all the non-zero values into different named groups.
Please help me write a query based on columns date and subscribers.
If you have SQL Server 2012 or newer, you can use few tricks with windows functions to get this kind of grouping without cursors, with something like this:
select
Date, Subscribers,
case when Subscribers = 0 then 'No group'
else 'Group' + convert(varchar, GRP) end as GRP
from (
select
Date, Subscribers,
sum (GRP) over (order by Date asc) as GRP
from (
select
*,
case when Subscribers > 0 and
isnull(lag(Subscribers) over (order by Date asc),0) = 0 then 1 else 0 end as GRP
from SubscribersCountByDay S
) X
) Y
Example in SQL Fiddle
In general I advocate AGAINST cursors but in this case it ill not hurt since it ill iterate, sum up and do the conditional all in one pass.
Also note I hinted it with FAST_FORWARD to not degrade performance.
I'm guessing you do want what #HABO commented.
See the working example below, it just sums up until find a ZERO, reset and starts again. Note the and #Sum > 0 handles the case where the first row is ZERO.
create table dbo.SubscribersCountByDay
(
[Date] date not null
,Subscribers int not null
)
GO
insert into dbo.SubscribersCountByDay
([Date], Subscribers)
values
('2015-10-01', 1)
,('2015-10-02', 2)
,('2015-10-03', 0)
,('2015-10-04', 4)
,('2015-10-05', 5)
,('2015-10-06', 0)
,('2015-10-07', 7)
GO
declare
#Date date
,#Subscribers int
,#Sum int = 0
,#GroupId int = 1
declare #Result as Table
(
GroupName varchar(10) not null
,[Sum] int not null
)
declare ScanIt cursor fast_forward
for
(
select [Date], Subscribers
from dbo.SubscribersCountByDay
union
select '2030-12-31', 0
) order by [Date]
open ScanIt
fetch next from ScanIt into #Date, #Subscribers
while ##FETCH_STATUS = 0
begin
if (#Subscribers = 0 and #Sum > 0)
begin
insert into #Result (GroupName, [Sum]) values ('Group ' + cast(#GroupId as varchar(6)), #Sum)
set #GroupId = #GroupId + 1
set #Sum = 0
end
else begin
set #Sum = #Sum + #Subscribers
end
fetch next from ScanIt into #Date, #Subscribers
end
close ScanIt
deallocate ScanIt
select * from #Result
GO
For the OP: Please next time write the table, just posting an image is lazy
In a version of SQL Server modern enough to support CTEs you can use the following cursorless query:
-- Sample data.
declare #SampleData as Table ( Id Int Identity, Subscribers Int );
insert into #SampleData ( Subscribers ) values
-- ( 0 ), -- Test edge case when we have a zero first row.
( 200 ), ( 100 ), ( 200 ),
( 0 ), ( 0 ), ( 0 ),
( 50 ), ( 50 ), ( 12 ),
( 0 ), ( 0 ),
( 43 ), ( 34 ), ( 34 );
select * from #SampleData;
-- Run the query.
with ZerosAndRows as (
-- Add IsZero to indicate zero/non-zero and a row number to each row.
select Id, Subscribers,
case when Subscribers = 0 then 0 else 1 end as IsZero,
Row_Number() over ( order by Id ) as RowNumber
from #SampleData ),
Groups as (
-- Add a group number to every row.
select Id, Subscribers, IsZero, RowNumber, 1 as GroupNumber
from ZerosAndRows
where RowNumber = 1
union all
select FAR.Id, FAR.Subscribers, FAR.IsZero, FAR.RowNumber,
-- Increment GroupNumber only when we move from a non-zero row to a zero row.
case when Groups.IsZero = 1 and FAR.IsZero = 0 then Groups.GroupNumber + 1 else Groups.GroupNumber end
from ZerosAndRows as FAR inner join Groups on Groups.RowNumber + 1 = FAR.RowNumber
)
-- Display the results.
select Id, Subscribers,
case when IsZero = 0 then 'no group' else 'Group' + Cast( GroupNumber as VarChar(10) ) end as Grouped
from Groups
order by Id;
To see the intermediate results just replace the final select with select * from FlagsAndRows or select * from Groups.

How to implement FIFO in sql

I working on FIFO implementation in sql.
I have Batch number concept in my application.
If suppose I am selling on inventory then my application should tell me that which inventory is the first come.
Lets. Say I purchased Inventory 'A' on 4th-Aug, 5th-Aug & 6th-Aug
On 4th Aug - A Inventory has batch number BT002 - 10 (Qty)
On 5th Aug - A's Inventory has batch number BT003 - 15 (Qty)
On 6th Aug - A's Inventory has batch number BT001 - 10 (Qty)
So, Now I am having stock Now in my hand as following :
A Inventory
BT002 - 10 - 4-Aug
BT003 - 15 - 5-Aug
BT001 - 10 - 6-Aug
Now If I want to sell that Inventory to anybody then my application should tell me that I should sell
BT002 (Batch number) inventory first beacause it came first.
That was the concept I am using in my application.
Now I want to sell 15 Qty from 'A' (Inventory).
Then O/p Should be like this :
BT002 - 10
BT003 - 5
Here's My Query :
SELECT ISNULL(SUM(qty),0) AS Qty,batch_no,accept_date FROM RS_GIN_Master
GROUP BY batch_no,accept_date
HAVING ISNULL(SUM(qty),0) <= 15
ORDER BY accept_date asc
O/p Of Given Query :
How can I get O/P like this :
BT002 - 10
BT003 - 5
Any Help will be appreciated.
Thank you in Advance.
This should work for you:
Working sample on Fiddle
CREATE FUNCTION [dbo].[GetBatchAmounts]
(
#requestedAmount int
)
RETURNS
#tBatchResults TABLE
(
Batch nvarchar(50),
Amount int
)
AS
BEGIN
/*This is just a mock of ersults of your query*/
DECLARE #RS_GIN_Master TABLE(
Qty int,
batch_no NVARCHAR(max),
accept_date DATETIME
)
insert into #RS_GIN_Master(Qty,batch_no,accept_date)
SELECT 10,'BT002', CAST(CAST(2014 AS varchar) + '-' + CAST(8 AS varchar) + '-' + CAST(4 AS varchar) AS DATETIME)
insert into #RS_GIN_Master(Qty,batch_no,accept_date)
SELECT 10,'BT003', CAST(CAST(2014 AS varchar) + '-' + CAST(8 AS varchar) + '-' + CAST(5 AS varchar) AS DATETIME)
insert into #RS_GIN_Master(Qty,batch_no,accept_date)
SELECT 10,'BT001', CAST(CAST(2014 AS varchar) + '-' + CAST(8 AS varchar) + '-' + CAST(6 AS varchar) AS DATETIME)
/*---------------------------*/
DECLARE #Qty int
DECLARE #batch_no NVARCHAR(max)
DECLARE #accept_date DATETIME
DECLARE myCursor CURSOR FOR
SELECT Qty, batch_no, accept_date FROM #RS_GIN_Master ORDER BY accept_date ASC
OPEN myCursor
FETCH NEXT FROM myCursor INTO #Qty, #batch_no,#accept_date
WHILE (##FETCH_STATUS = 0 AND #requestedAmount > 0 )
BEGIN
Declare #actualQty int
IF #requestedAmount > #Qty
SET #actualQty = #Qty
ELSE
SET #actualQty = #requestedAmount
INSERT INTO #tBatchResults (batch, Amount)
SELECT #batch_no, #actualQty
set #requestedAmount = #requestedAmount - #actualQty
FETCH NEXT FROM myCursor INTO #Qty, #batch_no,#accept_date
END /*WHILE*/
CLOSE myCursor
DEALLOCATE myCursor
RETURN
END
Just make sure to replace the marked part of the function with your query...
You need to create a stored procedure in your database and taking the quantity from your stock table. And you should also have the id of each record to update that records from where u have taken that qty.
Alter PROCEDURE sp_UpdateStockForSale
#batchNO varchar(10),
#qty decimal(9,3)
AS
BEGIN
Create Table #tmpOutput(ID int identity(1,1), StockID int, batchNo varchar(10), qty decimal(9,3));
SET NOCOUNT ON;
DECLARE #ID int;
DECLARE #Stock Decimal(9,3);
DECLARE #TEMPID int;
Select #TEMPID=(Max(ID)+1) From RS_GIN_Master Where qty > 0 And batch_no = #batchNO;
While (#qty > 0) BEGIN
Select #ID=ID, #Stock=qty From RS_GIN_Master Where qty > 0 And batch_no = #batchNO AND ID < #TEMPID Order By accept_date Desc;
--If Outward Qty is more than Stock
IF (#Stock < #qty) BEGIN
SET #qty = #qty - #Stock;
SET #Stock = 0;
END
--If Outward Qty is less than Stock
ELSE BEGIN
SET #Stock = #Stock - #qty;
SET #qty = 0;
END
Insert Into #tmpOutput(StockID,batchNo,qty)Values(#ID,#batchNO,#Stock);
SET #TEMPID = #ID;
--This will update that record don't need it now.
--Update RS_GIN_Master Set qty = #Stock Where ID=#ID
END
Select StockID, batchNo, qty From #tmpOutput;
END
GO
The above example is not compiled but, you can get the logic how you can retrieve the records from your stock table according to FIFO method. You can use accept_date instead of ID in RS_GIN_Master table. but, i would prefer to make it unique so, if i want to get a specific record then it can be possible.
One query .. like this
This should be tweaked for you case as you have groups and other stuff, is only for example purposes.
;with qty as (
select 15 as value
)
,l as (
select
ROW_NUMBER () over (order by accept_date desc) rn
,*
from xxx
)
,q as (
select
batch_no
,accept_date
,case when value>qty then value-qty else 0 end as remainder
,case when value>qty then qty else value end as used
,rn
from l
cross join qty
where rn=1
union all
select
r.batch_no
,r.accept_date
,case when q.remainder>r.qty then q.remainder-r.qty else 0 end as remainder
,case when q.remainder>r.qty then r.qty else q.remainder end as used
,r.rn
from q
join l r
on q.rn+1 = r.rn
where q.remainder!=0
)
select *
from q
where used != 0
and the fiffle for it http://sqlfiddle.com/#!6/9b063/34/0
Below should work for you
Create table RS_GIN_Master
(id int,
qty int,
batch_no varchar(5),
accept_date Datetime
)
GO
Insert into RS_GIN_Master (id, qty, batch_no, accept_date)
values(1,10,'BT001','2018-04-06')
Insert into RS_GIN_Master (id, qty, batch_no, accept_date)
values(2,10,'BT002','2018-04-04')
Insert into RS_GIN_Master (id, qty, batch_no, accept_date)
values(3,15,'BT003','2018-05-06')
GO
----------------------------
CREATE PROC FIFO
#TakenQty int
AS
BEGIN
WITH cte AS (SELECT *, SUM(qty) OVER (ORDER BY accept_date, id ASC) as CumQty FROM RS_GIN_Master WHERE qty>0)
SELECT TOP ((SELECT COUNT(*) FROM cte WHERE CumQty <#TakenQty)+1) batch_no, accept_date,
CASE
WHEN CumQty<#TakenQty THEN qty
ELSE #TakenQty -(CumQty-Qty)
END AS TakenOut
FROM cte
END
Result
| batch_no | accept_date | TakenOut |
|----------|----------------------|----------|
| BT002 | 2018-04-04T00:00:00Z | 10 |
| BT001 | 2018-04-06T00:00:00Z | 5 |
http://www.sqlfiddle.com/#!18/f7ee7/1