MySQLの前にグループ化する


243

ここには同様の質問がたくさんありますが、質問に対する適切な回答はないと思います。

私は現在最も人気のある質問から続けて、それが問題なければ、その例を使用します。

このインスタンスのタスクは、データベース内の各著者の最新の投稿を取得することです。

クエリ例では、常に最新の投稿が返されるとは限らないため、使用できない結果が生成されます。

SELECT wp_posts.* FROM wp_posts
    WHERE wp_posts.post_status='publish'
    AND wp_posts.post_type='post'
    GROUP BY wp_posts.post_author           
    ORDER BY wp_posts.post_date DESC

現在受け入れられている答えは

SELECT
    wp_posts.*
FROM wp_posts
WHERE
    wp_posts.post_status='publish'
    AND wp_posts.post_type='post'
GROUP BY wp_posts.post_author
HAVING wp_posts.post_date = MAX(wp_posts.post_date) <- ONLY THE LAST POST FOR EACH AUTHOR
ORDER BY wp_posts.post_date DESC

残念ながら、この答えは明白で単純な誤りであり、多くの場合、元のクエリよりも安定した結果が得られません。

私の最善の解決策は、フォームのサブクエリを使用することです

SELECT wp_posts.* FROM 
(
    SELECT * 
    FROM wp_posts
    ORDER BY wp_posts.post_date DESC
) AS wp_posts
WHERE wp_posts.post_status='publish'
AND wp_posts.post_type='post'
GROUP BY wp_posts.post_author 

私の質問は単純ですとにかく、サブクエリに頼らずにグループ化する前に行を並べ替えることはできますか?

編集:この質問は別の質問の続きで、私の状況の詳細は少し異なります。特定の投稿の一意の識別子であるwp_posts.idもあると想定できます(想定する必要があります)。


2
与えられた回答へのコメントで述べたように、同じタイムスタンプを持ついくつかの投稿がある可能性があります。もしそうなら、データと期待される結果の例を挙げてください。そして、この結果を期待する理由を説明してください。一意の行を取得するのに十分post_authorpost_dateはないため、一意の行を取得するにはさらに多くのものが必要ですpost_author
ルーフォ卿

@SirRufoその通り、私はあなたのために編集に追加しました。
Rob Forrest

There are plenty of similar questions to be found on here but I don't think that any answer the question adequately.それが賞金の目的です。
2013

@LightnessRacesinOrbit、現在の質問に私の意見では間違っているという受け入れられた回答がすでにある場合、何をすることを提案しますか?
Rob Forrest

1
なぜサブクエリを使用する回答を受け入れたのかと疑問に思うとき
TV-C-15

回答:


373

ORDER BYサブクエリでを使用することは、この問題の最良の解決策ではありません。

max(post_date)著者別の最適な解決策は、サブクエリを使用して最大日付を返し、それを最大日付の両方でテーブルに結合することpost_authorです。

解決策は次のとおりです。

SELECT p1.* 
FROM wp_posts p1
INNER JOIN
(
    SELECT max(post_date) MaxPostDate, post_author
    FROM wp_posts
    WHERE post_status='publish'
       AND post_type='post'
    GROUP BY post_author
) p2
  ON p1.post_author = p2.post_author
  AND p1.post_date = p2.MaxPostDate
WHERE p1.post_status='publish'
  AND p1.post_type='post'
order by p1.post_date desc

次のサンプルデータがある場合:

CREATE TABLE wp_posts
    (`id` int, `title` varchar(6), `post_date` datetime, `post_author` varchar(3))
;

INSERT INTO wp_posts
    (`id`, `title`, `post_date`, `post_author`)
VALUES
    (1, 'Title1', '2013-01-01 00:00:00', 'Jim'),
    (2, 'Title2', '2013-02-01 00:00:00', 'Jim')
;

サブクエリは最大日付と作成者を返します。

MaxPostDate | Author
2/1/2013    | Jim

