1

假设我有这张高分表:

id : primary key
username : string
score : int

用户名和分数本身可以重复,每个人只有 id 是唯一的。我还有一个索引可以快速获得高分:

UNIQUE scores ( score, username, id )

我怎样才能得到给定人下面的行?“低于”我的意思是他们在这个索引中的给定行之前。

例如对于(77,'name7',70)格式(分数,用户名,id)我想检索:

77, 'name7', 41
77, 'name5', 77
77, 'name5', 21
50, 'name9', 99

但不是

77, 'name8', 88 or
77, 'name7', 82 or
80, 'name2', 34 ...
4

3 回答 3

2

这是获得结果的一种方法:

SELECT t.score
     , t.username
     , t.id
  FROM scores t
 WHERE ( t.score < 77 ) 
    OR ( t.score = 77 AND t.username < 'name7' )
    OR ( t.score = 77 AND t.username = 'name7' AND t.id < 70 )
 ORDER
    BY t.score DESC
     , t.username DESC
     , t.id DESC

(注意:ORDER BY 子句可以帮助 MySQL 决定使用索引来避免“ Using filesort”操作。您的索引是查询的“覆盖”索引,因此我们希望Using indexEXPLAIN输出中看到“”。)


我进行了快速测试,在我的环境中,这确实执行了索引的范围扫描并避免了排序操作。

解释输出

id  select_type table type  possible_keys      key        rows Extra                     
--  ----------- ----- ----- ------------------ ---------- ---- --------------------------
 1  SIMPLE      t     range PRIMARY,scores_UX1 scores_UX1    3 Using where; Using index 

LIMIT n(如果您不需要返回所有满足条件的行,您可能需要向该查询添加一个。)

如果您有一个唯一的行 ID,您可以避免通过连接来指定表中的值。鉴于您问题中的数据:

在这里,我们使用对同一个表的第二次引用来获取行 id=70,然后使用连接来获取所有“较低”的行。

SELECT t.score
     , t.username
     , t.id
  FROM scores k
  JOIN scores t
    ON ( t.score < k.score ) 
    OR ( t.score = k.score AND t.username < k.username )
    OR ( t.score = k.score AND t.username = k.username AND t.id < k.id )
 WHERE k.id = 70
 ORDER
    BY t.score DESC
     , t.username DESC
     , t.id DESC
 LIMIT 1000

该查询的 EXPLAIN 还显示 MySQL 使用覆盖索引并避免排序操作:

id select_type table type  possible_keys      key         rows Extra
-- ----------- ----- ----- ------------------ ----------  ---- ------------------------
 1 SIMPLE      k     const PRIMARY,scores_UX1 PRIMARY       1
 1 SIMPLE      t     range PRIMARY,scores_UX1 scores_UX1    3  Using where; Using index
于 2013-08-12T22:56:00.423 回答
1

重复分数的“低于”的概念非常模糊:假设有 11 个用户具有相同的分数,但您希望“10 低于”是一个特殊的行。也就是说,您可以执行类似的操作(假设您从 id=70 开始)

SELECT score, username, id 
FROM scores
WHERE score<=(SELECT score FROM scores WHERE id=77)
ORDER BY if(id=77,0,1), score DESC
  -- you might also want e.g. username 
LIMIT 5 -- you might want such a thing
;

这将为您提供此模糊因子内有问题的行,首先是锚行。

编辑

重新阅读你的问题,你不想要锚行,所以你需要WHERE score<=(...) AND id<>77并忘记第一部分ORDER BY

编辑 2

在您更新问题后,我知道您只想要那些具有以下之一的行

  • 分数 < 锚行中的分数
  • 分数 == 锚行中的分数 AND 名称 < 锚行中的名称
  • score == 锚行中的分数 AND 名称 == 锚行中的名称 AND id < 锚行中的 id

我们只需将其放入查询中(再次假设您的锚行的 id=70):

SELECT score, username, id 
FROM scores, (
  SELECT 
    @ascore:=score, 
    @ausername:=username,
    @aid:=id
  FROM scores 
  WHERE id=70
) AS seed
WHERE
  score<@ascore
  OR (score=@ascore AND username<@ausername)
  OR (score=@ascore AND username=@ausername AND id<@aid)
ORDER BY
  score DESC,
  username DESC,
  id DESC
-- limit 5 //You might want that
;
于 2013-08-12T21:44:39.137 回答
0

我认为这是您想要的查询:

select s.*
from scores s
where s.score <= (select score
                  from scores
                  where id = 70
                 ) and
      s.id <> 70
order by scores desc
limit 4;
于 2013-08-12T21:44:25.980 回答