【问题标题】:How to get the number of users grouped by the number of comments they've made?如何获取按评论数量分组的用户数量?
【发布时间】:2014-08-23 22:47:08
【问题描述】:

我想按他们制作的 cmets 数分组的用户数。

[User]: ID
[Comment]: ID, UserID

所以如果user A has made 1 comment, user B has made 1 comment and user C has made 2 comments,那么输出将是:

0 comments => 0 users
1 comment  => 2 users (A+B)
2 comments => 1 user  (C)

你会如何查询这个?

【问题讨论】:

  • 您应该提供相关的表格布局和问题,以及您尝试过的内容(即使它不起作用)。您已经存在足够长的时间了,应该知道基础知识。
  • 嗨欧文!实际上,我不知道这条规则,很高兴你发表了评论。我认为一对多关联是如此基本和标准,以至于没有必要定义表格布局。无论如何,感谢您的反馈! :)
  • 有许多微妙的变体。 NOT NULL、UNIQUE、PRIMARY KEY 约束、数据类型、基数、行宽、索引、值的频率……它们对于最佳解决方案都很重要。最好使用\d tbl 提供您在 psql 中获得的实际表布局的(相关部分)。更好的是,在sql fiddle 中提供SSCCE(随机示例)。

标签: sql postgresql count group-by


【解决方案1】:

这取决于您的特定数据库结构,但假设您有一个 users 表和一个 cmets 表:

users table:
id: serial
name: text

comments table:
id: serial
user_id: integer (foreign key to the users table)
comment: text

您可以计算每个用户使用此查询进行的 cmets 数:

  SELECT users.id, users.name, count(comments.id) as comment_cnt
    FROM users LEFT JOIN
         comments ON users.id = comments.user_id
GROUP BY users.id, users.name

然后您可以在嵌套查询中使用此查询的结果来计算每个 cmets 数量的用户数量:

  SELECT comment_cnt, count(*) FROM
  (SELECT users.id, users.name, count(comments.id) as comment_cnt
    FROM users LEFT JOIN
         comments ON users.id = comments.user_id
GROUP BY users.id, users.name) AS comment_cnts
GROUP BY comment_cnt;

我不知道有什么优雅的方法可以填补给定数量的 cmets 用户为零的空白,但是临时表和另一个级别的嵌套可以工作:

CREATE TABLE wholenumbers (num integer);

INSERT INTO wholenumbers VALUES (0), (1), (2), (3), (4), (5), (6);

   SELECT num as comment_cnt, COALESCE(user_cnt,0) as user_cnt
     FROM wholenumbers
LEFT JOIN (SELECT comment_cnt, count(*) AS user_cnt
             FROM (  SELECT users.id, users.name, count(comments.id) AS comment_cnt
                       FROM users LEFT JOIN comments ON users.id = comments.user_id
                   GROUP BY users.id, users.name) AS comment_cnts
         GROUP BY comment_cnt) AS user_cnts ON wholenumbers.num = user_cnts.comment_cnt
ORDER BY num;

【讨论】:

  • 太棒了,它有效!额外的小请求:如果特定数量的 cmets(即0 => 20 users, 1 => 0 users [we want this], 2 => 5 users)没有用户,您将如何填补计数空白?
  • 我不知道有什么优雅的方法可以做到这一点。您可以手动创建一个具有单个整数列和 n 行的临时表,每行都有一个整数 1、2、3、4 ......无论您喜欢多少。然后,您可以添加另一层嵌套。我会将修改后的查询添加到我的答案中。
  • 感谢@ErwinBrandstetter。我不知道那件事。您的解决方案是否显示拥有 0 cmets 的用户数量?
  • @ClaytonC:很好,谢谢。现在确实如此。这比从最小的计数开始更有意义。
  • 再次感谢@ErwinBrandstetter。看起来您还需要更改 WITH 查询以引用 users 表,否则无法知道没有任何 cmets 有多少用户。就目前而言,您的解决方案根本不引用用户表。
【解决方案2】:

基于表格布局@ClaytonC provided:

WITH cte AS (
   SELECT msg_ct, count(*) AS users
   FROM  (
      SELECT count(*) AS msg_ct
      FROM   comments 
      GROUP  BY user_id
      ) sub
   GROUP  BY 1
   )
SELECT msg_ct, COALESCE(users, 0) AS users
FROM   generate_series(0, (SELECT max(msg_ct) FROM cte)) msg_ct
LEFT   JOIN cte USING (msg_ct)
ORDER  BY 1;

要点

  • 首先,计算每个用户的 cmets (msg_ct)。只要引用完整性由外键强制执行,您就完全不需要加入users 表来聚合每个用户的cmets。只需计算 comments 中的行数。
    接下来,按消息计数计算用户数 (users)。

  • 我在 CTE 中执行此操作,因为我在最终查询中使用了两次派生表。
    首先让generate_series()动态生成从最小值到最大值的所有计数,包括间隙。
    然后for table到LEFT JOIN去,得到最终结果。

  • 计数从 0 开始(在我更新之后)。如果你想让它从最小的实际msg_ct 开始,请考虑我在编辑历史中的答案的初稿。

  • 解释基础知识的密切相关的答案:

统计没有 cmets 的用户

正如@ClaytonC 评论的那样,上述答案确实包括没有 cmets 的用户。

要解决这个问题(如果你真的需要它),要么在开始时就 LEFT JOIN 到 users

WITH cte AS (
   SELECT msg_ct, count(*) AS users
   FROM  (
      SELECT count(c.user_id) AS msg_ct
      FROM   users u
      LEFT   JOIN comments c ON c.user_id = u.id
      GROUP  BY u.id
      ) sub
   GROUP  BY 1
   )
SELECT ...

或者,由于加入只是为了寻找没有 cmets 的用户,我们可能会更便宜:统计 所有个用户并减去有cmets(无论如何我们都处理过):

WITH cte AS (
   SELECT msg_ct, count(*)::int AS users
   FROM  (
      SELECT count(*)::int AS msg_ct
      FROM   comments 
      GROUP  BY user_id
      ) sub
   GROUP  BY 1
   )
, agg AS (
   SELECT max(msg_ct)   AS max_ct      -- maximum for generate_series
         ,((SELECT count(*) FROM users) - sum(users))::int AS users
                                       -- quiet rest with 0 comments
   FROM cte
   )
SELECT 0 AS msg_ct, users FROM agg     -- users with 0 comments
UNION  ALL
SELECT msg_ct, COALESCE(users, 0)
FROM  (SELECT generate_series(1, max_ct) AS msg_ct FROM agg) g
LEFT   JOIN cte USING (msg_ct)
ORDER  BY 1;

查询变得有点复杂,但对于大表可能更快。没有把握。使用EXPLAIN ANALYZE 进行测试(如果对结果发表评论,我将不胜感激。)

【讨论】:

  • 很好的答案,一如既往!谢谢!
猜你喜欢
  • 2011-06-25
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-06-23
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多