PostgreSQL中的GROUP BY和COUNT

时间:2012-08-04 09:16:16

标签: sql postgresql count distinct aggregate-functions

查询:

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中获得此上下文中的记录数?

4 个答案:

答案 0 :(得分:53)

我认为你只需要COUNT(DISTINCT post_id) FROM votes

请参阅http://www.postgresql.org/docs/current/static/sql-expressions.html中的“4.2.7。聚合表达式”部分。

编辑:根据欧文的评论纠正了我粗心的错误。

答案 1 :(得分:36)

还有EXISTS

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

其中,在PostgreSQL中,n一侧有多个条目,就像你无疑一样,通常比<{3}}更快

SELECT count(DISTINCT p.id) AS post_ct
FROM   posts p
JOIN   votes v ON v.post_id = p.id;

votes中每个帖子的行数越多,效果差异就越大。只需使用count(DISTINCT x)尝试。

count(DISTINCT x)将收集所有行,对它们进行排序或散列,然后仅考虑每个相同集合的第一行。 EXISTS只扫描votes(或者,最好是post_id上的索引),直到找到第一个匹配项。

如果每个post_ id都保证存在于表posts中(例如,通过外键约束),则此简短形式相当于较长的形式:

SELECT count(DISTINCT post_id) AS post_ct
FROM   votes;

这实际上可能比使用EXISTS的第一个查询更快,每个帖子没有或几个条目

您的查询也以更简单的形式工作:

SELECT count(*) AS post_ct
FROM  (
    SELECT 1
    FROM   posts 
    JOIN   votes ON votes.post_id = posts.id 
    GROUP  BY posts.id
    ) x;

基准

为了验证我的声明,我在资源有限的测试服务器上运行了基准测试。所有这些都在一个单独的模式中:

测试设置

假冒典型的投票/投票情况:

CREATE SCHEMA y;
SET search_path = y;

CREATE TABLE posts (
  id   int PRIMARY KEY -- I don't use "id" as column name
, 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 vote distribution
        , random()::int::bool AS up_down
   FROM   generate_series(1,70000)
   ) g
JOIN   posts p ON p.id = g.post_id;

以下所有查询都返回了相同的结果(9107个帖子中有8093个投票) 我在 Postgres 9.1.4 上使用EXPLAIN ANALYZE(最好的五个)进行了4次测试,并对三个查询中的每一个进行了测试,并附加了总运行时间

  1. 原样。

  2. After ..

    ANALYZE posts;
    ANALYZE votes;
    
  3. After ..

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

    VACUUM FULL ANALYZE posts;
    CLUSTER votes using foo;
    
  5. count(*) ... WHERE EXISTS

    1. 253 ms
    2. 220 ms
    3. 85 ms (对帖子进行seq扫描,对投票进行索引扫描,嵌套循环)
    4. 85 ms
    5. count(DISTINCT x) - 带连接的长格式

      1. 354 ms
      2. 358 ms
      3. 373 ms(对帖子进行索引扫描,对投票进行索引扫描,合并加入)
      4. 330 ms
      5. count(DISTINCT x) - 没有加入的简短形式

        1. 164 ms
        2. 164 ms
        3. 164 ms(总是seq扫描)
        4. 142 ms
        5. 有问题的原始查询的最佳时间:

          • 353 ms

          简化版

          • 348 ms

          @worldplasser的CTE查询使用与长格式相同的计划(对帖子进行索引扫描,对投票进行索引扫描,合并连接)以及CTE的一点开销。最佳时间:

          • 366 ms

          EXPLAIN ANALYZE可以更改每个查询的结果。

          DROP SCHEMA y CASCADE;  -- clean up
          

          相关的,更详细的Postgres 9.5基准测试(实际上检索不同的行,而不只是计算):

答案 2 :(得分:3)

使用OVER()LIMIT 1

SELECT COUNT(1) OVER()
FROM posts 
   INNER JOIN votes ON votes.post_id = posts.id 
GROUP BY posts.id
LIMIT 1;

答案 3 :(得分:2)

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;