SQL Query to retrieve the last records till the quantity purchased reaches the total quantity in stock - sql

I have a table that have the ItemCode and Quantity in stock and another table that contains the purchases.
I want a query to get the Quantity in stock (ex. Qty = 5) and to take the purchase table to get the purchase invoices by descending order and take the Item Prices.
The Query has to keep retrieving records from the Purchase table according to the Quantity till we reach sum of Quantity in stock = 5.
ex.
**Purchase No ItemCode Qty Cost Price**
2 123 2 100
3 123 10 105
6 123 2 100
8 123 1 90
9 123 2 120
---------------------------------------------
**ItemCode Qty in Stock**
123 5
--------------------------------------------
In this example I want the query to retrieve for me the last 3 invoices (9,8 and 6) because the Qty (2+1+2 = 5)
Is there any suggestion .
Thank you in advance

This script should do the job.
/* SQL SCRIPT BEGIN */
create table #tmp (PurchaseNo int, ItemCode int, Qty int)
insert into #tmp (PurchaseNo, ItemCode, Qty)
select
p1.PurchaseNo, p1.ItemCode, sum(t.Qty) as Qty
from
Purchases p1
join
(
select
p2.PurchaseNo,
p2.ItemCode, p2.Qty
from
Purchases p2
) t on p1.PurchaseNo <= t.PurchaseNo and p1.ItemCode = t.ItemCode
group by p1.PurchaseNo, p1.ItemCode
order by p1.ItemCode, sum(t.Qty) asc
select * From #tmp
where
ItemCode = 123
and
Qty < 5
union
select top 1 * From #tmp
where
ItemCode = 123
and
Qty >= 5
order by PurchaseNo desc
drop table #tmp
/* SQL SCRIPT END */

