12

各ユーザーが「foo」に賛成票または反対票を投じることができる、PostgreSQL に実装された投票システムを考えてみましょう。fooすべての「foo 情報」を格納するテーブルと、 +1 または -1である、、およびvotesを格納するテーブルがあります。user_idfoo_idvotevote

各 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 つのパフォーマンスにあります。(技術的には、最初のクエリがかなり遅かったとしても、2 番目のクエリを書いてもそれほど問題にはなりません。)

4

3 に答える 3

13

もちろん、最初の例の方が高速で単純で読みやすいです。水生生物に平手打ちされる前であっても、明らかなはずです。はsum()よりも少し高価ですcount()が、2 番目の例では 2 回のスキャンが必要であることが重要です。

しかし、実際の違いもあります: sum()can return NULLwhere count()not not. 集計関数に関するマニュアルを引用します。

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;

通常、同様のパフォーマンス。詳細:

ただし、 table から大部分またはすべてを読み取る場合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:

于 2013-02-21T12:24:45.783 に答える
2

最初のものはより速くなります。簡単な方法で試すことができます。

いくつかのデータを生成します。

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);

nullしかし、真実は、それらが同等ではないということです。最初のものが 2 番目として機能することを確認するには、ケースを処理する必要があります。

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 がどちらを最も使用しているかを確認する (そして他方を除外する) 必要があります。

于 2013-02-21T12:45:10.607 に答える
1

これは単一のクエリであり、より読みやすいため、最初のクエリがより高速に機能することを期待します(しばらくしてからこれに戻らなければならない場合に便利です)。

2番目のクエリは2つのクエリで構成されます。単一のクエリであるかのように結果を取得するだけです。

とはいえ、これらのどちらが適切に機能するかを確実に確認するために、両方のテーブルに大量のダミーデータを入力し、クエリの実行時間を確認します。

于 2013-02-21T09:42:50.910 に答える