【问题标题】:Using SQL Aggregate Functions With Multiple Joins使用具有多个连接的 SQL 聚合函数
【发布时间】:2014-09-07 10:47:34
【问题描述】:

我正在尝试在单个 SQL 查询中跨多个表使用多个聚合函数(使用 Postgres)。

我的表格结构类似于以下:

CREATE TABLE user (user_id INT PRIMARY KEY, user_date_created TIMESTAMP NOT NULL);
CREATE TABLE item_sold (item_sold_id INT PRIMARY KEY, sold_user_id INT NOT NULL);
CREATE TABLE item_bought (item_bought_id INT PRIMARY KEY, bought_user_id INT NOT NULL);

我想计算每个用户购买和出售的商品数量。我想出的解决方案不起作用:

SELECT user_id, COUNT(item_sold_id), COUNT(item_bought_id)
FROM user
LEFT JOIN item_sold ON sold_user_id=user_id
LEFT JOIN item_bought ON bought_user_id=user_id
WHERE user_date_created > '2014-01-01'
GROUP BY user_id;

这似乎执行 (item_sold_id, item_bought_id) 的所有组合,例如如果有 4 个卖出和 2 个买入,两个 COUNT() 都是 8。

如何正确查询表以获得两个计数?

【问题讨论】:

    标签: sql postgresql join left-join aggregate-functions


    【解决方案1】:

    查询的简单修复方法是使用distinct:

    SELECT user_id, COUNT(distinct item_sold_id), COUNT(distinct item_bought_id)
    FROM user
    LEFT JOIN item_sold ON sold_user_id=user_id
    LEFT JOIN item_bought ON bought_user_id=user_id
    WHERE user_date_created > '2014-01-01'
    GROUP BY user_id;
    

    但是,查询正在做不必要的工作。如果某人购买了 100 件商品并出售了 200 件商品,则连接会产生 20,000 个中间行。太多了。

    解决方案是预先聚合结果或使用select 中的相关子查询。在这种情况下,我更喜欢相关子查询解决方案(假设正确的索引可用):

    SELECT u.user_id,
           (select count(*) from item_sold s where u.user_id = s.sold_user_id),
           (select count(*) from item_bought b where u.user_id = b.bought_user_id)
    FROM user u
    WHERE u.user_date_created > '2014-01-01';
    

    正确的索引是item_sold(sold_user_id) 和item_bought(bought_user_id)。由于user 表上的过滤,我更喜欢这种预聚合。这只对今年创建的用户进行计算——这对于预聚合更难。

    【讨论】:

    • 您确定不会优化连接吗?
    • @uraf 。 . . “优化了”?我不明白你的意思。连接是查询所必需的,因此它们会发生。
    • 我的意思是一个足够聪明的查询优化器可以把第一个查询变成第二个。我想知道 Postgres 是否足够聪明地做到这一点。 ;)
    • @uraf...检查两个查询的解释计划并亲自查看。
    • @Tanaki 。 . .感谢您在第二个查询中发现语法问题。
    【解决方案2】:

    解决此问题的另一种方法是使用两个嵌套选择。

    select user_id,
           (select count(*) from item_sold where sold_user_id = user_id),
           (select count(*) from item_bought where bought_user_id = user_id)
    from user
    where user_date_created > '2014-01-01'
    

    【讨论】:

      【解决方案3】:

      SQL Fiddle

      通过横向连接,可以只预聚合过滤后的用户

      select user_id, total_item_sold, total_item_bought
      from
          "user" u
          left join lateral (
              select sold_user_id, count(*) as total_item_sold
              from item_sold
              where sold_user_id = u.user_id
              group by sold_user_id
          ) item_sold on user_id = sold_user_id
          left join lateral (
              select bought_user_id, count(*) as total_item_bought
              from item_bought
              where bought_user_id = u.user_id
              group by bought_user_id
          ) item_bought on user_id = bought_user_id
      where u.user_date_created >= '2014-01-01'
      

      请注意,您需要在过滤器中使用>=,否则可能会错过一年中的确切第一刻。尽管自然输入的数据不太可能出现该时间戳,但在自动化作业中却很常见。

      【讨论】:

        猜你喜欢
        • 2017-10-27
        • 1970-01-01
        • 1970-01-01
        • 2013-02-22
        • 2019-03-20
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多