I have a table of players each having an ID (indexed primary key), a name, and a score. The table is not sorted except by index. e.g.
[dbo].[PlayerScores]
ID | Name | Score
=================
1 | Bob | 17
2 | Carl | 24
3 | Ann | 31
4 | Joan | 11
5 | Lou | 17
6 | Dan | 25
7 | Erin | 33
8 | Fred | 29
I've defined a leaderboard such that all of the players are ordered by their score and assigned a rank, so I'm using the RANK() function:
SELECT RANK() OVER (ORDER BY [Score] DESC) AS [Score_Rank],
[Name],
[Score]
FROM [dbo].[PlayerScores]
So far so good. For the above data, I'll get
Rank | Name | Score
=================
1 | Erin | 33
2 | Ann | 31
3 | Fred | 29
4 | Dan | 25
5 | Carl | 24
6 | Bob | 17
6 | Lou | 17
8 | Joan | 11
However, when I present this leaderboard to the players, I don't need or want to show them everything - only the players immediately above and below them (there won't be any paged navigation - players only get to see a snapshot of their overall position).
I'm therefore trying to retrieve (n) rows of data surrounding a specific player, such that:
- If there are (n) or fewer rows in the table, all rows will be returned.
- Where there are at least (n) rows in the table, (n) rows of data will be returned.
- There should be (n/2) rows above and below the specified player.
- If there aren't (n/2) rows above the specified player, return all the rows above, and enough rows below to make up (n) rows total.
- If there aren't (n/2) rows below the specified player, return all the rows below, and enough rows above to make up (n) rows total.
How can I construct my query such that I can always return the minimum number of rows? E.g. for my above dataset and n=5, Erin
would see
Rank | Name | Score
=================
1 | Erin | 33
2 | Ann | 31
3 | Fred | 29
4 | Dan | 25
5 | Carl | 24
While Dan
would see
Rank | Name | Score
=================
2 | Ann | 31
3 | Fred | 29
4 | Dan | 25
5 | Carl | 24
6 | Bob | 17
And Lou
would see
Rank | Name | Score
=================
4 | Dan | 25
5 | Carl | 24
6 | Bob | 17
6 | Lou | 17
8 | Joan | 11
I found a partial solution for this using a UNION
on two queries (one getting n/2 rows above and one getting n/2 rows below the specified player), but it falls down if the player is at (or near) the top or bottom of the table - the resulting dataset is clipped, and I always want to retrieve a full (n) rows where possible.
I think the solution might have something to do with Window functions, making use of LAG and LEAD, but I honestly can't get my head around the syntax and most of the examples I've found don't care about not returning enough rows total. Thanks!