2012-01-05 21 views
45

Tengo un modelo simple como esto:Django: Grupo por fecha (día, mes, año)

class Order(models.Model): 
    created = model.DateTimeField(auto_now_add=True) 
    total = models.IntegerField() # monetary value 

y quiero salida de un desglose de mes a mes de:

  • cuántas ventas que había en un mes (COUNT)
  • El valor combinado (SUM)

I' No estoy seguro de cuál es la mejor manera de atacar esto. He visto algunas consultas extra-selectas de aspecto bastante aterrador, pero mi mente simple me dice que podría estar mejor simplemente iterando números, comenzando desde un año/mes de inicio arbitrario y contando hasta llegar al mes actual, descartando simples consultas de filtrado para ese mes. Más trabajo en la base de datos, ¡menos estrés para el desarrollador!

¿Qué tiene más sentido para usted? ¿Hay alguna buena manera de que pueda extraer una tabla rápida de datos? ¿O es mi método sucio probablemente la mejor idea?

Estoy usando Django 1.3. No estoy seguro de si han agregado una forma más agradable al GROUP_BY recientemente.

+0

Este es un duplicado de http://stackoverflow.com/questions/8596856/django-get-distinct-dates-from-timestamp y http://stackoverflow.com/questions/1236865/grouping-dates -in-django/8597940 # 8597940 – tback

Respuesta

148

Django 1,10 y por encima de

documentación de Django enumera extra como obsoleto pronto. (Gracias por señalar eso @seddonym, @ Lucas03). Abrí un ticket y esta es la solución que proporcionó jarshwah.

from django.db.models.functions import TruncMonth 
Sales.objects 
    .annotate(month=TruncMonth('timestamp')) # Truncate to month and add to select list 
    .values('month')       # Group By month 
    .annotate(c=Count('id'))     # Select the count of the grouping 
    .values('month', 'c')      # (might be redundant, haven't tested) select month and count 

Las versiones más antiguas

from django.db import connection 
from django.db.models import Sum, Count 
truncate_date = connection.ops.date_trunc_sql('month', 'created') 
qs = Order.objects.extra({'month':truncate_date}) 
report = qs.values('month').annotate(Sum('total'), Count('pk')).order_by('month') 

ediciones

  • Agregado cuentan
  • ha añadido información de Django> = 1,10
+16

Oh sí, eso tiene mucho más sentido que mi hacktastrophe. Tener algunos puntos – Oli

+9

+1 para el hack catastrófico – tback

+0

Supongo que no hay una forma sencilla y bonita de convertir la fecha en una 'datetime.date' adecuada? Ya sea en plantilla o como parte de la consulta? Actualmente estoy pasando esta salida a través de una lista de comprensión para ordenar la fecha. – Oli

0

Aquí está mi método sucio. Está sucio.

import datetime, decimal 
from django.db.models import Count, Sum 
from account.models import Order 
d = [] 

# arbitrary starting dates 
year = 2011 
month = 12 

cyear = datetime.date.today().year 
cmonth = datetime.date.today().month 

while year <= cyear: 
    while (year < cyear and month <= 12) or (year == cyear and month <= cmonth): 
     sales = Order.objects.filter(created__year=year, created__month=month).aggregate(Count('total'), Sum('total')) 
     d.append({ 
      'year': year, 
      'month': month, 
      'sales': sales['total__count'] or 0, 
      'value': decimal.Decimal(sales['total__sum'] or 0), 
     }) 
     month += 1 
    month = 1 
    year += 1 

Muy bien puede haber una mejor forma de bucle años/meses, pero eso no es realmente lo que me importa :)

+0

BTW Funcionará bien, pero usted sabe que el bucle durante meses tampoco es una gran idea. ¿Qué pasa si alguien quiere hacerlo en el día de un mes luego este ciclo se repetirá en 30-31 días. de lo contrario, funciona bien –

3

Otro enfoque es usar ExtractMonth. Tuve problemas al usar TruncMonth debido a que solo se devolvió un valor de fecha y hora del año. Por ejemplo, solo se devolvieron los meses de 2009.ExtractMonth fijado este problema perfectamente y se puede utilizar, como a continuación:

from django.db.models.functions import ExtractMonth 
Sales.objects 
    .annotate(month=ExtractMonth('timestamp')) 
    .values('month')       
    .annotate(count=Count('id'))     
    .values('month', 'count') 
-1

Por mes:

Order.objects.filter().extra({'month':"Extract(month from created)"}).values_list('month').annotate(Count('id')) 

Por Año:

Order.objects.filter().extra({'year':"Extract(year from created)"}).values_list('year').annotate(Count('id')) 

De día:

Order.objects.filter().extra({'day':"Extract(day from created)"}).values_list('day').annotate(Count('id')) 

No se olvide de importar Conde

from django.db.models import * 

Para Django < 1,10

8

Sólo una pequeña adición a @tback respuesta: No funcionó para mí con Django 1.10. 6 y postgres. Añadí order_by() al final para arreglarlo.

from django.db.models.functions import TruncMonth 
Sales.objects 
    .annotate(month=TruncMonth('timestamp')) # Truncate to month and add to select list 
    .values('month')       # Group By month 
    .annotate(c=Count('id'))     # Select the count of the grouping 
    .order_by() 
+0

yup: https://docs.djangoproject.com/en/1.11/topics/db/aggregation/#interaction-with-default-ordering-or-order-by ... no se siente como un buen diseño, pero ellos ' Son muy inteligentes esos muchachos django, así que realmente es así. – Williams

Cuestiones relacionadas