web-dev-qa-db-ja.com

PostgreSQLのGROUP BYおよびCOUNT

クエリ:

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;

Postgresqlのnレコードを返します。

 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でこのコンテキストのレコード数を取得するにはどうすればよいですか?

34
skinkelynet

COUNT(DISTINCT post_id) FROM votesだけが必要だと思います。

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

編集:アーウィンのコメントごとに私の不注意な間違いを修正しました。

55
Steve Jorgensen

また、 EXISTS

_SELECT count(*) AS post_ct
FROM   posts p
WHERE  EXISTS (SELECT FROM votes v WHERE v.post_id = p.id);
_

Postgresでは、おそらくn-sideに複数のエントリがあり、おそらくfasterよりも 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)all行を読み取り、それらをソートまたはハッシュし、同一セットごとに最初の1つだけを考慮する必要があります。 EXISTSは、最初の一致が見つかるまでvotes(または、できれば_post_id_のインデックス)のみをスキャンします。

If votes内のすべての_post_id_がテーブルposts(外部キー制約で実施される参照整合性)に存在することが保証されます。この短い形式は長い形式と同等です:

_SELECT count(DISTINCT post_id) AS post_ct
FROM   votes;
_

実際には、投稿ごとにnoまたは少数のエントリを指定した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の投稿に投票がありました)。
EXPLAIN ANALYZE_ antを使用して4つのテストを実行し、3つのクエリのそれぞれで Postgres 9.1.4 で5つのベストを取り、結果のランタイムの合計

  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 ms - winner (投稿のシーケンススキャン、投票のインデックススキャン、ネストループ)
  4. 85ミリ秒

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

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

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

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

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

  • 353ミリ秒

簡易バージョンの場合:

  • 348ミリ秒

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

  • 366ミリ秒

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

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

39

OVER()およびLIMIT 1

SELECT COUNT(1) OVER()
FROM posts 
   INNER JOIN votes ON votes.post_id = posts.id 
GROUP BY posts.id
LIMIT 1;
5
mnv
WITH uniq AS (
        SELECT DISTINCT posts.id as post_id
        FROM posts
        JOIN votes ON votes.post_id = posts.id
        -- GROUP BY not needed anymore
        -- GROUP BY posts.id
        )
SELECT COUNT(*)
FROM uniq;
2
wildplasser