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;

n在 Postgresql中返回记录:

 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. 聚合表达式”部分。

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

于 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_idinvotes都存在于表中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 ANALYZEant 进行了 4 次测试,在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 毫秒 ——获胜者(对帖子进行序列扫描,对投票进行索引扫描,嵌套循环)
  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 毫秒

@wildplasser 的 CTE 查询使用与长表单相同的计划(对帖子的索引扫描、对投票的索引扫描、合并连接)加上一点 CTE 的开销。最好的时间:

  • 366 毫秒

即将推出的 PostgreSQL 9.2 中的仅索引扫描可以改进每个查询的结果,尤其是对于EXISTS.

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

于 2012-08-04T14:45:02.723 回答
12

使用OVER()LIMIT 1

SELECT COUNT(1) OVER()
FROM posts 
   INNER JOIN votes ON votes.post_id = posts.id 
GROUP BY posts.id
LIMIT 1;
于 2016-12-15T07:48:12.067 回答
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;
于 2012-08-04T15:13:51.867 回答
0

对于追随者,我喜欢 OP 的内部查询方法:

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;

从那时起,您也可以在那里使用 HAVING:

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 HAVING count(*) > 1
) as x;

或等效的 CTE

with posts_coalesced as (
     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 )

select count(*) from posts_coalesced;
于 2021-11-03T18:07:25.690 回答