How to apply DENSE_RANK() to records ordered by multiple columns? - sql

I'm writing a SQL stored procedure that for each AliasName it will retrieve me MasterNames it's associated with in the NameAssociations table, which contains AliasNameId, MasterNameId, and MatchRank, and StatusCode columns. I'll be using the stored procedure for server-side paging the data in C#, so I'd like the startRow endRow part to stay the same.
CREATE PROCEDURE [dbo].[NameAssociationsGetNameAssociations]
#StartRow INT = 1,
#EndRow INT = 1
AS
WITH result_set AS (
SELECT DENSE_RANK() OVER (ORDER BY an.Id) AS rowNum,
an.Id as AliasId, an.AliasName,
mn.Id as MasterId, mn.MasterName, mn.CodeId, mn.RowUpdateVersion AS ConcurrencyToken, na.MatchRank
FROM
NameAssociations na
INNER JOIN AliasNames an
ON na.AliasNameId = an.Id
INNER JOIN MasterNames mn
ON na.MasterNameId = mn.Id
WHERE
na.StatusCode = 0
)
SELECT
rowNum,
AliasId as Id, AliasName,
MasterId as Id, MasterName, CodeId, ConcurrencyToken, MatchRank
FROM
result_set
WHERE
rowNum BETWEEN #StartRow AND #EndRow
This procedure works and retrieves rows numbered correctly:
rowNum
Id
AliasName
Id
MasterName
CodeId
ConcurrencyToken
MatchRank
1
5
BName
34
SomeName1
2
0x0000021
1
2
6
DName
21
SomeName2
3
0x0000003
2
2
6
DName
2
SomeName3
1
0x00000A2
1
2
6
DName
40
SomeName4
1
0x00000B4
3
3
7
AName
11
SomeName5
1
0x000005B
1
So basically, I get a list of MasterNames for every AliasName.
But the rows are not ordered in the way I would like them to be, which is by AliasName ASC, Id (of alias) ASC, and MatchRank ASC, which would look like this:
rowNum
Id
AliasName
Id
MasterName
CodeId
ConcurrencyToken
MatchRank
1
7
AName
11
SomeName5
1
0x000005B
1
2
5
BName
34
SomeName1
2
0x0000021
1
3
6
DName
2
SomeName3
1
0x00000A2
1
3
6
DName
21
SomeName2
3
0x0000003
2
3
6
DName
40
SomeName4
1
0x00000B4
3
You can see the names of aliases are ordered alphabetically, and then the associated masters were reordered so the MatchRank is in ASC order. All of this while the rowNum is correct, which is what I've been having trouble with attaining.
I've tried doing PARTITION BY an.Id ORDER BY an.AliasName ASC, an.Id ASC, na.MatchRank ASC, but I must be misunderstanding what ORDER BY does in this case because the results come out wrong. The results look like it ordered the records only by MatchRank ASC and partitioned by it. I'm expecting it to order the data by those three columns, and then partition it by the an.Id.
How can I write the query so that the output result looks like the second example of table? I hope this was all clear.

Ignoring what looks like a transcription/obfuscation error in your script that actually renders your SQL as invalid (dense_rank must have an order by), I think your solution is actually pretty simple.
The dense_rank function uses the values in the order by to determine which records get the same ranking value, so if you want to sort the output by the AliasName, but also want to make sure records with the same AliasName but different id values (not sure why this would be possible, but defensive coding is generally a good practice), you just need to include both values in your order by, in the order that you want to apply sorting. In your case this looks like the following minimal solution:
Query
declare #t table(id int, AliasName varchar(10), val int);
insert into #t values (1,'b',1),(2,'c',2),(2,'c',3),(2,'cc',4),(22,'c',5),(3,'a',6);
select *
,dense_rank() over (order by id) as rid
,dense_rank() over (order by AliasName, id) as rAliasName
from #t;
Output
id
AliasName
val
rid
rAliasName
3
a
6
3
1
1
b
1
1
2
2
c
2
2
3
2
c
3
2
3
22
c
5
4
4
2
cc
4
2
5

Related

Update new foreign key column of existing table with ids from another table in SQL Server

