2011-01-16 22 views
11

Tengo un blog. En mi página de índice, comparto todas las publicaciones del blog. Para cada publicación de blog, cuento el número de comentarios en esa publicación. Esto conduce a un problema N + 1. Mis consultas mirada como sigue:Rails SQL COUNT N + 1 ineficiencia

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) 

¿Hay una manera de rieles para incluir el COUNT de la misma manera que incluyo los usuarios de SQL (línea 2)?

Respuesta

21

Puede utilizar caché contador: http://guides.rubyonrails.org/association_basics.html#counter_cache

"Con esta declaración, los carriles se mantendrá el valor de caché hasta la fecha, y luego regresar ese valor en respuesta al método tamaño".

class BlogPost < ActiveRecord::Base 
    has_many :blog_comments 
end 

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

Blog post tendría una columna denominada blog_comments_count.

+0

+1 Eso es estupendo! –

+0

¡Esto es exactamente lo que estaba buscando! ¡Gracias! – Mike

3

En general, usted quiere una consulta SQL como:

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

Usted puede utilizar esto para crear un hash de blog_post_id al recuento de los comentarios.

0

Esta es una consulta de ActiveRecord que busca en mi tabla "site_access_log", que consiste en accesos web a un sitio.

Selecciona el campo 'remote_addr' de los primeros 15 registros, junto con el recuento de esa IP, ordenados en orden descendente por el conteo seguido de orden ascendente para los números IP que tienen el mismo número de cuenta.

Uso Postgres, que comprende los números de IPv4, por lo tanto, transfiero el campo a un tipo inet para permitir una ordenación correcta por valor, en lugar de hacerlo por valor ASCII. Si su base de datos no admite valores inet, siempre puede convertir de IP a inet usando Ruby's Socket o la biblioteca IPSocket, y luego ordenar los resultados recuperados.

@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 

El SQL generado se ve así:

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

También puede ver algo como esto:

BlogComment.group('blog_post_id').count 

En puramente rieles Láctea. :)

0

Puede usar la gema dase o una de las técnicas explicadas en that video.

Ejemplo con Dase:

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