2012-01-05 2 views
45

У меня есть простая модель, как это:Джанго: Группа по дате (день, месяц, год)

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

И я хочу, чтобы вывести разбивку месяца за месяцем:

  • Сколько продаж были в течение месяца (COUNT)
  • Объединенное значение (SUM)

Я m не уверен, что лучший способ атаковать это. Я видел некоторые довольно страшные лишние запросы, но мой простой ум говорит мне, что мне может быть лучше просто перебирать числа, начиная с произвольного начала года/месяца и подсчета до тех пор, пока я не дойду до текущего месяца, выбрасывая простые фильтрации запросов за этот месяц. Больше работы с базами данных - меньше стресса разработчиков!

Что для вас больше всего подходит? Есть ли хороший способ, которым я могу отменить быстрый стол данных? Или мой грязный метод, вероятно, лучшая идея?

Я использую Django 1.3. Не уверен, что недавно они добавили еще один способ: GROUP_BY.

+0

Это дубликат http://stackoverflow.com/questions/8596856/django-get-distinct-dates-from-timestamp и http://stackoverflow.com/questions/1236865/grouping-dates -in-django/8597940 # 8597940 – tback

ответ

148

Джанго 1,10 и выше

Джанго документации перечислены extra в осуждается скоро. (Спасибо, что указали, что вне @seddonym, @ Lucas03). Я открыл ticket, и это решение, предоставленное 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 

Старые версии

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') 

редактирует

  • Добавлено подсчитывать
  • Добавлена ​​информация для Джанго> = 1,10
+16

О да, это намного больше смысла, чем моя hacktastrophe. Есть несколько моментов. – Oli

+9

+1 для hacktastrophe – tback

+0

Я не думаю, что есть простой простой способ конвертировать дату в правильный 'datetime.date'? Либо в шаблоне, либо как часть запроса? В настоящее время я передаю этот результат через список, чтобы сортировать дату. – Oli

0

Вот мой грязный метод. Грязно.

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 

Там вполне может быть лучшим способом зацикливание лет/месяцев, но это не совсем то, что я забочусь о :)

+0

BTW Это будет нормально работать, но вы знаете, что цикл за месяцы также не отличная идея. Что, если кто-то захочет сделать это в День месяца, тогда этот цикл будет проходить через 30-31 дней. в противном случае его рабочий тон –

3

Другой подход заключается в использовании ExtractMonth. Я столкнулся с трудностями при использовании TruncMonth из-за возврата только одного значения года datetime. Например, возвращались только месяцы 2009 года.ExtractMonth устранили эту проблему совершенно и может быть использован, как показано ниже:

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

По месяцу:

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

по годам:

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

Днем:

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

Не забудьте импортировать COUNT

from django.db.models import * 

Для Джанго < 1,10

8

Просто небольшое дополнение к @tback ответ: Это не работает для меня с Django 1.10. 6 и postgres. Я добавил order_by() в конце, чтобы исправить его.

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 ... не похож на хороший дизайн, очень очень умные парни джанго, так что это на самом деле. – Williams