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

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...

Related

Best combination to satisfy a quantity

I have a list of items with the same article but different quantities, and I want to find different subgroups of items that could satisfy a specific quantity.
UNIT
ITEM
Quantity
1
1
40
2
1
50
3
1
60
4
1
60
5
1
60
The quantity to satisfy is between 110 and 120
I want to find all the combinations of units that can reach the sum between 110 and 120.
My solution, for now, is to use sql programming, but don't know if can be done with a query.
declare #units table ( id int,Item int,Quantity int)
insert into #units
values
(1 ,1 ,40),
(2 ,1 ,50),
(3 ,1 ,60),
(4 ,1 ,60),
(5 ,1 ,60)
DECLARE #TEMP TABLE ( ID INT, QUANTITY INT , RANKc INT)
DECLARE #TEMP2 TABLE ( Rmin INT, Rmax INT , sumQ INT)
INSERT INTO #TEMP
SELECT
Id,
Quantity,
row_number() over (order by Quantity asc) as rank_c
FROM #units
where Item=1
select * from #TEMP
DECLARE #RMIN INT =1
DECLARE #RMax INT =2
DECLARE #MaxQ int=120
DECLARE #MinQ int=110
--DECLARE #Count int=1
DECLARE #SumQuantity int
WHILE (#RMax<=(SELECT MAX(RANKc) FROM #TEMP))
BEGIN
select #SumQuantity=sum(QUANTITY) from #TEMP where RANKc between #RMIN and #RMax
if (#SumQuantity between #MinQ and #MaxQ)
begin
insert into #TEMP2 values
(#RMIN,#RMax,#SumQuantity)
set #RMIN=#RMIN+1
set #RMax=#RMIN+1
--break;
end
IF (#SumQuantity < #MinQ)
begin
set #RMax=#RMax+1
end
if (#SumQuantity>#MaxQ)
begin
set #RMIN=#RMIN+1
set #RMax=#RMIN+1
end
END
select * from #TEMP2
Rmin
Rmax
sumQ
2
3
110
3
4
120
4
5
120

How to create loop based on value of row?

I have problem when I use my query bellow to have a looping inside the cursor.
data in table1 will be like this:
id | data
----|---------
A | 4
B | 2
C | 5
the result in table2 should be like this:
id | data
----|---------
A | 1
A | 1
A | 1
A | 1
B | 1
B | 1
C | 1
C | 1
C | 1
C | 1
C | 1
I have SQL query with cursor like this:
DECLARE #table2 table ( id VARCHAR(500), data INTEGER)
DECLARE Cur CURSOR FOR
SELECT id, data FROM table1
OPEN Cur
WHILE ( ##FETCH_STATUS = 0 )
BEGIN
DECLARE #LoopNum INTEGER
DECLARE #tempID VARCHAR(255)
DECLARE #tempDATA INTEGER
FETCH NEXT FROM Cur INTO #tempID, #tempDATA
set #LoopNum = 0
WHILE #LoopNum < #tempDATA
BEGIN
INSERT INTO table2 (id, data)
VALUES( #tempID, 1)
SET #LoopNum = #LoopNum + 1
END
END
CLOSE Cur
DEALLOCATE Cur
SELECT * FROM table2
but the query didn't work. is there something wrong with my query?
Thank you.
Use this query to the expected result.
CREATE TABLE #test
(id CHAR(1),data INT)
INSERT #test VALUES ('A',4)
INSERT #test VALUES('B',2)
INSERT #test VALUES('C',5);
SELECT s.id, 1 AS data
FROM #test s
INNER JOIN
master.dbo.spt_values t ON t.type='P'
AND t.number BETWEEN 1 AND s.data
Note: Refer this Why (and how) to split column using master..spt_values?
You actually don't need a loop
IF OBJECT_ID('TEMPDB..#TEMP') IS NOT NULL
DROP TABLE #TEMP
SELECT 'A' AS ID, 4 AS DATA
INTO #TEMP UNION
SELECT 'B', 2 UNION
SELECT 'C', 5
;WITH CTE AS
(
SELECT 1 AS NUMBER
UNION ALL
SELECT NUMBER + 1
FROM CTE
WHERE NUMBER < 100
)
SELECT T.ID, 1
FROM CTE C
INNER JOIN #TEMP T
ON C.NUMBER <= T.DATA
ORDER BY T.ID
Carefull that if you want ot generate a large set of numbers in the CTE it may become slower.
Use a Recursive CTE which will help you to loop through the records.
CREATE TABLE #test
(id CHAR(1),data INT)
INSERT #test
VALUES ('A',4),('B',2),('C',5);
WITH cte
AS (SELECT 1 AS da,id,data
FROM #test a
UNION ALL
SELECT da + 1,id,data
FROM cte a
WHERE da < (SELECT data
FROM #test b
WHERE a.id = b.id))
SELECT id,
1 AS data
FROM cte
ORDER BY id
i used two loops
1. for each row
2. for number for duplicate insert
SET NOCOUNT on;
DECLARE #t table(row int IDENTITY(1,1),id varchar(10),data int)
INSERT INTO #t
SELECT * from xyz
DECLARE #x table(id varchar(10),data int) --table to hold the new data
DECLARE #i int=(SELECT count (*) from xyz) --number of rows main table
DECLARE #y int --number of duplicate
DECLARE #p int=1 --number of rows
WHILE #i!=0 --loop until last row of main table
BEGIN
SET #y=(SELECT data FROM #t WHERE row=#p) --set #y for number of 'row duplicate'
WHILE #y!=0
BEGIN
INSERT INTO #x
SELECT id,1
FROM #t
WHERE row=#p
SET #y=#y-1
END
SET #p=#p+1
SET #i=#i-1
END
SELECT * FROM #x

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

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

Fill in missing rows in a table SQL

I have a report that needs the top 18 id codes for each case. Some cases only have all 18 rows and some only have a few. Here is an example of the output:
Case idcode value
2 3 122
2 6 52
2 15 121
3 1 111
3 3 555
3 6 322
What I need the output to have is 18 rows per record (idcodes 1-18) and to put "none" for the value if it is added. What is the best way to add in the missing rows if I do not know which ones are missing ahead of time?
Here is my query:
SELECT
rcl.CaseCaseId as Case, cce.StringValue as Value, cce.CorpIdCodeId as idcode
FROM
CaseIdCodeEntry AS cce
INNER JOIN
CorpIdCodes AS cid ON cce.CorpIdCodeId = cid.CorpIdCodeId
INNER JOIN
PhdRpt.ReportCaseList_542 AS rcl ON cce.CaseCaseId = rcl.CaseCaseId
WHERE
(cce.CorpIdCodeId < 19)
I would use a recursive CTE to auto-generate a numbered list of 1-18, and then LEFT JOIN off of that. Then use a CASE statement to adjust the Value field.
;WITH cte AS
( SELECT DISTINCT CaseCaseId AS CaseID, 1 AS idcode
FROM PhdRpt.ReportCaseList_542 UNION ALL
SELECT CaseID, idcode+1 FROM cte WHERE idcode < 18 )
SELECT cte.CaseID AS [Case],
CASE WHEN cce.CorpIdCodeId IS NULL THEN 'None' ELSE cce.StringValue END AS Value,
cte.idcode AS idcode
FROM cte
LEFT JOIN CaseIdCodeEntry cid ON cid.CorpCodeId = cte.idcode
LEFT JOIN CorpIdCodes cid ON cce.CorpIdCodeId = cid.CorpIdCodeId
LEFT JOIN PhdRpt.ReportCaseList_542 rcl ON cce.CaseCaseId = rcl.CaseCaseId
Try this seems works fine
create table #temp(iCase int, idcode int,value int)
Insert into #temp values(2,3,122)
Insert into #temp values(2,6,52)
Insert into #temp values(2,15,121)
Insert into #temp values(3,1,11)
Insert into #temp values(3,3,555)
Insert into #temp values(3,6,322)
create table #Val(Id int)
declare #count int =1
while (#count<=18)
begin
insert into #Val values(#count)
set #count=#count+1
end
DECLARE #CaseId INT
DECLARE #DataCursor CURSOR
SET #DataCursor = CURSOR FOR
SELECT distinct iCase
From #temp
OPEN #DataCursor
FETCH NEXT
FROM #DataCursor INTO #CaseId
WHILE ##FETCH_STATUS = 0
BEGIN
INSERT INTO #temp
SELECT #CaseId,Id,null
FROM #Val
WHERE Id NOT IN (
SELECT idcode
FROM #temp
WHERE iCase=#CaseId )
FETCH NEXT
FROM #DataCursor INTO #CaseId
END
CLOSE #DataCursor
DEALLOCATE #DataCursor
Select * from #temp
TL;DR SQL Fiddle
Like Jon of All Trades I also favour the use of a sequence (or number) table.
CREATE TABLE Seq ( seq_num int)
You can use this to populate the missing rows from your original data; assuming you have a table T holding your data
CREATE TABLE T ( case_num int
,code_id int
,value char(4))
You can use the following query to get your fully populated results
WITH All_Codes AS (
SELECT DISTINCT case_num, seq_num AS code_id
FROM T, Seq
)
SELECT All_Codes.case_num
,All_Codes.code_id
,CASE WHEN value IS NULL THEN 'none' ELSE value END AS value
FROM All_Codes LEFT JOIN T
ON All_Codes.case_num = T.case_num AND All_Codes.code_id = T.code_id
The result is
case_num code_id value
2 1 none
2 2 none
2 3 122
2 4 none
2 5 none
2 6 52
2 7 none
2 8 none
2 9 none
2 10 none
2 11 none
2 12 none
2 13 none
2 14 none
2 15 121
2 16 none
2 17 none
2 18 none
3 1 111
3 2 none
3 3 555
3 4 none
3 5 none
3 6 322
3 7 none
3 8 none
3 9 none
3 10 none
3 11 none
3 12 none
3 13 none
3 14 none
3 15 none
3 16 none
3 17 none
3 18 none
Humpty and Matt's solutions should work, but as a purist I'd recommend using a Numbers table rather than a cursor or CTE. It's simpler (IMHO) and for large quantities it should be significantly faster:
SELECT
X.CaseId, N.Number, X.Value
FROM
Numbers AS N
LEFT JOIN
(
SELECT
CICE.CaseCaseId AS CaseId, CICE.StringValue AS Value, CICE.CorpIdCodeId AS IdCode
FROM
CaseIdCodeEntry AS CICE
INNER JOIN CorpIdCodes AS CIC ON CICE.CorpIdCodeId = CIC.CorpIdCodeId
INNER JOIN PhdRpt.ReportCaseList_542 AS RCL ON CICE.CaseCaseId = RCL.CaseCaseId
) AS X ON N.Number = X.IdCode
WHERE
N.Number BETWEEN 1 AND 18
Incidentally, are you sure you need to join CaseIdCodeEntry to CorpIdCodes and ReportCaseList_542? If they're there to filter the data, that's fine, but as they're not contributing to the output I have to wonder.
This is the solution that I used. I used some of #Jon of All Trades and #huMpty duMpty code.
SELECT cice.CaseCaseId, cice.CorpIdCodeId, cice.DateValue, cice.DoubleValue, cice.StringValue, cic.Label
into #TT
FROM CaseIdCodeEntry AS cice INNER JOIN
CorpIdCodes AS cic ON cice.CorpIdCodeId = cic.CorpIdCodeId INNER JOIN
PhdRpt.ReportCaseList_542 AS rcl ON cice.CaseCaseId = rcl.CaseCaseId
WHERE (cice.CorpIdCodeId <= 18)
ORDER BY cice.CaseCaseId, cice.CorpIdCodeId
create table #Val(Id int)
declare #count int =1
while (#count<=18)
begin
insert into #Val values(#count)
set #count=#count+1
end
DECLARE #CaseId INT
DECLARE #DataCursor CURSOR
SET #DataCursor = CURSOR FOR
SELECT distinct CaseCaseId
From #TT
OPEN #DataCursor
FETCH NEXT
FROM #DataCursor INTO #CaseId
WHILE ##FETCH_STATUS = 0
BEGIN
INSERT INTO #TT
SELECT #CaseId,Id,null, null, null, 'none'
FROM #Val
WHERE Id NOT IN (
SELECT CorpIdCodeId
FROM #TT
WHERE CaseCaseId=#CaseId )
FETCH NEXT
FROM #DataCursor INTO #CaseId
END
CLOSE #DataCursor
DEALLOCATE #DataCursor
Select * from #TT
order by CaseCaseId, CorpIdCodeId
drop table #TT, #Val

How to Split Sql Int Value into Multiple Rows

Lets say I have the following table in MS SQL 2000
Id | description | quantity |
-------------------------------
1 my desc 3
2 desc 2 2
I need to display multiple rows based on the quantity, so I need the following output:
Id | description | quantity |
-----------------------------
1 my desc 1
1 my desc 1
1 my desc 1
2 desc 2 1
2 desc 2 1
Any ideas how to accomplish this?
This works just fine, no need for any cursors on this one. It may be possible to fangle something out without a number table as well.
Note if going for this kind of solution I would keep a number table around and not recreate it every time I ran the query.
create table #splitme (Id int, description varchar(255), quantity int)
insert #splitme values (1 ,'my desc', 3)
insert #splitme values (2 ,'desc 2', 2)
create table #numbers (num int identity primary key)
declare #i int
select #i = max(quantity) from #splitme
while #i > 0
begin
insert #numbers default values
set #i = #i - 1
end
select Id, description, 1 from #splitme
join #numbers on num <= quantity
DECLARE #Id INT
DECLARE #Description VARCHAR(32)
DECLARE #Quantity INT
DECLARE #Results TABLE (Id INT, [description] VARCHAR(32), quantity INT)
DECLARE MyCursor CURSOR FOR
SELECT Id, [description], quantity
FROM
MyTable
OPEN MyCursor
FETCH NEXT FROM MyCursor INTO #Id, #Description, #Quantity
WHILE ##FETCH_STATUS = 0
BEGIN
WHILE #Quantity > 0
BEGIN
INSERT INTO #Results (
Id,
[description],
quantity
) VALUES (
#Id,
#Description,
1
)
SET #Quantity = #Quantity - 1
END
FETCH NEXT FROM MyCursor INTO #Id, #Description, #Quantity
END
CLOSE MyCursor
DEALLOCATE MyCursor
SELECT *
FROM
#Results
By the way, cursors are generally considered evil. So I will both recommend against something like this, and thank everyone in advance for their flames ;) (But it should work)
Any one looking for a CTE based solution, here is one:
create table test_splitme (Id int, description varchar(255), quantity int)
insert test_splitme values (1 ,'my desc', 3)
insert test_splitme values (2 ,'desc 2', 2)
;
--TSQL solution
with splitcte as(
select Id,description, 1 as quantity, quantity as orig_quantity, 1 as cnt
from test_splitme
union all
select Id, description, quantity, orig_quantity,cnt+1
from splitcte
where cnt < orig_quantity
)
select Id, description, quantity
from splitcte
order by 1