67

是否可以在 Rails > 3.2 中向方法生成的连接语句添加条件includes

假设我有两个模型,Person 和 Note。每个人有很多笔记,每个笔记属于一个人。每个音符都有一个属性important

我想找到所有只预加载重要笔记的人。在 SQL 中,这将是:

SELECT *
FROM people
LEFT JOIN notes ON notes.person_id = people.id AND notes.important = 't'

在 Rails 中,唯一类似的方法是使用includes(注意:joins不会预加载注释),如下所示:

Person.includes(:notes).where(:important, true)

但是,这将生成以下 SQL 查询,该查询返回不同的结果集:

SELECT *
FROM people
LEFT JOIN notes ON notes.person_id = people.id
WHERE notes.important = 't'

请注意,第一个结果集包括所有人员,第二个结果集仅包括与重要笔记相关的人员。

另请注意 :conditions 自 3.1 起已弃用。

4

7 回答 7

41

根据本指南Active Record Querying

您可以像这样为急切加载指定包含条件

Person.includes(:notes).where("notes.important", true)

它建议joins无论如何都要使用。

一种解决方法是创建另一个这样的关联

class Person < ActiveRecord::Base
  has_many :important_notes, :class_name => 'Note', 
           :conditions => ['important = ?', true]
end

然后你就可以做到这一点

Person.find(:all, include: :important_notes)
于 2013-05-02T22:10:46.090 回答
37

Rails 5+ 语法:

Person.includes(:notes).where(notes: {important: true})

嵌套:

Person.includes(notes: [:grades]).where(notes: {important: true, grades: {important: true})
于 2017-09-11T10:44:46.660 回答
22

导轨 4.2+:

选项 A -“预加载”:多选,使用“id IN (...)”

class Person < ActiveRecord::Base
  has_many :notes
  has_many :important_notes, -> { where(important: true) }, class_name: "Note"
end

Person.preload(:important_notes)

SQL:

SELECT "people".* FROM "people"

SELECT "notes".* FROM "notes" WHERE "notes"."important" = ? AND "notes"."person_id" IN (1, 2)

选项 B - “eager_load”:一个巨大的选择,使用“LEFT JOIN”

class Person < ActiveRecord::Base
  has_many :notes
  has_many :important_notes, -> { where(important: true) }, class_name: "Note"
end

Person.eager_load(:important_notes)

SQL:

SELECT "people"."id" AS t0_r0, "people"."name" AS t0_r1, "people"."created_at" AS t0_r2, "people"."updated_at" AS t0_r3, "notes"."id" AS t1_r0, "notes"."person_id" AS t1_r1, "notes"."important" AS t1_r2 
FROM "people" 
LEFT OUTER JOIN "notes" ON "notes"."person_id" = "people"."id" AND "notes"."important" = ?
于 2016-07-13T10:09:40.277 回答
7

日语 stackoverflow 中也有同样的讨论。相当hacky,但跟随似乎工作,至少在rails 5上。

Person.eager_load(:notes).joins("AND notes.important = 't'")

一个重要方面是,通过这种方式,您可以编写任意连接条件。不利的一面是您不能使用占位符,因此在使用参数作为连接条件时需要小心。

https://ja.stackoverflow.com/q/22812/754

于 2018-08-16T06:00:51.397 回答
5

我无法在像 Leo Correa's answer 这样的条件下使用包含。我需要使用:

Lead.includes(:contacts).where("contacts.primary" =>true).first

或者你也可以

Lead.includes(:contacts).where("contacts.primary" =>true).find(8877)

最后一个将检索 ID 为 8877 的潜在客户,但仅包括其主要联系人

于 2015-12-01T22:21:29.933 回答
2

对于感兴趣的人,我在记录属性为假的情况下尝试了这个

Lead.includes(:contacts).where("contacts.primary" => false).first

这不起作用。不知何故,布尔值只true适用,所以我把它转过来包括where.not

Lead.includes(:contacts).where.not("contacts.primary" => true).first

这完美地工作

于 2016-10-18T15:07:21.070 回答
0

一种方法是使用连接自己编写 LEFT JOIN 子句:

Person.joins('LEFT JOIN "notes" ON "notes"."person_id" = "people.id" AND "notes"."important" IS "t"')

不过不漂亮。

于 2013-11-19T14:41:25.227 回答