Django: Groupe par date (jour, mois, année)
j'ai un modèle simple comme celui-ci:
class Order(models.Model):
created = model.DateTimeField(auto_now_add=True)
total = models.IntegerField() # monetary value
et je veux produire une ventilation mensuelle de:
- combien de ventes il y avait dans un mois (
COUNT
) - la valeur combinée (
SUM
)
Je ne sais pas quelle est la meilleure façon d'attaquer ceci. J'ai vu des requêtes assez effrayantes mais mon esprit simple me dit que je pourrais être mieux hors juste itérer des nombres, à partir d'une année de début/mois arbitraire et Compter jusqu'à ce que j'atteigne le mois en cours, en rejetant des requêtes simples filtrage pour ce mois. Plus de travail de base de données - moins de stress pour les développeurs!
Qu'est-ce qui a le plus de sens pour vous? Est-il une belle façon de me remonter un rapide tableau de données? Ou est-ce mon sale méthode probablement la meilleure idée?
J'utilise Django 1.3. Je ne sais pas s'ils ont ajouté une meilleure façon de GROUP_BY
récemment.
5 réponses
Django 1.10 et plus
documentation de Django listes extra
comme obsolète bientôt . (Merci de le signaler sur @seddonym, @Lucas03). J'ai ouvert un billet et c'est la solution que jarshwah fourni.
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
anciennes versions
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')
modifications
- compte ajouté
- ajout d'information pour django >= 1.10
juste un petit ajout à la réponse @tback: Ça n'a pas marché pour moi avec Django 1.10.6 et postgres. J'ai ajouté order_by() à la fin de la corriger.
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()
une autre approche consiste à utiliser ExtractMonth
. J'ai eu des problèmes avec TruncMonth à cause d'une seule valeur de l'année datetime. Par exemple, seuls les mois de 2009 étaient retournés. ExtractMonth a parfaitement corrigé ce problème et peut être utilisé comme ci-dessous:
from django.db.models.functions import ExtractMonth
Sales.objects
.annotate(month=ExtractMonth('timestamp'))
.values('month')
.annotate(count=Count('id'))
.values('month', 'count')
voici ma méthode sale. Il est sale.
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
il peut bien y avoir une meilleure façon de boucler des années / mois, mais ce n'est pas vraiment ce à quoi je tiens:)
par mois:
Order.objects.filter().extra({'month':"Extract(month from created)"}).values_list('month').annotate(Count('id'))
Par An:
Order.objects.filter().extra({'year':"Extract(year from created)"}).values_list('year').annotate(Count('id'))
par jour:
Order.objects.filter().extra({'day':"Extract(day from created)"}).values_list('day').annotate(Count('id'))
N'oubliez pas de compter les importations
from django.db.models import *
pour django < 1.10