【问题标题】:ActiveRecord/ARel modify `ON` in a left out join from includesActiveRecord/ARel 修改“ON”从包含的遗漏连接中
【发布时间】:2011-11-17 18:55:15
【问题描述】:

我想知道是否可以使用 ActiveRecord includes 指定其他 JOIN ON 条件?

例如:我正在获取一条记录并包含与某些条件的关联

record.includes(:other_record).where(:other_record => {:something => :another})

这给了我(大致):

select * from records 
left outer join other_records on other_records.records_id = records.id 
where other_records.something = another

有谁知道我可以如何指定一个额外的连接条件,这样我就可以实现类似的目标。

select * from records 
left outer join other_records on other_records.records_id = records.id 
    and other_records.some_date > now()
where other_records.something = another

我希望我的包含拉入other_records,但我需要在我的加入中添加其他标准。任何使用 ARel 的东西都会很棒,我只是不知道如何将 ARel 的左外连接插入到 ActiveRecord::Relation

【问题讨论】:

  • 你能让你的例子更具体吗? something_else 有点难以理解。
  • 只是将其更改为不同的条件,基本上,我希望能够加入的不仅仅是外键。

标签: sql activerecord left-join arel


【解决方案1】:

我可以让你接近 ARel。 注意:我的代码最终会在后台调用两个查询,我将对此进行解释。

最近,我不得不自己在 ARel 中解决 LEFT JOIN。玩 ARel 时最好的办法是启动 Rails 控制台或 IRB 会话并在 ARel 对象上运行 #to_sql 方法,看看它们代表什么类型的 SQL。尽早并经常这样做!

这是您的 SQL,为了保持一致性而稍作修改:

SELECT *
FROM records
  LEFT OUTER JOIN other_records ON other_records.record_id = records.id
    AND other_records.some_date > now()
WHERE other_records.something = 'another'

我假设您的 records 模型是 Record,other_records 是 OtherRecord。翻译成 ARel 和 ActiveRecord:

class Record < ActiveRecord::Base

  # Named scope that LEFT JOINs OtherRecords with some_date in the future
  def left_join_other_in_future
    # Handy ARel aliases
    records = Record.arel_table
    other   = OtherRecord.arel_table

    # Join criteria
    record_owns_other = other[:record_id].eq(records[:id])
    other_in_future   = other[:some_date].gt(Time.now)

    # ARel's #join method lets you specify the join node type. Defaults to InnerJoin.
    # The #join_sources method extracts the ARel join node. You can plug that node
    #   into ActiveRecord's #joins method. If you call #to_sql on the join node,
    #   you'll get 'LEFT OUTER JOIN other_records ...'
    left_join_other = records.join(other, Arel::Nodes::OuterJoin).
                              on(record_owns_other.and(other_in_future)).
                              join_sources

    # Pull it together back in regular ActiveRecord and eager-load OtherRecords.
    joins(left_join_other).includes(:other_records)
  end

end

# MEANWHILE...

# Elsewhere in your app
Record.left_join_other_in_future.where(other_records: {something: 'another'})

我将联接装瓶在一个命名范围内,因此您无需将所有 ARel 与您的应用程序逻辑混在一起。

我的 ARel 最终在后台调用了两个查询:第一个使用您的 JOIN 和 WHERE 条件获取记录,第二个使用所有记录的大列表获取所有 OtherRecords "WHERE other_records.record_id IN (...)"第一个查询的 ID。

Record.includes() 肯定会为您提供所需的 LEFT JOIN,但我不知道如何将您自己的标准注入到联接中。如果您想自己编写 SQL,可以使用 Record.joins() 而不是 ARel:

Record.joins('LEFT OUTER JOIN other_records' +
             ' ON other_records.record_id = records.id' +
             ' AND other_records.some_date > NOW()')

我真的非常喜欢让数据库适配器编写我的 SQL,所以我使用了 ARel。

如果是我,我会考虑将附加连接条件放在 WHERE 子句中。我假设您之所以问,是因为将附加条件放在连接上会使查询的 EXPLAIN 看起来更好,或者是因为当没有任何相关的 other_records 时,您不想处理 other_records.some_date 列中的 NULL。

【讨论】:

    【解决方案2】:

    如果您有一个简单(相等)的额外连接条件,它可能只是

    record.includes(:other_record).where(:other_record => {:something => :another,
                                                           :some_date => Time.now})
    

    但如果您需要大于比较,则应该这样做。

    record.includes(:other_record).where([
      'other_records.something = ? and other_records.some_date > ?',
       another, Time.now])
    

    希望对您有所帮助。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2019-12-21
      • 1970-01-01
      • 1970-01-01
      • 2015-12-04
      • 1970-01-01
      相关资源
      最近更新 更多