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

Rails active record querying association with 'exists'

我正在开发一个允许会员进行调查的应用程序(会员与"响应"具有一对多关系)。 响应包含member_id,question_id及其答案。

该调查是全部提交或不提交的,因此,如果"响应"表中有该成员的任何记录,则表示他们已完成调查。

我的问题是,如何重新编写下面的查询,使其真正起作用? 在SQL中,这将是EXISTS关键字的主要候选对象。

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

您可以使用includes,然后测试是否存在相关响应,如下所示:

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

这是使用joins的替代方法:

1
2
3
def surveys_completed
  members.joins(:responses)
end

使用Rails 4的解决方案:

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

使用activerecord_where_assoc的替代解决方案:
这个gem确实满足了这里的要求:使用EXISTS进行条件处理。
它与最新版本的Rails 4.1一起使用。

1
members.where_assoc_exists(:responses)

它还可以做更多!

类似问题:

  • 如何根据属于第一个模型的另一个模型的属性查询一个模型?
  • 关联未找到名称可能在Rails关联中拼写错误
  • Rails 3,具有lambda条件的has_one / has_many
  • Rails 4范围寻找没有孩子的父母
  • 连接具有活动记录的多个表


您可以使用Where Exists gem以优雅的Rails-ish方式使用SQL EXISTS关键字:

1
members.where_exists(:responses).count

当然,您也可以使用原始SQL:

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


如果您在Rails 5和更高版本上,则应使用left_joins。否则,手动"左外接头"也将起作用。这比使用https://stackoverflow.com/a/18234998/3788753中提到的includes具有更高的性能。 includes将尝试将相关对象加载到内存中,而left_joins将构建" LEFT OUTER JOINS"查询。

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

即使没有相关记录(例如上面通过nil查找的查询),includes仍会使用更多的内存。在我的测试中,我发现在Rails 5.2.1上使用的内存增加了约33倍。在Rails 4.2.x上,与手动执行连接相比,它的内存增加了约44倍。

请参阅以下要点进行测试:
https://gist.github.com/johnathanludwig/96fc33fc135ee558e0f09fb23a8cf3f1


您还可以使用子查询:

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

与使用includes的内容相比,它不会加载关联的模型(如果不需要它们,则可以提高性能)。