web-dev-qa-db-ja.com

MySQL「グループ化」および「注文」

電子メールのテーブルから一連の行を選択し、送信者ごとにグループ化できるようにしたい。私のクエリは次のようになります。

SELECT 
    `timestamp`, `fromEmail`, `subject`
FROM `incomingEmails` 
GROUP BY LOWER(`fromEmail`) 
ORDER BY `timestamp` DESC

クエリはほぼ希望どおりに機能します。電子メールでグループ化されたレコードを選択します。問題は、件名とタイムスタンプが特定の電子メールアドレスの最新のレコードに対応していないことです。

たとえば、次のように返されます。

fromEmail: [email protected], subject: hello
fromEmail: [email protected], subject: welcome

データベース内のレコードが次の場合:

fromEmail: [email protected], subject: hello
fromEmail: [email protected], subject: programming question
fromEmail: [email protected], subject: welcome

「プログラミングの質問」の件名が最新の場合、電子メールをグループ化するときにMySQLにそのレコードを選択させるにはどうすればよいですか?

89
John Kurlak

簡単な解決策は、ORDERステートメントfirstでクエリを副選択にラップし、GROUP BYlaterを適用することです。

SELECT * FROM ( 
    SELECT `timestamp`, `fromEmail`, `subject`
    FROM `incomingEmails` 
    ORDER BY `timestamp` DESC
) AS tmp_table GROUP BY LOWER(`fromEmail`)

これは、結合の使用に似ていますが、見た目はずっと良くなります。

GROUP BY句を含むSELECTで非集計列を使用することは標準ではありません。 MySQLは通常、最初に見つかった行の値を返し、残りを破棄します。 ORDER BY句は、返された列の値にのみ適用され、破棄された値には適用されません。

重要な更新実際に動作するために使用される非集計列の選択は、依存するべきではありません。 MySQLドキュメント "これは、GROUP BYで指定されていない各非集計列のすべての値が各グループで同じ場合に主に役立ちます。サーバーは任意の値を自由に選択 =各グループから、つまりそれらが同じでない限り、選択される値は不定です。」

5.6.21の時点で、ORDER BYソートを元に戻す一時テーブルのGROUP BYの問題に気付きました。

5.7.5 ONLY_FULL_GROUP_BYはデフォルトで有効になっています。つまり、非集計列を使用することはできません。

http://www.cafewebmaster.com/mysql-order-sort-grouphttps://dev.mysql.com/doc/refman/5.6/en/group-byを参照してください-handling.htmlhttps://dev.mysql.com/doc/refman/5.7/en/group-by-handling.html

132
b7kich

1つのアプローチを次に示します。

SELECT cur.textID, cur.fromEmail, cur.subject, 
     cur.timestamp, cur.read
FROM incomingEmails cur
LEFT JOIN incomingEmails next
    on cur.fromEmail = next.fromEmail
    and cur.timestamp < next.timestamp
WHERE next.timestamp is null
and cur.toUserID = '$userID' 
ORDER BY LOWER(cur.fromEmail)

基本的に、テーブル自体を結合し、後の行を検索します。 where句では、後の行は存在できないと述べています。これにより、最新の行のみが表示されます。

同じタイムスタンプを持つ複数の電子メールが存在する可能性がある場合、このクエリは絞り込みが必要になります。電子メールテーブルに増分ID列がある場合、JOINを次のように変更します。

LEFT JOIN incomingEmails next
    on cur.fromEmail = next.fromEmail
    and cur.id < next.id
42
Andomar

次のようにクエリをGROUP BYでラップすることにより、ORDER BYの後にGROUP BYを実行します。

SELECT t.* FROM (SELECT * FROM table ORDER BY time DESC) t GROUP BY t.from
29
11101101b

すでに返信で指摘されているように、GROUP BYはウィンドウからレコードを任意に選択するため、現在の回答は間違っています。

MySQL 5.6、またはONLY_FULL_GROUP_BYでMySQL 5.7を使用している場合、正しい(決定論的な)クエリは次のとおりです。

SELECT incomingEmails.*
  FROM (
    SELECT fromEmail, MAX(timestamp) `timestamp`
    FROM incomingEmails
    GROUP BY fromEmail
  ) filtered_incomingEmails
  JOIN incomingEmails USING (fromEmail, timestamp)
GROUP BY fromEmail, timestamp

クエリを効率的に実行するには、適切なインデックス作成が必要です。

簡略化のために、ほとんどの場合使用されないLOWER()を削除したことに注意してください。

24
Marcus

SQL標準に従って、選択リストで非集計列を使用することはできません。 MySQLはそのような使用を許可します(ONLY_FULL_GROUP_BYモードを使用しない限り)が、結果は予測できません。

ONLY_FULL_GROUP_BY

最初にfromEmail、MIN(read)、次に2番目のクエリ(またはサブクエリ)-件名を選択する必要があります。

21
noonex

示されているものよりも複雑なクエリでは、これらのアプローチの両方に苦労しました。サブクエリアプローチは、どのインデックスを付けてもひどく非効率的であり、Hibernateを介して外部自己結合を取得できなかったためです。

これを行うための最良の(そして最も簡単な)方法は、必要なフィールドの連結を含むように構築されたものでグループ化し、SELECT句の式を使用してそれらを引き出すことです。 MAX()を実行する必要がある場合は、MAX()するフィールドが常に連結されたエンティティの最上位にあることを確認してください。

これを理解する鍵は、これらの他のフィールドがMax()を満たすエンティティに対して不変である場合にのみ、クエリが意味をなすことです。したがって、ソートに関して、連結の他の部分は無視できます。このリンクの一番下でこれを行う方法を説明します。 http://dev.mysql.com/doc/refman/5.0/en/group-by-hidden-columns.html

挿入/更新イベント(トリガーなど)を取得してフィールドの連結を事前計算できる場合は、インデックスを付けることができ、クエリは実際に希望するフィールドだけでグループ化されたように高速になりますMAX( )。それを使用して、最大で複数のフィールドを取得することもできます。ネストされたセットとして表現された多次元ツリーに対してクエリを実行するために使用します。

2
Mike N