简体   繁体   English

Rails 活动记录查询与“存在”的关联

[英]Rails active record querying association with 'exists'

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 answer. Response 包含 member_id、question_id 及其答案。

The survey is submitted all or nothing, so if there are any records in the Response table for that Member they have completed the survey.调查要么全部提交,要么不提交,因此如果该成员的响应表中有任何记录,则他们已完成调查。

My question is, how do I re-write the query below so that it actually works?我的问题是,如何重新编写下面的查询以使其真正起作用? In SQL this would be a prime candidate for the EXISTS keyword.在 SQL 中,这将是 EXISTS 关键字的主要候选对象。

 def surveys_completed
    members.where(responses: !nil ).count
 end 

You can use includes and then test if the related response(s) exists like this:您可以使用includes ,然后测试相关响应是否存在,如下所示:

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

Here is an alternative, with joins :这是一个替代方案,使用joins

def surveys_completed
  members.joins(:responses)
end

The solution using Rails 4 :使用 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.使用activerecord_where_assoc替代解决方案:这个 gem 完全按照这里的要求执行:使用EXISTS来执行条件。 It works with Rails 4.1 to the most recent.它适用于最新的 Rails 4.1。

members.where_assoc_exists(:responses)

It can also do much more!它还可以做得更多!


Similar questions:类似问题:

You can use SQL EXISTS keyword in elegant Rails-ish manner using Where Exists gem:您可以使用Where Exists gem 以优雅的 Rails-ish 方式使用 SQL EXISTS关键字:

members.where_exists(:responses).count

Of course you can use raw SQL as well:当然,您也可以使用原始 SQL:

members.where("EXISTS" \
  "(SELECT 1 FROM responses WHERE responses.member_id = members.id)").
  count

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).includes东西相比,它不会加载关联的模型(如果你不需要它们,这是一个性能优势)。

If you are on Rails 5 and above you should use left_joins .如果您使用 Rails 5 及更高版本,则应使用left_joins Otherwise a manual "LEFT OUTER JOINS" will also work.否则,手动“LEFT OUTER JOINS”也将起作用。 This is more performant than using includes mentioned in https://stackoverflow.com/a/18234998/3788753 .这比使用https://stackoverflow.com/a/18234998/3788753 中提到的includes高效 includes will attempt to load the related objects into memory, whereas left_joins will build a "LEFT OUTER JOINS" query. includes将尝试将相关对象加载到内存中,而left_joins将构建一个“LEFT OUTER JOINS”查询。

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.即使没有相关记录(如上面的查询,您通过 nil 查找) includes仍然使用更多内存。 In my testing I found includes uses ~33x more memory on Rails 5.2.1.在我的测试中,我发现包括在 Rails 5.2.1 上使用了大约 33 倍的内存。 On Rails 4.2.x it was ~44x more memory compared to doing the joins manually.在 Rails 4.2.x 上,与手动连接相比,内存增加了大约 44 倍。

See this gist for the test: https://gist.github.com/johnathanludwig/96fc33fc135ee558e0f09fb23a8cf3f1请参阅此测试要点: https : //gist.github.com/johnathanludwig/96fc33fc135ee558e0f09fb23a8cf3f1

where.missing (Rails 6.1+) where.missing (Rails 6.1+)

Rails 6.1 introduces a new way to check for the absence of an association - where.missing . Rails 6.1 引入了一种检查关联缺失的新方法—— where.missing

Please, have a look at the following code snippet:请查看以下代码片段:

# Before:
Post.left_joins(:author).where(authors: { id: nil })

# After:
Post.where.missing(:author)

And this is an example of SQL query that is used under the hood:这是一个在后台使用的 SQL 查询示例:

Post.where.missing(:author)
# SELECT "posts".* FROM "posts"
# LEFT OUTER JOIN "authors" ON "authors"."id" = "posts"."author_id"
# WHERE "authors"."id" IS NULL

As a result, your particular case can be rewritten as follows:因此,您的特定情况可以改写如下:

def surveys_completed
  members.where.missing(:response).count
end 

Thanks.谢谢。

Sources:资料来源:

Notes:笔记:

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM