web-dev-qa-db-ja.com

sum()とcount()

各ユーザーが「foo」に賛成または反対票を投じることができる、PostgreSQLに実装された投票システムについて考えてみます。すべての「foo情報」を格納するfooテーブルと、user_idfoo_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番目のクエリを作成することはそれほど犯罪ではありません。)

11
ryanrhee

もちろん、最初の例はより速く、より簡単で、読みやすくなっています。 水生生物と平手打ち になる前でも明らかなはずです。 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について:

12

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

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

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
MatheusOl

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

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

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

1
Mike