次に、それをテーブルに結合するので、両方の値でその投稿の完全な詳細を返します。

SQL Fiddle with Demoを参照してください。

サブクエリを使用してこのデータを正確に返すことについての私のコメントを拡張します。

MySQLはGROUP BYSELECTリストに含まれるすべての列を強制するわけではありません。その結果、GROUP BY1つの列だけが合計10列を返す場合、その列に属する他の列の値post_authorが返される保証はありません。列がGROUP BYMySQLにない場合は、返される値を選択します。

集計関数でサブクエリを使用すると、正しい作者と投稿が毎回返されることが保証されます。

補足として、MySQLではORDER BYサブクエリでを使用できGROUP BYSELECTリストのすべての列にを適用できますが、SQL Serverを含む他のデータベースではこの動作は許可されていません。


4
そこで行ったことはわかりますが、これは単に、最新の投稿の行全体ではなく、最新の投稿が行われた日付を返すだけです。
Rob Forrest

1
@RobForrestは、結合が行うことです。著者によるサブクエリで最新の投稿日を返し、wp_posts両方の列で自分に結合して完全な行を取得します。
タリン

7
@RobForrest 1つは、GROUP BY1つの列だけにを適用した場合、他の列の値が一貫して正しいことを保証するものではありません。残念ながら、MySQLではこのタイプのSELECT / GROUPを許可していますが、他の製品ではできません。2つ目は、ORDER BYMySQLで許可されているサブクエリでを使用する構文は、SQL Serverを含む他のデータベース製品では許可されていません。実行されるたびに適切な結果を返すソリューションを使用する必要があります。
タリン

2
スケーリングには、化合物INDEX(post_author, post_date)が重要です。
リックジェームズ

1
@ jtcotton63正しいですがpost_id、内部クエリを入力する場合、技術的にはグループ化する必要があります。これにより、結果が歪む可能性が高くなります。
タリン

20

ソリューションでは、一部のフィールド(この場合は)でグループ化することを許可するGROUP BY句の拡張を利用していますpost_author

GROUP BY wp_posts.post_author

集計されていない列を選択します。

SELECT wp_posts.*

group by句にリストされていないもの、または集約関数(MI​​N、MAX、COUNTなど)で使用されていないもの。

GROUP BY句の拡張の正しい使用

これは、非集計列のすべての値がすべての行で等しい場合に役立ちます。

たとえば、テーブルGardensFlowersname庭でflower育つ庭のテーブル)があるとします。

INSERT INTO GardensFlowers VALUES
('Central Park',       'Magnolia'),
('Hyde Park',          'Tulip'),
('Gardens By The Bay', 'Peony'),
('Gardens By The Bay', 'Cherry Blossom');

そして、複数の花が育つ庭で育つすべての花を抽出したいとします。次に、サブクエリを使用する必要があります。たとえば、次のように使用できます。

SELECT GardensFlowers.*
FROM   GardensFlowers
WHERE  name IN (SELECT   name
                FROM     GardensFlowers
                GROUP BY name
                HAVING   COUNT(DISTINCT flower)>1);

代わりにガーダー内で唯一の花であるすべての花を抽出する必要がある場合は、HAVING条件をに変更するだけで済みますがHAVING COUNT(DISTINCT flower)=1、MySqlではこれを使用することもできます。

SELECT   GardensFlowers.*
FROM     GardensFlowers
GROUP BY name
HAVING   COUNT(DISTINCT flower)=1;

サブクエリはなく、標準SQLではありませんが、より単純です。

GROUP BY句の拡張の誤った使用

しかし、すべての行で等しくない非集計列を選択するとどうなりますか?MySqlがその列に選択する値はどれですか?

MySqlは常に最初に遭遇した値を選択するようです。

遭遇する最初の値が正確に希望する値であることを確認するGROUP BYには、順序付けされたクエリにを適用する必要があるため、サブクエリを使用する必要があります。それ以外の場合はできません。

