Reputation: 5338
I am trying to get the number of comments from each of the posts in the database. The following, however:
Post.includes(:comments).group("posts.id").count("comments.id")
raises the mysql error "Unknown column comments.id", since the generated sql seems to completely ignore the includes():
SELECT COUNT(comments.id) AS count_comments_id, posts.id AS posts_id
FROM `posts` GROUP BY posts.id
Interestingly, replacing includes() with joins() will produce working sql:
Post.joins(:comments).group("posts.id").count("comments.id")
SELECT COUNT(comments.id) AS count_comments_id, posts.id AS posts_id
FROM `posts` INNER JOIN `comments` ON `comments`.`post_id` = `posts`.`id`
GROUP BY posts.id
but the above query excludes all posts with 0 comments, which is not what I want. What I do need is to produce the following SQL (but without writing SQL, he he he)
SELECT COUNT(comments.id) AS count_comments_id, posts.id AS posts_id
FROM `posts` LEFT OUTER JOIN `comments` ON `comments`.`post_id` = `posts`.`id`
GROUP BY posts.id
Upvotes: 17
Views: 19228
Reputation: 156
For rails 5 or above, we have a activerecord method left_outer_joins
& we don't need to write the join logic manually.
Post.left_outer_joins(:comments)
.distinct
.select('posts.*, COUNT(comments.*) AS comments_count')
.group('posts.id')
Which produces:
SELECT DISTINCT posts.*, COUNT(comments.*) AS comments_count FROM posts
LEFT OUTER JOIN comments ON comments.post_id = posts.id GROUP BY posts.id
Upvotes: 0
Reputation: 3507
This problem is actually way simpler nowadays:
Comment.joins(:post).group(:post_id).count(:post_id)
This will give you a map of {<post_id> => <count of comments>}
Upvotes: 11
Reputation: 23648
The includes
method will not do a join in all cases but rather batch-fetch the association for performance reasons (see Rails :include vs. :joins).
What you need to do is a joins and you where almost on the correct path but got the group clause a bit wrong:
Post.select("posts.*, COUNT(comments.id) as comment_count").joins("LEFT OUTER JOIN comments ON (comments.post_id = posts.id)").group("posts.id")
Note that this solution has the benefit or actually returning Post objects (using .count()
returns a Hash on my Rails 3.2) so you can loop through actual post objects in your view and also access the property comment_count
.
Upvotes: 20
Reputation: 64363
Try this:
Post.select("COUNT(comments.id) AS count_comments_id, posts.id AS posts_id").
includes(:comments).group("posts.id")
Upvotes: 2
Reputation: 5338
If all you need is the hash of post counts: {post_id1 => cnt1, post_id2 => cnt2, ...}
then forcing a left join on the association will work:
Post.joins("LEFT OUTER JOIN comments on posts.id=comments.post_id").
group("posts.id").count("comments.id")
Upvotes: 0