I have an existing table to which I have added a new column which is supposed to hold the Id of a record in another (new) table.
Simplified structure is sort of like this:
Customer table
[CustomerId] [GroupId] [LicenceId] <-- new column
Licence table <-- new table
[LicenceId] [GroupId]
The Licence table has a certain number of licences per group than can be assigned to customers in that same group. There are multiple groups, and each group has a variable number of customers and licences.
So say there are 100 licences available for group 1 and there are 50 customers in group 1, so each can get a license. There are never more customers than there are licences.
Sample
Customer
[CustomerId] [GroupId] [LicenceId]
1 1 NULL
2 1 NULL
3 1 NULL
4 1 NULL
5 2 NULL
6 2 NULL
7 2 NULL
8 3 NULL
9 3 NULL
Licence
[LicenceId] [GroupId]
1 1
2 1
3 1
4 1
5 1
6 1
7 2
8 2
9 2
10 2
11 2
12 3
13 3
14 3
15 3
16 3
17 3
Desired outcome
Customer
[CustomerId] [GroupId] [LicenceId]
1 1 1
2 1 2
3 1 3
4 1 4
5 2 7
6 2 8
7 2 9
8 3 12
9 3 13
So now I have to do this one time update to give every customer a licence and I have no idea how to go about it.
I'm not allowed to use a cursor. I can't seem to do a MERGE UPDATE, because joining the Customer to the Licence table by GroupId will result in multiple hits.
How do I assign each customer the next available LicenceId within their group in one query?
Is this even possible?
You can use window functions:
with c as (
select c.*, row_number() over (partition by groupid order by newid()) as seqnum
from customers c
),
l as (
select l.*, row_number() over (partition by groupid order by newid()) as seqnum
from licenses c
)
update c
set c.licenceid = l.licenseid
from c join
l
on c.seqnum = l.seqnum and c.groupid = l.groupid;
This assigns the licenses randomly. That is really just for fun. The most efficient method is to use:
row_number() over (partition by groupid order by (select null)) as seqnum
SQL Server often avoids an additional sort operation in this case.
But you might want to order them by something else -- for instance by the ordering of the customer ids, or by some date column, or something else.
Gordon has put it very well in his answer.
Let me break it down into simpler steps for you.
Step 1. Use the ROW_NUMBER() function to assign a SeqNum to the Customers. Use PARTITION BY GroupId so that the number starts from 1 in every group. I would ORDER BY CustomerId
Step 2. Use the ROW_NUMBER() function to assign a SeqNum to the Licences. Use PARTITION BY GroupId so that the number starts from 1 in every group. ORDER BY LicenseId because your ask is to "assign each customer the next available LicenceId within their group".
Now use these 2 queries to update LicenseId in Customer table.

Sort rows for duplicate ID numbers

I have been trying to find ways to sort rows based on duplicate ID numbers per below but have been unsuccessful.
ID SortOrder PersonID
1 0 100
2 1 100
3 0 200
4 0 200
5 1 200
I am trying to sort the sortOrder column so the number 1 will display at the top for every PersonID. So the end results are like below:
ID SortOrder PersonID
1 1 100
2 0 100
3 1 200
4 0 200
5 0 200
Any Suggestions? Thanks!
You can use CASE like this.
ORDER BY PersonID,CASE WHEN SortOrder = 1 THEN 1 ELSE 2 END
You can change the CASE appropriately, if you want to change how following rows after SortOrder = 1 should be ordered for the same PersonID.
Like ORDER BY PersonID,CASE SortOrder WHEN 1 THEN 1 WHEN 0 THEN 2 ELSE 3 END
One other solution:
select id,SortOrder,PersonId from
(select id,SortOrder,PersonId,row_number() over (partition by PersonId order by SortOrder desc) as rn from table) A
order by A.rn asc
Based on your comments regarding wanting the ID value to stay in constant order, you're likely looking for something like this:
Select Row_Number() Over (Order By PersonID Asc, SortOrder Desc) As ID,
SortOrder,
PersonID
From YourTable
Order By PersonID Asc, SortOrder Desc
But, I'd caution against naming the ROW_NUMBER field ID, as it can be misleading since it's not actually the unique identifier for the stored record.

SQL - Order by amount of occurrences

It's my first question here so I hope I can explain it well enough,
I want to order my data by amount of occurrences in the table.
My table is like this:
id Daynr
1 2
1 4
2 4
2 5
2 6
3 1
4 2
4 5
And I want it to sort it like this:
id Daynr
3 1
1 2
1 4
4 2
4 5
2 4
2 5
2 6
Player #3 has one day in the table, and Player #1 has 2.
My table is named "dayid"
Both id and Daynr are foreign keys, together making it a primary key
I hope this explains my problem enough, Please ask for more information it's my first time here.
Thanks in advance
You can do this by counting the number of times that things occur for each id. Most databases support window functions, so you can do this as:
select id, daynr
from (select t.*, count(*) over (partition by id) as cnt
from table t
) t
order by cnt, id;
You can also express this as a join:
select t.id, t.daynr
from table as t inner join
(select id, count(*) as cnt
from table
group by id
) as tg
on t.id = tg.id
order by tg.cnt, id;
Note that both of these include the id in the order by. That way, if two ids have the same count, all rows for the id will appear together.

