私は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
これを達成する最良の方法は何でしょうか?私はサブクエリをいじっていますが、DISTINCT
とGROUP BY
を実行するときにサブクエリを注文するときは、壁にぶつけ続けます。
これを行うにはおそらく多くの方法があります。最初に頭に浮かぶのは、ウィンドウ関数を使用することです。
_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つの列(id
とpostcode
)の値の分布に依存します。 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)
_にもインデックスを追加することをお勧めします。
通常、別のテーブルにすべての個別の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;
_
postcode
がtext
またはvarchar
であると想定します。 postcode
にタイプ修飾子(varchar(50)
など)がある場合、この特定のクエリで問題が発生する可能性があります。
_(id, date_created)
_のインデックスは、bigテーブルでのパフォーマンスに不可欠です。
_CREATE INDEX postcode_foo_idx ON postcode(id, date_created DESC NULLS LAST);
_
_NULLS LAST
_が定義されている場合は、どこでも _date_created
_ をスキップできます_NOT NULL
_。
id
ごとに実質的に5行を超える行がまれの場合、 @ ypercubeのクエリ が高速になります。 _EXPLAIN ANALYZE
_でテストします。
違い:私のrCTEはオーバーヘッドが大きくなりますが、パフォーマンスは古い余剰行の影響をほとんど受けません(これらはクエリで変更されません)。 @ypercubeのクエリはどちらもオーバーヘッドが少なくなりますが、id
あたりの行数が増えると遅くなります。
リンク付きの基本と詳細な説明:
テーブルtbl
がない場合は、同様の手法を使用して、最初にid
からpostcode
を区別することができます。