MySqlは常に最初に遭遇する最初の行を選択するという前提で、GROUP BYの前に行を正しくソートしています。しかし残念ながら、ドキュメントを注意深く読むと、この仮定が正しくないことに気付くでしょう。

常に同じではない非集計列を選択する場合、MySqlは任意の値を自由に選択できるため、実際に表示される結果の値は不確定です。

非集計列の最初の値を取得するためのこのトリックが頻繁に使用されていることがわかり、通常/ほとんど常に機能しますが、(自分のリスクで)ときどき使用します。しかし、それは文書化されていないため、この動作に依存することはできません。

このリンク(ypercubeに感謝!)GROUP BYトリックは最適化されていますが、おそらく最適化エンジンが異なるため、同じクエリがMySqlとMariaDBの間で異なる結果を返す状況を示しています。

したがって、このトリックが機能する場合、それは運の問題です。

他の質問への受け入れ答えは 私には間違っているになります。

HAVING wp_posts.post_date = MAX(wp_posts.post_date)

wp_posts.post_dateは非集計列であり、その値は公式には未定ですが、最初にpost_date遭遇する可能性があります。ただし、GROUP BYトリックは順序付けられていないテーブルに適用されるため、どちらが最初にpost_date発生するかはわかりません。

おそらく、単一の著者の唯一の投稿である投稿を返しますが、これは必ずしも確実ではありません。

可能な解決策

これは可能な解決策になると思います:

SELECT wp_posts.*
FROM   wp_posts
WHERE  id IN (
  SELECT max(id)
  FROM wp_posts
  WHERE (post_author, post_date) = (
    SELECT   post_author, max(post_date)
    FROM     wp_posts
    WHERE    wp_posts.post_status='publish'
             AND wp_posts.post_type='post'
    GROUP BY post_author
  ) AND wp_posts.post_status='publish'
    AND wp_posts.post_type='post'
  GROUP BY post_author
)

内部クエリでは、すべての著者の最大投稿日を返しています。次に、同じ作者が理論的に同時に2つの投稿を持つことができるという事実を考慮に入れているので、最大IDのみを取得しています。そして、それらの最大IDを持つすべての行を返します。IN句の代わりに結合を使用すると、より高速にできます。

(それIDが増加しているだけであることが確かで、それがをID1 > ID2意味しているpost_date1 > post_date2場合は、クエリをはるかに簡単にすることができますが、これが当てはまるかどうかはわかりません)。


それextension to GROUP Byは興味深い読み物です、ありがとう。
Rob Forrest


GROUP BYを使用した選択式の非集計列は、MySQL 5.7ではデフォルトで動作しなくなりました:stackoverflow.com/questions/34115174/…。どのIMHOの方がはるかに安全で、一部の人々はより効率的なクエリを作成する必要があります。
rink.attendant.6 2017年

この回答はサブクエリを使用していませんか?元の投稿者がサブクエリを使用しないソリューションを求めていませんか?
TV-C-15

1
@ TV-C-15問題はサブクエリの再使用にあります。私は、サブクエリの再使用が機能しない理由を説明しています。受け入れられた答えでさえサブクエリを使用しますが、再分類が悪いアイデアである理由を説明し始めます(サブクエリでORDER BYを使用することはこの問題に対する最良の解決策ではありません
fthiella

9

これから読むものはかなりハッキリしているので、家でこれを試さないでください!

SQLでは一般に、質問に対する答えはNOですが、GROUP BY@bluefeetで言及されている)のリラックスモードのため、MySQL では答えはYESです。

(post_status、post_type、post_author、post_date)にBTREEインデックスがあるとします。インデックスは内部ではどのように見えますか?

(post_status = 'publish'、post_type = 'post'、post_author = 'user A'、post_date = '2012-12-01')(post_status = 'publish'、post_type = 'post'、post_author = 'user A'、 post_date = '2012-12-31')(post_status = 'publish'、post_type = 'post'、post_author = 'user B'、post_date = '2012-10-01')(post_status = 'publish'、post_type = ' post '、post_author =' user B '、post_date =' 2012-12-01 ')

