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.

57
demandé sur ayhan 2012-01-05 20:30:08

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
174
répondu tback 2016-05-23 06:44:21

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()
14
répondu Rani 2017-05-08 20:37:07

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')  
4
répondu Turtle 2016-12-01 21:54:48

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

0
répondu Oli 2012-01-05 16:59:35

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

-2
répondu jatin 2016-12-28 07:25:34