Mam taki prosty model:
class Order(models.Model):
created = model.DateTimeField(auto_now_add=True)
total = models.IntegerField() # monetary value
Chcę przedstawić podział na miesiąc po miesiącu:
- Ile sprzedaży było w miesiącu (
COUNT
) - Połączona wartość (
SUM
)
Nie jestem pewien, jaki jest najlepszy sposób na zaatakowanie tego. Widziałem kilka dość przerażająco wyglądających zapytań z dodatkowym wyborem, ale mój prosty umysł podpowiada mi, że może lepiej byłoby po prostu iterować liczby, zaczynając od dowolnego początku roku / miesiąca i licząc aż do osiągnięcia bieżącego miesiąca, wyrzucając proste zapytania filtrujące w tym miesiącu. Więcej pracy z bazą danych - mniej stresu dla programistów!
Co ma dla ciebie największy sens? Czy istnieje dobry sposób na wycofanie szybkiej tabeli danych? A może moja brudna metoda jest prawdopodobnie najlepszym pomysłem?
Używam Django 1.3. Nie jestem pewien, czy ostatnio dodali ładniejszy sposób GROUP_BY
.
Odpowiedzi:
Django 1.10 i nowsze
Dokumentacja Django wkrótce zostanie uznana
extra
za przestarzałą . (Dziękuję za wskazanie tego @seddonym, @ Lucas03). Otworzyłem bilet i to jest rozwiązanie, które zapewniło 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
starsza wersja
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')
Zmiany
źródło
>>> 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
ustawienie Django jest takieTrue
, te dwie wersje nie są dokładnie równoważne. Wersja używającaTruncMonth
konwertuje sygnaturę czasową na strefę czasową określoną przezTIME_ZONE
ustawienie przed obcięciem, podczas gdy wersja używającadate_trunc_sql
obetnie surowy znacznik czasu UTC w bazie danych.Tylko mały dodatek do odpowiedzi @tback: Nie działało to dla mnie z Django 1.10.6 i postgres. Na koniec dodałem order_by (), aby to naprawić.
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()
źródło
TruncDate
umożliwia grupowanie według daty (dnia miesiąca)Innym podejściem jest użycie
ExtractMonth
. Podczas korzystania z TruncMonth napotkałem problemy z powodu zwrócenia tylko jednej wartości daty i godziny. Na przykład zwracano tylko miesiące w 2009 roku. ExtractMonth rozwiązał ten problem doskonale i może być używany jak poniżej:from django.db.models.functions import ExtractMonth Sales.objects .annotate(month=ExtractMonth('timestamp')) .values('month') .annotate(count=Count('id')) .values('month', 'count')
źródło
metrics = { 'sales_sum': Sum('total'), } queryset = Order.objects.values('created__month') .annotate(**metrics) .order_by('created__month')
To
queryset
lista Zamówień, jedna linia miesięcznie, łącząca sumę sprzedaży:sales_sum
@Django 2.1.7
źródło
Oto moja brudna metoda. To jest brudne.
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
Może istnieć lepszy sposób na zapętlanie lat / miesięcy, ale nie na tym mi zależy :)
źródło
Oto, jak możesz grupować dane według dowolnych okresów:
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))
źródło
mam tabelę zamówień w mojej bazie danych. mam zamiar liczyć zamówienia miesięcznie w ciągu ostatnich 3 miesięcy
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 to pole typu data i godzina. dzięki dodatkowej funkcji to, co zrobiono, pobiera datę z wartości datetime. podczas korzystania z daty i godziny możemy nie uzyskać poprawnej liczby, ponieważ obiekty są tworzone o różnych porach dnia.
Pętla for wypisze datę i liczbę zliczeń
źródło
Według miesiąca:
Order.objects.filter().extra({'month':"Extract(month from created)"}).values_list('month').annotate(Count('id'))
Według roku:
Order.objects.filter().extra({'year':"Extract(year from created)"}).values_list('year').annotate(Count('id'))
W dzień:
Order.objects.filter().extra({'day':"Extract(day from created)"}).values_list('day').annotate(Count('id'))
Nie zapomnij zaimportować Count
from django.db.models import Count
Dla django <1.10
źródło