MySQLの「Group By」と「Order By」


96

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

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

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

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

fromEmail: john@example.com, subject: hello
fromEmail: mark@example.com, subject: welcome

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

fromEmail: john@example.com, subject: hello
fromEmail: john@example.com, subject: programming question
fromEmail: mark@example.com, subject: welcome

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

回答:


141

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

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

これは結合の使用に似ていますが、見栄えがよくなります。

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

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

以下のよう5.7.5非集計列がクエリエラー(ER_WRONG_FIELD_WITH_GROUP)を引き起こすようONLY_FULL_GROUP_BYはデフォルトで有効になっています

@mikepが以下で指摘するように、解決策はANY_VALUE()を使用することです 5.7

http://www.cafewebmaster.com/mysql-order-sort-group https://dev.mysql.com/doc/refman/5.6/en/group-by-handling.html https://dev.mysqlを 参照して ください.com / doc / refman / 5.7 / en / group-by-handling.html https://dev.mysql.com/doc/refman/5.7/en/miscellaneous-functions.html#function_any-value


7
私は数年前に同じ解決策を思いつきました、そしてそれは素晴らしい解決策です。b7kichへの称賛。ここで2つの問題... GROUP BYは大文字と小文字を区別しないため、LOWER()は不要です。次に、$ userIDはPHPから直接変数であるように見えます。$ userIDがユーザー指定であり、強制されていない場合、コードにSQLインジェクションの脆弱性がある可能性があります整数になる。
velcrow 2013

重要な更新プログラムは、MariaDBに適用されます。mariadb.com/kb/en/mariadb/...
アーサーShipkowski

1
As of 5.7.5 ONLY_FULL_GROUP_BY is enabled by default, i.e. it's impossible to use non-aggregate columns.SQLモードは実行時に管理者権限なしで変更できるため、ONLY_FULL_GROUP_BYを無効にするのは非常に簡単です。次に例を示しますSET SESSION sql_mode = '';。デモ:db-fiddle.com/f/esww483qFQXbXzJmkHZ8VT/3
mikep

1
または、有効なONLY_FULL_GROUP_BYをバイパスする別の方法は、ANY_VALUE()を使用することです。詳細については、dev.mysql.com
doc

42

これが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

textIDあいまいだったと言いました= /
ジョンカーラック

1
次に、ambuigityを削除し、cur.textIDのようなテーブル名をプレフィックスとして付けます。答えも変わりました。
Andomar 2009年

これはDoctrine DQLで実行できる唯一のソリューションです。
VisioN 2016

これは、複数の列を自己結合しようとする場合にはうまくいきません。最新の電子メールと最新のユーザー名を見つけようとしているときに、単一のクエリでこの操作を実行するために複数の自己左結合が必要な場合のIE。
Loveen Dyall

過去と未来のタイムスタンプ/日付を操作する場合、結果セットを非未来の日付に制限するには、LEFT JOIN基準に別の条件を追加する必要がありますAND next.timestamp <= UNIX_TIMESTAMP()
fyrye

31

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

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

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()ほとんどの場合は使用されないを削除したことに注意してください。


2
これが正解です。私のウェブサイトでこれに関連するバグを発見しました。order by他の回答で副選択では、全く効果がありません。
Jetteは

1
OMG、これを受け入れられた答えにしてください。受け入れられたものは私の時間の5時間を無駄にしました:(
Richard Kersey '

29

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

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

1
したがって、GROUP BY`は自動的に最新のtime、または最新のtime、またはランダムを選択しますか?
xrDDDD 2013

1
順序付けを行っているので最新の時刻が選択されtime DESC、次にグループ化されたものが最初の時刻(最新)を取得します。
11101101b 2013

mysql 5.1で、VIEWSの副選択に対してJOINSを実行できた場合のみです。たぶん、その機能は新しいリリースに含まれています。
IcarusNM 2015年

21

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

ONLY_FULL_GROUP_BY

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


MIN(read)は、「read」の最小値を返します。代わりに、最新のメールの「既読」フラグを探している可能性があります。
Andomar 2009年

2

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

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

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

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

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.