2010-01-26 4 views
1

J'ai une collection de classes liées à la géographie: Etat, Msa, Comté, Ville, etc. Les classes descendent toutes d'une classe de base. Les classes sont liées, pour la plupart, à travers une table de jointure dénormalisée appelée géographie. J'ai donc ...Associations avec: uniq => true ne retourne pas toujours des résultats uniques

class Geography < ActiveRecord::Base 
    belongs_to :state 
    belongs_to :county 
    belongs_to :city 
    # ... etc. 
end 

class Location < ActiveRecord::Base 
end 

class State < Location 
    has_many :geographies 
    has_many :msas, :through => :geographies, :uniq => true 
    # ... etc. 
end 

class Msa < Location 
    has_many :geographies 
    has_many :states, :through => :geographies, :uniq => true 
    # ... etc. 
end 

Maintenant, quand je lance ce qui suit de la console:

>> msas = Msa.find(:all, :include=>"states", :conditions=>{"states_locations"=>{"id"=>"1"}}) 

Je reviens le nombre correct de résultats (13 dans ce cas). Cependant, en exécutant le SQL que cet appel de recherche produit, je récupère des milliers de résultats (encore une fois la table de géographie est une sorte de datamart, c'est pourquoi j'utilise l'option: uniq sur l'association).

SELECT   `locations`.`id` AS t0_r0, 
       `locations`.`parent_id` AS t0_r1, 
       `locations`.`type` AS t0_r2, 
       `locations`.`name` AS t0_r3, 
       `states_locations`.`id` AS t1_r0, 
       `states_locations`.`parent_id` AS t1_r1, 
       `states_locations`.`type` AS t1_r2, 
       `states_locations`.`name` AS t1_r3 
FROM   `locations` 
LEFT OUTER JOIN `geography` 
ON    `locations`.`id` = `geography`.`msa_id` 
LEFT OUTER JOIN `locations` states_locations 
ON    `states_locations`.`id` = `geography`.`state_id` 
AND    `states_locations`.`type` = 'State' 
WHERE   `states_locations`.`id` = '1' 
AND    `locations`.`type` = 'Msa' 

Je suppose que cela signifie que Rails est en train de charger 1,000s des enregistrements en mémoire, puis, dans Ruby, réduisant les résultats à l'ensemble distinct de MSA (dans ce cas); semble un peu inefficace. En outre, les appels suivants après le retour des résultats variables:

>> msas.first.states.size # incorrect count 
=> 192 
>> msas.first.states.count # correct count 
=> 1 
>> msas.first.states   # incorrect number of State objects 
=> [#<State id: 1, ... >, ..., #<State id: 1, ... >] 
>> msas.first.reload.states 
=> [#<State id: 1, ... >] # correct number of State objects 

Mes questions sont les suivantes:

  1. Pourquoi ne Rails utilise DISTINCT dans la requête, il produit de la trouver appel? Je suppose que c'est parce que je l'ai demandé à: include =>: states. Dois-je utiliser: jointures à la place?
  2. Pourquoi Rails renvoie-t-il des résultats non uniques lors de l'appel de msas.first.states? L'association ayant un: uniq => true ne devrait-elle pas imposer l'unicité des résultats?
  3. Pourquoi ai-je besoin d'utiliser l'alias de table que Rails utilise pour les états "version" de la table d'emplacements, c'est-à-dire: conditions => {: states_locations => {: id => 1}}? Rails ne semble pas comprendre: include =>: states,: conditions => {: states => {: id => 1}}. Existe-t-il un moyen de prédire de manière déterministe l'alias de la table?

Toute idée serait grandement appréciée.

Merci à l'avance, Jason

Répondre

1

Vous avez beaucoup de questions là, permettez-moi de voir si cela vous aidera ...

Vous avez raison rails déclencheront un appel sql pour obtenir tous les résultats , puis active_record devrait filtrer les enregistrements uniques.

Si vous voulez éviter que vous pouvez effectuer les opérations suivantes:

has_many :states, :through => :geographies, :select => "DISTINCT states.*"

Ce post a une analyse intéressante

également avec votre ligne:

msas = Msa.find(:all, :include=>"states", :conditions=>{"states_locations"=>{"id"=>"1"}})

Il est ne pas retourner des résultats uniques parce que vous n'utilisez pas le relat ions que vous avez mis en place.Vous voulez sans doute faire quelque chose comme:

@msas = State.find(state_id).msas

Bonne chance

+0

Salut Jonathan, merci pour la réponse. J'ai essayé d'utiliser l'option: select, cependant, j'ai trouvé que: select est ignoré lors de l'utilisation de: include. Aussi, pour être clair, la deuxième ligne de code que vous avez référencée * est * renvoyant des résultats uniques * dans Ruby * mais pas en SQL. Je jetterai un coup d'œil sur le post de Josh et je verrai si cela apporte un nouvel éclairage. Merci encore. – Jason

+0

La publication de Josh confirme que Rails utilise effectivement Ruby, pas SQL, pour "unique-ify" les résultats dans une association has_many: through => xxx,: uniq => true. Cependant, cela ne permet pas de comprendre pourquoi msas.first.states renvoie des résultats non uniques. On pourrait penser que, puisque Rails est assez intelligent pour que le jeu de résultats original soit unique, quoique dans Ruby, il serait également assez intelligent pour que les associations d'objets dans le jeu de résultats soient également uniques. Peut-être que j'ai besoin de plonger dans le code des associations dans Rails. – Jason

Questions connexes