简体   繁体   English

Mysql,在多列索引中的特定行之前获取行

[英]Mysql, get rows before specific row in a multi-column index

Say I have this table for high-scores: 说我有这个表高分:

id : primary key
username : string
score : int

User names and scores themselves can be repeating, only id is unique for each person. 用户名和分数本身可以重复,只有id对每个人都是唯一的。 I also have an index to get high-scores fast: 我还有一个快速获得高分的索引:

UNIQUE scores ( score, username, id )

How can I get rows below the given person? 如何在给定人员下方获得行? By 'below' I mean they go before the given row in this index. “低于”我的意思是它们在此索引中的给定行之前。

Eg for ( 77, 'name7', 70 ) in format ( score, username, id ) I want to retrieve: 例如(77,'name7',70)格式(得分,用户名,id)我要检索:

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

but not 但不是

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

Here's one way to get the result: 这是获得结果的一种方法:

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

(NOTE: the ORDER BY clause may help MySQL decide to use the index to avoid a " Using filesort " operation. Your index is a "covering" index for the query, so we'd expect to see " Using index " in the EXPLAIN output.) (注意:ORDER BY子句可以帮助MySQL决定使用索引来避免“ Using filesort ”操作。你的索引是查询的“覆盖”索引,所以我们期望在EXPLAIN看到“ Using index ”输出)。


I ran a quick test, and in my environment, this does perform a range scan of the index and avoids a sort operation. 我运行了一个快速测试,在我的环境中,它确实执行了索引的范围扫描并避免了排序操作。

EXPLAIN OUTPUT EXPLAIN OUTPUT

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

(You may want to add a LIMIT n to that query, if you don't need to return ALL the rows that satisfy the criteria.) (如果不需要返回满足条件的所有行,则可能需要向该查询添加LIMIT n 。)

If you have an unique id of a row, you could avoid specifying the values in the table by doing a join. 如果您具有行的唯一ID,则可以避免通过执行连接来指定表中的值。 Given the data in your question: 根据您的问题中的数据:

Here we use a second reference to the same table, to get the row id=70, and then a join to get all the rows "lower". 这里我们使用对同一个表的第二个引用来获取行id = 70,然后使用连接来获取所有行“lower”。

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

The EXPLAIN for this query also shows MySQL using the covering index and avoiding a sort operation: 此查询的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

The concept of "below" for repeating scores is quite fuzzy: Think of 11 users having the same score, but you want the "10 below" a special row. 重复分数的“下方”概念非常模糊:想想11个用户具有相同的分数,但您希望“10以下”成为特殊行。 That said, you can do something like (assuming you start with id=70) 也就是说,你可以做一些事情(假设你从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
;

Which will give you the rows in question inside this fuzzy factor, with the anchor row first. 这将在这个模糊因子中给出有问题的行,首先是锚行。

Edit 编辑

Re-reading your question, you don't want the anchor row, so you need WHERE score<=(...) AND id<>77 and forget the first part of the ORDER BY 重新阅读你的问题,你不需要锚行,所以你需要WHERE score<=(...) AND id<>77并忘记ORDER BY的第一部分

Edit 2 编辑2

After your update to the question, I understand you want only those rows, that have one of 在您对问题进行更新后,我了解到您只想要那些拥有其中一行的行

  • score < score in anchor row 得分<锚行中的得分
  • score == score in anchor row AND name < name in anchor row 得分==锚行中的得分和名称<锚行中的名称
  • score == score in anchor row AND name == name in anchor row AND id < id in anchor row 得分==锚行中的得分和名称==锚行中的名称和锚点行中的ID <id

We just have to put that into a query (again assuming your anchor row has id=70): 我们只需将其放入查询中(再次假设您的锚行具有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
;

I think this is the query you want: 我想这是你想要的查询:

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;

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM