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

Lee*_*Lee 47 ruby-on-rails

我正在开发一个允许会员进行调查的应用程序(会员与Response有一对多的关系).Response保存member_id,question_id及其答案.

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

我的问题是,如何重新编写下面的查询,以便它实际工作?在SQL中,这将是EXISTS关键字的主要候选者.

 def surveys_completed
    members.where(responses: !nil ).count
 end 
Run Code Online (Sandbox Code Playgroud)

MrY*_*iji 101

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

def surveys_completed
  members.includes(:responses).where('responses.id IS NOT NULL')
end
Run Code Online (Sandbox Code Playgroud)

这是一个替代方案,包括joins:

def surveys_completed
  members.joins(:responses)
end
Run Code Online (Sandbox Code Playgroud)

使用Rails 4的解决方案:

def surveys_completed
  members.includes(:responses).where.not(responses: { id: nil })
end
Run Code Online (Sandbox Code Playgroud)

类似的问题:

  • @MrYoshiji实际上可以在Rails 3中执行此操作而不使用文字SQL字符串:members.includes(:responses).where(Response.arel_table [:id] .not_eq(nil)).count (3认同)
  • 无法避免弃用通知,因为我们需要键入一个字符串以表示"IS NOT NULL"(除非您使用Rails 4,否则无法在纯ActiveRecord中进行转换).我会在@lee中放一个第三个可能的东西 (2认同)

Eug*_*Zol 7

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

members.where_exists(:responses).count
Run Code Online (Sandbox Code Playgroud)

当然你也可以使用原始SQL:

members.where("EXISTS" \
  "(SELECT 1 FROM responses WHERE responses.member_id = members.id)").
  count
Run Code Online (Sandbox Code Playgroud)


Mar*_*n13 7

where.missing(Rails 6.1+)

Rails 6.1 引入了一种检查关联是否缺失的新方法 - where.missing

请看一下下面的代码片段:

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

# After:
Post.where.missing(:author)
Run Code Online (Sandbox Code Playgroud)

这是在后台使用的 SQL 查询的示例:

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

因此,您的具体情况可以重写如下:

def surveys_completed
  members.where.missing(:response).count
end 
Run Code Online (Sandbox Code Playgroud)

谢谢。

资料来源:

笔记:


Mar*_*kus 6

您还可以使用子查询:

members.where(id: Response.select(:member_id))
Run Code Online (Sandbox Code Playgroud)

includes它相比,它不会加载关联的模型(如果您不需要它们,这是一个性能优势)。


小智 6

如果您使用的是 Rails 5 及更高版本,则应使用left_joins. 否则,手动“LEFT OUTER JOINS”也将起作用。这比/sf/answers/1276449891/ 中includes提到的使用性能更高。将尝试将相关对象加载到内存中,而将构建“LEFT OUTER JOINS”查询。includesleft_joins

def surveys_completed
  members.left_joins(:responses).where.not(responses: { id: nil })
end
Run Code Online (Sandbox Code Playgroud)

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

请参阅此测试要点:https : //gist.github.com/johnathanludwig/96fc33fc135ee558e0f09fb23a8cf3f1