Is there a way to update groups of rows with separate incrementing values in one query

Lets say you have the following table:
Id Index
1 3
1 1
2 1
3 3
1 5
what I would like to have is the following:
Id Index
1 0
1 1
2 0
3 0
1 2
As you might notice, the goal is for every row where Id is the same, to incrementally update the Index column, starting from zero.
Now, I know this is fairly simple with using cursors, but out of curiosity is there a way to do this with single UPDATE query, somehow combining with temp tables, common table expressions or something similar?
Yes, assuming that the you don't really care about the order of the values for the new index values. SQL Server offers updatable CTEs and window functions that do exactly what you want:
with toupdate as (
select t.*, row_number() over (partition by id order by (select NULL)) as newindex
from table t
)
update toupdate
set index = newindex;
If you want them in a specific order, then you need another column to specify the ordering. The existing index column doesn't work.
With Row_number() -1 and CTE you can write as:
CREATE TABLE #temp1(
Id int,
[Index] int)
INSERT INTO #temp1 VALUES (1,3),(1,1),(2,1),(3,3),(1,5);
--select * from #temp1;
With CTE as
(
select t.*, row_number() over (partition by id order by (select null))-1 as newindex
from #temp1 t
)
Update CTE
set [Index] = newindex;
select * from #temp1;
Demo
I'm not sure why you would want to do this really, but I had fun figuring it out!
This solution relies on your table having a primary key for the self join... but you could always create an auto inc index if none exists and this is a one off job... This will also have the added benefit of getting you to think about the precise ordering of this you want... as currently there is no way of saying which order [ID] will get [Index] in.
UPDATE dbo.Example
SET [Index] = b.newIndex
FROM dbo.Example a
INNER JOIN (
select
z.ID,
z.[Index],
(row_number() over (partition by ID order by (select NULL))) as newIndex
from Example z
) b ON a.ID = b.ID AND a.[Index]=b.[Index] --Is this a unique self join for your table?.. no PK provided. You might need to make an index first.
Probably, this is what you want
SELECT *,RANK() OVER(PARTITION BY Id ORDER BY [Index])-1 AS NewIndex FROM
(
SELECT 1 AS Id,3 [Index]
UNION
SELECT 1,1
UNION
SELECT 2,1
UNION
SELECT 3,3
UNION
SELECT 1,5
) AS T
& the result will come as
Now if you want to update the table then execute this script
UPDATE tblname SET Index=RANK() OVER(PARTITION BY t.Id ORDER BY t.[Index])-1
FROM tblname AS t
In case I am missing something or any further assistance is required please let me know.
CREATE TABLE #temp1(
Id int,
Value int)
INSERT INTO #temp1 VALUES (1,2),(1,3),(2,3),(4,5)
SELECT
Id
,Value
,ROW_NUMBER() OVER (PARTITION BY Id ORDER BY Id) Id
FROM #temp1
Start with this :)
Gave me results like
Id Value Count
1 2 1
1 3 2
1 2 3
1 3 4
1 2 5
1 3 6
1 2 7
1 3 8
2 3 1
2 4 2
2 5 3
2 3 4
2 4 5
2 5 6
2 4 7
2 5 8
2 3 9
2 3 10
3 4 1
4 5 1
4 5 2
4 5 3
4 5 4

SQL MAX(column) With Additional Criteria

I have a single table, where I want to return a list of the MAX(id) GROUPed by another identifier. However I have a third column that, when it meets a certain criteria, "trumps" rows that don't meet that criteria.
Probably easier to explain with an example. Sample table has:
UniqueId (int)
GroupId (int)
IsPriority (bit)
Raw data:
UniqueId GroupId IsPriority
-----------------------------------
1 1 F
2 1 F
3 1 F
4 1 F
5 1 F
6 2 T
7 2 T
8 2 F
9 2 F
10 2 F
So, because no row in groupId 1 has IsPriority set, we return the highest UniqueId (5). Since groupId 2 has rows with IsPriority set, we return the highest UniqueId with that value (7).
So output would be:
5
7
I can think of ways to brute force this, but I am looking to see if I can do this in a single query.
SQL Fiddle Demo
WITH T
AS (SELECT *,
ROW_NUMBER() OVER (PARTITION BY GroupId
ORDER BY IsPriority DESC, UniqueId DESC ) AS RN
FROM YourTable)
SELECT UniqueId,
GroupId,
IsPriority
FROM T
WHERE RN = 1