领导板的高效SQL查询/架构

时间:2009-02-22 21:03:13

标签: sql

我写了一个愚蠢的小游戏,并希望拥有某种排行榜网站。

通常排行榜仅限于10或20名顶级球员,但我认为如果能够为每位球员录制,他们的最高分会更好。然后,我总能展示他们的世界级别。

一个简单的架构,例如:

create table leaderboard (
    userid varchar(128) not null,
    score real not null,
    when datetime not null
);
create index on leaderboard(userid);

将存储我需要的最少量信息 - 每个用户获得最佳分数1个条目。

我的问题围绕如何有效地确定某人在排行榜上的位置。一般的想法是,我希望他们在列表中的位置返回:

select userid from leaderboard order by score desc

但从数据库性能的角度来看,运行此查询然后线性搜索列表似乎有点荒谬。即使这样,我也很难想象一个可以快速操作的查询/模式。

有什么想法吗?

(我更希望保持数据库架构和查询通用(不依赖于供应商)。但是,如果一个供应商使这很容易,我很乐意使用MS SQL或MySQL。

7 个答案:

答案 0 :(得分:13)

怎么样:

select count(*)+1 as rank from leaderboard  
where score > (select score from leaderboard where userid = ?)

您还需要分数列上的索引。

使用count()+1执行score > (...)即使多个玩家获得相同分数,也会为您提供准确的排名;使用count()执行score >= (...)不会。

答案 1 :(得分:2)

在SQL Server 2005及更高版本中,您可以使用RANK()函数根据每个用户的分数返回排名

SELECT 
    userid,
    RANK() OVER 
    (ORDER BY score) AS rank
FROM leaderboard

如果您有多种类型的“游戏类型”,那么您可以将其包含在排行榜表格中,并使用PARTITION BY功能中的RANK子句来确定每种游戏类型的排名。

答案 2 :(得分:1)

显而易见的选择是在“得分”上创建一个索引,这是完全合理的。 (听起来你想要保留两个值 - 累积分数和高水分 - 否则我会误解?)

除非您期望成千上万的用户,否则即使是表扫描也不应该是具有那么多记录的表上的大问题。

答案 3 :(得分:1)

看起来你想查询一下:

select userid , max(score)
from leaderboard 
group by userid
order by max(score) desc

这会为每个用户返回一个带有1个条目的排行榜。

以下编辑: 我在评论中看到你想看到排名,而不是分数。 为此我不知道ANSI SQL答案,但具体数据:

在MySQL中:

SELECT @rownum:=@rownum+1 rank
, t.userid 
FROM (SELECT @rownum:=0) r, 
 (select userid , score
from leaderboard 
order by score desc
) t;

在Oracle中,您可以使用RANK语句。

答案 4 :(得分:1)

如果不必是实时的(例如,如果每天更新一次是可接受的),请添加一个额外的“位置”字段,并使用按分数排序的查询定期更新。

答案 5 :(得分:1)

在Sql server 2005中,Rank()几乎可以帮到你。但是,如果你拥有数百万条记录,那么无论何时基础统计数据发生变化,都会实时对其进行排名。

我尝试在select查询的基础上创建一个Indexed视图...(这个帖子中的投票答案)但是Sql 2005不会让我创建它因为你不能在索引视图中使用子查询,自引用。

所以我们的解决方法是使用Row()函数每晚更新排名表。为了避免在执行此更新时阻塞,我们保留了2个正在更新的排名表副本和一个正在应用程序中使用的副本。我们有一个RankingView,它指向任何给定时间的活跃排名表。

我想知道是否有针对真正大型表的实时排名更新的解决方案?

答案 6 :(得分:0)

我在考虑选择"选择计数(*)+ 1作为排行榜的排名 得分> (从用户ID =?的排行榜中选择分数)"

考虑以下情况: 球员1,得分100 球员2,得分100 球员3,得分50

使用该SQL,玩家3的等级将为3,其中正确答案应为2,因为玩家1和玩家2在第一个位置并列。在这种情况下,count()聚合函数考虑2个记分,其中Score列> 50。

为了处理这种情况,我认为正确的选择是制作一个分组,因此可重复的分数值将作为单个排名位置处理:

select score, count(*)+1 as rank from leaderboard  
group by score having (score) > (select score from leaderboard where userid = ?)