PostgreSQL中的GROUP BY和COUNT

ski*_*net 34 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;
Run Code Online (Sandbox Code Playgroud)

返回nPostgresql 中的记录:

 count_all | post_id
-----------+---------
 1         | 6
 3         | 4
 3         | 5
 3         | 1
 1         | 9
 1         | 10
(6 rows)
Run Code Online (Sandbox Code Playgroud)

我只想检索返回的记录数: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;
Run Code Online (Sandbox Code Playgroud)

如何在PostgreSQL中获得此上下文中的记录数?

Ste*_*sen 57

我想你只需要COUNT(DISTINCT post_id) FROM votes.

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

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

  • @skinkelynet:那是因为答案是巧妙的错误 - 它必须是`FROM votes`.我在答案中添加了正确的表格. (2认同)

Erw*_*ter 39

还有EXISTS:

SELECT count(*) AS post_ct
FROM   posts p
WHERE  EXISTS (SELECT FROM votes v WHERE v.post_id = p.id);
Run Code Online (Sandbox Code Playgroud)

其中,在PostgreSQL中,并且count(DISTINCT post_id)像你一样拥有多个条目,通常比以下更快votes:

SELECT count(DISTINCT p.id) AS post_ct
FROM   posts p
JOIN   votes v ON v.post_id = p.id;
Run Code Online (Sandbox Code Playgroud)

每个帖子的行数越多EXPLAIN ANALYZE,性能差异就越大.试试吧count(DISTINCT post_id).

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

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

SELECT count(DISTINCT post_id) AS post_ct
FROM   votes;
Run Code Online (Sandbox Code Playgroud)

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

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

SELECT count(*) AS post_ct
FROM  (
    SELECT FROM posts 
    JOIN   votes ON votes.post_id = posts.id 
    GROUP  BY posts.id
    ) sub;
Run Code Online (Sandbox Code Playgroud)

基准

为了验证我的声明,我在资源有限的测试服务器上运行了基准测试.全部在一个单独的架构中:

测试设置

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

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;
Run Code Online (Sandbox Code Playgroud)

以下所有查询都返回了相同的结果(9107个帖子中有8093个有投票).
EXPLAIN ANALYZEPostgres 9.1.4中使用三个查询中的每一个运行了4个(最好的五个)测试,并附加了生成的总运行时间.

  1. 原样.

  2. 之后......

    ANALYZE posts;
    ANALYZE votes;
    
    Run Code Online (Sandbox Code Playgroud)
  3. 之后......

    CREATE INDEX foo on votes(post_id);
    
    Run Code Online (Sandbox Code Playgroud)
  4. 之后......

    VACUUM FULL ANALYZE posts;
    CLUSTER votes using foo;
    
    Run Code Online (Sandbox Code Playgroud)

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毫秒

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

  • 366毫秒

即将推出的PostgreSQL 9.2中的仅索引扫描可以更改每个查询的结果.

SELECT count(*) AS post_ct
FROM   posts p
WHERE  EXISTS (SELECT FROM votes v WHERE v.post_id = p.id);
Run Code Online (Sandbox Code Playgroud)

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


mnv*_*mnv 6

使用OVER()LIMIT 1

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