ActiveRecord / ARel изменяют `ON` в пропущенном соединении из включенных - PullRequest
2 голосов
/ 17 ноября 2011

Мне интересно, возможно ли указать дополнительные JOIN ON критерии, используя ActiveRecord includes?

пример: я беру запись и включаю ассоциацию с некоторыми условиями

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

.

Ответы [ 2 ]

2 голосов
/ 15 сентября 2012

Я могу познакомить тебя с Арелем. ПРИМЕЧАНИЕ : Мой код заканчивает тем, что вызывает за кулисами два запроса, которые я объясню.

Недавно мне пришлось самому разрабатывать ЛЕВЫЕ СОЕДИНЕНИЯ в ARel.Лучшее, что вы можете сделать, играя с ARel, - запустить консоль Rails или сеанс IRB и запустить метод #to_sql на ваших объектах ARel, чтобы увидеть, какой 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 (...)», используя большой список всех идентификаторов записей из первого запроса.

Record.includes() определенно дает вам левостороннее соединение, которое вы хотите, но я не знаю, как внедрить ваши собственные критерии в объединение.Вы можете использовать Record.joins() вместо ARel, если вы хотите написать SQL самостоятельно:

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

Я действительно, действительно предпочитаю, чтобы адаптер базы данных писал мой SQL, поэтому я использовал ARel.

Если бы это был я, я бы подумал о включении дополнительного критерия соединения в предложение WHERE.Я предполагаю, что вы спрашиваете, потому что добавление дополнительного критерия к объединению делает EXPLAIN запроса более привлекательным или потому что вы не хотите иметь дело с NULL в столбце other_records.some_date, когда нет других связанных с другими other_records.

0 голосов
/ 19 декабря 2011

Если у вас есть простое (равенство) дополнительное условие соединения, оно может быть просто

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])

Надеюсь, это поможет.

...