Rails包括条件

gus*_*usa 54 sql ruby-on-rails

是否可以在Rails> 3.2中为includes方法生成的join语句添加条件?

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

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

SELECT *
FROM people
LEFT JOIN notes ON notes.person_id = people.id AND notes.important = 't'
Run Code Online (Sandbox Code Playgroud)

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

Person.includes(:notes).where(:important, true)
Run Code Online (Sandbox Code Playgroud)

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

SELECT *
FROM people
LEFT JOIN notes ON notes.person_id = people.id
WHERE notes.important = 't'
Run Code Online (Sandbox Code Playgroud)

请注意,第一个结果集包括所有人,第二个结果集仅包括与重要注释相关联的人.

另请注意:条件自3.1以来已弃用.

Leo*_*rea 28

根据本指南Active Record Querying

您可以为此类的预先加载指定包含条件

Person.includes(:notes).where("notes.important", true)
Run Code Online (Sandbox Code Playgroud)

joins无论如何它建议使用.

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

class Person < ActiveRecord::Base
  has_many :important_notes, :class_name => 'Note', 
           :conditions => ['important = ?', true]
end
Run Code Online (Sandbox Code Playgroud)

然后你就可以做到这一点

Person.find(:all, include: :important_notes)
Run Code Online (Sandbox Code Playgroud)

  • Person.includes(...).其中(...)仅返回与重要笔记相关联的人.我需要所有人,渴望加载重要的笔记. (8认同)
  • 第二个解决方案解决了我为了清楚起见而绘制的示例.然而,现实更复杂.现在假设我需要一段时间内的重要注释(在start_date和end_date之间). (3认同)

Gra*_*ick 22

Rails 5+语法:

Person.includes(:notes).where(notes: {important: true})
Run Code Online (Sandbox Code Playgroud)

嵌套:

Person.includes(notes: [:grades]).where(notes: {important: true, grades: {important: true})
Run Code Online (Sandbox Code Playgroud)

  • 这不像这里提到的那样有效:[Active Record Guide](http://guides.rubyonrails.org/active_record_querying.html).它不返回与Note无关的Person对象.它只会返回具有master属性等于true的音符的人 (9认同)

Dan*_*iro 14

Rails 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)
Run Code Online (Sandbox Code Playgroud)

SQL:

SELECT "people".* FROM "people"

SELECT "notes".* FROM "notes" WHERE "notes"."important" = ? AND "notes"."person_id" IN (1, 2)
Run Code Online (Sandbox Code Playgroud)

选项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)
Run Code Online (Sandbox Code Playgroud)

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" = ?
Run Code Online (Sandbox Code Playgroud)


Yuk*_*oue 6

在日语 stackoverflow 中也讨论了同样的问题。相当hacky,但以下似乎有效,至少在rails 5上。

Person.eager_load(:notes).joins("AND notes.important = 't'")
Run Code Online (Sandbox Code Playgroud)

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

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


jul*_*lez 5

我无法在条件如 Leo Correa 的回答中使用包含。Insted 我需要使用:

Lead.includes(:contacts).where("contacts.primary" =>true).first
Run Code Online (Sandbox Code Playgroud)

或者你也可以

Lead.includes(:contacts).where("contacts.primary" =>true).find(8877)
Run Code Online (Sandbox Code Playgroud)

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