2012-09-19 4 views
1

J'ai des modèles User et Gift. Un utilisateur peut envoyer des cadeaux à d'autres utilisateurs. J'ai une table relationnelle qui me dit quels utilisateurs ont reçu un cadeau. D'un autre côté, un utilisateur appartient à un School, qui peut être gratuit ou payant.Rendre cette requête ActiveRecord plus efficace?

Je veux connaître le nombre d'utilisateurs qui ont reçu un cadeau la semaine dernière pour un type d'école spécifique (ceci est gratuit ou payant).

je peux faire:

Gift.joins(:schools).where("created_at >= ? AND schools.free_school = ?", Time.now.beggining_of_week, true).collect(&:gift_recipients).flatten.uniq.count. 

Ou, je veux savoir combien d'utilisateurs ont envoyé des cadeaux de la dernière semaine. Cela fonctionne:

Gift.joins(:schools).where("created_at >= ? AND schools.free_school = ?", Time.now.beggining_of_week, true).collect(&:user_id).uniq.count. 

Si je veux savoir combien d'utilisateurs ont envoyé ou reçu un cadeau la semaine dernière que je peux faire:

(Gift.joins(:schools).where("created_at >= ? AND schools.free_school = ?", Time.now.beggining_of_week, true).collect(&:gift_recipients).flatten + Gift.joins(:schools).where("created_at >= ? AND schools.free_school = ?", Time.now.beggining_of_week, true).collect(&:user_id)).uniq.count 

Tout cela fonctionne très bien, mais si la base de données est assez grand C'est vraiment lent. Avez-vous des suggestions pour le rendre plus efficace, peut-être en utilisant du SQL brut si nécessaire?

"gifts" 
    user_id (integer) 
    school_id (integer) 
    created_at (datetime) 
    updated_at (datetime) 
"gift_recipients" is a table like 
    gift_id | recipient_id, 
+0

Veuillez publier le/les modèles de l'utilisateur, du cadeau et de l'école. – Winfield

Répondre

2

Vous ne voulez pas le faire en utilisant la collecte(), qui est le chargement de tous les résultats en mémoire et de les filtrer dans un tableau de ActiveRecords. Ceci est lent et dangereux, car il pourrait fuir/utiliser toute la mémoire disponible, en fonction de la taille des données par rapport à votre serveur. Une fois que vous postez votre schéma, je peux vous aider à interroger/agréger ceci dans SQL, ce qui est la bonne façon de le faire.

Par exemple, au lieu de:

Gift.joins(:schools).where("created_at >= ? AND schools.free_school = ?", Time.now.beggining_of_week, true).collect(&:user_id).uniq.count 

Vous devez utiliser:

Gift.joins(:schools).where("created_at >= ? AND schools.free_school = ?", Time.now.beggining_of_week, true).count('distinct user_id') 

... qui comptera les user_ids distincts dans SQL et retourner le résultat au lieu de retourner tous les objets et en les comptant en mémoire.

+0

bon .. "cadeaux" user_id (entier) school_id (entier) created_at (datetime) updated_at (datetime) "gift_recipients" est une table comme gift_id | recipient_id, avez-vous besoin de plus d'informations? Je vous remercie! – Johnny

+0

c'est génial! Je ne savais pas que vous pouvez passer un paramètre comme ça à compter .. Où puis-je trouver la documentation de ces requêtes? – Johnny

+0

http://api.rubyonrails.org/ Rechercher count() – Winfield

0

J'ai vu ce vieux poste et je voulais faire quelques commentaires: Comme Winfield dit

Gift.joins(:school).where("created_at >= ? AND schools.free_school = ?", Time.now.beggining_of_week, true).count('distinct user_id') 

est une bonne façon de le faire. Je ferais

Gift.joins(:school).count('distinct user_id', :conditions => ["gifts.created_at >= ? AND free_school = ?", Time.now.beginning_of_week, true]) 

mais juste parce que cela est plus agréable à mes yeux, une chose personnelle, vous pouvez vérifier que les deux produit exactement la même requête SQL. Notez que faut écrire

gifts.created_at 

pour éviter toute ambiguïté, car les deux tables ont une colonne avec ce nom, dans le cas du nom de la colonne

free_school 

il n'y a pas d'ambiguïté que ce n'est pas une colonne nom dans les tableaux de cadeaux. Pour la première requête je faisais

Gift.joins(:school).where("created_at >= ? AND schools.free_school = ?", Time.now.beginning_of_week, true).collect(&:user_id).uniq.count 

ce qui est gênant.Cela fonctionne mieux

Gift.joins(:school).count("distinct user_id", :conditions => ["gifts.created_at >= ? AND free_school = ?", Time.now.beginning_of_week, true]) 

qui permettent d'éviter le problème d'apporter des cadeaux à la mémoire et les filtrer avec rubis.

Jusqu'à présent, il n'y a rien de nouveau. Le point clé ici est que mon problème était de calculer le nombre d'utilisateurs qui ont envoyé ou reçu un cadeau au cours de la dernière semaine. Pour cela, je suis venu avec les éléments suivants

senders_ids = Gift.joins(:school).find(:all, :select => 'distinct user_id', :conditions => ['gifts.created_at >= ? AND free_school = ?', Time.now.beginning_of_week, type]).map {|g| g.user_id} 
    receivers_ids = Gift.joins(:school).find(:all, :select => 'distinct rec.recipient_id', :conditions => ['gifts.created_at >= ? AND free_school = ?', Time.now.beginning_of_week, type], :joins => "INNER JOIN gifts_recipients as rec on rec.gift_id = gifts.id").map {|g| g.recipient_id} 
    (senders_ids + receivers_ids).uniq.count 

Je suis assez sûr qu'il existe une meilleure façon de le faire, je veux dire, revenir exactement ce numéro dans une seule requête SQL, mais au moins les résultats sont des tableaux de objets contenant uniquement l'identifiant (recipient_id pour le cas des récepteurs), ne mettant pas tous les objets en mémoire. Eh bien, c'est juste l'espoir d'être utile pour quelqu'un de nouvelles requêtes SQL via des rails comme moi :).

Questions connexes