以当前用户 ID 为中心的排行榜排行榜 - SQL 查询

时间:2021-03-06 17:12:33

标签: sql sql-server tsql

我有下表:

+---------+------------+----------+-------+
| userId  | campaignId | countryId| points| 
+---------+------------+----------+-------+
|      10 | 1          |      101 |    72 |
|       3 | 1          |      101 |    30 |
|       6 | 1          |      101 |    72 |
|       4 | 1          |      101 |    49 |
|       1 | 1          |      101 |    53 |
|       8 | 1          |      101 |    67 |
|       5 | 1          |      101 |     6 |
|       7 | 1          |      101 |    87 |
|       2 | 1          |      101 |    41 |
|      11 | 1          |      101 |    76 |
|       9 | 1          |      101 |    50 |
+---------+------------+----------+-------+

我已经用这样的查询创建了一个排行榜:

    select 
       RANK() OVER(order by T.points desc) AS rowRank,
          T.UserID, T.points
        from table as T 
        where T.campaignId=@campaignId
OFFSET (@page-1)*@limit ROWS FETCH NEXT @limit ROWS ONLY

上面的查询返回一个从上到下的常规顶部列表。

但是,下一个要求是创建一个排行榜toplist,返回当前用户Id排名+上面2个排名的用户+排名下面2个用户,总共5个用户应该以当前用户为中心列出。

所以额外的输入参数是:

set @userId = 8 // current user where leader board should center around
set @maxTopLimit = 2 // include 2 users ranked above current user
set @maxBottomLimit = 2 // include 2 users ranked below current user

返回的排行榜应如下所示,中间为 userId 8

        +---------+------------+----------+-------+---------|
        | userId  | campaignId | countryId| points| rowRank |
        +---------+------------+----------+-------+---------+
        |      11 | 1          |      101 |    76 |   3     |
        |      10 | 1          |      101 |    72 |   4     |
   #####|###### 8 | 1 #########|##### 101 |### 67 |## 5 ####|########
        |       9 | 1          |      101 |    50 |   6     |
        |       2 | 1          |      101 |    49 |   7     |
        +---------+------------+----------+-------+------+--+

我该如何编写这样的 SQL 查询?

1 个答案:

答案 0 :(得分:2)

  1. 在子查询或公用表表达式中移动排名结果。
    with cte_rank as (...)
  2. 选择目标用户。
    from cte_rank cr where cr.UserId = @userId
  3. 将目标行与定义的间隔内的所有行连接起来。
    join cte_rank cr2 on cr2.RowRank >= cr.RowRank - @before and cr2.RowRank <= cr.RowRank + @after
  4. 选择区间内的所有行。
    select cr2.*

示例数据

create table CampaignPoints
(
  UserId int,
  CampaignId int,
  CountryId int,
  Points int
);

insert into CampaignPoints (UserId, CampaignId, CountryId, Points) values
(10, 1, 101, 72),
( 3, 1, 101, 30),
( 6, 1, 101, 72),
( 4, 1, 101, 49),
( 1, 1, 101, 53),
( 8, 1, 101, 67),
( 5, 1, 101,  6),
( 7, 1, 101, 87),
( 2, 1, 101, 41),
(11, 1, 101, 76),
( 9, 1, 101, 50);

解决方案

declare @userId int = 8;
declare @before int = 2;
declare @after  int = 2;

with cte_rank as
(
  select cp.UserId,
         cp.CampaignId,
         cp.CountryId,
         cp.Points,
         rank() over(order by cp.Points desc) as RowRank
  from CampaignPoints cp
)
select cr2.*
from cte_rank cr
join cte_rank cr2
  on  cr2.RowRank >= cr.RowRank - @before
  and cr2.RowRank <= cr.RowRank + @after
where cr.UserId = @userId
order by cr2.RowRank;

结果

UserId  CampaignId  CountryId  Points  RowRank
------  ----------  ---------  ------  ------- 
10      1           101        72      3
 6      1           101        72      3
 8      1           101        67      5
 1      1           101        53      6
 9      1           101        50      7

Fiddle 查看实际情况。