Autojointure sur une table avec ActiveRecord
-
30-09-2019 - |
Question
J'ai un ActiveRecord appelé Name
qui contient les noms dans divers Languages
.
class Name < ActiveRecord::Base
belongs_to :language
class Language < ActiveRecord::Base
has_many :names
Les noms Trouver dans une langue est assez facile:
Language.find(1).names.find(whatever)
Mais je dois trouver des paires de correspondance où les deux langues 1 et la langue 2 ont le même nom. Dans SQL, ce qui exige une simple auto-inscription:
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;
Comment puis-je faire une requête comme ça avec ActiveRecord? Notez que je dois trouver des paires de noms (= les deux côtés du match), et pas seulement une liste de noms dans la langue 1 qui arrive à correspondre à quelque chose.
Pour les points de bonus, remplacer n1.normalized=n2.normalized
avec n1.normalized LIKE n2.normalized
, puisque le champ peut contenir des jokers SQL.
Je suis aussi ouvert aux idées sur la modélisation des données différemment, mais je préfère éviter d'avoir des tables séparées pour chaque langue si je peux.
La solution
Essayez ceci:
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:. Assurez-vous désinfectez les paramètres passés à la condition de jointure
Autres conseils
On dirait que vous pouvez utiliser plusieurs à plusieurs entre la langue et le nom au lieu de 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"]
Ce niveau de normalisation supplémentaire devrait faire ce que vous essayez de faire plus facile.