【问题标题】:Sum of two counts from one table with additional data from another table一个表中的两个计数与另一表中的附加数据相加
【发布时间】:2021-08-31 15:19:14
【问题描述】:

我有两张表如下:

TABLE A

| id | col_a | col_b | user_id |
--------------------------------
| 1  | false | true  | 1       |
| 2  | false | true  | 2       |
| 3  | true  | true  | 2       |
| 4  | true  | true  | 3       |
| 5  | true  | false | 1       |

TABLE B

| id | name  |
--------------
| 1  | Bob   |
| 2  | Jim   | 
| 3  | Helen |
| 4  | Michael|
| 5  | Jen   |

我想得到两个计数的总和,即col_atrue 值的数量和col_btrue 值的数量。我想按user_id 对这些数据进行分组。我还想加入表 B 并获取每个用户的名称。结果如下所示:

|user_id|total (col_a + col_b)|name
------------------------------------
| 1     | 2                   | Bob
| 2     | 3                   | Jim
| 3     | 2                   | Helen

到目前为止,我通过以下查询得到了总和:

 SELECT
(SELECT COUNT(*) FROM "TABLE_A" WHERE "col_a" is true)+
(SELECT COUNT(*) FROM "TABLE_A" WHERE "col_b" is true)
as total

但是,我不确定如何按 user_id 对这些计数进行分组。

【问题讨论】:

    标签: sql postgresql join count aggregate


    【解决方案1】:

    这样的事情通常是最快的

    SELECT *
    FROM   "TABLE_B" b
    JOIN  (
       SELECT user_id AS id
            , count(*) FILTER (WHERE col_a)
            + count(*) FILTER (WHERE col_b) AS total
       FROM   "TABLE_A"
       GROUP  BY 1
       ) a USING (id);
    

    在获取所有行时,先聚合,然后再加入。那更便宜。见:

    聚合FILTER 子句通常最快。见:


    通常,您希望在结果中保持总计数为 0。你确实说过:

    获取每个用户的名称。

    SELECT b.id AS user_id, b.name, COALESCE(a.total, 0) AS total
    FROM   "TABLE_B" b
    LEFT   JOIN (
       SELECT user_id AS id
            , count(col_a OR NULL)
            + count(col_b OR NULL) AS total
       FROM   "TABLE_A"
       GROUP  BY 1
       ) a USING (id);
    ...
    

    count(col_a OR NULL) 是一个等效的替代方案,最短,但仍然很快。 (使用上面的 FILTER 子句以获得最佳性能。)
    LEFT JOIN 将来自 "TABLE_B" 的所有行保留在结果中。
    COALESCE() 返回 0 而不是 NULL 的总数。


    如果 col_acol_b 只有很少的 true,这通常(快得多) - 基本上是你已经拥有的:

    SELECT b.*, COALESCE(aa.ct, 0) + COALESCE(ab.ct, 0) AS total
    FROM   "TABLE_B" b
    LEFT   JOIN (
       SELECT user_id AS id, count(*) AS ct
       FROM   "TABLE_A"
       WHERE  col_a
       GROUP  BY 1
       ) aa USING (id)
    LEFT   JOIN (
       SELECT user_id AS id, count(*) AS ct
       FROM   "TABLE_A"
       WHERE  col_b
       GROUP  BY 1
       ) ab USING (id);
    

    尤其是(在这种情况下很小!)部分索引,例如:

    CREATE INDEX a_true_idx on "TABLE_A" (user_id) WHERE col_a;
    CREATE INDEX b_true_idx on "TABLE_A" (user_id) WHERE col_b;
    

    除此之外:在 Postgres 中使用合法的、小写的、不带引号的名称以使您的喜欢更简单。

    【讨论】:

    • 很好的答案。谢谢!
    【解决方案2】:
    select user_id,name
     , count(case when col_a = true then 1 end)
     + count(case when col_b = true then 1 end) total
    from tableA a
    join TableB b on a.user_id= b.id 
    group by user_id,name
    

    【讨论】:

    • 或者count(*) filter (where col_a) + count(*) filter (where col_b)
    【解决方案3】:

    您在重复计算 JIM,如果不应该这样做,因为它只显示在两行而不是三行,也许您可​​以执行以下操作:

    with cte_A as (
        select col_a as col, user_id 
        from A 
        where col_a=true
        union -- ALL -- (if you want to double count Jim)
        select col_b as col, user_id 
        from A 
        where col_b=true
    )
    select B.user_id, sum(*) as total, B.name
    from cte_A
    join B
    on cte_A.user_id = B.user_id
    group by B.user_id 
    

    如果您想实际重复计算,请使用 UNION ALL 而不是 UNION

    【讨论】:

      猜你喜欢
      • 2014-10-11
      • 2019-02-02
      • 1970-01-01
      • 2010-10-25
      • 1970-01-01
      • 1970-01-01
      • 2019-05-24
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多