つまり、データはこれらのすべてのフィールドで昇順にソートされます。

GROUP BYデフォルトでaを実行している場合、グループ化フィールド(post_authorこの場合は; post_status、post_typeはWHERE句で必須)によってデータをソートし、一致するインデックスがある場合、昇順で各最初のレコードのデータを取得します。つまり、クエリは以下をフェッチします(各ユーザーの最初の投稿):

(post_status = 'publish'、post_type = 'post'、post_author = 'user A'、post_date = '2012-12-01')(post_status = 'publish'、post_type = 'post'、post_author = 'user B'、 post_date = '2012-10-01')

しかしGROUP BY、MySQLでは、順序を明示的に指定できます。またpost_user、降順でリクエストすると、インデックスが逆の順序で処理され、実際に最後である各グループの最初のレコードが取得されます。

あれは

...
WHERE wp_posts.post_status='publish' AND wp_posts.post_type='post'
GROUP BY wp_posts.post_author DESC

私たちに与える

(post_status = 'publish'、post_type = 'post'、post_author = 'user B'、post_date = '2012-12-01')(post_status = 'publish'、post_type = 'post'、post_author = 'user A'、 post_date = '2012-12-31')

これで、グループ化の結果をpost_dateで並べ替えると、必要なデータが得られます。

SELECT wp_posts.*
FROM wp_posts
WHERE wp_posts.post_status='publish' AND wp_posts.post_type='post'
GROUP BY wp_posts.post_author DESC
ORDER BY wp_posts.post_date DESC;

注意

これは、この特定のクエリに推奨するものではありません。この場合、@ bluefeetの提案を少し変更したバージョンを使用します。しかし、この手法は非常に役立つ場合があります。ここで私の答えを見てください:各グループの最後のレコードを取得する

落とし穴:このアプローチの欠点は、

  • クエリの結果はインデックスに依存しますが、これはSQLの精神に反します(インデックスはクエリを高速化するだけです)。
  • インデックスはクエリへの影響について何も認識していません(将来的には、あなたや他の誰かがインデックスをリソースを消費しすぎて何らかの形で変更し、パフォーマンスだけでなくクエリ結果を壊す可能性があります)
  • クエリのしくみがわからない場合は、おそらく1か月で説明を忘れてしまい、クエリによって自分と同僚が混乱することになります。

利点は、ハードケースでのパフォーマンスです。この場合、並べ替えに必要なデータの量が原因で、クエリのパフォーマンスは@bluefeetのクエリと同じになります(すべてのデータが一時テーブルにロードされてから並べ替えられます。ところで、彼のクエリには(post_status, post_type, post_author, post_date)インデックスも必要です)。 。

私が提案すること

先ほど述べたように、これらのクエリは、MySQLを一時テーブル内の潜在的に膨大な量のデータのソートに時間を浪費させます。ページングが必要な場合(つまり、LIMITが関係している場合)、ほとんどのデータは破棄されます。私が行うことは、並べ替えられたデータの量を最小限に抑えることです。つまり、並べ替えを行い、サブクエリのデータの最小値を制限してから、テーブル全体に結合します。

SELECT * 
FROM wp_posts
INNER JOIN
(
  SELECT max(post_date) post_date, post_author
  FROM wp_posts
  WHERE post_status='publish' AND post_type='post'
  GROUP BY post_author
  ORDER BY post_date DESC
  -- LIMIT GOES HERE
) p2 USING (post_author, post_date)
WHERE post_status='publish' AND post_type='post';

上記のアプローチを使用した同じクエリ:

SELECT *
FROM (
  SELECT post_id
  FROM wp_posts
  WHERE post_status='publish' AND post_type='post'
  GROUP BY post_author DESC
  ORDER BY post_date DESC
  -- LIMIT GOES HERE
) as ids
JOIN wp_posts USING (post_id);

