2011-01-16 2 views
11

J'ai un blog. Sur ma page d'index, j'insère tous les articles du blog. Pour chaque article de blog, je compte le nombre de commentaires sur ce post. Cela conduit à un problème N + 1. Mes questions se présenter comme suit:Rails SQL COUNT N + 1 inefficacité

SELECT "blog_posts".* FROM "blog_posts" WHERE ("blog_posts"."published" = 't') ORDER BY published_at DESC 
SELECT "users".* FROM "users" WHERE ("users"."id" IN (1, 2, 3)) 
SELECT COUNT(*) FROM "blog_comments" WHERE ("blog_comments".blog_post_id = 10) 
SELECT COUNT(*) FROM "blog_comments" WHERE ("blog_comments".blog_post_id = 9) 
SELECT COUNT(*) FROM "blog_comments" WHERE ("blog_comments".blog_post_id = 8) 
SELECT COUNT(*) FROM "blog_comments" WHERE ("blog_comments".blog_post_id = 2) 
SELECT COUNT(*) FROM "blog_comments" WHERE ("blog_comments".blog_post_id = 7) 

Est-il possible dans Rails pour inclure le COUNT de la même manière que je les utilisateurs (inclure la ligne de SQL 2)?

Répondre

21

Vous pouvez utiliser le cache du compteur: http://guides.rubyonrails.org/association_basics.html#counter_cache

« Avec cette déclaration, Rails maintenir la valeur de cache à jour, puis retourner cette valeur en réponse à la méthode de la taille. »

class BlogPost < ActiveRecord::Base 
    has_many :blog_comments 
end 

class BlogComment < ActiveRecord::Base 
    belongs_to :blog_post, :counter_cache => true 
end 

Blog post aurait une colonne nommée blog_comments_count.

+0

+1 C'est génial! –

+0

C'est exactement ce que je cherchais! Merci! – Mike

3

En général, vous voulez une requête SQL comme:

SELECT COUNT(*), blog_post_id 
    FROM blog_comments 
GROUP BY blog_post_id; 

Vous pouvez l'utiliser pour créer un hachage de blog_post_id au compte des commentaires.

0

Il s'agit d'une requête ActiveRecord qui recherche ma table "site_access_log", constituée d'accès Web à un site.

Il sélectionne le champ 'remote_addr' parmi les 15 premiers enregistrements, ainsi que le compte de cette adresse IP, triés par ordre décroissant par le nombre suivi de l'ordre croissant pour les numéros IP ayant le même nombre count_number. J'utilise Postgres, qui comprend les numéros IPv4, donc je jette le champ au type inet pour permettre un tri correct par valeur, plutôt que par valeur ASCII. Si votre base de données ne prend pas en charge les valeurs inet, vous pouvez toujours convertir IP en inet en utilisant la bibliothèque Socket ou IPSocket de Ruby, puis trier les résultats récupérés.

@remote_addr_results = SiteAccessLog.all(
    :select  => 'remote_addr, count(remote_addr) as remote_addr_count', 
    :group  => :remote_addr, 
    :order  => 'remote_addr_count desc, cast(remote_addr as inet)', 
    :limit  => 15 
) 
puts @remote_addr_results.map{ |r| r.remote_addr_count << ' : ' << r.remote_addr } 

>> 985 : 68.228.61.183 
>> 572 : 205.203.134.197 
>> 500 : 68.32.220.153 
>> 460 : 72.200.64.128 
>> 281 : 24.121.196.194 
>> 262 : 99.91.9.155 
>> 241 : 68.99.237.178 
>> 213 : 68.99.119.137 
>> 208 : 70.167.157.162 
>> 204 : 201.165.6.2 
>> 164 : 72.201.233.147 
>> 155 : 75.245.177.106 
>> 150 : 97.123.246.154 
>> 149 : 201.165.190.98 
>> 145 : 74.37.165.220 

Le SQL généré ressemble:

SELECT remote_addr, count(remote_addr) as remote_addr_count                  
FROM "site_access_logs"                           
GROUP BY remote_addr                            
ORDER BY remote_addr_count desc, cast(remote_addr as inet)                   
LIMIT 15 
2

Vous pouvez aussi voir quelque chose comme ceci:

BlogComment.group('blog_post_id').count 

Dans purement Way Rails. :)

0

Vous pouvez utiliser dase gem ou l'une des techniques expliquées dans that video.

Exemple avec Dase:

Author.includes_count_of(:articles).each do |author| puts "#{author.name} has #{author.articles_count} articles" end