2

詳細

次の表を結合しました

試験結果

--------------------------------------------------------------------
| index | uid|         start         |          stop        | score| 
--------------------------------------------------------------------
|   1   | 23 |   2012-06-06 07:30:20 | 2012-06-06 07:30:34  | 100  |
--------------------------------------------------------------------
|   2   | 34 |   2012-06-06 07:30:21 | 2012-06-06 07:30:40  | 100  |
--------------------------------------------------------------------

ユーザーテーブル

------------------------------
| id  |       username       |  
------------------------------
| 23  |    MacGyver’s mum    | 
------------------------------
| 34  |       Gribblet       | 
------------------------------

このSQLを使用して

SELECT a.username, b.duration, b.score
FROM usertable AS a
JOIN    (SELECT `uid`, `score`,
TIMESTAMPDIFF( SECOND, start, stop ) AS `duration`
FROM `testresults`
WHERE `start` >= DATE(NOW())
ORDER BY `score` DESC, `duration` ASC
LIMIT 100) AS b
ON a.id = b.uid

問題はI want to rank the results。PHPではなくSQLで実行する方がおそらく簡単/高速だと思うので、http://code.openark.org/blog/mysql/sql-ranking-without-self-joinに基づいてこれを試しました

SELECT a.username, b.duration, b.score, COUNT(DISTINCT b.duration, b.score) AS rank
FROM usertable AS a
JOIN    (SELECT `uid`, `score`,
TIMESTAMPDIFF( SECOND, start, stop ) AS `duration`
FROM `testresults`
WHERE `start` >= DATE(NOW())
ORDER BY `score` DESC, `duration` ASC
LIMIT 100) AS b
ON a.id = b.uid

しかし、期待されるランクを取り戻すことはできません。1 行だけを返します。

質問

私は何を間違っていますか?期間とスコアが一意の場合にのみランクを上げるにはどうすればよいですか?

更新1

bdenham の「遅い方法」を使用するとうまくいきましたが、2 番目の方法はうまくいきませんでした。「速い方法」で何が起こっているのかよくわかりません。使用していたデータと結果の表を投稿しました。ランキングがめちゃくちゃになっているのがわかります。

 -------------------------------------------------------------------
| index | uid|         start         |          stop        | score| 
--------------------------------------------------------------------
|   1   | 32 |  2012-08-27 05:47:18  |  2012-08-27 05:47:36 |  100 | 18s
|   2   | 32 |  2012-08-27 05:50:36  |  2012-08-27 05:50:42 |   0  |  6s
|   3   | 32 |  2012-08-27 05:51:18  |  2012-08-27 05:51:25 |  100 |  7s
|   4   | 32 |  2012-08-27 05:51:30  |  2012-08-27 05:51:35 |   0  |  5s
|   5   | 32 |  2012-08-27 05:51:39  |  2012-08-27 05:51:44 |   50 |  5s
--------------------------------------------------------------------

-------------------------------------------------------------------------------------------------------------------------------------------------------------------------
| username | score | duration | @prevScore:=@currScore | @prevDuration:=@currDuration | @currScore:=r.score | @currDuration:=timestampdiff(second,r.start,r.stop) |rank |
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|   bob    |  100  |    7     |     [BLOB - 1B]        |         [BLOB - 1B]          |     100             |                                7                    |  3  |
|   bob    |  100  |    18    |     [BLOB - 0B]        |         [BLOB - 0B]          |     100             |                               18                    |  1  |
|   bob    |   50  |    5     |     [BLOB - 1B]        |         [BLOB - 1B]          |      50             |                                5                    |  5  |
|   bob    |   0   |    5     |     [BLOB - 3B]        |         [BLOB - 1B]          |       0             |                                5                    |  4  |
|   bob    |   0   |    6     |     [BLOB - 3B]        |         [BLOB - 2B]          |       0             |                                6                    |  2  |
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------
4

1 に答える 1

4

質問のリンクからの両方の方法は、MySQL 5.5.25 で機能します。これがSQL Fiddleです。しかし、少し複雑なモデルにメソッドを適応させることはできません。追加の結合があり、ランクは 1 つではなく 2 つの列に基づいています。

あなたの試みはどちらの方法にも従いませんが、遅い「伝統的な」解決策に従おうとしていたのではないかと思います。他の人が指摘しているように、その解決策には、完全に欠けている自己結合とグループ化が必要です。

