Самостоятельное присоединение к таблице с помощью ActiveRecord

У меня есть ActiveRecord с именем Name, который содержит имена в различных Languages.

class Name < ActiveRecord::Base
  belongs_to :language

class Language < ActiveRecord::Base
  has_many :names

Найти имена на одном языке достаточно просто:

Language.find(1).names.find(whatever)

Но мне нужно найти совпадающие пары, в которых язык 1 и язык 2 имеют одинаковое имя. В SQL это требует простого самосоединения:

SELECT n1.id,n2.id FROM names AS n1, names AS n2
  WHERE n1.language_id=1 AND n2.language_id=2
    AND n1.normalized=n2.normalized AND n1.id != n2.id;

Как я могу сделать такой запрос с помощью ActiveRecord? Обратите внимание, что мне нужно найти пары имен (= обе стороны совпадения), а не просто список имен на языке 1, который с чем-то совпадает.

Для бонусных баллов замените n1.normalized=n2.normalized на n1.normalized LIKE n2.normalized, так как поле может содержать подстановочные знаки SQL.

Я также открыт для идей о моделировании данных по-разному, но я бы предпочел избегать отдельных таблиц для каждого языка, если это возможно.


person lambshaanxy    schedule 04.09.2010    source источник


Ответы (2)


Попробуй это:

ids = [1,2]
Name.all(:select    => "names.id, n2.id AS id2",
         :joins     => "JOIN names AS n2 
                              ON n2.normalized = names.normalized AND 
                                 n2.language_id != names.language_id AND
                                 n2.language_id IN (%s)" % ids.join(','),
         :conditions => ["names.language_id IN (?)", ids]
).each do |name|
  p "id1 : #{name.id}"
  p "id2 : #{name.id2}"
end

PS: убедитесь, что вы санируете параметры, переданные в условие соединения.

person Harish Shetty    schedule 05.09.2010
comment
Что ж, это, безусловно, работает (после исправления незначительной опечатки должно быть :joins => "JOIN names as...), но он возвращает только объекты Name на языке 1 (с добавленным id2). Извлечение объектов для имен в языке 2 требует вызова Name.find(name.id2) для каждого совпадения, что приводит к довольно большому снижению производительности. Как-нибудь обойти это? - person lambshaanxy; 06.09.2010
comment
Хорошо, это возвращает список всех совпадений на обоих языках (после добавления AND names.language_id != n2.language_id для фильтрации совпадений с самим собой), но это гораздо более медленный запрос, и он возвращает один гигантский список вместо списка пар - мне все еще нужно использовать Name.find(name.id2), чтобы определить совпадающую пару имени. - person lambshaanxy; 07.09.2010
comment
Сколько строк возвращается в списке? В идеале это должно возвращать одну строку (при условии, что у вас нет повторяющихся записей для одного и того же ключа). Какие данные вы пытаетесь получить во второй находке? Вы можете обновить список выбора, чтобы добавить нужные поля из таблицы names. Я обновил ответ. Может на этот раз получится. - person Harish Shetty; 07.09.2010

Похоже, вы, возможно, захотите использовать отношение «многие ко многим» между языком и именем вместо has_many/belongs_to.

>> Language.create(:name => 'English')
 => #<Language id: 3, name: "English", created_at: "2010-09-04 19:15:11", updated_at: "2010-09-04 19:15:11"> 
>> Language.create(:name => 'French')
 => #<Language id: 4, name: "French", created_at: "2010-09-04 19:15:13", updated_at: "2010-09-04 19:15:13"> 
>> Language.first.names << Name.find_or_create_by_name('Dave')
 => [#<Name id: 3, name: "Dave", language_id: 3, created_at: "2010-09-04 19:16:50", updated_at: "2010-09-04 19:16:50">] 
>> Language.last.names << Name.find_or_create_by_name('Dave')
 => [#<Name id: 3, name: "Dave", language_id: 4, created_at: "2010-09-04 19:16:50", updated_at: "2010-09-04 19:16:50">]
>> Language.first.names.first.languages.map(&:name)
 => ["English", "French"] 

Этот дополнительный уровень нормализации должен облегчить то, что вы пытаетесь сделать.

person Dave Pirotte    schedule 04.09.2010
comment
О, интересно. Проблема в том, что напр. Финское «Джони» и еврейское «Йони» на самом деле являются разными именами с разными свойствами (написание в исходном сценарии и т. д.), которые просто имеют одно и то же нормализованное поле имени, а не просто одно имя. - person lambshaanxy; 06.09.2010