PostgreSQL中的分组方式和计数

PostgreSQL中的分组方式和计数,sql,postgresql,count,distinct,aggregate-functions,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

查询:

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中,如何正确获取此上下文中的记录数?

我认为您只需要从投票中获得
计数(不同的post\u id)

请参见中的“4.2.7.聚合表达式”一节

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

还有:

在Postgres中,如果n端有多个条目,通常比:

投票中每个帖子的行数越多,性能差异就越大。测试

<代码>计数(不同PASSIDID)必须读取所有行,对它们进行排序或散列,然后只考虑第一个相同的集合。code>EXISTS

将只扫描
投票
(或者最好扫描
post\u id
)上的索引,直到找到第一个匹配项

如果
投票
中的每个
post\u id
都保证出现在表
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篇有投票权)。
我对
EXPLAIN ANALYZE
ant进行了4次测试,在postgres9.1.4的三次查询中,每一次都取了五次中最好的一次,并附加了结果总运行时间

  • 原来如此

  • 之后

    ANALYZE posts;
    ANALYZE votes;
    
  • 之后

    CREATE INDEX foo on votes(post_id);
    
    VACUUM FULL ANALYZE posts;
    CLUSTER votes using foo;
    
  • 之后

    CREATE INDEX foo on votes(post_id);
    
    VACUUM FULL ANALYZE posts;
    CLUSTER votes using foo;
    
  • count(*)。。。存在的位置
  • 253毫秒
  • 220毫秒
  • 85毫秒--获胜者(帖子顺序扫描、投票索引扫描、嵌套循环)
  • 85毫秒
  • 计数(不同的x)
    -带连接的长格式
  • 354毫秒
  • 358毫秒
  • 373毫秒--(帖子索引扫描、投票索引扫描、合并加入)
  • 330毫秒
  • 计数(不同的x)
    -不带连接的缩写形式
  • 164毫秒
  • 164毫秒
  • 164毫秒--(始终按顺序扫描)
  • 142毫秒
  • 有问题的原始查询的最佳时间:

    • 353毫秒
    对于简化版

    • 348毫秒
    @wildplasser使用CTE的查询使用了与长表单相同的计划(帖子索引扫描、投票索引扫描、合并连接),外加一点CTE开销。最佳时间:

    • 366毫秒
    可以改善每个查询的结果,尤其是
    存在时的结果

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

    使用
    OVER()
    限制1

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

    为什么您认为它不是最佳的?这似乎是一种非常常见的操作,有一种更简单的方法。PG::Error:Error:column“posts.id”必须出现在GROUP BY子句中或在聚合中使用function@skinkelynet:这是因为答案有细微的错误-它必须是来自投票的
    。我在答案中添加了正确的表单。@LostCrotchet事实证明,您可以在PostgreSQL中这样做。你需要把字段列表放在括号里,例如…
    选择COUNT(DISTINCT(firstname,lastname))FROM people
    。你说的“更便携”是什么意思?@a_horse_和_no_name:“更便携”真的是胡说八道。删除了那个位,谢谢你指出。我错误地认为SQLite不支持聚合函数中的
    DISTINCT
    就像所有其他主要的RDBMS一样。作为补偿(因为我想为自己澄清这一点),我用一个基准阐述了性能角度。如果我读得正确,你就错过了我的CTE版本。不过,它应该相当于一个子查询。@wildplasser:很抱歉,重新创建了场景(不完全相同,但从安装程序中可以看到非常接近),并为CTE版本添加了结果。正如预期的那样,CTE在这里对性能没有帮助。
    SELECT COUNT(1) OVER()
    FROM posts 
       INNER JOIN votes ON votes.post_id = posts.id 
    GROUP BY posts.id
    LIMIT 1;