勝者(および共同勝者)を確実に獲得したい場合。次のSQL文でそれを行う必要があります...
SELECT athleteId, a.eventId, a.score
FROM tests AS a
JOIN (
-- This select finds the top score for each event
SELECT eventId, MAX(score) AS score
FROM tests
GROUP BY eventId
) AS b
-- Join on the top scores
ON a.eventId = b.eventId
AND a.score = b.score
サブセレクトを実行して各イベントの最高スコアを取得してから、内部結合を実行して、イベントで最高スコアを達成した個々のレコードを取得しています。
追加情報
コメントでの会話から次の情報をまとめました。
ソリューションによる基本グループが信頼できないのはなぜですか?
SELECT athleteId, eventId, score
FROM (
SELECT athleteId, eventId, score
FROM tests
ORDER BY eventId, score DESC
) AS a
GROUP BY eventId
イベントとスコアで注文したレコードセットからグループを作成しています。次に、グループ化を使用して列から値を選択し、イベントごとに 1 つのレコードを選択します。
最初に注意すること
句を使用しているGROUP BY
場合は、個々のレコードではなく、順序付けられていない一連のレコードについて話していることになります!
集約関数を使用して、MySQL http://dev.mysql.com/doc/refman/5.1/en/group-by-functions.htmlで非常に強力で便利なクロスレコード計算を行うことができますが、グループを関連付けるために個々のレコードに戻ると、JOIN
.
2 番目の例では、個々のレコードであるかのようにグループを返しています。
2 番目の例が機能しているように見えるのはなぜですか?
SQL 言語では集計されていない列が違法であるのではなく、MySQL では許可されています。理由はわかりませんが、非正規化された列のパフォーマンス上の理由か、何らかの理由でグループ内の列は変更されません。
MySQL は、グループ内の集計されていない列に対して最も簡単に返す値を選択します。グループ化される前のレコードセットの順序付けの結果として、たまたま最初に見つかった値が選択されますが、常にこれが行われるとは限りません!
MySQL のドキュメントには、a を含む select 内の非集計列の値GROUP BY
は不確定であると記載されています。これは、集計されていない列の結果の値が、グループ化前のイベント (つまり、レコードセット内の任意の順序付け) の結果であると見なされるべきではないことを意味しますが、実際にはこの現在の実装ではそのように見えます。
将来のバージョンではそうではないかもしれませんし、2 回実行しても結果が同じにならないかもしれません。それが明示的に文書化されているという事実は、私がそれを避けるのに十分な理由です!
集計されていない列が不確定なのはなぜですか?
グループ化前のレコードの元の順序を無視または破る可能性のある将来の最適化のために、グループ化のためのアルゴの実装を開いたままにしておくつもりであると推測します。
概念的には、個々のレコードの集まりではなく、レコードのグループを 1 つのユニットとして想像することは理にかなっています。非集計列の場合、返される可能性のある値が多数あり、選択の時点でいずれかを選択する暗黙の条件はありません。グループ化する前のレコードの方法を覚えておく必要があります。
リスク
このアプローチを使用したすべてのクエリは、ある時点で機能し始める可能性があります。イベントの最高スコアを取得できなかったレコードの値が返される場合があります。
また、このバグはすぐには明らかにならないため、最近の MySQL のアップグレードの原因を追跡するにはしばらく時間がかかります。また、この潜在的な落とし穴を忘れていたことを保証できます。これが発生した場合、これがすべての問題であったため、デバッグする機会が得られるまで、安全性の低い古いバージョンの MySQL に行き詰まる可能性があります。ちゃんと…etc…痛い…
結合ソリューションが異なるのはなぜですか?
ステートメントのサブ選択ではJOIN
、集計されていない列は使用されません。集計は、個々のレコードではなくグループ全体に関連するため、決定的です。グループ化される前のレコードの順序に関係なく、答えは常に同じになります。
ステートメントを使用しJOIN
て、グループを関心のある個々のレコードに関連付けました。場合によっては、各グループに複数の個々のレコードがあることを意味する場合があります。たとえば、2 人のアスリートが同じ最高得点を持っている抽選の場合、両方の記録を返すか、任意に 1 つを選択する必要があります。最高得点者全員が必要になると確信しているので、引き分けの可能性がある 2 人のアスリートから選択するためのルールは提供していません。
勝者として1つの記録を選ぶ
明確な勝者として 1 つのレコードを選択するには、勝者と次点者を区別できる方法が必要です。最初に最高得点を獲得したアスリートとして最終的な勝者を選ぶかもしれません。別のアスリートがリードするためには、前のスコア セットよりも優れている必要があります。
これを行うには、テストの順序を決定する方法が必要なので、testId
新しい結果が得られるたびにインクリメントされる列を導入します。これを取得したら、次のクエリを実行できます...
SELECT a.eventId, athleteId, a.score
FROM tests AS a
JOIN (
-- This select finds the first testId for each score + event combination
SELECT MIN(testId) AS testId, c.eventId, c.score
FROM tests AS c
JOIN (
-- This select finds the top score for each event
SELECT eventId, MAX(score) AS score
FROM tests
GROUP BY eventId
) AS d
ON c.eventId = d.eventId
AND c.score = d.score
GROUP BY eventId, score
) AS b
ON a.testId = b.testId
ここで何が起こるかというと、各イベントの最高スコアを表すグループを作成し、それを各スコアとイベントの組み合わせの最小の testId を表すグループと内部結合し、最後にテスト テーブルのレコードと内部結合して個々のレコードを取得します。 .
これは、次のように (実行プランが少し異なりますが) 書くこともできます。
SELECT a.eventId, athleteId, a.score
FROM tests AS a
JOIN (
-- This select finds the top score for each event
SELECT eventId, MAX(score) AS score
FROM tests
GROUP BY eventId
) AS b
ON a.eventId = b.eventId
AND a.score = b.score
JOIN (
-- This select finds the first testId for each score + event combination
SELECT MIN(testId) AS testId, eventId, score
FROM tests
GROUP BY eventId, score
) AS c
ON a.testId = c.testId
基本的なグループ化ソリューションは、より少ない SQL で同じ結果を達成しますが、比較すると最適化は非常に不十分です。テーブルにインデックスを追加すると、ソリューションによる基本的なグループ化はインデックスを利用せず、tests テーブルのすべてのレコードに対して 2 つのファイルソート (テーブルを順番に並べるための追加の実行) が必要になります。ただし、上記の元のネストされた副選択クエリは非常に適切に最適化されます。