Home > database >  Rails 6 How to find element where association count is less than 2
Rails 6 How to find element where association count is less than 2

Time:06-27

I have a problem with extracting data with one query. My two models looks like that:

# school.rb
class School < ActiveRecord::Base
  has_many :meetings
end

# meetings.rb

class Meeting < ActiveRecord::Base
  belongs_to :school
  belongs_to :user
  
  # fields
  # status: [accepted, not_accepted, finished]
en

I want to take every school where passed User has less than 2 meetings with status finished. I was trying something like this:

School.joins(:meetings).where(meetings: { user: User, status: 'finished' }).group(:id).having( 'count(meetings.id) < 2')

But it works if User has one finished meeting in each school. I wonder if it is possible to solve this with one query? Maybe some of you knows if it is possible to do this and how?

@Edit An example for easier understanding of what I would like to receive:

  School | User | Meeting
 1.  A   | Adam | finished 
 2.  A   | Adam | accepted
 3.  A   | Adam | finished
 4.  B   | Adam | accepted
 5.  C   | John | finished
 6.  D   |  -   |    -
 7.  E   | John | finished

So I want to create query which will returns school B, C, D and E for user Adam

CodePudding user response:

I think you need to reverse your thinking here: get the schools you want to EXCLUDE from the results instead of trying to add extra ones to your query. Something like this:

# Get schools with more than 1 meeting for the user
excluded_schools = School.joins(:meetings)
  .where(meetings: { user: User, status: 'finished' })
  .group(:id)
  .having('count(meetings.id) > 1')

# Get ALL schools except for the ones you didn't want
filtered_schools = School.where.not(id: excluded_schools.select(:id))

Using select(:id) instead of pluck(:id) avoids triggering a new database query, so all of this should take just 1 database query when used inside a method. Of course in the console you'll have to nest these instead of storing in variables if you want to accomplish everything in 1 query.

CodePudding user response:

School.left_joins(:meetings).where(meetings: { user: user })
  .or(School.left_joins(:meetings).where(meetings: { id: nil }))
  .group(:id)
  .having("(count(meetings.id) < 2 AND meetings.status = 'finished') OR meetings.id IS NULL")
  • Related