Rails order by results count of has_many association

Ruby on-RailsRuby on-Rails-3Activerecord

Ruby on-Rails Problem Overview


Is there anyway I can order the results (ASC/DESC) by number of items returned from the child model (Jobs)?

@featured_companies = Company.joins(:jobs).group(Job.arel_table[:company_id]).order(Job.arel_table[:company_id].count).limit(10)

For example: I need to print the Companies with highest jobs on top

Ruby on-Rails Solutions


Solution 1 - Ruby on-Rails

Rails 5+

Support for left outer joins was introduced in Rails 5 so you can use an outer join instead of using counter_cache to do this. This way you'll still keep the records that have 0 relationships:

Company
  .left_joins(:jobs)
  .group(:id)
  .order('COUNT(jobs.id) DESC')
  .limit(10)

The SQL equivalent of the query is this (got by calling .to_sql on it):

SELECT "companies".* FROM "companies" LEFT OUTER JOIN "jobs" ON "jobs"."company_id" = "companies"."id" GROUP BY "company"."id" ORDER BY COUNT(jobs.id) DESC

Solution 2 - Ruby on-Rails

If you expect to use this query frequently, I suggest you to use built-in counter_cache

# Job Model
class Job < ActiveRecord::Base
  belongs_to :company, counter_cache: true
  # ...
end

# add a migration
add_column :company, :jobs_count, :integer, default: 0

# Company model
class Company < ActiveRecord::Base
  scope :featured, order('jobs_count DESC')
  # ...
end

and then use it like

@featured_company = Company.featured

Solution 3 - Ruby on-Rails

Something like:

Company.joins(:jobs).group("jobs.company_id").order("count(jobs.company_id) desc")

Solution 4 - Ruby on-Rails

@user24359 the correct one should be:

Company.joins(:jobs).group("companies.id").order("count(companies.id) DESC")

Solution 5 - Ruby on-Rails

Added to Tan's answer. To include 0 association

Company.joins("left join jobs on jobs.company_id = companies.id").group("companies.id").order("count(companies.id) DESC")

by default, joins uses inner join. I tried to use left join to include 0 association

Solution 6 - Ruby on-Rails

Adding to the answers, the direct raw SQL was removed from rails 6, so you need to wrap up the SQL inside Arel (if the raw SQL is secure meaning by secure avoiding the use of user entry and in this way avoid the SQL injection).

Arel.sql("count(companies.id) DESC")

Solution 7 - Ruby on-Rails

Company.where("condition here...")
       .left_joins(:jobs)
       .group(:id)
       .order('COUNT(jobs.id) DESC')
       .limit(10)

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionrandikaView Question on Stackoverflow
Solution 1 - Ruby on-RailsSheharyarView Answer on Stackoverflow
Solution 2 - Ruby on-RailsBilly ChanView Answer on Stackoverflow
Solution 3 - Ruby on-Railsuser24359View Answer on Stackoverflow
Solution 4 - Ruby on-RailsTan NguyenView Answer on Stackoverflow
Solution 5 - Ruby on-RailsChristian Angel GalamayView Answer on Stackoverflow
Solution 6 - Ruby on-Railsr4cc00nView Answer on Stackoverflow
Solution 7 - Ruby on-Railsfaisal bhattiView Answer on Stackoverflow