各ユーザーが「foo」に賛成または反対票を投じることができる、PostgreSQLに実装された投票システムについて考えてみます。すべての「foo情報」を格納するfoo
テーブルと、user_id
、foo_id
、およびvotes
を格納するvote
テーブルがあります。 、ここで、vote
は+1または-1です。
各fooの投票集計を取得するには、次のクエリが機能します。
SELECT sum(vote) FROM votes WHERE foo.foo_id = votes.foo_id;
ただし、以下も同様に機能します。
(SELECT count(vote) FROM votes
WHERE foo.foo_id = votes.foo_id
AND votes.vote = 1)
- (SELECT count(vote) FROM votes
WHERE foo.foo_id = votes.foo_id
AND votes.vote = (-1))
現在、votes.foo_id
にインデックスがあります。
より効率的なアプローチはどれですか? (言い換えれば、どちらがより速く実行されますか?)PostgreSQL固有の回答と一般的なSQLの回答の両方に興味があります。
[〜#〜]編集[〜#〜]
vote
がnullの場合を考慮に入れて多くの回答がありました。投票列にNOT NULL
制約があることを忘れました。
また、多くの人が最初のものがはるかに読みやすいと指摘しています。はい、それは間違いなく真実です。同僚が2番目の曲を書いた場合、パフォーマンスの必要性がない限り、私は怒りを爆発させます。それでもなお、問題は2つのパフォーマンスにあります。 (技術的には、最初のクエリがway遅い場合、2番目のクエリを作成することはそれほど犯罪ではありません。)
もちろん、最初の例はより速く、より簡単で、読みやすくなっています。 水生生物と平手打ち になる前でも明らかなはずです。 sum()
はcount()
よりもわずかに高価ですが、さらに重要なのは、2番目の例では2回のスキャンが必要なことです。
しかし、実際の違いもあります:sum()
はNULL
を返すことができます。ここでcount()
そうではありません。 集計関数のマニュアル を引用します:
Countを除いて、これらの関数は行が選択されていない場合にnull値を返すことに注意してください。特に、行がない合計はnullを返しますが、予想どおりゼロではありません。
パフォーマンスの最適化には弱点があるように思われるので、以下に詳細を示します。count(*)
はcount(vote)
。投票がNOT NULL
の場合にのみ同等です。 EXPLAIN ANALYZE
でパフォーマンスをテストします。
どちらのクエリも構文的にナンセンスであり、独立しています。次のような大きなクエリのSELECT
リストからコピーした場合にのみ意味があります。
SELECT *, (SELECT sum(vote) FROM votes WHERE votes.foo_id = foo.foo_id)
FROM foo;
ここで重要な点は、相関サブクエリです。これは、クエリでvotes
の小さな部分のみを読み取っている場合は問題ない可能性があります。追加のWHERE
条件が表示され、一致するインデックスが必要です。
Postgres 9.3以降では、代替の、よりクリーンな、100%同等のソリューションはLEFT JOIN LATERAL ... ON true
を使用することです。
SELECT *
FROM foo f
LEFT JOIN LATERAL (
SELECT sum(vote) FROM votes WHERE foo_id = f.foo_id
) v ON true;
通常、同様のパフォーマンス。詳細:
ただし、テーブルvotes
、これは(はるかに)高速になります:
SELECT f.*, v.score
FROM foo f
JOIN (
SELECT foo_id, sum(vote) AS score
FROM votes
GROUP BY 1
) v USING (foo_id);
最初にサブクエリの値を集計してから、結果に結合します。USING
について:
最初のものはより速くなります。簡単な方法で試すことができます。
いくつかのデータを生成します。
CREATE TABLE votes(foo_id integer, vote integer);
-- Insert 1000000 rows into 100 foos (1 to 100)
INSERT INTO votes SELECT round(random()*99)+1, CASE round(random()) WHEN 0 THEN -1 ELSE 1 END FROM generate_series(1, 1000000);
CREATE INDEX idx_votes_id ON votes (foo_id);
両方を確認してください
EXPLAIN ANALYZE SELECT SUM(vote) FROM votes WHERE foo_id = 5;
EXPLAIN ANALYZE SELECT (SELECT COUNT(*) AS count FROM votes WHERE foo_id=5 AND vote=1) - (SELECT COUNT(*)*-1 AS count FROM votes WHERE foo_id=5 AND vote=-1);
しかし、真実はそれらが同等ではないということです。最初のものが2番目として機能することを確認するには、null
の場合を処理する必要があります。
SELECT COALESCE(SUM(vote), 0) FROM votes WHERE foo_id = 5;
もう一つ。 PostgreSQL 9.2を使用している場合は、両方の列を含むインデックスを作成できます。これにより、インデックスのみのスキャンを使用できるようになります。
CREATE INDEX idx_votes_id ON votes (foo_id, vote);
だが!状況によっては、このインデックスが最悪の場合があるため、両方を試してEXPLAIN ANALYZE
を実行し、どちらが最適かを確認するか、両方を作成して、どちらのPostgreSQLが最も使用しているかを確認する(そしてもう一方を除外する)必要があります。
これは単一のクエリであり、より読みやすいため、最初のクエリがより高速に機能することを期待します(しばらくしてからこれに戻る必要がある場合に便利です)。
2番目のクエリは2つのクエリで構成されます。単一のクエリであるかのように結果を取得するだけです。
とはいえ、これらのどちらがより適切に機能するかを完全に確認するために、両方のテーブルに大量のダミーデータを入力し、クエリの実行時間を確認します。