Home > Software engineering >  How to count on join a table with 2 conditions?
How to count on join a table with 2 conditions?

Time:10-07

I have an items table

id name
1 Nganu
2 Kae
3 Lho

Also I have an item_usages table:

id item_id user_id usage_time
1 1 99 2021-10-07 00:00:00
2 2 99 2021-10-07 00:00:00
3 1 99 2021-10-08 00:00:00
4 1 22 2021-10-08 00:00:00
5 3 22 2021-10-08 00:00:00
6 1 99 2021-10-08 00:00:00

I want to find an item's total usage and user usage in a query. an example I would like to find user_id 99 usage, expected result:

id name total_usage user_usage
2 Kae 1 1
1 Nganu 4 3
3 Lho 1 0

I tried:

select 
    "items".*,
    count(total_usage.id) as total_usage, 
    count(user_usage.id) as user_usage 
from 
    "items"
left join 
    "item_usages" as "total_usage" on "items"."id" = "total_usage"."item_id"
left join 
    "item_usages" as "user_usage" on "user_usage"."item_id" = "items"."id" 
                                  and "user_usage"."user_id" = 99
group by 
    "items"."id";

but it returns:

id name total_usage user_usage
2 Kae 1 1
1 Nganu 12 12
3 Lho 1 0

item_usages only have 6 rows, why Nganu have 12 on both usage? How to fix my query?

I tried on PostgreSQL 12.8 and 13.4, I also tested on SQLFiddle(PostgreSQL 9.6), Here is the link:

http://sqlfiddle.com/#!17/f1aac/5

I got the query that returned the correct result:

select 
    "items".*,
    min(total_usage.total_count) as total_usage, 
    count(user_usage.id) as user_usage 
from "items"
left join 
    (select item_id,count(item_id) as total_count  from item_usages group by item_id) as total_usage 
     on "items"."id" = "total_usage"."item_id"
left join "item_usages" as "user_usage" 
     on "user_usage"."item_id" = "items"."id" and "user_usage"."user_id" = 99
 group by "items"."id";

But I don't know about the performance, so I still find faster query if possible and still wondering:

Why does my first query give wrong result?

CodePudding user response:

The reason your query returns high numbers is that you join 2 times.

(From the side of Nganu) The first join will result in 4 rows, the second will map those 4 rows with 3 rows of the same table, resulting in 12 rows.

You can solve this problem with only 1 join:

select "items".id, 
count(total_usage.id) as total_usage, 
sum(case when total_usage.user_id = 99 then 1 else 0 end) as user_usage
from "items"
left join "item_usages" as "total_usage" on "items"."id" = "total_usage"."item_id"
group by "items".id

And it should work faster (though, on a small dataset is not visible)

  • Related