46

クエリ:

SELECT COUNT(*) as count_all, 
       posts.id as post_id 
FROM posts 
  INNER JOIN votes ON votes.post_id = posts.id 
GROUP BY posts.id;

nPostgresql でレコードを返します。

 count_all | post_id
-----------+---------
 1         | 6
 3         | 4
 3         | 5
 3         | 1
 1         | 9
 1         | 10
(6 rows)

返されたレコードの数を取得したいだけです: 6.

サブクエリを使用して目的を達成しましたが、これは最適ではないようです。

SELECT COUNT(*) FROM (
    SELECT COUNT(*) as count_all, posts.id as post_id 
    FROM posts 
    INNER JOIN votes ON votes.post_id = posts.id 
    GROUP BY posts.id
) as x;

PostgreSQL でこのコンテキストのレコード数を取得するにはどうすればよいですか?

4

5 に答える 5

65

私はあなたがちょうど必要だと思いますCOUNT(DISTINCT post_id) FROM votes

http://www.postgresql.org/docs/current/static/sql-expressions.htmlの「4.2.7. 集計式」セクションを参照してください

編集: Erwin のコメントに従って私の不注意な間違いを修正しました。

于 2012-08-04T09:25:52.873 に答える
44

もあります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

  1. そのまま。

  2. 後 ..

    ANALYZE posts;
    ANALYZE votes;
    
  3. 後 ..

    CREATE INDEX foo on votes(post_id);
    
  4. 後 ..

    VACUUM FULL ANALYZE posts;
    CLUSTER votes using foo;
    

count(*) ... WHERE EXISTS

  1. 253ミリ秒
  2. 220ミリ秒
  3. 85 ミリ秒 --勝者(投稿の seq スキャン、投票のインデックス スキャン、ネストされたループ)
  4. 85ミリ秒

count(DISTINCT x)- 結合付きの長い形式

  1. 354ミリ秒
  2. 358ミリ秒
  3. 373 ミリ秒 -- (投稿のインデックス スキャン、投票のインデックス スキャン、マージ結合)
  4. 330ミリ秒

count(DISTINCT x)- 結合なしの短い形式

  1. 164ミリ秒
  2. 164ミリ秒
  3. 164 ミリ秒 -- (常に seq スキャン)
  4. 142ミリ秒

問題の元のクエリの最適な時間:

  • 353ミリ秒

簡易版の場合:

  • 348ミリ秒

CTE を使用した @wildplasser のクエリは、長い形式と同じプラン (投稿のインデックス スキャン、投票のインデックス スキャン、マージ結合) に加えて、CTE のわずかなオーバーヘッドを使用します。ベストタイム:

  • 366ミリ秒

今後の PostgreSQL 9.2 でのインデックスのみのスキャンは、これらの各クエリの結果を改善できEXISTSます。

関連する Postgres 9.5 のより詳細なベンチマーク (カウントするだけでなく、実際に個別の行を取得する):

于 2012-08-04T14:45:02.723 に答える