我有一个玩家表,每个玩家都有一个ID(索引主键),名称和分数。除索引外,表不排序。 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
我已经定义了一个排行榜,以便所有玩家按照他们的分数排序并分配排名,所以我使用的是RANK()函数:
SELECT RANK() OVER (ORDER BY [Score] DESC) AS [Score_Rank],
[Name],
[Score]
FROM [dbo].[PlayerScores]
到目前为止一切顺利。对于上述数据,我会得到
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
然而,当我向玩家展示这个排行榜时,我不需要或想要向他们展示一切 - 只有玩家紧接在他们上方和下方(不会有任何分页导航 - 玩家只能看到一个他们总体位置的快照。)
因此我试图检索(n)特定玩家周围的数据行,例如:
如何构建查询以便始终返回最小行数?例如。对于我的上述数据集和n = 5,Erin
会看到
Rank | Name | Score
=================
1 | Erin | 33
2 | Ann | 31
3 | Fred | 29
4 | Dan | 25
5 | Carl | 24
虽然Dan
会看到
Rank | Name | Score
=================
2 | Ann | 31
3 | Fred | 29
4 | Dan | 25
5 | Carl | 24
6 | Bob | 17
Lou
会看到
Rank | Name | Score
=================
4 | Dan | 25
5 | Carl | 24
6 | Bob | 17
6 | Lou | 17
8 | Joan | 11
我在两个查询上找到了UNION
的部分解决方案(一个在上面有n / 2行,另一个在指定玩家下面有n / 2行),但如果玩家在(或附近)表格的顶部或底部 - 剪切结果数据集,并且总是想要尽可能检索完整(n)行。
我认为该解决方案可能与Window函数有关,使用了LAG和LEAD,但老实说我无法理解语法,我发现的大部分示例都不关心返回足够的行数。谢谢!
答案 0 :(得分:1)
这将做你想要的。
WITH cte AS (
SELECT RANK() OVER (ORDER BY [Score] DESC) AS [Score_Rank],
ROW_NUMBER() OVER (ORDER BY [Score] DESC) AS [RowNum],
COUNT(ID) OVER (PARTITION BY (Select NULL)) AS MaxRow,
[Name],
[Score],
[ID]
FROM @playScores
)
SELECT Score_Rank, Name, Score
FROM
cte
CROSS APPLY (SELECT RowNum AS AnchorRN FROM cte WHERE ID = @playerID) tmp
WHERE
(
RowNum <=
CASE WHEN tmp.AnchorRN < ((@n)/2) THEN @n
ELSE tmp.AnchorRN + ((@n)/2) END
)
AND
(
RowNum >=
CASE WHEN tmp.AnchorRN > (MaxRow - (@n)/2) THEN (MaxRow -@n + 1)
ELSE tmp.AnchorRN - ((@n)/2) END
);
SELECT *
, ROW_NUMBER() OVER (ORDER BY Score) AS RowNum
FROM
@playScores
ORDER BY
RowNum;
这是完整的答案和测试代码。
DECLARE @playScores TABLE (
ID INT
, Name NVARCHAR(50)
, Score INT
);
INSERT INTO @playScores (ID, Name, Score)
VALUES
(1 ,' Bob ', 17),
(2 ,' Carl ', 24),
(3 ,' Ann ', 31),
(4 ,' Joan ', 11),
(5 ,' Lou ', 17),
(6 ,' Dan ', 25),
(7 ,' Erin ', 33),
(8 ,' Fred ', 29);
DECLARE @n INT = 5;
DECLARE @playerID INT =5;
SELECT *
FROM
@playScores
ORDER BY
Score DESC;
WITH cte AS (
SELECT RANK() OVER (ORDER BY [Score] DESC) AS [Score_Rank],
ROW_NUMBER() OVER (ORDER BY [Score] DESC) AS [RowNum],
COUNT(ID) OVER (PARTITION BY (Select NULL)) AS MaxRow,
[Name],
[Score],
[ID]
FROM @playScores
)
SELECT Score_Rank, Name, Score
FROM
cte
CROSS APPLY (SELECT RowNum AS AnchorRN FROM cte WHERE ID = @playerID) tmp
WHERE
(
RowNum <=
CASE WHEN tmp.AnchorRN < ((@n)/2) THEN @n
ELSE tmp.AnchorRN + ((@n)/2) END
)
AND
(
RowNum >=
CASE WHEN tmp.AnchorRN > (MaxRow - (@n)/2) THEN (MaxRow -@n + 1)
ELSE tmp.AnchorRN - ((@n)/2) END
);
SELECT *
, ROW_NUMBER() OVER (ORDER BY Score) AS RowNum
FROM
@playScores
ORDER BY
RowNum;
SELECT *
, ROW_NUMBER() OVER (ORDER BY Score) AS RowNum
FROM
@playScores
ORDER BY
RowNum;
答案 1 :(得分:1)
同一程序的两个版本,一个按顺序输出结果集,第二个不按顺序输出。
rextester链接试用:http://rextester.com/JLQU48329
create table dbo.PlayerScores (Id int, Name nvarchar(64), Score int)
insert into dbo.PlayerScores (Id, Name, Score) values
(1,'Bob',17) ,(2,'Carl',24) ,(3,'Ann',31) ,(4,'Joan',11)
,(5,'Lou',17) ,(6,'Dan',25) ,(7,'Erin',33) ,(8,'Fred',29);
go
/* ordered resultset */
create procedure dbo.PlayerScores_getMiddle_byId (@PlayerId int, @Results int = 5) as
begin;
with cte as (
select
Score_Order = row_number() over (order by Score desc)
, Score_Rank = rank() over (order by Score desc)
, Id
, Name
, Score
from dbo.PlayerScores
)
select c.Score_Rank, c.Name, c.Score
from (
select top (@Results) i.*
from cte i
cross apply (select Score_Order from cte where Id = @PlayerId) as x
order by abs(i.Score_Order-x.Score_Order)
) as c
order by Score_Rank;
end
go
exec dbo.PlayerScores_getMiddle_byId 7,5; -- Erin
exec dbo.PlayerScores_getMiddle_byId 6,5; --Dan
exec dbo.PlayerScores_getMiddle_byId 5,5; --Lou
go
/* unordered result set */
/*
create procedure dbo.PlayerScores_getMiddle_byId (@PlayerId int,@Results int = 5) as
begin;
with cte as (
select
Score_Order = row_number() over (order by Score desc)
, Score_Rank = rank() over (order by Score desc)
, Id
, Name
, Score
from dbo.PlayerScores
)
select top (@Results) c.Score_Rank, c.Name, c.Score
from cte as c
cross apply (select
Score_Order
from cte
where Id = @PlayerId) as x
order by abs(c.Score_Order-x.Score_Order)
end
--go
exec dbo.PlayerScores_getMiddle_byId 7,5; -- Erin
exec dbo.PlayerScores_getMiddle_byId 6,5; --Dan
exec dbo.PlayerScores_getMiddle_byId 5,5; --Lou
--*/
答案 2 :(得分:0)
或使用标准SQL:
with pRank(id, name, rank)
as (Select p.Id, p.Name nam,
(Select count(*) from players
where score <= p.score) rnk
from players p)
Select p.id, p.nam, p.score,
n.id, n.nam, n.score
from pRank p join pRank n
on n.Rnk between
case when p.Rnk < @n/2 then 0
else p.Rnk - @n / 2 end
and case when p.Rnk < @n/2 then @n
else p.Rnk + @n / 2 end
order by p.rnk, p.Id, n.rnk
测试:
declare @t table
(id integer primary key not null,
nam varchar(30) not null, score int not null)
insert @t(id, nam, score)
values
(1, 'Bob ',17),
(2, 'Carl',24),
(3, 'Ann ',31),
(4, 'Joan',11),
(5, 'Lou ',17),
(6, 'Dan ',25),
(7, 'Erin',33),
(8, 'Fred',29)
declare @n int = 4;
with pRank(id, nam, rnk)
as (Select p.Id, p.Nam,
(Select count(*) from @t
where score <= p.score) rank
from @t p)
Select p.id, p.Nam, p.rnk,
n.id, n.nam, n.rnk
from pRank p join pRank n
on n.rnk between
case when p.rnk < @n/2 then 0
else p.rnk - @n / 2 end
and case when p.rnk < @n/2 then @n
else p.rnk + @n / 2 end
order by p.rnk, p.id, n.rnk .