Home > Software engineering >  Rails: multi find out if a category has members with a single fast query
Rails: multi find out if a category has members with a single fast query

Time:01-25

Given the model:

  • Member -> belongs_to :category
  • Category -> has_many :members

I would like to know for every category whether or not it has members, in the style of:

----------------------
| name | has_members |
======================
| cat1 | true        |
| cat2 | false       |
| cat3 | true        |
----------------------

The following solutions are way too slow to achieve this:

  • Category.eager_load(:members).map{|s| s.members.any?}
  • Category.includes(:members).map{|s| s.members.any?}

This would be a use case for a counter cache, but I wonder whether this could be solved by some smart ARel or SQL.

What would be the fastest way to achieve this?

CodePudding user response:

What if you inverse the problem like this :

Member.includes(:category).order(:category_id).distinct.map{ |m| m.category !=nil ? m.category.name : "" }

CodePudding user response:

This query should return a hash of values with the name and the count of members.

Category.includes(:members).distinct.select('categorys.name, COUNT(members.*)').group('category.name').count(:members)

If you wanted JUST the Categories that have more than zero members you can do:

Category.joins(:members).distinct.select('categorys.name, COUNT(members.*)').group('category.name').count(:members)

Which will ignore the Categories where members = 0.

The query itself is probably not what's slowing you down, it's the fact that you then pass them all to .map which is a ruby method, not SQL. I think for anything faster than what I've given you, you'll have to go straight to the DB with a counter cache. Also in a pure SQL query you could add a CASE statement to swap true/false for numbers > or < 0.

  • Related