Django: группа по дате (день, месяц, год)
У меня есть простая модель:
class Order(models.Model):
created = model.DateTimeField(auto_now_add=True)
total = models.IntegerField() # monetary value
И я хочу вывести разбивку по месяцам:
- Сколько продаж было за месяц (
COUNT
)
- Комбинированное значение (
SUM
)
Я не уверен, что лучший способ атаковать это. Я видел некоторые довольно страшные лишние запросы, но мой простой ум говорит мне, что мне может быть лучше просто перебирать числа, начиная с произвольного начала года/месяца и подсчета до тех пор, пока я не дойду до текущего месяца, выбрасывая простые фильтрации запросов за этот месяц. Больше работы с базами данных - меньше стресса для разработчиков!
Что для вас наиболее важно? Есть ли хороший способ, которым я могу отменить быстрый стол данных? Или мой грязный метод, вероятно, лучшая идея?
Я использую Django 1.3. Не уверен, что недавно они добавили лучший способ GROUP_BY
.
Ответы
Ответ 1
Джанго 1.10 и выше
Документация Django перечисляет extra
как устаревшие в ближайшее время. (Спасибо, что указали на @seddonym, @Lucas03). Я открыл билет, и это решение, которое предоставил jarshwah.
from django.db.models.functions import TruncMonth
from django.db.models import Count
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')
Правки
- Добавлен счет
- Добавлена информация для django> = 1.10
Ответ 2
Просто небольшое дополнение к ответу @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()
Ответ 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')
Ответ 4
Вот мой грязный метод. Это грязно.
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
Там может быть лучший способ зацикливания лет/месяцев, но это не совсем то, о чем я забочусь:)
Ответ 5
Вот как вы можете сгруппировать данные по произвольным периодам времени:
from django.db.models import F, Sum
from django.db.models.functions import Extract, Cast
period_length = 60*15 # 15 minutes
# Annotate each order with a "period"
qs = Order.objects.annotate(
timestamp=Cast(Extract('date', 'epoch'), models.IntegerField()),
period=(F('timestamp') / period_length) * period_length,
)
# Group orders by period & calculate sum of totals for each period
qs.values('period').annotate(total=Sum(field))
Ответ 6
По месяцам:
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'))
Не забудьте импортировать граф
from django.db.models import Count
Для Джанго <1,10