次のようなテーブルus_customers
があります(数十万行あります)。
+----------+----------+
| id | us_state |
+----------+----------+
| 12345678 | MA |
| 23456781 | AL |
| 34567812 | GA |
| 45678123 | FL |
| 56781234 | AZ |
| 67812345 | MA |
| 78123456 | CO |
| 81234567 | FL |
+----------+----------+
...そして、各us_state
からn
の顧客のサンプルを選択したいと思います。
PostgreSQL 9.3でそれをきれいに行う方法はありますか?
各us_state
から1人の顧客を簡単に取得できます。
SELECT DISTINCT ON (us_state) id
FROM us_customers
ORDER BY us_state;
しかし、たとえば、各州から3人の顧客が必要な場合、同じクエリを複数回実行せずにこれを実行できる方法はありますか?
ROW_NUMBER()
ウィンドウ関数を使用してus_state
によってid
に番号を付けて順序付けでき、n
の最初の値のみを保持します。
SELECT *
FROM (
SELECT *
, ROW_NUMBER() OVER(PARTITION BY us_state ORDER BY id) as n
FROM data
) as ord
WHERE n <= 2
ORDER BY us_state
;
または、サブクエリを使用してCROSS JOINを実行できます。
SELECT l.*
FROM (
SELECT DISTINCT us_state FROM data
) as s
CROSS JOIN LATERAL (
SELECT *
FROM data d
WHERE d.us_state = s.us_state
ORDER BY id
LIMIT 2
) as l
ORDER BY l.us_state
;
ids
で注文しましたが、それを変更して、最適な方法で注文できます。私の小さなサンプルでの出力:
id | us_state | n
123 | AL | 1
456 | AL | 2
56781234 | AZ | 1
78123456 | CO | 1
45678123 | FL | 1
81234567 | FL | 2
34567812 | GA | 1
123 | MA | 1
456 | MA | 2
NはROW_NUMBERの結果であり、2番目のクエリには存在しないことに注意してください。大きなテーブルでは、パーティション(us-state)および順序(ここではid)列のインデックスが役立ちます。
使用したサンプル:
CREATE TABLE data
("id" int, "us_state" varchar(2))
;
INSERT INTO data
("id", "us_state")
VALUES
(12345678, 'MA'),
(123, 'MA'),
(456, 'MA'),
(23456781, 'AL'),
(123, 'AL'),
(456, 'AL'),
(34567812, 'GA'),
(45678123, 'FL'),
(56781234, 'AZ'),
(67812345, 'MA'),
(78123456, 'CO'),
(81234567, 'FL')
;
us_states
テーブルus_state
の可能な値の完全なセットを含む別のテーブルがない場合は、すぐに作成することをお勧めします。
CREATE TABLE us_states (
us_state varchar(2) PRIMARY KEY
-- ... more columns?
);
1つは、us_customers.us_state
にFK制約を追加して、正当な値を適用することです。しかし、もっと重要な点として、かなり高速なクエリが可能です:
SELECT u.us_state, d.id -- or more columns?
FROM us_states u
LEFT JOIN LATERAL (
SELECT id -- or more columns?
FROM us_customers
WHERE us_state = u.us_state
LIMIT 3 -- three customers from each state
) d ON true
ORDER BY u.us_state;
毎回DISTINCT
を使用して「数十万行」から50個の異なる値を抽出することは、残りのクエリよりもコストがかかる可能性があり、時間の大きな無駄になります。
しないでくださいサブクエリにORDER BY
を追加してください。選択する行を定義していないため、任意の選択で十分です。
LEFT JOIN
に一致する行が見つからない場合でも、us_customers
に少なくとも1回はeveryの状態を含めます。
us_states
テーブルなしus_states
テーブルがない場合、まだかなり高速です方法があります @ Julienによって提案された標準的な手法 よりも。 CTEでルーズインデックススキャンをエミュレートします。 (あなたは以下に概説するインデックスが必要です。):
WITH RECURSIVE us_states AS (
(SELECT us_state FROM data ORDER BY us_state LIMIT 1) -- parentheses required
UNION ALL
SELECT (SELECT us_state FROM data WHERE us_state > u.us_state
ORDER BY us_state LIMIT 1) -- correlated subquery
FROM us_states u
WHERE u.us_state IS NOT NULL
)
SELECT us_state FROM us_states
WHERE us_state IS NOT NULL;
このクエリは、不足しているus_states
テーブルのドロップイン置換であるか、または不足しているテーブルの作成に使用できます。
WITH RECURSIVE us_states AS (
(SELECT us_state FROM us_customers ORDER BY us_state LIMIT 1)
UNION ALL
SELECT (SELECT us_state FROM us_customers WHERE us_state > c.us_state
ORDER BY us_state LIMIT 1)
FROM us_states c
WHERE c.us_state IS NOT NULL
)
SELECT u.us_state, d.id -- or more columns?
FROM us_states u
CROSS JOIN LATERAL (
SELECT id -- or more columns?
FROM us_customers
WHERE us_state = u.us_state -- eliminates NULL value from CTE
LIMIT 3 -- three customers from each state
) d
ORDER BY u.us_state;
今回はCROSS JOIN
を使用します。これは、CTEが既存の値を検出しただけであり、WHERE us_state IS NOT NULL
をこの方法で追加する必要がないためです。
または、可能であれば保証us_state
ごとに少なくとも3行あり、これは非常に高速です:
WITH RECURSIVE cte AS (
(SELECT us_state, id FROM us_customers3 ORDER BY us_state LIMIT 3)
UNION ALL
SELECT u.us_state, id
FROM (SELECT us_state FROM cte LIMIT 1) c
, LATERAL (
SELECT us_state, id
FROM us_customers3
WHERE us_state > c.us_state
ORDER BY us_state
LIMIT 3
) u
)
TABLE cte
ORDER BY us_state;
状態が3行未満の場合、最後のクエリは失敗します。
いずれにせよ、このような複数列のインデックスを作成してください!
CREATE INDEX data_covering_idx ON data (us_state, id);
関連、詳細な説明: