2011-01-16 8 views
11

Mam bloga. Na mojej stronie indeksu wciągam wszystkie posty na blogu. W przypadku każdego posta na blogu liczę liczbę komentarzy do tego wpisu. Prowadzi to do problemu N + 1. Moje pytania wyglądają następująco:Rails SQL COUNT N + 1 nieefektywność

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) 

Czy istnieje sposób w Rails włączenie liczy się w ten sam sposób I obejmują użytkowników (linia 2) SQL?

Odpowiedz

21

Można użyć licznika cache: http://guides.rubyonrails.org/association_basics.html#counter_cache

„Dzięki tej deklaracji Railsy utrzymać wartość cache na bieżąco, a następnie powrócić tę wartość w odpowiedzi na metodę size.”

class BlogPost < ActiveRecord::Base 
    has_many :blog_comments 
end 

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

Wpis na blogu miałby kolumnę o nazwie blog_comments_count.

+0

+1 To miłe! –

+0

To jest dokładnie to, czego szukałem! Dzięki! – Mike

3

W ogóle, chcesz zapytań SQL jak:

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

Można to wykorzystać, aby stworzyć skrót od blog_post_id do liczby komentarzy.

0

To jest zapytanie ActiveRecord, które przeszukuje moją tabelę "site_access_log", składającą się z dostępu do strony internetowej.

Wybiera pole "remote_addr" z pierwszych 15 rekordów, wraz z liczbą dla tego adresu IP, posortowaną w porządku malejącym według liczby, po której następuje kolejność rosnąca dla numerów IP, które mają ten sam numer_liczby.

Używam PostgreSQL, który rozumie numery IPv4, więc rzutuję pole na typ inet, aby umożliwić prawidłowe sortowanie według wartości, a nie wartości ASCII. Jeśli twoja baza danych nie obsługuje wartości inet, możesz zawsze przekonwertować z IP na inet przy użyciu biblioteki Socket lub IPSocket Ruby, a następnie posortuj pobrane wyniki.

@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 

Wygenerowany SQL wygląda następująco:

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

Można również zobaczyć coś takiego:

BlogComment.group('blog_post_id').count 

W czysto Rails Way. :)

0

Możesz użyć klejnotu dase lub jednej z technik wyjaśnionych w that video.

Przykład z Dase:

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