使用具有多个联接的SQL聚合函数

时间:2014-07-16 19:06:22

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

我试图在一个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()s均为8。

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

3 个答案:

答案 0 :(得分:7)

您的查询的简单方法是使用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表上进行过滤,我更喜欢这种预聚合。这只对今年创建的用户进行了计算 - 这对于预聚合来说更难。

答案 1 :(得分:2)

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'

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

答案 2 :(得分:1)

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

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'