もありますEXISTS
:
SELECT count(*) AS post_ct
FROM posts p
WHERE EXISTS (SELECT FROM votes v WHERE v.post_id = p.id);
Postgres では、n側に複数のエントリがある可能性がありますが、一般的には次よりも高速count(DISTINCT post_id)
です。
SELECT count(DISTINCT p.id) AS post_ct
FROM posts p
JOIN votes v ON v.post_id = p.id;
の投稿あたりの行数が多いvotes
ほど、パフォーマンスの差が大きくなります。でテストしEXPLAIN ANALYZE
ます。
count(DISTINCT post_id)
すべての行を読み取り、それらをソートまたはハッシュしてから、同一のセットごとに最初の行のみを考慮する必要があります。は、最初の一致が見つかるまでEXISTS
スキャンvotes
(または、できれば のインデックス) のみを行います。post_id
すべてpost_id
の入力votes
がテーブルに存在することが保証されている場合posts
(参照整合性が外部キー制約で強制される)、この短い形式は長い形式と同等です。
SELECT count(DISTINCT post_id) AS post_ct
FROM votes;
実際には、投稿ごとにエントリがないか少ないEXISTS
クエリよりも高速になる場合があります。
あなたが持っていたクエリも、より単純な形式で機能します。
SELECT count(*) AS post_ct
FROM (
SELECT FROM posts
JOIN votes ON votes.post_id = posts.id
GROUP BY posts.id
) sub;
基準
私の主張を検証するために、リソースが限られているテスト サーバーでベンチマークを実行しました。すべて別のスキーマで:
テスト設定
典型的な投稿/投票の状況を偽造する:
CREATE SCHEMA y;
SET search_path = y;
CREATE TABLE posts (
id int PRIMARY KEY
, post text
);
INSERT INTO posts
SELECT g, repeat(chr(g%100 + 32), (random()* 500)::int) -- random text
FROM generate_series(1,10000) g;
DELETE FROM posts WHERE random() > 0.9; -- create ~ 10 % dead tuples
CREATE TABLE votes (
vote_id serial PRIMARY KEY
, post_id int REFERENCES posts(id)
, up_down bool
);
INSERT INTO votes (post_id, up_down)
SELECT g.*
FROM (
SELECT ((random()* 21)^3)::int + 1111 AS post_id -- uneven distribution
, random()::int::bool AS up_down
FROM generate_series(1,70000)
) g
JOIN posts p ON p.id = g.post_id;
次のクエリはすべて同じ結果を返しました (9107 件の投稿のうち 8093 件に投票がありました)。ant を
使用して 4 つのテストを実行し、 Postgres 9.1.4で 3 つのクエリのそれぞれについてベスト オブ 5 を取得し、結果の合計ランタイムを追加しました。EXPLAIN ANALYZE
そのまま。
後 ..
ANALYZE posts;
ANALYZE votes;
後 ..
CREATE INDEX foo on votes(post_id);
後 ..
VACUUM FULL ANALYZE posts;
CLUSTER votes using foo;
count(*) ... WHERE EXISTS
- 253ミリ秒
- 220ミリ秒
- 85 ミリ秒 --勝者(投稿の seq スキャン、投票のインデックス スキャン、ネストされたループ)
- 85ミリ秒
count(DISTINCT x)
- 結合付きの長い形式
- 354ミリ秒
- 358ミリ秒
- 373 ミリ秒 -- (投稿のインデックス スキャン、投票のインデックス スキャン、マージ結合)
- 330ミリ秒
count(DISTINCT x)
- 結合なしの短い形式
- 164ミリ秒
- 164ミリ秒
- 164 ミリ秒 -- (常に seq スキャン)
- 142ミリ秒
問題の元のクエリの最適な時間:
簡易版の場合:
CTE を使用した @wildplasser のクエリは、長い形式と同じプラン (投稿のインデックス スキャン、投票のインデックス スキャン、マージ結合) に加えて、CTE のわずかなオーバーヘッドを使用します。ベストタイム:
今後の PostgreSQL 9.2 でのインデックスのみのスキャンは、これらの各クエリの結果を改善できEXISTS
ます。
関連する Postgres 9.5 のより詳細なベンチマーク (カウントするだけでなく、実際に個別の行を取得する):