我怎样才能在MySQL中找到每天最高分?

时间:2013-03-13 18:34:44

标签: mysql sql greatest-n-per-group

我们有一个游戏,玩家通过地图,并为其得分。我们需要找出哪些尝试在每张地图上都有高分,并将其isHighScore标记设置为true。只有一次尝试可以获得每个地图的高分 - 如果两次尝试具有相同的分数,则只有按时间顺序排列的尝试才应设置isHighScore标记。

我们目前的SQL看起来像这样:

UPDATE attempts AS A1
SET isHighScore = 1
WHERE A1.ID =
(
    SELECT A2.ID
    FROM (SELECT ID, score, mapID, `date` FROM attempts) AS A2
    WHERE A2.mapID = A1.mapID
    ORDER BY A2.score DESC, A2.date ASC
    LIMIT 1
)

(SELECT ... FROM attempts)子查询归于this issue

上面的时间要比在超过75k条目的表上运行超时(并且是的,mapID, score, date上有一个索引)。我假设这是因为最里面的查询将整个attempts表复制到临时表,但将WHERE mapID = A1.mapID条件移动到该查询会产生语法错误,所以我不知道该怎么做那。此外,内部查询为每一行运行一次 - 也许有办法解决这个问题?

有没有人知道在MySQL中编写此查询的更好方法?

4 个答案:

答案 0 :(得分:1)

您可以尝试使用update-join。

UPDATE attempts
RIGHT JOIN
(
    SELECT id FROM attempts a1
    WHERE NOT EXISTS
    (
        SELECT 0 FROM attempts a2
        WHERE a2.mapID = a1.mapID AND 
            (a2.score > a1.score OR (a2.score = a1.score AND a2.date < a1.date))
    )
) tmp ON tmp.id = attempts.id
SET attempts.isHighestScore = 1;

如果您在mapID列和得分列上放置索引,这应该相当快。

答案 1 :(得分:0)

您可以尝试使用相关子查询仅匹配相同地图的行,其中没有其他行具有更高分数,或者如果存在具有相同分数的行,则没有其他行具有更早的日期:

UPDATE attempts
   SET isHighScore = 1
 WHERE ID IN (
           SELECT ID FROM (
               SELECT ID
                 FROM attempts a1
                WHERE NOT EXISTS (
                          SELECT 0
                            FROM attempts a2
                           WHERE a2.mapID = a1.mapID
                             AND (a2.score > a1.score OR
                                 (a2.score = a1.score AND a2.date < a1.date))
                      )
           ) a0
       )

A SQLFiddle

不要忘记将旧的高分设置为0。

答案 2 :(得分:0)

这有效,但我不确定性能,试一试。这是fiddle

UPDATE attempts SET isHighScore = 1 WHERE ID IN
(
  SELECT ID FROM
  (
    SELECT a5.* FROM attempts a5
    INNER JOIN
    (
      SELECT mapID, max(score) as max_score, min(date) as min_date FROM
      (
         SELECT a1.id, a1.mapID, a1.score, a1.date 
         FROM attempts a1 
         INNER JOIN
        (
          SELECT mapID, max(score) as max_score 
          FROM attempts
          GROUP BY mapID
         ) a2
         ON
            a1.mapID = a2.mapID and a1.score = a2.max_score
      ) a3
      GROUP BY mapID
    ) a4
    ON a5.mapID = a4.mapID and a5.score = a4.max_score and a5.date = a4.min_date
   )a6
);

<强>解释

1- inner most group by为每个mapID提供max_score和mapID

2-这与原始表连接以查找这些尝试的ID和日期,但是当max_score上存在平局时,每个mapID可能包含多行

3-再次按mapID分组以获得分钟(日期)

4-再次与原始表连接以获取更新的ID

答案 3 :(得分:-1)

对于这样的事情,我总是推荐一个Temp表。在这种情况下,临时表可能有列 - (id,highest_score,map) -

create temporary table temp1
select min(id) as id, highest_score, map 
from attempts 
where <<attempts of today>>
group by map;

现在直接使用临时表连接尝试并标记它们。

update temp1 as t inner join attempts as a on a.id = t.id
set a.isHighestScore = 1;

Drop temporary table temp1;

(如果启用了复制,请使用永久表或事务处于更安全的一面)