2011-02-23 11 views
6

Moin,Rails 3: Diferencia entre Relation.count y Relation.all.count

i topé con un inconsistencia en ActiveRecord.
Intenté obtener todas las combinaciones de valores usadas en dos columnas de una tabla grande. primera idea:

SELECT DISTINCT col1, col2 FROM table 

imaginar una aplicación rieles que organiza comidas como modelo y cada comida has_many :noodles Cada fideos tiene los atributos (y por tanto las columnas de las tablas de base de datos) y colorshape. Mi objetivo es obtener el número de todas las combinaciones presentes de color y shape para una sola comida.

Desde AR no proporciona un método "distinta" Solía ​​

my_meal.noodles.select("distinct color, shape") 

y dieron (en la consola de carriles stdout) una salida de seis líneas de 8 objetos de fideos chinos (respectivamente sus representaciones de cadena). Pero:

>> my_meal.noodles.select("distinct color, shape").count 
=> 1606 

De hecho my_meal contiene 1606 fideos. Si convierto la relación a una matriz y obtengo su tamaño o uso .all.count el resultado es correcto.

Entonces mi pregunta es, ¿por qué AR saca 8 Objetos pero cuenta todas las líneas DB?

Un problema similar seems to be mentioned here pero no se da respuesta.

Gracias y un saludo, Tim

+0

Se puede investigar lo que se está generando SQL y publicar aquí? Esto se almacenará en 'log/development.log' y generalmente es muy revelador. – tadman

+0

Para mirar más profundo intente 'to_sql':' my_meal.noodles.select ("distinct color, shape"). Count.to_sql'. – fl00r

+0

En realidad, acabo de intentar con la misma prueba y en mi caso todo funciona bien. Count ha hecho su trabajo perfecto – fl00r

Respuesta

12

bien, gracias a tadman para mí empujando en la dirección correcta.

He cavado algo más profundo (especialmente en los archivos de registro) y lo que encontré es un poco extraño.

El problema fue causado por el número de columnas seleccionadas. Si uno selecciona sólo una columna y cuenta el resultado

my_meal.noodles.select("distinct color").count 

ActiveRecord crea la siguiente instrucción SQL:

SELECT COUNT(distinct color) AS count_id FROM "NOODLES" WHERE ("NOODLES".meal_id = 295) 

En caso de que uno selecciona dos o más columnas y se aplica count a ella

my_meal.noodles.select("distinct color, shape").count 

ActiveRecord se olvida de esa cláusula de selección y crea:

SELECT COUNT(*) AS count_id FROM "NOODLES" WHERE ("NOODLES".meal_id = 295) 

Esto puede ser correcto, ya que (SQL) COUNT permite solo una o menos columnas como parámetros. Añadir un group antes de la count y todo está bien:

my_meal.noodles.select("distinct color, shape").group("color, shape").count 

SELECT COUNT(*) AS count_all, color, shape AS color_shape FROM "NOODLES" WHERE ("NOODLES".meal_id = 295) GROUP BY color, shape 

Aparte de esto AS color_shape es exactamente lo que esperaba. PERO...solamente devuelve esto:

>> my_meal.noodles.select("distinct color, shape").group("color, shape").count 
=> {star=>309, circle=>111, spaghetti=>189, square=>194, triangle=>179, bowtie=>301, shell=>93, letter=>230} 

>> my_meal.noodles.select("distinct color, shape").group("color, shape").count.class 
=> ActiveSupport::OrderedHash 

Este valor de retorno extraño es (aparte de orden que depende de la DB) idéntico con el valor del resultado y el retorno de

my_meal.noodles.group("shape").count 

Conclusión:
Como pointed out here hay todavía hay una brecha entre las relaciones (pueden ser relaciones matemáticas o areales) y ActiveRecord :: Relations.
Veo las ventajas de presionar el resultado en los patrones de un modelo tan a menudo como sea posible (al menos en el contexto de una aplicación de Rails).
Sin embargo, las relaciones reales no son el resultado de la combinación de varias operaciones, sino el resultado de la concatenación de esas operaciones. En general, la capacidad de ocultación de ActiveRecord :: Relations es una gran cosa, pero hay algunas decisiones de diseño que no puedo seguir.
Si no puede confiar en la seguridad de que cada acción devuelve una nueva relación para trabajar, pierde gran parte de su atractivo inicial.

En cuanto a la solución de mi problema, voy a utilizar la solución mencionada group y algún tipo de solución sucia para la operación de conteo:

my_meal.noodles.select("distinct color, shape").group("color, shape").all.count 

Esto comprime los resultados a un mínimo aceptable antes de tirar de ellas fuera de la base de datos y crear objetos caros solo para contarlos. Alternativamente, uno podría usar una consulta SQL manuscrita, pero ¿por qué tener un Rails y no usarlo, eh? ;-)

Gracias por su ayuda,
Tim

Cuestiones relacionadas