【问题标题】:ActiveRecord : LEFT OUTER self-JOIN with conditions on joined tableActiveRecord : LEFT OUTER self-JOIN with conditions on join table
【发布时间】:2013-11-21 11:15:52
【问题描述】:

我有一个具有自反关联的 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(&amp;:parent) 时有 n+1 个查询)。

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

【问题讨论】:

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


    【解决方案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)
    

    这应该会正确执行所有急切加载。

    【讨论】:

    • +1,感谢您对此进行调查。我没有提到这个解决方案,但我也测试了它(我只是错过了参考部分)。我想这比我现在拥有的有所改进,但我不太喜欢“猜测”AR 会从它的魔术师帽子中抽出什么样的别名......
    猜你喜欢
    • 2021-06-11
    • 1970-01-01
    • 2018-12-04
    • 1970-01-01
    • 2013-12-20
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多