PostgreSQLのグループ化されたLIMIT:各グループの最初のN行を表示しますか?


179

各グループの最初のN行をカスタム列で並べる必要があります。

次の表があるとします。

db=# SELECT * FROM xxx;
 id | section_id | name
----+------------+------
  1 |          1 | A
  2 |          1 | B
  3 |          1 | C
  4 |          1 | D
  5 |          2 | E
  6 |          2 | F
  7 |          3 | G
  8 |          2 | H
(8 rows)

section_idの最初の2行(名前順)が必要です。つまり、次のような結果が必要です。

 id | section_id | name
----+------------+------
  1 |          1 | A
  2 |          1 | B
  5 |          2 | E
  6 |          2 | F
  7 |          3 | G
(5 rows)

PostgreSQL 8.3.5を使用しています。

回答:


279

新しいソリューション(PostgreSQL 8.4)

SELECT
  * 
FROM (
  SELECT
    ROW_NUMBER() OVER (PARTITION BY section_id ORDER BY name) AS r,
    t.*
  FROM
    xxx t) x
WHERE
  x.r <= 2;

8
これはPostgreSQL 8.4でも機能します(ウィンドウ関数は8.4から始まります)。
Bruno

2
グループ化された制限を行うための教科書回答
piggybox 2015年

4
驚くばかり!それは完璧に動作します。私は興味がありますが、これを行う方法はありgroup byますか?
NurShomik 2016年

1
何百万もの行で作業し、これを実行するための本当にパフォーマンスの高い方法を求めている人にとっては-最上級の答えは行く方法です。適切なインデックス付けでtiを強化することを忘れないでください。
勤勉なキープレッサー

37

v9.3以降、ラテラル結合を実行できます

select distinct t_outer.section_id, t_top.id, t_top.name from t t_outer
join lateral (
    select * from t t_inner
    where t_inner.section_id = t_outer.section_id
    order by t_inner.name
    limit 2
) t_top on true
order by t_outer.section_id;

より高速になる可能性がありますが、もちろん、特にデータとユースケースでパフォーマンスをテストする必要があります。


4
特にこれらの名前を持つ非常に不可解なソリューションIMOですが、良いものです。
villasv

1
LATERALとこのソリューションは、JOIN かもしれないあなたがして索引を持っている場合は(場合によっては)ウィンドウ機能を大幅に高速化1上記以外t_inner.nameの列
アルトゥルRashitov

クエリに自己結合が含まれていないと、クエリが理解しやすくなります。その場合distinctは必要ありません。投稿されたposhestのリンクに例が示されています。
gillesB 2018

おい、これは意地悪です。「ROW_NUMBER」ソリューションでは、9秒ではなく120ミリ秒が得られました。ありがとうございました!
勤勉なキープレッサー

t_topのすべての列を選択するにはどうすればよいですか。Tテーブルは、JSONの列が含まれており、私が選択したとき、私は、エラー「型のJSONのpostgresのための等価演算子を識別できませんでした」を取得distinct t_outer.section_id, t_top.*
suat

12

これは別のソリューションです(PostgreSQL <= 8.3)。

SELECT
  *
FROM
  xxx a
WHERE (
  SELECT
    COUNT(*)
  FROM
    xxx
  WHERE
    section_id = a.section_id
  AND
    name <= a.name
) <= 2

2
SELECT  x.*
FROM    (
        SELECT  section_id,
                COALESCE
                (
                (
                SELECT  xi
                FROM    xxx xi
                WHERE   xi.section_id = xo.section_id
                ORDER BY
                        name, id
                OFFSET 1 LIMIT 1
                ),
                (
                SELECT  xi
                FROM    xxx xi
                WHERE   xi.section_id = xo.section_id
                ORDER BY 
                        name DESC, id DESC
                LIMIT 1
                )
                ) AS mlast
        FROM    (
                SELECT  DISTINCT section_id
                FROM    xxx
                ) xo
        ) xoo
JOIN    xxx x
ON      x.section_id = xoo.section_id
        AND (x.name, x.id) <= ((mlast).name, (mlast).id)

クエリは2行未満のセクションを表示しないことを除いて、必要なクエリに非常に近いです。つまり、ID = 7の行は返されません。そうでなければ私はあなたのアプローチが好きです。
クーバーサパレフ2009

ありがとう、私はCOALESCEで同じソリューションにたどり着きましたが、あなたはより速いです。:-)
クーバーサパレフ2009

実際、最後のJOINサブ句は、次のように簡略化できます。... AND x.id <=(mlast).id。IDが名前フィールドに従ってすでに選択されているためです。
クーバーサパレフ2009

@Kouber:あなたの例では、nameidは同じ順序でソートされているので、表示されません。名前を逆の順序で作成すると、これらのクエリが異なる結果をもたらすことがわかります。
Quassnoi 2009

2
        -- ranking without WINDOW functions
-- EXPLAIN ANALYZE
WITH rnk AS (
        SELECT x1.id
        , COUNT(x2.id) AS rnk
        FROM xxx x1
        LEFT JOIN xxx x2 ON x1.section_id = x2.section_id AND x2.name <= x1.name
        GROUP BY x1.id
        )
SELECT this.*
FROM xxx this
JOIN rnk ON rnk.id = this.id
WHERE rnk.rnk <=2
ORDER BY this.section_id, rnk.rnk
        ;

        -- The same without using a CTE
-- EXPLAIN ANALYZE
SELECT this.*
FROM xxx this
JOIN ( SELECT x1.id
        , COUNT(x2.id) AS rnk
        FROM xxx x1
        LEFT JOIN xxx x2 ON x1.section_id = x2.section_id AND x2.name <= x1.name
        GROUP BY x1.id
        ) rnk
ON rnk.id = this.id
WHERE rnk.rnk <=2
ORDER BY this.section_id, rnk.rnk
        ;

CTEとウィンドウ関数は同じバージョンで導入されたため、最初のソリューションの利点はわかりません。
a_horse_with_no_name

ポストは3歳です。その上、それらを欠いた実装がまだあるかもしれません(ナッジナッジはこれ以上言うことはありません)。また、古くからあるクエリ構築の練習と考えることもできます。(CTEはそれほど古くからありません)
wildplasser

投稿には「postgresql」というタグが付けられ、CTEを導入したPostgreSQLバージョンにはウィンドウ関数も導入されました。したがって、私のコメント(私はそれが古いことを確認しました-そしてPG 8.3はどちらも持っていませんでした)
a_horse_with_no_name

投稿では8.3.5について触れていますが、8.4で導入されたと思います。その上、私見、代替シナリオについて知っておくことも良いです。
wildplasser

それがまさに私の言いたいことです。8.3にはCTEもウィンドウ機能もありませんでした。したがって、最初のソリューションは8.3では機能しません
a_horse_with_no_name
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.