SQLFiddleでのすべてのクエリと実行プラン。


それはあなたがそこに行っている面白いテクニックです。二つのこと:あなたはこれを家でやらないと言います、潜在的な落とし穴は何ですか?次に、bluefeetの回答を少し変更したバージョンについて言及しますが、それは何でしょうか?
Rob Forrest

おかげで、別の方法で問題を攻撃している人を見るのは興味深いことです。私のデータセットは18M以上の行の近くにないので、パフォーマンスは保守性ほど重要ではないと思うので、後の方がおそらくより適切だと思います。サブクエリの内部の制限の考え方が気に入っています。
Rob Forrest

8

これを試してみてください。各著者から最新の投稿日のリストを取得するだけです。それでおしまい

SELECT wp_posts.* FROM wp_posts WHERE wp_posts.post_status='publish'
AND wp_posts.post_type='post' AND wp_posts.post_date IN(SELECT MAX(wp_posts.post_date) FROM wp_posts GROUP BY wp_posts.post_author) 

@Rob Forrest、私の解決策を確認してください。うまくいけば、あなたの質問を解決します!
sanchitkhanna26 2013

1
すみません、それでうまくいくとは思いません。たとえば、作成者1と作成者2の両方が01/02/13に何かを公開し、次に作成者2が08/02/13に何か新しいものを投稿した場合、3つの投稿すべてが返されます。はい、日時フィールドには時刻が含まれているため、状況が発生する可能性は低くなりますが、十分に大きなデータセットで保証されるわけではありません。
Rob Forrest

を使用するための+1 post_date IN (select max(...) ...)。これは、サブ選択でグループ化するよりも効率的です。dev.mysql.com
doc /

明確にするために、post_authorにインデックスを付けている場合にのみ最適です。
Seaux

1
IN ( SELECT ... )同等のJOINよりも効率がはるかに劣ります。
リックジェームズ

3

いいえ。グループ化すると結果セットが変化するため、グループ化する前にレコードを並べ替えても意味がありません。サブクエリの方法が推奨されます。これが遅すぎる場合は、たとえば、各著者の最後の投稿のIDを別のテーブルに保存するか、著者ごとに最後の投稿を示すブール列を導入するなどして、テーブルデザインを変更する必要があります。 1。


デンマーク語、このタイプのクエリは正しいSQL構文ではないため、データベースプラットフォーム間で移植できないというBluefeetのコメントにどのように応答しますか?また、毎回正しい結果が得られる保証がないことも懸念されます。
Rob Forrest

2

max関数とgroup関数を使用するだけです

    select max(taskhistory.id) as id from taskhistory
            group by taskhistory.taskid
            order by taskhistory.datum desc

3
最も高いIDを持つものが最近投稿されたものでない場合はどうなりますか?この例として、投稿者が投稿をドラフトで長期間投稿してから投稿したことが考えられます。
Rob Forrest

0

要約すると、標準的なソリューションは無相関のサブクエリを使用し、次のようになります。

SELECT x.*
  FROM my_table x
  JOIN (SELECT grouping_criteria,MAX(ranking_criterion) max_n FROM my_table GROUP BY grouping_criteria) y
    ON y.grouping_criteria = x.grouping_criteria
   AND y.max_n = x.ranking_criterion;

MySQLの古いバージョン、またはかなり小さいデータセットを使用している場合は、次の方法を使用できます。

SELECT x.*
  FROM my_table x
  LEFT
  JOIN my_table y
    ON y.joining_criteria = x.joining_criteria
   AND y.ranking_criteria < x.ranking_criteria
 WHERE y.some_non_null_column IS NULL;  

古代バージョンと言うと、これはどのバージョンのMySQLで動作しますか?申し訳ありませんが、私の例ではデータセットが非常に大きくなっています。
Rob Forrest

