web-dev-qa-db-ja.com

各IDの最後の5つの異なる値を取得する

私はPostgreSQL 9.4を使用しています。

次のエントリを含むテーブルがあります。

 id | postcode | date_created
 ---+----------+-----------------
 14 | al2 2qp  | 2015-09-23 14:46:57
 14 | al2 2qp  | 2015-09-23 14:51:07
 14 | sp2 8ag  | 2015-09-23 14:56:11
 14 | se4      | 2015-09-23 16:12:05
 17 | e2       | 2015-09-23 16:15:35
 17 | fk20 8ru | 2015-09-23 16:28:35
 17 | fk20 8ru | 2015-09-23 16:35:51
 17 | se2      | 2015-09-23 16:36:17
 17 | fk20 8ru | 2015-09-23 16:36:22
 17 | fk20 8ru | 2015-09-23 16:37:04
 17 | se1      | 2015-09-23 16:37:11
 17 | fk20 8ru | 2015-09-23 16:37:15
 17 | se1 8ga  | 2015-09-24 09:52:46
 17 | se1      | 2015-09-24 10:01:19
 17 | hp27 9rz | 2015-09-24 10:05:27
 17 | hp27 9rz | 2015-09-24 10:05:29
 17 | se1      | 2015-09-24 10:19:46
 14 | tn21 8qb | 2015-09-24 14:49:05
 14 | tn21 8qb | 2015-09-24 15:42:45
 14 | tn21 8qb | 2015-09-24 17:38:06
 14 | n4 1ny   | 2015-09-25 14:49:10

私が達成したいのは、各IDに対して最新の5つのnique郵便番号レコードを返すクエリです。

 id | postcode
 ---+---------
 14 | n4 1ny
 14 | tn21 8qb
 14 | se4
 14 | sp2 8ag
 14 | al2 2qp
 17 | se1
 17 | hp27 9rz
 17 | se1 8ga
 17 | fk20 8ru
 17 | se2

これを達成する最良の方法は何でしょうか?私はサブクエリをいじっていますが、DISTINCTGROUP BYを実行するときにサブクエリを注文するときは、壁にぶつけ続けます。

7
R3b3cca

これを行うにはおそらく多くの方法があります。最初に頭に浮かぶのは、ウィンドウ関数を使用することです。

_SELECT 
    id, postcode
FROM
  ( SELECT id, postcode, 
           ROW_NUMBER() OVER (PARTITION BY id
                              ORDER BY MAX(date_created) DESC
                             ) AS rn
    FROM tablename
    GROUP BY id, postcode
  ) AS t
WHERE
    rn <= 5
ORDER BY 
    id, rn ;
_

SQLfiddle でテストします。

ネクタイがある場合、postcodeの5番目、6番目、7番目のidが同じ_date_created_であるとすると、結果にはそのうちの1つ(選択は任意)のみが含まれます。 。これらの場合にすべての関連付けられた郵便番号が必要な場合は、RANK()ではなくROW_NUMBER()を使用してください。


別のオプションは、LATERAL構文を使用することです。どちらがより効率的かはわかりませんが、おそらく2つの列(idpostcode)の値の分布に依存します。 IDごとの多数の異なる郵便番号と、(ID、郵便番号)の組み合わせごとの行数。

_SELECT 
    t.id, ti.postcode
FROM
    ( SELECT DISTINCT id
      FROM tablename
    ) AS t
  CROSS JOIN LATERAL
    ( SELECT tt.postcode,
             MAX(tt.date_created) AS date_created
      FROM tablename AS tt
      WHERE tt.id = t.id
      GROUP BY tt.postcode
      ORDER BY date_created DESC
      LIMIT 5
    ) AS ti 
ORDER BY 
    t.id, ti.date_created DESC;
_

_(id, postcode, date_created)_または_(id, postcode, date_created DESC)_にもインデックスを追加することをお勧めします。

9
ypercubeᵀᴹ

通常、別のテーブルにすべての個別のtbl値を別々の行に含む別のテーブル(idという名前を付けます)があります。そうでない場合は、作成します。

_CREATE TABLE tbl AS 
SELECT DISTINCT id FROM postcode ORDER BY id;  -- ORDER is optional
_

または、以下のクエリでtblをサブクエリと同じSELECTに置き換えますが、それは(はるかに)費用がかかります。

idごとにmany行が存在する可能性がある場合、再帰CTEが最速です:

_WITH RECURSIVE cte AS (
   SELECT t.id, 1 AS rnk, p.*, ARRAY[postcode] AS arr
   FROM   tbl t
        , LATERAL (
      SELECT postcode, date_created
      FROM   postcode
      WHERE  id = t.id
      ORDER  BY date_created DESC NULLS LAST
      LIMIT  1
      ) p

   UNION ALL
   SELECT t.id, rnk + 1, p.*, arr || p.postcode
   FROM   cte t
        , LATERAL (
      SELECT postcode, date_created
      FROM   postcode
      WHERE  id = t.id
      AND    date_created < t.date_created
      AND    postcode <> ALL (t.arr)
      ORDER  BY date_created DESC NULLS LAST
      LIMIT  1
      ) p
   WHERE rnk < 5
   )
SELECT id, rnk, postcode, date_created
FROM   cte
ORDER  BY id, rnk;
_

postcodetextまたはvarcharであると想定します。 postcodeにタイプ修飾子(varchar(50)など)がある場合、この特定のクエリで問題が発生する可能性があります。

_(id, date_created)_のインデックスは、bigテーブルでのパフォーマンスに不可欠です。

_CREATE INDEX postcode_foo_idx ON postcode(id, date_created DESC NULLS LAST);
_

SQLフィドル

_NULLS LAST_が定義されている場合は、どこでも _date_created_ をスキップできます_NOT NULL_。

idごとに実質的に5行を超える行がまれの場合、 @ ypercubeのクエリ が高速になります。 _EXPLAIN ANALYZE_でテストします。

違い:私のrCTEはオーバーヘッドが大きくなりますが、パフォーマンスは古い余剰行の影響をほとんど受けません(これらはクエリで変更されません)。 @ypercubeのクエリはどちらもオーバーヘッドが少なくなりますが、idあたりの行数が増えると遅くなります。

リンク付きの基本と詳細な説明:

テーブルtblがない場合は、同様の手法を使用して、最初にidからpostcodeを区別することができます。

5