Rails active record querying association with 'exists'

后端 未结 4 1880
感动是毒
感动是毒 2020-11-27 03:22

I am working on an app that allows Members to take a survey (Member has a one to many relationship with Response). Response holds the member_id, question_id, and their answe

相关标签:
4条回答
  • 2020-11-27 03:32

    If you are on Rails 5 and above you should use left_joins. Otherwise a manual "LEFT OUTER JOINS" will also work. This is more performant than using includes mentioned in https://stackoverflow.com/a/18234998/3788753. includes will attempt to load the related objects into memory, whereas left_joins will build a "LEFT OUTER JOINS" query.

    def surveys_completed
      members.left_joins(:responses).where.not(responses: { id: nil })
    end
    

    Even if there are no related records (like the query above where you are finding by nil) includes still uses more memory. In my testing I found includes uses ~33x more memory on Rails 5.2.1. On Rails 4.2.x it was ~44x more memory compared to doing the joins manually.

    See this gist for the test: https://gist.github.com/johnathanludwig/96fc33fc135ee558e0f09fb23a8cf3f1

    0 讨论(0)
  • 2020-11-27 03:33

    You can use SQL EXISTS keyword in elegant Rails-ish manner using Where Exists gem:

    members.where_exists(:responses).count
    

    Of course you can use raw SQL as well:

    members.where("EXISTS" \
      "(SELECT 1 FROM responses WHERE responses.member_id = members.id)").
      count
    
    0 讨论(0)
  • 2020-11-27 03:40

    You can use includes and then test if the related response(s) exists like this:

    def surveys_completed
      members.includes(:responses).where('responses.id IS NOT NULL')
    end
    

    Here is an alternative, with joins:

    def surveys_completed
      members.joins(:responses)
    end
    

    The solution using Rails 4:

    def surveys_completed
      members.includes(:responses).where.not(responses: { id: nil })
    end
    

    Alternative solution using activerecord_where_assoc: This gem does exactly what is asked here: use EXISTS to to do a condition. It works with Rails 4.1 to the most recent.

    members.where_assoc_exists(:responses)
    

    It can also do much more!


    Similar questions:

    • How to query a model based on attribute of another model which belongs to the first model?
    • association named not found perhaps misspelled issue in rails association
    • Rails 3, has_one / has_many with lambda condition
    • Rails 4 scope to find parents with no children
    • Join multiple tables with active records
    0 讨论(0)
  • 2020-11-27 03:51

    You can also use a subquery:

    members.where(id: Response.select(:member_id))
    

    In comparison to something with includes it will not load the associated models (which is a performance benefit if you do not need them).

    0 讨论(0)
提交回复
热议问题