Tengo un modelo que se parece a esto:
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 seleccionar el recuento (solo el recuento) de elementos 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
¿Existe un equivalente a hacer esto "al estilo Django"? ¿O es SQL simple la única opción? Estoy familiarizado con el método count () en Django, sin embargo, no veo cómo encajaría group by allí.
Respuestas:
Aquí, como acabo de descubrir, es cómo hacer esto con la API de agregación Django 1.1:
fuente
order_by()
si'category'
no es el pedido predeterminado. (Vea la respuesta más completa de Daniel.).annotate()
funciona de forma ligeramente diferente después de.values()
: "Sin embargo, cuando se usa una cláusula de valores () para restringir las columnas que se devuelven en el conjunto de resultados, el método para evaluar las anotaciones es ligeramente diferente. En lugar de devolver una anotación resultado para cada 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 (). "( Actualización : el soporte completo de agregación de ORM ahora está incluido en Django 1.1 . Fiel a la siguiente advertencia sobre el uso de API privadas, el método documentado aquí ya no funciona en versiones posteriores a la 1.1 de Django. No he investigado para averiguar por qué; si está en 1.1 o posterior, debe usar la API de agregación real de todos modos).
El soporte de agregación central ya estaba allí en 1.0; simplemente no está documentado, no es compatible y aún no tiene una API amigable. Pero así es como puede usarlo de todos modos hasta que llegue 1.1 (bajo su propio riesgo y con pleno conocimiento de que el atributo query.group_by no es parte de una API pública y podría cambiar):
Si luego itera sobre query_set, cada valor devuelto será un diccionario con una clave de "categoría" y una clave de "recuento".
No tiene que ordenar por recuento aquí, eso solo se incluye para demostrar cómo se hace (debe hacerse en la llamada .extra (), no en otra parte de la cadena de construcción del conjunto de consultas). Además, también 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 configurar .query.group_by, los valores deben ser nombres de columna de base de datos reales ('category_id') y no nombres de campo de Django ('categoría'). Esto se debe a que está modificando los aspectos internos de la consulta a un nivel en el que todo está en términos de DB, no en términos de Django.
fuente
Dado que estaba un poco confundido acerca de cómo funciona la agrupación en Django 1.1, pensé en detallar aquí cómo exactamente se usa. Primero, para repetir lo que dijo Michael:
¡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
. Dependiendo del orden predeterminado, esto puede ser todo lo que necesita, pero si el orden predeterminado utiliza un campo diferente acategory
este, no funcionará . La razón de esto es que los campos requeridos para ordenar también están seleccionados y hacen que cada fila sea única, por lo que no obtendrá las cosas agrupadas como lo desea. Una forma rápida de solucionar este problema es restablecer el pedido:Esto debería producir exactamente los resultados que desea. Para establecer el nombre de la anotación, puede usar:
Entonces tendrás 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 la API de agregación de Django para obtener información más detallada.
fuente
¿Cómo es esto? (Aparte de lento).
Tiene la ventaja de ser corto, incluso si obtiene muchas filas.
Editar.
La versión de una consulta. Por cierto, esto suele ser más rápido que SELECT COUNT (*) en la base de datos. Pruébelo para ver.
fuente