Retrieve a count from a one-to-many relationship even if there are no rows at the “many”-end of the relationship

Posted on

Question :

I have the following table, company_likes:

                     Table "public.company_likes"
   Column   |              Type              |       Modifiers        
------------+--------------------------------+------------------------
 company_id | integer                        | not null
 user_id    | integer                        | not null
 created_at | timestamp(0) without time zone | not null default now()
Indexes:
    "company_likes_pkey" PRIMARY KEY, btree (company_id, user_id)
Foreign-key constraints:
    "company_likes_company_id_foreign" FOREIGN KEY (company_id) REFERENCES companies(id) ON DELETE CASCADE
    "company_likes_user_id_foreign" FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE

The structure of the companies table is not important as you can deduce whatever needed for the purpose of this question from the foreign keys details in the d company_likes output.

I am trying to fetch the number of likes for a set of companies. I tried the following:

select company_id, count(company_id) as likes_count from company_likes where company_id in (1,2,3,4) group by company_id;

And per this StackOverflow answer, I have also tried this:

select company_id, count(company_id) as likes_count from company_likes
left join companies on companies.id = company_likes.company_id
where company_id in (1,2,3,4)
group by company_id;

However the output in both cases is an empty result. I would have liked something like this

 company_id | likes_count  
------------+-------------
 1          | 0            
 2          | 0            
 3          | 0            
 4          | 0            

What query should I use to get a result like this?

Answer :

Try

select 
  C.id
, count( CL.company_id ) likes_count
from companies C
  left join company_likes CL on C.id = CL.company_id 
group by C.id 
order by C.id ;

See dbfiddle (Postgres 9.6).

-- test tables and data
create table companies ( id int4 primary key ) ;

insert into companies ( id ) values (1),(2),(3),(4);

select * from companies ;

id
1
2
3
4

create table company_likes (
  company_id int4 references companies( id )
);

insert into company_likes ( company_id ) values (1),(4),(4),(4),(4) ;

select * from company_likes ;

company_id
1
4
4
4
4

Query

select 
  C.id
, count( CL.company_id ) likes_count
from companies C
  left join company_likes CL on C.id = CL.company_id 
group by C.id 
order by C.id ;

id  likes_count
1   1
2   0
3   0
4   4

Start out with the VALUES that you want to be included.

SELECT company_id, count(company_id) as likes_count
FROM (VALUES (1),(2),(3),(4))
  AS gs(id)
LEFT OUTER JOIN company_likes USING (id)
LEFT OUTER JOIN companies
  ON companies.id = company_likes.company_id
GROUP BY company_id;

But I’m not sure that’s a stellar idea anyway, I have to question why you’re selecting IDs that may not be there.

UPDATE

I see what the problem is in your query you have

where company_id in (1,2,3,4)

However, company_id is only there if the join succeeds. There is nothing to join it against if there are no likes, so you need,

where companies.id in (1,2,3,4)

Leave a Reply

Your email address will not be published.