ActiveRecord:LEFT OUTER自连接条件在连接表上

时间:2013-11-21 11:15:52

标签: mysql sql ruby-on-rails activerecord ruby-on-rails-4

我有一个带有反身关联的ActiveRecord类(注意:由于不公开原因,类名被编辑):

   class Thing < ActiveRecord::Base

     belongs_to :parent, 
                class_name: 'Thing'

     has_many :children, 
              class_name:  'Thing', 
              foreign_key: :parent_id

     scope :children, ->{ where(arel_table[:parent_id].not_eq(nil)) }

   end

我想让所有的孩子,左外连接到他们的父母,如果它存在,父母的附加条件,同时仍然急于加载父母。

通常在这种情况下我会做类似的事情:

 Thing.includes(:other_thing).merge(OtherThing.some_scope)

但在这种情况下不可能:因为这是一个自我加入,如果我合并Thing的范围,条件将适用于孩子的事情,而不是父母。

我尝试在原始SQL中执行此操作:

  scope :some_scope, ->{
    children.joins(<<-SQL).references(:parent)
      LEFT OUTER JOIN things AS parents
        ON  things.parent_id = parents.id 
        AND parents.some_field IN ('foo', 'bar')
    SQL
  }

查询没问题,但似乎父进程没有得到加载(在此范围内尝试map(&:parent)时n + 1查询)。

目前,我或多或少的工作解决方案是使用子查询(与父条件匹配的拔符号,然后选择具有parent_id IN此子集的子项),但我想知道是否有一个更合适/优雅的。

1 个答案:

答案 0 :(得分:1)

我没有找到使用merge执行此操作的方法(这似乎坚持使用模型table_name),但您可以使用includes执行此操作并围绕预期别名构建条件 - 您需要根据自己的命名修改此别名:

User.includes(:parent).
      where('parents_users.some_field IN (?)', ['foo', 'bar']).
      references(:parents_users)

但是,您可能需要注意NULL父ID,因为条件将添加到WHERE子句而不是LEFT JOIN本身:

User.includes(:parent).
      where('parents_users.some_field IN (?) OR parents_users.id IS NULL', ['foo', 'bar']).
      references(:parents_users)

这应该正确执行所有预先加载。