どのバージョンでも(ゆっくりと)動作します。古いバージョンではサブクエリを使用できません。
イチゴ

はい、方法#2(私が試したバージョンはここからです)は大きなデータセット(数百万行)では機能せず、失われた接続エラーをスローします。メソッド#1は、クエリの実行に最大15秒かかります。最初はネストされたクエリの使用を避けたかったのですが、そのために再考しました。ありがとうございました!
aexl

@TheSexiestManinJamaicaはい。3。5年であまり変わっていません。クエリ自体が効率的であると仮定すると、クエリの実行にかかる時間は、データセットのサイズ、インデックスの配置、および使用可能なハードウェアに大きく依存します。
Strawberry

-1

**大規模なデータセットで使用すると、サブクエリがパフォーマンスに悪影響を及ぼす可能性があります**

元のクエリ

SELECT wp_posts.*
FROM   wp_posts
WHERE  wp_posts.post_status = 'publish'
       AND wp_posts.post_type = 'post'
GROUP  BY wp_posts.post_author
ORDER  BY wp_posts.post_date DESC; 

変更されたクエリ

SELECT p.post_status,
       p.post_type,
       Max(p.post_date),
       p.post_author
FROM   wp_posts P
WHERE  p.post_status = "publish"
       AND p.post_type = "post"
GROUP  BY p.post_author
ORDER  BY p.post_date; 

==> で使用maxしているため、サブ選択クエリを回避し、グループの後に最大列で並べ替えることができます。select clausemax(p.post_date)


1
実際、これは作成者ごとに最新のpost_dateを返しますが、返される残りのデータが最新のpost_dateの投稿に関連しているという保証はありません。
Rob Forrest 2014年

@RobForrest->理由がわかりませんか?回答を詳しく説明し、クレームを破棄することをお勧めします。私が理解している限り、関連するデータをフィルタリングするためにwhere句を使用すると、データが関連していることが保証されます。
guykaplan 2014年

1
ある程度、あなたは完全に正しいです。選択している4つのフィールドのそれぞれは、その最大のpost_dateに関連しますが、これは、尋ねられた質問には答えません。たとえば、post_idまたは投稿のコンテンツを追加した場合、それらの列が最大日付と同じレコードからのものであるとは限りません。上記のクエリで残りの投稿の詳細を返すには、2番目のクエリを実行する必要があります。質問が最新の投稿の日付を見つけることに関するものである場合、はい、あなたは答えは大丈夫です。
Rob Forrest

@guykaplan、サブクエリは遅くありません。データセットのサイズは重要ではありません。使い方次第です。percona.com/blog/2010/03/18/when-the-subselect-runs-faster
Pacerier

@Pacerier:この記事は確かにサブクエリからパフォーマンスのメリットを得る方法を示していますが、特定のシナリオをより良いパフォーマンスに変換することを期待しています。また、データサイズは重要です。投稿した所定の記事でも、使用するテーブルは1つだけであると想定しています。データサイズは行サイズではなく、複雑度サイズです。とは言っても、本当に大きなテーブル(多くのテーブルは関係ない)で作業している場合は、サブクエリの方がはるかにパフォーマンスが良いかもしれません。
guykaplan 2015年

-4

まず、selectで*を使用しないでください。*はパフォーマンスに影響し、group byおよびorder byの使用を妨げます。このクエリを試してください:

SELECT wp_posts.post_author, wp_posts.post_date as pdate FROM wp_posts
WHERE wp_posts.post_status='publish'
AND wp_posts.post_type='post'
GROUP BY wp_posts.post_author           
ORDER BY pdate DESC

ORDER BYでテーブルを指定せず、エイリアスのみを指定すると、選択の結果が並べ替えられます。


select *は無視してください。これらは、この例では簡潔にするためです。あなたの答えは、私が最初に挙げた例とまったく同じです。
Rob Forrest

エイリアスは、返される行や結果の並べ替えには影響しません。
Rob Forrest
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.