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!
 
     
     
     
    