web-dev-qa-db-ja.com

グループ化前のMySQL順

ここにはたくさんの似たような質問がありますが、その質問に適切に答えることはできないと思います。

私は現在最も人気のある 質問 から続けて、それで大丈夫なら彼らの例を使用します。

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

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

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もあると仮定することができます(そしてそうすべきです)。

210
Rob Forrest

サブクエリで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は、あなたがSELECTリストに含めるすべてのカラムをGROUP BYすることを強制しません。その結果、1つの列だけをGROUP BY、合計10の列を返す場合、他の列の値が属するpost_authorに属しているという保証はありません。列がGROUP BYにない場合、MySQLはどの値を返すべきかを選択します。

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

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

330
Taryn

あなたの解決策は、いくつかのフィールドでグループ化することを許可する GROUP BYへの拡張 節を利用します(この場合、ちょうどpost_author):

GROUP BY wp_posts.post_author

そして、非集約列を選択します。

SELECT wp_posts.*

group by節にリストされていないもの、または集約関数で使用されていないもの(MIN、MAX、COUNTなど).

GROUP BY句の拡張子の正しい使い方

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

たとえば、テーブルGardensFlowers(庭のname、庭で育つ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句の拡張子の使い方が誤っている

しかし、行ごとに等しくない集約されていない列をSELECTした場合はどうなりますか。その列にMySqlが選択する値はどれですか?

MySqlは常に _ first _ という値を選択するようです。

最初に見つかった値が正確に必要な値であることを確認するには、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 > ID2post_date1 > post_date2を意味している場合は、照会をもっと単純にすることができますが、そうであるかどうかはわかりません).

18
fthiella

あなたが読むことにしているのはややハッピーなので、家でこれを試してはいけません!

SQLでは一般的に、あなたの質問に対する答えはNOですが、GROUP BYの緩和モード(@bluefeetで述べられている)のため、答えはYESです。 MySQLでは。

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

(post_status = '公開'、post_type = '投稿'、post_author = 'ユーザーA'、post_date = '2012-12-01')(post_status = '公開'、post_type = '投稿'、post_author = 'ユーザーA'、 post_date = '2012-12-31')(post_status = '公開'、post_type = '投稿'、post_author = 'ユーザB'、post_date = '2012-10-01')(post_status = '公開'、post_type = ' post '、post_author ='ユーザーB '、post_date =' 2012-12-01 ')

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

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

(post_status = '公開'、post_type = '投稿'、post_author = 'ユーザーA'、post_date = '2012-12-01')(post_status = '公開'、post_type = '投稿'、post_author = 'ユーザーB'、 post_date = '2012-10-01')

しかし、MySQLのGROUP BYでは、順番を明示的に指定することができます。そして、あなたが降順でpost_userを要求するとき、それはまだ実際に最後である各グループのための最初のレコードを取って、反対の順序で我々のインデックスを通り抜けます。

あれは

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

私たちに与える

(post_status = '公開'、post_type = '投稿'、post_author = 'ユーザーB'、post_date = '2012-12-01')(post_status = '公開'、post_type = '投稿'、post_author = 'ユーザー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;

NB

これは、この特定のクエリに対して推奨するものではありません。この場合、私は@bluefeetが提案するものを少し修正したものを使用します。しかし、このテクニックはとても役に立つかもしれません。ここに私の答えを見てみましょう: 各グループの最後のレコードを取得する

Pitfalls:このアプローチの欠点は、

  • クエリの結果はインデックスに依存します。これはSQLの精神に反します(インデックスはクエリを高速化するだけです)。
  • indexは、クエリへの影響について何も知りません(あなたや将来の他の誰かが、インデックスがリソースを浪費しすぎてインデックスを変更し、クエリの結果を壊し、パフォーマンスだけでなく)
  • クエリがどのように機能するのか理解していないと、たぶん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 に実行計画を持つすべてのクエリ。

9
newtover

これを試してください。 各作者から最新の投稿日のリストを入手する 。それでおしまい

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) 
8
sanchitkhanna26

グループ化は結果セットを変更するため、グループ化する前にレコードを並べ替えることは意味がありません。副照会方法が好ましい方法です。これが遅すぎる場合は、各著者の最後の投稿のIDを別のテーブルに格納するなど、テーブルデザインを変更するか、各投稿者の最後の投稿を示すブール列を導入する必要があります。 1。

3
Dennisch

要約すると、標準的な解決策は無相関の副照会を使用し、次のようになります。

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;  
0
Strawberry

Max関数とgroup関数を使うだけ

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