Hi This can be the solution :
Here I have Used Result Table which will store the result.
I have used three tables Purchage(PurchageNo,ItemCode,Qty) , Stock(ItemCode,QtyInStock) and result(PurchageNo).
Full Workable Code is Here:
DECLARE #ItemCode int;
DECLARE #AvailableQty int;
SET #ItemCode = 123 ;
SET #AvailableQty = (select QtyInStock from Stock where ItemCode = #ItemCode);
SELECT
RowNum = ROW_NUMBER() OVER(ORDER BY PurchageNo),*
INTO #PurchageTemp
FROM Purchage
DECLARE #MaxRownum INT;
SET #MaxRownum = (select COUNT(*)from #PurchageTemp);
DECLARE #Iter INT;
SET #Iter = 1;
DECLARE #QtySum int=0;
DECLARE #QtySumTemp int=0;
DECLARE #CurrentItem int;
WHILE (#Iter <= #MaxRownum and #QtySum <= #AvailableQty)
BEGIN
set #QtySumTemp=#QtySum;
set #QtySumTemp = #QtySumTemp + (SELECT Qty FROM #PurchageTemp WHERE RowNum = #Iter and ItemCode=#ItemCode);
IF #QtySumTemp <= #AvailableQty
BEGIN
set #QtySum=#QtySumTemp;
set #CurrentItem= (SELECT PurchageNo FROM #PurchageTemp WHERE RowNum = #Iter and ItemCode=#ItemCode);
insert into [Result] values (#CurrentItem);
END
SET #Iter = #Iter + 1
END
DROP TABLE #PurchageTemp

Related

PARTITION based on a column and GROUP BY another column [duplicate]

This question already has answers here:
Get top 1 row of each group
(19 answers)
Select top 10 records for each category
(14 answers)
Closed 6 months ago.
Please consider the below script:
declare #tbl Table
(
CustomerId INT,
CountryID int,
Amount int
);
insert into #tbl values
(1,1,100),
(1,2,200),
(1,3,300),
(1,4,400),
(2,1,800),
(2,1,1000),
(3,1,500),
(2,4,200),
(2,3,900),
(3,1,3000),
(5,1,100),
(5,2,200),
(5,4,5000),
(6,1,1000),
(6,3,900),
(7,2,400),
(8,3,4000),
(2,1,100),
(1,1,100)
Declare #Result Table
(
CountryID int,
CustomerID int,
SumAmount int
);
Declare #CountryID int;
DECLARE db_cursor CURSOR FOR
SELECT distinct CountryID
FROM #tbl
OPEN db_cursor
FETCH NEXT FROM db_cursor INTO #CountryID
WHILE ##FETCH_STATUS = 0
BEGIN
insert into #Result
select top 2 #CountryID, CustomerID, SUM(Amount)
from #tbl
where CountryID = #CountryID
group by CustomerId
order by 3 desc
FETCH NEXT FROM db_cursor INTO #CountryID
END
CLOSE db_cursor
DEALLOCATE db_cursor
select *
from #Result
It returns this result :
CountryID CustomerID SumAmount
----------------------------------
1 3 3500
1 2 1900
2 7 400
2 5 200
3 8 4000
3 6 900
4 5 5000
4 1 400
In fact I want to get Top 2 customers that have maximum Amount in each Country.
How can I get that result without CURSOR and single query?
Thanks
The solution is :
WITH T AS
(
SELECT CountryID, CustomerId, SUM(Amount) AS SumAmount,
RANK() OVER(PARTITION BY CountryID ORDER BY SUM(Amount) DESC) AS R
FROM #tbl
GROUP BY CountryID, CustomerId
)
SELECT *
FROM T
WHERE R <= 2
But remember that when you want a top n rank , you will not systematically have exactly n rows returning because of ex aequo... You can have more, you can have less, depending of which ranking function you use and how many equal mesure you are ranking...

How to select and export 5,000 lines of debit and credit transactions at a time and have the debits and credits balance to zero?

The ERP system we're migrating to requires csv files with 5,000 or less rows for the GL. The debit and credit transactions within each file must balance to zero. There are multiple debit and credit transaction rows that share a common transaction ID.
Using offset and fetch next I've been able to extract 5000 rows at a time, however the credits and debits do not balance.
Data Example:
TranID Credit Debit Balance Account#
1 250 0 250 ABC
1 0 250 0 DEF
2 0 100 -100 GHI
2 50 0 -50 JKL
2 50 0 0 MNO
declare #batchsize INT = 5000,
#loopcount INT = 0;
while (#loopcount*#batchsize < (select count(*) from [Apps2].[dbo].[GLTrans]))
begin
SELECT * FROM [Apps2].[dbo].[GLTrans]
ORDER BY tranID
offset (#batchsize * #loopcount) rows
fetch next (#batchsize) rows only
set #loopcount= #loopcount + 1
end
A simple solution is pre-process all the transactions and assign a batch no (for each CSV files). The temp table stored the number of lines per TranID.
It is assumed that the Debit & Credit will balance for each TranID.
After that you can generate the CSV based on the temp table.
-- create the temp table
create table #trans
(
TranID int identity,
Cnt int,
Batch int
)
-- populate the temp table
insert into #trans (TranID, Cnt)
select TranID, Cnt = count(*)
from [Apps2].[dbo].[GLTrans]
group by TranID
declare #batchsize int = 5000,
#batch int = 1
while exists (select * from #trans where Batch is null)
begin
update t
set Batch = #batch
from
(
select *, cumm = sum(Cnt) over (order by TranID)
from #trans
where Batch is null
) t
where cumm <= #batchsize
select #batch = #batch + 1
end
-- Verify
select *, sum(Cnt) over (partition by Batch order by TranID)
from #trans
order by TranID
Use a table variable to iterate through your data. Kinda like using a cursor in Oracle...
If I'm understanding your sample data correctly and my assumption that each transID set nets to 0, you change your loop logic to function more like a do...while like this example here where you grab the next transaction set and decide if it keeps the batch under 5k.
This should cover populating one batch of 5000 rows or fewer rows that net to $0 assuming that each transaction ID set nets to $0
Declare #batchCursor TABLE (
TransID INT,
Credit INT, -- chose int for expediency
Debit INT,
Balance INT,
AccountNo Varchar(4)
),
#batchsize INT = 5000,
#rowCount INT = 0,
#transID INT = 1,
#transSize INT = 0;
while (#rowcount <= 5000)
BEGIN
INSERT INTO #batchCursor
SELECT * FROM [Apps2].[dbo].[GLTrans] -- you might need to enumerate all your column names
WHERE TransID = #transID;
SELECT #transSize = COUNT(*) FROM #batchCursor where TransID = #transID);
IF(#transSize > 0)
BEGIN
IF (#transSize + #rowCount < #batchSize)
BEGIN
Set #rowCount += transSize;
Set #transID += 1;
END;
END;
ELSE Set #transID += 1;
IF((Select count(*) FROM [Apps2].[dbo].[GLTrans] WHERE TransID = #transID) + #rowCount > #batch)
BREAK;
END;

SYBASE: SQL call to find first available (island) ID's

I need to return an ordered list of specific length of available customerIDs.
for example:
I need to find the FIRST 5 unused customerIDs between 1500 and 3000
Table= customer
Column= customerIDs
customerIDs value's= 1500,1502,1503,1507,1508
Return desired= 1501,1504,1505,1506,1509
I am running an old version of SYBASE that does not have "TOP" command. So far I have found the following Query to give me the next available customerID(1501), but do not know how to modify it to return the first 5 results rather then just 1.
set rowcount 5
SELECT MIN(c.customerIDs )+1 AS NextID
FROM customer c
WHERE NOT EXISTS
(SELECT NULL
FROM customer cu
WHERE cu.customerIDs =c.customerIDs +1
AND cu.customerIDs >1500)
AND c.customerIDs <3000
Well assuming you are using set rowcount 5, this will limit the query to return 5 results.
However, you are using MIN which will for sure return only 1 record. I think you want to use
set rowcount 5
SELECT c.customerIDs +1 AS NextID
FROM customer c
WHERE (c.customerIDs + 1 BETWEEN 1500 and 3000)
AND c.customerIDs + 1 NOT IN (SELECT c2.customerIDs
FROM customer c2)
ORDER BY c.customerIDs
Here's a worked example, expanding on David Faber's comment:
-- Create a temp table with all possible IDs
declare #min int, #max int
select #min = 1500, #max = 3000
create table #all_ids (id int)
insert #all_ids select #min
-- Keep doubling the number of rows until the limit is reached
while (select max(id) from #all_ids) < #max
begin
insert #all_ids
select id + (select count(*) from #all_ids)
from #all_ids
where id + (select count(*) from #all_ids) <= #max
end
-- Now find the 5 missing IDs by joining to the customer table on customerIDs, and only returning rows
-- where there is no match, i.e. customerIDs is null
set rowcount 5
select tmp.id
from #all_ids tmp
left join customer c
on tmp.id = c.customerIDs
where c.customerIDs is null
order by tmp.id
set rowcount 0
One trick to do this is to generate a sequence on the fly from the master..spt_values table, which is usually available in most servers.
This table contains a natural sequence that can be joined to itself to generate an even larger sequence. Example:
create table #customer (customerID int)
go
insert #customer (customerID) values (1500)
insert #customer (customerID) values (1502)
insert #customer (customerID) values (1503)
insert #customer (customerID) values (1507)
insert #customer (customerID) values (1508)
go
set rowcount 5
select customerID
from (
-- Generate a sequence of 1500-3000
select 1500 + p2.number * 1000 + p1.number as customerID
from
master..spt_values p1
, master..spt_values p2
where
p1.type = 'P'
and p2.type = 'P'
and p1.number between 0 and 999
and p2.number between 0 and 1
and p2.number * 1000 + p1.number <= 1500) seq
where customerID not in (select customerID from #customer)
order by 1
set rowcount 0
go
Returns:
customerID
-------------
1501
1504
1505
1506
1509

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

SQL Server Data Query on Possible Rows

SQL Server 2000
I have a table which lists out the months where a particular product can be sold.
ProductCode MonthNum MonthName
XXX 1 January
XXX 2 February
YYY 1 January
YYY 3 March
YYY 5 May
ZZZ 6 June
ZZZ 7 July
I need to construct a query that would allow me to pass some parameters:
ProductCode
LatestMonthNum
LatestYear
FutureForecast
that would allow me to construct a set of data list with the total rows based on the number of FutureForecast value and the rows' value would be beyond the passed LatestMonthNum and LatestYear parameter values.
For example, if I pass the following values to the query:
ProductCode YYY
LatestMonthNum 5
LatestYear 2012
FutureForecast 5
I would have the data as follows:
ProductCode MonthNum Year
YYY 1 2013
YYY 3 2013
YYY 5 2013
YYY 1 2014
YYY 3 2014
I would create a table of months and years in the future (something like a numbers table):
CREATE TABLE future (month int, year int);
DECLARE #m int;
SET #m = 1;
DECLARE #y int;
SET #y = 2000;
WHILE (#y < 2200) -- will my brain be in a jar by now?
BEGIN
WHILE (#m < 13)
BEGIN
INSERT INTO future (month, year) VALUES (#m, #y);
SET #m += 1;
END
SET #m = 1;
SET #y += 1;
END;
Then you can join your products table with the future!
SELECT TOP #FutureForecast
p.ProductCode, p.MonthNum, f.[year]
FROM Products AS p
JOIN future AS f ON f.[month] = p.MonthNum
WHERE
p.ProductCode = #ProductCode
AND (p.MonthNum > #LatestMonthNum OR f.[year] > #LatestYear)
ORDER BY f.[year], p.MonthNum
Like this?
Select * From
(SELECT ProductCode, MonthNum, Year, RANK() OVER
(PARTITION BY ProductCode ORDER BY
CAST('1/' + CAST(MonthNum AS varchar) + '/' + CAST(Year AS varchar)
AS datetime)) AS 'RANK'
FROM dbo.MyTable
WHERE (ProductCode = #productcode) AND (MonthNum > #monthnum) AND
(Year > #latestyear)) tbl Where tbl.Rank <= #futureforcast
Where is your year column?
I believe this should work. You could turn the loop into a CTE (only for SQL 2005+), but I am not sure if that gains you that much in this case.
CREATE TABLE #TempOrder
(Order INT IDENTITY (1,1), ProductCode VARCHAR(MAX), MonthNum INT)
INSERT INTO #TempOrder
SELECT ProductCode, MonthNum
FROM ProductTable
WHERE ProductCode = #ProductCode AND MonthNum > #LatestMonthNum
ORDER BY MonthNum DESC
--Increment the year from the start if it is already over the max stored
IF NOT EXISTS (SELECT 1 FROM #TempOrder)
SET #LatestYear = #LatestYear + 1
INSERT INTO #TempOrder
SELECT ProductCode, MonthNum
FROM ProductTable
WHERE ProductCode = #ProductCode AND MonthNum <= #LatestMonthNum
ORDER BY MonthNum DESC
DECLARE #MaxId
SELECT #MaxId = MAX(Id) FROM #TempOrder
CREATE TABLE #ForecastData (ProductCode VARCHAR(MAX), MonthNum INT, Year INT)
DECLARE #CurrentId INT
SET #CurrentId = 1
DECLARE #CurrentCount INT
SET #CurrentCount = 0
WHILE(#CurrentCount < #FutureForeCast)
BEGIN
INSERT INTO #ForecastData
SELECT ProductCode, MonthNum, #LatestYear
FROM #TempOrder
WHERE Id = #CurrentId
--Increment the Id, and if it's over the max in the table
--Reset to 1 and increment for a new year
SET #CurrentId = #CurrentId + 1
IF #CurrentId > #MaxId
BEGIN
SET #CurrentId = 1
SET #LatestYear = #LatestYear + 1
END
SET #CurrentCount = #CurrentCount + 1
END
SELECT #ForecastData
(This solution only for SQL 2005+)I am extremely rusty on CTE's but I figured I would give my attempt at this as a CTE also. This replaces everything from above from CREATE TABLE #ForecastData.... down:
;
WITH ForecastData (ProductCode, MonthNum, Year)
AS
(
-- Anchor member definition
SELECT ProductCode, MonthNum, #LatestYear AS Year, Id AS LastId
FROM #TempOrder
WHERE Id = 1
UNION ALL
-- Recursive member definition
SELECT ProductCode, MonthNum,
CASE WHEN Id = 1 THEN Year + 1 ELSE Year,
Id AS LastId
FROM #TempOrder
JOIN ForecastData
ON Id = CASE
WHEN LastId = #MaxId THEN 1
ELSE LastId + 1
END
)
-- Statement that executes the CTE
SELECT *
FROM ForecastData;
GO