Select top 2 distinct for each id and date - sql-server

I have a table like this :
Table1:
[Id] [TDate] [Score]
1 1.1.00 50
1 1.1.00 60
2 1.1.01 50
2 1.1.01 70
2 1.3.01 40
3 1.1.00 80
3 1.1.00 30
3 1.2.00 40
My desired output should be like this:
[ID] [TDate] [Score]
1 1.1.00 60
2 1.1.01 70
2 1.3.01 40
3 1.1.00 80
3 1.2.00 40
So fare, I have written this:
SELECT DISTINCT TOP 2 Id, TDate, Score
FROM
( SELECT Id, TDate, Score, ROW_NUMBER() over(partition by TDate order by Score) Od
FROM Table1
) A
WHERE A.Od = 1
ORDER BY Score
But it gives me :
[ID] [TDate] [Score]
2 1.1.01 70
3 1.1.00 80
of course I can do this:
"select top 2 ...where ID = 1"
and then:
union
`"Select top 2 ... where ID = 2"`
etc..
but I have a 100,000 of this..
Any way to generalize it to any Id?
Thank you.

WITH TOPTWO AS (
SELECT Id, TDate, Score, ROW_NUMBER()
over (
PARTITION BY TDate
order by SCORE
) AS RowNo
FROM [table_name]
)
SELECT * FROM TOPTWO WHERE RowNo <= 2

Your output doesn't make sense. Let me assume you want two rows per id. Then the query would look like:
SELECT TOP 2 Id, TDate, Score
FROM (SELECT Id, TDate, Score,
ROW_NUMBER() over (partition by id order by Score DESC) as seqnum
FROM Table1
) t
WHERE seqnum <= 2
ORDER BY Score;
Notes:
This assumes that you want two rows per id. Hence, id is in the PARTITION BY.
The WHERE now selects two rows per group in the PARTITION BY.
There is no need for SELECT DISTINCT in the outer query -- at least for this question.

Try this : Make partition by ID and TDate and sort by score in descending order
ROW_NUMBER() over(partition by ID,TDate order by Score DESC) Od
Complete script
WITH CTE AS(
SELECT *,
ROW_NUMBER() over(partition by ID,TDate order by Score DESC) RN
FROM TableName
)
SELECT *
FROM CTE
WHERE RN = 1

Unless I am missing something this can be done with a simple group by
First I prepare a temp table for testing :
declare #table table (ID int, TDate varchar(10), Score int)
insert into #Table values(1, '1.1.00', 50)
insert into #Table values(1, '1.1.00', 60)
insert into #Table values(2, '1.1.01', 50)
insert into #Table values(2, '1.1.01', 70)
insert into #Table values(2, '1.3.01', 40)
insert into #Table values(3, '1.1.00', 80)
insert into #Table values(3, '1.1.00', 30)
insert into #Table values(3, '1.2.00', 40)
Now lets do a select on this table
select ID, TDate, max(Score) as Score
from #table
group by ID, TDate
order by ID, TDate
The result is this :
ID TDate Score
1 1.1.00 60
2 1.1.01 70
2 1.3.01 40
3 1.1.00 80
3 1.2.00 40
So all you need to do is change #table to your table name and you are done

Related

Get sum up of every 2nd day data between a selected date range

