2009-08-05 4 views
2

J'ai un modèle utilisateur et un modèle cd connecté via une table de jointure 'cds_users'. J'essaye de retourner un hachage d'utilisateurs plus chaque CD qu'ils ont en commun avec l'utilisateur original.Le moyen le plus rapide de rechercher deux modèles connectés via une table de joint dans des rails ayant un grand jeu de données

@user.users_with_similar_cds(1,4,5) 
# => {:bob => [4], :tim => [1,5]} 

Y a-t-il un moyen meilleur/plus rapide de le faire sans trop boucler? Peut-être un moyen plus direct?

def users_with_similar_cds(*args) 
    similar_users = {} 
    Cd.find(:all, :conditions => ["cds.id IN (?)", args]).each do |cd| 
    cd.users.find(:all, :conditions => ["users.id != ?", self.id]).each do |user| 
     if similar_users[user.name] 
     similar_users[user.name] << cd.id 
     else 
     similar_users[user.name] = [cd.id] 
     end 
    end 
    end 
    similar_users 
end 

[plus]

Prendre la jointure idée modèle, je pourrais faire quelque chose comme ça. Je vais appeler le modèle «joint».

def users_with_similar_cds(*args) 
    similar_users = {} 
    Joined.find(:all, :conditions => ["user_id != ? AND cd_id IN (?)", self.id, args]).each do |joined| 
    if similar_users[joined.user_id] 
     similar_users[joined.user_id] << cd_id 
    else 
     similar_users[joined.user_id] = [cd_id] 
    end 
    end 
    similar_users 
end 

Serait-ce le moyen le plus rapide pour les grands ensembles de données?

Répondre

0

Yap, vous pouvez, avec seulement 2 Selects:

Faire un modèle de table de jointure nommée CdUser (utilisation has_many .. par)

# first select 
cd_users = CdUser.find(:all, :conditions => ["cd_id IN (?)", args]) 
cd_users_by_cd_id = cd_users.group_by{|cd_user| cd_user.cd_id } 

users_ids = cd_users.collect{|cd_user| cd_user.user_id }.uniq 
#second select 
users_by_id = User.find_all_by_id(users_ids).group_by{|user| user.id} 

cd_users_by_cd_id.each{|cd_id, cd_user_hash| 
    result_hash[:cd_id] = cd_users_hash.collect{|cd_user| users_by_id[cd_user.user_id]} 
} 

Ceci est juste un ideea, ne sont pas testés:)

pour votre information: http://railscasts.com/episodes/47-two-many-to-many

1

Vous pouvez utiliser find_by_sql sur le modèle des utilisateurs et active Record ajouterez dynamiquement des méthodes pour tout champ supplémentaire s renvoyé par la requête. Par exemple:

similar_cds = Hash.new 
peeps = Users.find_by_sql("SELECT Users.*, group_concat(Cds_Users.cd_id) as cd_ids FROM Users, Cds_Users GROUP BY Users.id") 
peeps.each { |p| similar_cds[p.name] = p.cd_ids.split(',') } 

Je ne l'ai pas testé ce code, et cette requête particulière ne fonctionnera que si votre base de données prend en charge group_concat (par exemple, MySQL, les versions récentes d'Oracle, etc), mais vous devriez être en mesure de le faire quelque chose de similaire avec n'importe quelle base de données que vous utilisez.

+0

Dans mes tests, cela n'a pas été plus rapide. Peut-être que sur des ensembles de données plus grands ce serait. – MediaJunkie

+0

Hrm. Il semble probable que ce serait - dans le code original, vous faites une requête pour obtenir les CD, puis une autre requête pour chaque utilisateur trouvé. La variante find_by_sql fait tout le shebang dans une requête, bien qu'il s'agisse d'une jointure avec un group_concat. Si votre jeu de données est petit, le fait que les requêtes sur une seule table soient plus rapides que les jointures pourrait même faire l'affaire, mais une fois que vous aurez obtenu beaucoup d'utilisateurs similaires, le surcoût de toutes ces requêtes vous embêtera. Vous pouvez également vérifier que vous avez des index sur toutes les colonnes foo_id. –

Questions connexes