У меня есть такая простая модель:
class Order(models.Model):
created = model.DateTimeField(auto_now_add=True)
total = models.IntegerField() # monetary value
И я хочу вывести разбивку по месяцам:
- Сколько продаж было за месяц (
COUNT
) - Комбинированное значение (
SUM
)
Я не уверен, как это лучше всего атаковать. Я видел несколько довольно пугающих запросов с дополнительным выбором, но мой простой разум подсказывает мне, что мне может быть лучше просто перебирать числа, начиная с произвольного года / месяца начала и считая до тех пор, пока я не дойду до текущего месяца, выбрасывая простые фильтрация запросов за этот месяц. Больше работы с базами данных - меньше стресса разработчика!
Что для вас наиболее важно? Есть ли хороший способ вернуть быструю таблицу данных? Или мой грязный метод, вероятно, лучшая идея?
Я использую Django 1.3. Не уверен, что GROUP_BY
недавно они добавили более приятный способ .
Ответы:
Django 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')
Правки
источник
>>> qs.extra({'month':td}).values('month').annotate(Sum('total')) [{'total__sum': Decimal('1234.56'), 'month': datetime.datetime(2011, 12, 1, 0, 0)}]
'{}.timestamp'.format(model._meta.db_table)
USE_TZ
установленTrue
, две версии не совсем эквивалентны. Версия, использующаяTruncMonth
, преобразует метку времени в часовой пояс, указанный вTIME_ZONE
настройке, перед усечением, в то время как версия, использующая,date_trunc_sql
будет усекать необработанную метку времени UTC в базе данных.Небольшое дополнение к ответу @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()
источник
TruncDate
позволяет группировать по дате (дню месяца)Другой подход - использовать
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')
источник
metrics = { 'sales_sum': Sum('total'), } queryset = Order.objects.values('created__month') .annotate(**metrics) .order_by('created__month')
Это
queryset
список заказов, одна строка в месяц, объединяющий сумму продаж:sales_sum
@Django 2.1.7
источник
Вот мой грязный метод. Грязно.
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
Возможно, есть лучший способ зацикливать годы / месяцы, но это не совсем то, что меня волнует :)
источник
Вот как вы можете группировать данные по произвольным периодам времени:
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))
источник
у меня есть таблица заказов в моей базе данных. я буду считать заказы в месяц за последние 3 месяца
from itertools import groupby from dateutil.relativedelta import relativedelta date_range = datetime.now()-relativedelta(months=3) aggs =Orders.objects.filter(created_at=date_range)\ .extra({'date_created':"date(created_at)"}).values('date_created') for key , group in groupby(aggs): print(key,len(list(group)))
created_at - поле даты и времени. с помощью дополнительной функции то, что сделано, берет дату из значений datetime. при использовании datetime мы можем не получить правильный счет, потому что объекты создаются в разное время дня.
Цикл for напечатает дату и количество отсчетов.
источник
По месяцам:
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
Для django <1.10
источник