How does one create a scope to find “Authors who have zero posts”?
-
29-09-2019 - |
Question
Using Rails 3, this scope works as would be expected:
scope :with_posts, lambda {
joins(:posts).
select("authors.*, count(posts.id) posts_count").
group("posts.author_id").
having("posts_count > 0")
}
The generated SQL is:
SELECT authors.*, count(posts.id) posts_count FROM `authors` INNER JOIN `posts` ON `posts`.`author_id` = `author`.`id` GROUP BY posts.author_id HAVING posts_count > 0
But its inverse returns no results:
scope :with_posts, lambda {
joins(:posts).
select("authors.*, count(posts.id) posts_count").
group("posts.author_id").
having("posts_count < 1")
}
I'm assuming that authors with zero posts are simply not being selected by line three... so what is the solution?
Solution
You are correct, the join is excluding all authors with zero posts, what you need is an outer join.
I don't know rails 3 syntax, but in rails 2, you can specify the joins statement with an SQL fragment instead of simply using the relation name.
User.all(:joins => 'outer join posts on users.id = posts.user_id')
Rails 3 must have an equivalent notation, but I never used it so I don't know the exact syntax. This should be the general idea though.
OTHER TIPS
Since inner joing SQL is being created it will show up the results with records available in both tables. So try creating outer join and find/count the null values in next table
try outer join
I don't know much about this but I would recommend you to have a look at this Railscast
Hope this works for you.
scope :without_posts, lambda {
where("id NOT IN (?)", Posts.select("author_id").group("author_id") )
}
This will work best if you have an index on author_id in your DB.