2011-01-16 6 views
11

Ich habe einen Blog. Auf meiner Indexseite ziehe ich alle Blogposts ein. Für jeden Blogeintrag zähle ich die Anzahl der Kommentare zu diesem Beitrag. Dies führt zu einem N + 1 Problem. Meine Anfragen wie folgt aussehen:Rails SQL COUNT N + 1 Ineffizienz

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) 

Gibt es eine Möglichkeit in Rails die COUNT in der gleichen Art, wie ich die Benutzer (SQL Linie 2) sind zu enthalten?

Antwort

21

Sie Zähler Cache verwenden können: http://guides.rubyonrails.org/association_basics.html#counter_cache

„Mit dieser Erklärung Rails wird der Cache-Wert dem Laufenden halten, und dann diesen Wert als Reaktion auf die Größe Methode zurückzukehren.“

class BlogPost < ActiveRecord::Base 
    has_many :blog_comments 
end 

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

Blogpost würde eine Spalte namens blog_comments_count haben.

+0

+1 Das ist ordentlich! –

+0

Genau das habe ich gesucht! Vielen Dank! – Mike

3

In der Regel wollen Sie eine SQL-Abfrage wie:

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

Hiermit können Sie einen Hash von blog_post_id auf die Anzahl der Kommentare erstellen.

0

Dies ist eine ActiveRecord-Abfrage, die meine Tabelle "site_access_log" durchsucht, die aus Webzugriffen auf eine Site besteht.

Er wählt das ‚remote_addr‘ Feld aus den ersten 15 Aufzeichnungen, zusammen mit der Zählung für diese IP, in absteigender Reihenfolge nach der durch aufsteigende Reihenfolge, die die gleichen count_number haben für IP-Nummern gefolgt Zahl sortiert.

Ich benutze Postgres, die IPv4-Nummern versteht, so dass ich das Feld auf einen inet Typ umwandeln, um korrekte Sortierung nach Wert statt nach ASCII-Wert zu ermöglichen. Wenn Ihre Datenbank keine inet-Werte unterstützt, können Sie immer mithilfe von Rubys Socket- oder IPSocket-Bibliothek von IP zu inet konvertieren und dann die abgerufenen Ergebnisse sortieren.

@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 

Die generierten SQL wie folgt aussieht:

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

Sie auch etwas sehen kann:

BlogComment.group('blog_post_id').count 

In rein Rails Way. :)

0

Sie können dase gem oder eine der in that video erläuterten Techniken verwenden.

Beispiel mit Dase:

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