これは、遅い方法をモデルに適応させる私の壊れた試みです。問題は、MySQL が特定のランクで見つかった最後の行のユーザー名のみを保持することです。同じランクの前の行は結果から破棄されます。GROUP BY にはユーザー名が含まれていないため、クエリはほとんどのデータベースでは実行されません。MySQL には、GROUP BY の非標準ルールがあります。適度に複雑なモデルが機能しない理由はわかりませんが、単純なリンク モデルは機能します。とにかく GROUP BY 用語が欠落しているのは悪い考えだと思います。

select u.username,
       r1.score,
       timestampdiff(second,r1.start,r1.stop) duration,
       count( distinct concat(r2.score,',',timestampdiff(second,r2.start,r2.stop)) ) rank
  from testresults r1
  join testresults r2
    on r2.score>r1.score
     or( r2.score=r1.score
         and
         timestampdiff(second,r2.start,r2.stop)<=timestampdiff(second,r1.start,r1.stop)
       )
  join usertable u
    on u.id=r1.uid
 where r1.start>=date(now())
   and r2.start>=date(now())
 group by r1.score, duration
 order by score desc, duration asc limit 100

これが遅い方法の修正です。最初に、一意のスコア/期間のペアごとにランクを計算し、次にその結果を各テスト結果と結合します。これは機能しますが、元の壊れた方法よりもさらに遅くなります。

select username,
       r.score,
       r.duration,
       r.rank
  from testresults tr
  join usertable u
    on u.id=tr.uid
  join (
          select r1.score,
                 timestampdiff(second,r1.start,r1.stop) duration,
                 count( distinct concat(r2.score,',',timestampdiff(second,r2.start,r2.stop)) ) rank
            from testresults r1
            join testresults r2
              on r2.score>r1.score
               or( r2.score=r1.score
                   and
                   timestampdiff(second,r2.start,r2.stop)<=timestampdiff(second,r1.start,r1.stop)
                 )
           where r1.start>=date(now())
             and r2.start>=date(now())
           group by r1.score, duration
       ) r
    on r.score=tr.score
   and r.duration=timestampdiff(second,tr.start,tr.stop)
 where tr.start>=date(now())
 order by rank limit 100

これが、高速メソッドをモデルに適応させるための私の失敗した試みです。選択した変数は並べ替え操作の前に計算されるため、この方法は機能しません。繰り返しますが、リンクの単純なモデルが機能する理由がわかりませんが、モデルは機能しません。

select u.username,
       r.score,
       timestampdiff(second,r.start,r.stop) duration,
       @prevScore:=@currScore,
       @prevDuration:=@currDuration,
       @currScore:=r.score,
       @currDuration:=timestampdiff(second,r.start,r.stop),
       @rank:=if(@prevScore=@currScore and @prevDuration=@currDuration, @rank, @rank+1) rank
  from testresults r
  join usertable u
    on u.id=r.uid
  cross join (select @currScore:=null, @currDuration:=null, @prevScore:=null, @prevDuration:=null, @rank:=0) init
 where r.start>=date(now())
 order by score desc, duration asc limit 100

これは高速メソッドの「固定」バージョンです。ただし、サブクエリでソートされた行の順序に依存しています。一般に、明示的な SORT 操作がない限り、クエリは行の順序に依存するべきではありません。外側のクエリはソートされていません。ソートされていたとしても、変数が外側のソートの前または後に計算されるかどうかはわかりません。

select username,
       score,
       duration,
       @prevScore:=@currScore,
       @prevDuration:=@currDuration,
       @currScoure:=score,
       @currDuration:=duration,
       @rank:=if(@prevScore=score and @prevDuration=duration, @rank, @rank+1) rank
  from (
          select u.username,
                 r.score,
                 timestampdiff(second,r.start,r.stop) duration
            from testresults r
            join usertable u
              on u.id=r.uid
           where r.start>=date(now())
           order by score desc, duration asc limit 100
       ) scores,
       (
          select @currScore:=null, 
                 @currDuration:=null, 
                 @rank:=0
       ) init

ランクなしで、スコアと期間で並べ替えた結果を選択するだけでも、同じくらい良いパフォーマンスが得られると思います。結果はすでにソートされているため、PHP はランクを効率的に計算できます。PHP は、ランクを 0 に初期化し、前のスコアと期間を null に初期化できます。次に、各行を前の値と比較し、違いがある場合はランクを上げます。並べ替えられた結果を PHP にランク付けさせることの大きな利点は、データベース エンジンのブランドやバージョンに関係なく、常に機能することです。そして、それはまだ速いはずです。

これは、4 つのクエリすべてを示すSQL Fiddleです。WHERE 句を変更して、クエリがどの日付でも引き続き機能するようにしました。

于 2012-08-26T15:29:27.230 に答える