在 ActiveRecord 中急切加载关联时 JOIN ON ... AND 条件

max*_*max 3 activerecord ruby-on-rails ruby-on-rails-4

请注意,这不是一个关于向关联添加 WHERE 条件的问题,而是一个关于在使用 eager_load 时如何更改 JOIN 子句的相当高级的问题。

假设我有这些模型

class Parent
  has_many :children
  have_many :grades, through: :children
end

class Child
  belongs_to :parent
end

class Grade
  belongs_to :child
end
Run Code Online (Sandbox Code Playgroud)

因此,为了急切地加载父母,我会这样做:

Parent.eager_load(:grades)
Run Code Online (Sandbox Code Playgroud)

但如果我想要所有的父母 - 但只是渴望加载最高分,如下所示:

LEFT OUTER JOIN grades ON grades.child_id = children.id AND grades.level = 'A+'
Run Code Online (Sandbox Code Playgroud)

我尝试过使用includes(:children).joins("LEFT OUTER JOIN grades ON grades.child_id = children.id AND grades.level = 'A+'"),但由于 Rails 不构建关联对象,因此会导致对每个父级进行额外的查询。

我还没有找到任何关于使用eager_load自定义 SQL 字符串的参考资料,并且已经深入研究了源代码,但没有得到任何更明智的结果。

小智 6

如果我是对的,您只想立即加载关联的子集。那么,为了做到这一点,您需要智胜轨道。您基本上可以创建另一个关联,例如“highest_grade”,以使用 ruby​​ lambda 内的 where 子句获取 A+ 级的成绩。这可以通过使用以下代码片段来实现

class Parent
  has_many :children
  has_many :grades, through: :children
  has_many :highest_grade, ->{where(level: 'A+')}, class_name: "Grade", through: :children
end
Run Code Online (Sandbox Code Playgroud)

现在您需要立即加载最高等级关联。

 Parent.eager_load(:highest_grades)
Run Code Online (Sandbox Code Playgroud)

这基本上会加载所有家长以及仅 A+ 级别的成绩。

它生成以下查询。

 SQL (0.3ms)  SELECT `parents`.`id` AS t0_r0, `parents`.`created_at` AS    t0_r1, `parents`.`updated_at` AS t0_r2, `grades`.`id` AS t1_r0,    `grades`.`child_id` AS t1_r1, `grades`.`created_at` AS t1_r2, `grades`.`updated_at` AS t1_r3, `grades`.`level` AS t1_r4 FROM `parents` LEFT OUTER JOIN `children` ON `children`.`parent_id` = `parents`.`id` LEFT OUTER JOIN `grades` ON `grades`.`child_id` = `children`.`id` AND `grades`.`level` = '
Run Code Online (Sandbox Code Playgroud)

如果您想了解这是如何工作的。请使用此链接作为参考