2008-11-29 20 views
86

que tiene un modelo que se parece a esto:Django equivalente para el recuento y el grupo de

class Category(models.Model): 
    name = models.CharField(max_length=60) 

class Item(models.Model): 
    name = models.CharField(max_length=60) 
    category = models.ForeignKey(Category) 

Quiero select count (sólo el recuento) de artículos para cada categoría, por lo que en SQL sería tan simple como esto:

select category_id, count(id) from item group by category_id 

¿Hay un equivalente de hacer esto "el camino de Django"? ¿O es simple SQL la única opción? Estoy familiarizado con el método count() en Django, sin embargo, no veo cómo cabría el grupo por.

+0

Posible duplicado de [¿Cómo consultar como GROUP BY en django?] (Http://stackoverflow.com/questions/629551/how-to-query-as-group-by-in-django) –

+0

El consenso actual es cerrar por "calidad": Dado que la "calidad" no es medible, simplemente me someto a upvotes. ;-) Probablemente se deba a qué pregunta golpear a las mejores palabras clave de Google novato en el título. –

Respuesta

127

Aquí, como acabo de descubrir, es cómo hacer esto con el Django 1.1 agregación API:

from django.db.models import Count 
theanswer = Item.objects.values('category').annotate(Count('category')) 
+3

como la mayoría de las cosas en Django, nada de esto tiene sentido mirar pero (a diferencia de la mayoría de las cosas en Django) una vez que lo probé, fue increíble: P – jsh

+3

ten en cuenta que necesitas usar 'order_by()' if '' category'' no es el orden predeterminado. (Consulte la respuesta más completa de Daniel.) –

+0

La razón por la que esto funciona es porque ['.annotate()' funciona de forma ligeramente diferente después de '.values ​​()'] (https://docs.djangoproject.com/en/dev/ temas/db/aggregation/# values): "Sin embargo, cuando se utiliza una cláusula values ​​() para restringir las columnas que se devuelven en el conjunto de resultados, el método para evaluar las anotaciones es levemente diferente. En lugar de devolver un resultado anotado para cada Como resultado, en el QuerySet original, los resultados originales se agrupan de acuerdo con las combinaciones únicas de los campos especificados en la cláusula values ​​() ". – mgalgs

58

(actualización:.. La agregación de apoyo ORM completa Ahora se incluye en Django 1.1 Fiel a la continuación de advertencia sobre el uso de las API privadas, el método documentado aquí ya no trabaja en la post-1.1 versiones de Django no he cavado en para averiguar por qué, si está en 1.1 o posterior debe utilizar el aggregation API real de todos modos.)

El soporte de agregación de núcleo ya estaba allí en 1.0; simplemente no está documentado, no es compatible y aún no cuenta con una API amigable. Pero aquí es cómo se puede utilizar de todos modos hasta que llega 1.1 (a su propio riesgo, y en pleno conocimiento de que el atributo query.group_by no es parte de una API pública y podría cambiar):

query_set = Item.objects.extra(select={'count': 'count(1)'}, 
           order_by=['-count']).values('count', 'category') 
query_set.query.group_by = ['category_id'] 

Si a continuación iterar sobre query_set, cada valor devuelto será un diccionario con una clave de "categoría" y una clave de "conteo".

No tiene que hacer un pedido por -count aquí, solo se incluye para demostrar cómo se hace (tiene que hacerse en la llamada .extra(), no en otra parte de la cadena de construcción de queryset). Además, podría decir count (id) en lugar de count (1), pero este último puede ser más eficiente.

Tenga en cuenta también que al establecer .query.group_by, los valores deben ser nombres de columna DB reales ('category_id') no nombres de campo Django ('categoría'). Esto se debe a que está ajustando las consultas internas en un nivel donde todo está en términos de DB, no en términos de Django.

+0

+1 para el método anterior. Incluso si actualmente no es compatible, es esclarecedor por decir lo menos. Increíble, de verdad. –

+0

Eche un vistazo a la API de agregación de Django en https://docs.djangoproject.com/en/dev/topics/db/aggregation/#cheat-sheet. Se pueden hacer otras tareas complejas con él, allí encontrará algunos ejemplos poderosos – serfer2

+0

@serfer2 sí, esos documentos ya están vinculados desde la parte superior de esta respuesta. –

2

¿Cómo es esto? (Aparte lento.)

counts= [ (c, Item.filter(category=c.id).count()) for c in Category.objects.all() ] 

Tiene la ventaja de ser corto, incluso si lo hace traer un montón de registros.


Editar.

La única versión de consulta. Por cierto, esto es a menudo más rápido que SELECCIONAR COUNT (*) en la base de datos. Pruébalo para ver.

counts = defaultdict(int) 
for i in Item.objects.all(): 
    counts[i.category] += 1 
+0

Es bueno y corto, sin embargo, me gustaría evitar tener una llamada de base de datos separada para cada categoría. –

+0

Este es un enfoque realmente bueno para casos simples. Se cae cuando tiene un gran conjunto de datos, y desea pedir + límite (es decir, paginación) de acuerdo con un conteo, sin tirar toneladas de datos innecesarios. –

+0

@Carl Meyer: Es cierto: puede ser perrito para un gran conjunto de datos; Sin embargo, es necesario compararlo para estar seguro de eso. Además, tampoco depende de elementos no compatibles; funciona mientras tanto hasta que se admitan las características no compatibles. –

55

Desde que era un poco confundido acerca de cómo agrupar en Django 1.1 trabajos que pensé que' Elabora aquí cómo exactamente vas sobre usarlo. Primero, para repetir lo que Michael dijo:

Aquí, como acabo de descubrir, es cómo hacer esto con el Django 1.1 agregación API:

from django.db.models import Count 
theanswer = Item.objects.values('category').annotate(Count('category')) 

Tenga en cuenta también que es necesario from django.db.models import Count!

Esto seleccionará solo las categorías y luego agregará una anotación llamada category__count. Según el orden predeterminado, esto puede ser todo lo que necesita, , pero si el pedido predeterminado utiliza un campo que no sea category, esto no funcionará. La razón de esto es que los campos necesarios para realizar el pedido también se seleccionan y hacen que cada fila sea única, por lo que no se agruparán las cosas como se desee. Una forma rápida de solucionar este problema es restablecer el orden:

Item.objects.values('category').annotate(Count('category')).order_by() 

Esto debe producir exactamente los resultados que desea. Para establecer el nombre de la anotación que puede utilizar:

...annotate(mycount = Count('category'))... 

, entonces tendrá una anotación llamada mycount en los resultados.

Todo lo demás sobre la agrupación fue muy sencillo para mí. Asegúrese de consultar el Django aggregation API para obtener información más detallada.

+12

+1 por mencionar el problema 'order_by()'. –

+1

para realizar el mismo conjunto de acciones en el campo de clave foránea Item.objects.values ​​('category__category'). Annotate (Count ('category__category')) order order_by() – Mutant

+0

¿Cómo se determina cuál es el campo de ordenamiento predeterminado? – Bogatyr

Cuestiones relacionadas