2

プレイヤーがマップを駆け抜けてスコアを付けるゲームがあります。各マップでどの試行が高得点であったかを調べ、それらのisHighScoreフラグをtrueに設定する必要があります。各マップのスコアが高くなるのは1回だけです。2回の試行で同じスコアが得られる場合は、時系列で最初に発生した試行のみに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)サブクエリはこの問題によるものです)

上記は、約75kエントリのテーブルで実行するのにタイムアウトよりも時間がかかります(はい、にインデックスがありますmapID, score, dateattempts最も内側のクエリがテーブル全体を一時テーブルにコピーするためだと思いましたが、WHERE mapID = A1.mapID条件をそのクエリに移動すると構文エラーが発生するため、どうすればよいかわかりません。また、内部クエリは行ごとに1回実行されます。これを修正する方法はあるのでしょうか?

MySQLでこのクエリを書くためのより良い方法を知っている人はいますか?

4

4 に答える 4

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列とスコア列にインデックスを付けると、これはかなり高速になるはずです。

于 2013-03-13T18:58:06.287 に答える
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
       )

SQLFiddle。_

古いハイスコアを0に設定することを忘れないでください。

于 2013-03-13T18:52:12.663 に答える
0

これは機能しますが、パフォーマンスについてはよくわかりません。試してみてください。これはフィドルです。

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-最も内側のgroupbyは、各mapIDのmax_scoreとmapIDを示します

2-これは元のテーブルと結合されてそれらの試行のIDと日付を検索しますが、max_scoreに同点がある場合、mapIDごとに複数の行が含まれる可能性があります

3-もう一度mapIDでグループ化して、min(date)を取得します

4-もう一度元のテーブルと結合して、更新用のIDを取得します

于 2013-03-13T19:59:39.480 に答える
-1

このようなものについては、私は常に一時テーブルをお勧めします。この場合、一時テーブルには列が含まれている可能性があります-(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;

(レプリケーションが有効になっている場合は、より安全な側にするために永続テーブルまたはトランザクションを使用してください)

于 2013-03-13T19:47:40.120 に答える