I having table like below in Sql Server. I need to get data within in a date range, for example -: StartDate = '2020-09-01' and EndDate = '2020-09-11'. Its quite simple to get data between a date range but complicated part is that,i need to Sum up data in every 2nd day in the selected date range.
For Example -:
As in the above image, i need to Sum up of SKU in every 2nd day in single column. Could anyone help me out with the query for this result output.
CREATE TABLE #Temp
(
Sku Nvarchar(50),
OrderDate DateTime,
Quantity Int,
)
INSERT INTO #Temp(Sku,OrderDate,Quantity)Values('#xyz','2020-09-01 00:00:00.000',2)
INSERT INTO #Temp(Sku,OrderDate,Quantity)Values('#xyz','2020-09-02 00:00:00.000',1)
INSERT INTO #Temp(Sku,OrderDate,Quantity)Values('#xyz','2020-09-03 00:00:00.000',3)
INSERT INTO #Temp(Sku,OrderDate,Quantity)Values('#xyz','2020-09-04 00:00:00.000',4)
INSERT INTO #Temp(Sku,OrderDate,Quantity)Values('#xyz','2020-09-05 00:00:00.000',5)
INSERT INTO #Temp(Sku,OrderDate,Quantity)Values('#xyz','2020-09-06 00:00:00.000',6)
INSERT INTO #Temp(Sku,OrderDate,Quantity)Values('#xyz','2020-09-07 00:00:00.000',2)
INSERT INTO #Temp(Sku,OrderDate,Quantity)Values('#xyz','2020-09-08 00:00:00.000',1)
INSERT INTO #Temp(Sku,OrderDate,Quantity)Values('#xyz','2020-09-09 00:00:00.000',3)
INSERT INTO #Temp(Sku,OrderDate,Quantity)Values('#xyz','2020-09-10 00:00:00.000',1)
INSERT INTO #Temp(Sku,OrderDate,Quantity)Values('#xyz','2020-09-11 00:00:00.000',10)
INSERT INTO #Temp(Sku,OrderDate,Quantity)Values('#abc','2020-09-01 00:00:00.000',1)
INSERT INTO #Temp(Sku,OrderDate,Quantity)Values('#abc','2020-09-02 00:00:00.000',10)
INSERT INTO #Temp(Sku,OrderDate,Quantity)Values('#abc','2020-09-03 00:00:00.000',10)
select * from #Temp
Use row_number() window function to generate a sequence number per Sku. Do a GROUP BY (rn - 1) / 2. HAVING COUNT(*) = 2 is to only consider those with 2 rows
; with
cte as
(
select *, rn = row_number() over (partition by Sku order by OrderDate)
from #Temp
)
select Sku, sum(Quantity)
from cte
group by Sku, (rn - 1) / 2
having count(*) = 2
order by Sku , (rn - 1) / 2
Use STRING_AGG if you want the result in CSV.
With ROW_NUMBER() and LAG() window functions:
select Sku, Quantity
from (
select Sku,
row_number() over (partition by Sku order by OrderDate) rn,
Quantity + lag(Quantity) over (partition by Sku order by OrderDate) Quantity
from #Temp
where OrderDate between '20200901' and '20200911'
) t
where rn % 2 = 0
order by Sku, rn;
See the demo.
Results:
> Sku | Quantity
> :--- | -------:
> #abc | 11
> #xyz | 3
> #xyz | 7
> #xyz | 11
> #xyz | 3
> #xyz | 4
Something like this
;with
string_cte(Sku, OrderDate, Quantity, rn_grp) as(
select *, (row_number() over (partition by Sku order by OrderDate)+1)/2
from #Temp),
sum_cte(Sku, rn_grp, sum_quantity) as (
select Sku, rn_grp, sum(quantity)
from string_cte
group by Sku, rn_grp
having count(*)>1)
select
Sku, string_agg(sum_quantity, ',') within group (order by rn_grp) SecondDaySumUp
from sum_cte
group by Sku
order by 1 desc;
Output
Sku SecondDaySumUp
#xyz 3,7,11,3,4
#abc 11

Variable within SQL query

I have this:
SELECT NEWID() as id,
'OwnerReassign' as name,
1 as TypeId,
'MyOrganisation' as OrgName,
'07DA8E53-74BD-459C-AF94-A037897A51E3' as SystemUserId,
0 as StatusId,
GETDATE() as CreatedAt,
'{"EntityName":"account","Ids":["'+CAST(AccountId as varchar(50))+'"],"OwnerId":"0C01C994-1205-E511-988E-26EE4189191B"}' as [Parameters]
FROM Account
WHERE OwnerIdName IN ('John Smith') AND New_AccountType = 1
Within the parameter field is an id (0C01C994-1205-E511-988E-26EE4189191B). Is it possible it could sequentially assign a different id from a list for each row? There are 5 id's in total.
What i'm trying to get to is this result set equally split between the 5 different id's.
Thanks
You can add one more NEWID() in the sub query and handle in the SELECT as below:
SELECT id, [name], TypeId, OrgName, SystemUserId, StatusId, CreatedAt,
'{"EntityName":"account","Ids":["' + AccountId +'"],"OwnerId":"' + ParamId + '"}' as [Parameters]
FROM (
SELECT NEWID() as id,
'OwnerReassign' as name,
1 as TypeId,
'MyOrganisation' as OrgName,
'07DA8E53-74BD-459C-AF94-A037897A51E3' as SystemUserId,
0 as StatusId,
GETDATE() as CreatedAt,
CAST(NEWID() AS VARCHAR (36)) as ParamId,
CAST(AccountId as varchar(50)) as AccountId
FROM Account
WHERE OwnerIdName IN ('John Smith') AND New_AccountType = 1
) A
You can use something like the following. Basically, use a row number for both your IDs and your data table to update, then do a MOD (%) operation with the amount of ID's you want to assign, so your data table to update is split into N groups. Then use that group ID to assign each ID.
IF OBJECT_ID('tempdb..#IDsToAssign') IS NOT NULL
DROP TABLE #IDsToAssign
CREATE TABLE #IDsToAssign (
IDToAssign VARCHAR(100))
-- 3 IDs example
INSERT INTO #IDsToAssign (
IDToAssign)
SELECT IDToAssign = NEWID()
UNION ALL
SELECT IDToAssign = NEWID()
UNION ALL
SELECT IDToAssign = NEWID()
DECLARE #AmountIDsToAssign INT = (SELECT COUNT(1) FROM #IDsToAssign)
IF OBJECT_ID('tempdb..#Account') IS NOT NULL
DROP TABLE #Account
CREATE TABLE #Account (
PrimaryKey INT PRIMARY KEY,
AssignedID VARCHAR(100))
-- 10 Rows example
INSERT INTO #Account (
PrimaryKey)
VALUES
(100),
(200),
(351),
(154),
(194),
(345),
(788),
(127),
(124),
(14)
;WITH DataRowNumber AS
(
SELECT
A.*,
RowNumber = ROW_NUMBER() OVER (ORDER BY (SELECT NULL))
FROM
#Account AS A
),
IDsRowNumbers AS
(
SELECT
D.IDToAssign,
RowNumber = ROW_NUMBER() OVER (ORDER BY D.IDToAssign)
FROM
#IDsToAssign AS D
),
NewIDAssignation AS
(
SELECT
R.*,
IDRowNumberAssignation = (R.RowNumber % #AmountIDsToAssign) + 1
FROM
DataRowNumber AS R
)
UPDATE A SET
AssignedID = R.IDToAssign
FROM
NewIDAssignation AS N
INNER JOIN IDsRowNumbers AS R ON N.IDRowNumberAssignation = R.RowNumber
INNER JOIN #Account AS A ON N.PrimaryKey = A.PrimaryKey
SELECT
*
FROM
#Account AS A
ORDER BY
A.AssignedID
/* Results:
PrimaryKey AssignedID
----------- ------------------------------------
124 1CC7F0F1-7EDE-4F7F-B0A3-739D74A62390
194 1CC7F0F1-7EDE-4F7F-B0A3-739D74A62390
351 1CC7F0F1-7EDE-4F7F-B0A3-739D74A62390
788 2A58A573-EDCB-428E-A87A-6BFCED265A9C
200 2A58A573-EDCB-428E-A87A-6BFCED265A9C
127 2A58A573-EDCB-428E-A87A-6BFCED265A9C
14 2A58A573-EDCB-428E-A87A-6BFCED265A9C
100 FD8036DA-0E15-453E-8A59-FA3C2BDB8FB1
154 FD8036DA-0E15-453E-8A59-FA3C2BDB8FB1
345 FD8036DA-0E15-453E-8A59-FA3C2BDB8FB1
*/
The ordering of the ROW_NUMBER() function will determine how ID's are assigned.
You could potentially do this by using the ROW_NUMBER() field in a subquery; for example:
SELECT NEWID() as id, 'OwnerReassign' as name, 1 as TypeId,
'MyOrganisation' as OrgName,
'07DA8E53-74BD-459C-AF94-A037897A51E3' as SystemUserId,
0 as StatusId, GETDATE() as CreatedAt,
case B / ##ROWCOUNT
when 0 then '0C01C994-1205-E511-988E-26EE4189191B'
when 1 then '12345677-1205-E511-988E-26EE4189191B'
when 2 then '66666666-1205-E511-988E-26EE4189191B'
etc...
end
FROM
(
SELECT ROW_NUMBER() OVER (ORDER BY A.Id)
FROM Account A
WHERE OwnerIdName IN ('John Smith') AND New_AccountType = 1
) AS B
If you want the system to pick those values then you could put then in their own temporary table, too.

How to select the values of multiple columns in sorted order in mssql?

In a table there are three columns, each containing numeric values (in my case representing length, width and height, but could be anything).
How can I select them, ordered by their value?
For example, given the values:
id | length | width | height
1 | 100 | 30 | 50
2 | 6 | 12 | 9
Expected output would be:
id | min | mid | max
1 | 30 | 50 | 100
2 | 6 | 9 | 12
Use the Table Value Constructor with ORDER BY to get the relevent values in sorted order.
Using OFFSET X ROWS and FETCH NEXT 1 ROWS ONLY, you can access a specific position inside the sorted column values.
Use it repeatedly (increasing OFFSET by 1 in each step) to access the sorted column values at each position.
SELECT
length,
width,
height,
(
SELECT dimensions
FROM ( VALUES (length),(width),(height) ) AS compare(dimensions)
ORDER BY dimensions ASC
OFFSET 0 ROWS
FETCH NEXT 1 ROWS ONLY
) AS minDimension,
(
SELECT dimensions
FROM ( VALUES (length),(width),(height) ) AS compare(dimensions)
ORDER BY dimensions ASC
OFFSET 1 ROWS
FETCH NEXT 1 ROWS ONLY
) AS midDimension,
(
SELECT dimensions
FROM ( VALUES (length),(width),(height) ) AS compare(dimensions)
ORDER BY dimensions ASC
OFFSET 2 ROWS
FETCH NEXT 1 ROWS ONLY
) AS maxDimension
FROM sometable
Try this
DECLARE #T TABLE
(
Id INT,
[Length] INT,
Width INT,
Height INT
)
INSERT INTO #T
VALUES(1,100,30,50),(2,6,9,12)
;WITH CTE
AS
(
SELECT
Id,
Val = [Length]
FROM #T
UNION ALL
SELECT
Id,
Val = Width
FROM #T
UNION ALL
SELECT
Id,
Val = Height
FROM #T
),C2
AS
(
SELECT
SeqNo = ROW_NUMBER() OVER(PARTITION BY ID ORDER BY Val),
Id,
Val
FROM CTE
)
SELECT
Id,
[1] "min",
[2] "mid",
[3] "max"
FROM C2
PIVOT
(
MAX(VAL)
FOR
SeqNo IN
(
[1],[2],[3]
)
)q
One alternate solution, using PERCENTILE_CONT to obtain the Median:
WITH Measurement AS(
SELECT *
FROM (VALUES (1,100,30,50),
(2,6,12,9)) V(ID, [Length], Width, Height)),
Pvt AS(
SELECT ID,
D.Measurement AS MeasurementType,
CASE WHEN D.Measurement = 'Length' THEN M.[Length]
WHEN D.Measurement = 'Width' THEN M.Width
WHEN D.Measurement = 'Height' THEN M.Height
END AS Measurement
FROM Measurement M
CROSS APPLY (VALUES ('Length'),('Width'),('Height')) D(Measurement)),
Median AS(
SELECT ID,
PERCENTILE_CONT(0.5) WITHIN GROUP (ORDER BY Measurement) OVER (PARTITION BY ID) AS Median,
MeasurementType, Measurement
FROM Pvt)
SELECT ID,
MIN(Measurement) AS [Min],
Median,
MAX(Measurement) AS [Max]
FROM Median
GROUP BY ID, Median
ORDER BY ID;
First, make the values of columns length, width and height to one column using union. And then use this result-set as a sub-query and give a row number based on group by id and descending order of the newly added column. Then by using a CASE expression take the row having row number 1 as max, 2 as mid and 3 as min.
Query
;with cte as(
select [rn] = row_number() over(
partition by t.[id]
order by t.[new_col] desc
), *
from (
select [id], [length] as [new_col] from [your_table_name]
union all
select [id], [width] from [your_table_name]
union all
select [id], [height] from [your_table_name]
) t
)
select [id],
max(case [rn] when 1 then [new_col] end) as [max],
max(case [rn] when 2 then [new_col] end) as [mid],
max(case [rn] when 3 then [new_col] end) as [min]
from cte
group by [id];
Fiddle demo
This seems to work:
Query
declare #table table (id integer, length integer, width integer, height integer)
insert into #table select 1,100,30,50
insert into #table select 2,6,12,9
select id, MIN(a) min
,(select top 1 max(c.a) from
(
select id,length a from #table
union all
select id,width a from #table
union all
select id,height a from #table
)c
where c.id = b.id and c.a <> max(b.a) and c.a <> min(b.a) group by c.id) mid, max(a) max
from
(
select id,length a from #table
union all
select id,width a from #table
union all
select id,height a from #table
)b
group by id

Select a random row from each group SQL Server

I have a table like below
ID Code Age
----------------
1 4758 21
1 7842 14
1 9821 23
1 6842 9
2 8472 24
2 7558 31
2 7841 28
3 7881 38
3 8794 42
3 4871 43
For each ID, I want to select one of the rows at random like so
ID Code Age
----------------
1 7842 14
2 7841 28
3 4871 43
Is this possible in SQL Server?
select top 1 with ties id,code,age
from
table
order by row_number() over (partition by id order by rand())
Update: as per this Return rows in random order, you have to use NEWId,since RAND() is fixed for the duration of the SELECT on MS SQL Server.
select top 1 with ties id,code,age
from
table
order by row_number() over (partition by id order by NEWID())
Use Newid() in order by clause of Row_number()
SELECT [ID], [Code], [Age]
FROM (SELECT *,
Row_number()
OVER(
PARTITION BY ID
ORDER BY Newid()) RNO
FROM #Table1)A
WHERE RNO = 1
with cte as
(
select *,rank() over ( partition by id order by Newid()) as rn from #c
)
select id,code,age from cte where rn=1
To select different sets each time, use checksum(newid()) in the order by clause.
Query
;with cte as(
select *, rn = row_number() over(
partition by ID
order by abs(checksum(newid())) % 15
)
from [your_table_name]
)
select * from cte
where rn = 1;

Maximum and Minimum Rows Alternatively in SQL Server

This is an Employee table,
Id Name Salary
1 A.J 7000
2 B.S 30000
3 C.K 2000
4 D.O 10000
5 E.L 500
Now i want to display 1st highest salary then minimum salary then 2nd maximum salary then 2nd minimum salaray and so on..up to nth row.
Expected Output,
Id Name Salary
2 B.S 30000
5 E.L 500
4 D.O 10000
3 C.K 2000
1 A.J 7000
One more variant without explicit COUNT. SQL Fiddle.
Try also to add this row to sample data (6, 'X.Y', 7000) in the fiddle. The query still returns correct results.
DECLARE #Employee TABLE (ID int, Name nvarchar(50), Salary money);
INSERT INTO #Employee (ID, Name, Salary) VALUES
(1, 'A.J', 7000),
(2, 'B.S', 30000),
(3, 'C.K', 2000),
(4, 'D.O', 10000),
(5, 'E.L', 500);
WITH
CTE
AS
(
SELECT *, NTILE(2) OVER (ORDER BY Salary, ID) AS n
FROM #Employee AS E
)
SELECT
*
,SIGN(n-1.5) AS s
,SIGN(n-1.5)*Salary AS ss
,ROW_NUMBER() OVER(PARTITION BY n ORDER BY SIGN(n-1.5)*Salary DESC) AS rn
FROM CTE
ORDER BY rn, ss DESC;
Result
ID Name Salary n s ss rn
2 B.S 30000.00 2 1.0 30000.00000 1
5 E.L 500.00 1 -1.0 -500.00000 1
4 D.O 10000.00 2 1.0 10000.00000 2
3 C.K 2000.00 1 -1.0 -2000.00000 2
1 A.J 7000.00 1 -1.0 -7000.00000 3
I left intermediary columns in the output to illustrate how it works.
Using Row_Number() and Count()
Fiddle Demo
declare #count int=(select count(1) from Employee);
with cte1 as
(
select ROW_NUMBER() over(order by salary desc) as rn,0 Sort,Id,Name,Salary, count(Id) over () cnt from Employee
union all
select ROW_NUMBER() over(order by salary) as rn,1 Sort,Id,Name,Salary, count(Id) over () cnt from Employee
)
select top (#count) Id,Name,Salary from cte1 where rn <= (floor(cnt/2) + cnt%2) order by rn,sort
Below is the solution:
--Create dummy employee table
CREATE TABLE tbl_Employee
(
Id INT,
Name VARCHAR(100),
Salary NUMERIC(9, 2)
)
GO
--Insert few dummy rows in the table
INSERT INTO #Employee
(Id, Name, Salary)
VALUES(100, 'John', 7000),
(101, 'Scott', 30000),
(102, 'Jeff', 2000),
(103, 'Jimy', 10000),
(104, 'Andrew', 500),
(105, 'Alister', 100)
GO
--Get data as required
DECLARE #Cnt INT = 0, #SeqLimit INT = 0
SELECT #Cnt = COUNT(1) FROM tbl_employee
SET #SeqLimit = CEILING(#Cnt / 2.0)
SELECT * FROM
(
SELECT ROW_NUMBER() OVER(ORDER BY Salary DESC) AS SEQ, Id, Name, Salary FROM tbl_employee
)DT1
WHERE SEQ <= #SeqLimit
UNION ALL
SELECT * FROM
(
SELECT ROW_NUMBER() OVER(ORDER BY Salary ASC) AS SEQ, Id, Name, Salary FROM tbl_employee
)DT2
WHERE SEQ <= #SeqLimit - (#Cnt % 2)
ORDER BY SEQ ASC, Salary DESC
The same can be achieved with different approaches and here you can find more on this:
http://www.sqlrelease.com/order-max-and-min-value-rows-alternatively-in-sql-server

Resources