120 votes

Django : Grouper 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 mois par mois :

  • Combien de ventes ont été réalisées en un mois ( COUNT )
  • La valeur combinée ( SUM )

Je ne sais pas quelle est la meilleure façon d'aborder cette question. J'ai vu des requêtes extra-sélectives assez effrayantes, mais mon esprit simple me dit que je ferais mieux d'itérer des nombres, en commençant par une année/mois arbitraire et en comptant jusqu'au mois en cours, en lançant des requêtes simples filtrant pour ce mois. Plus de travail sur la base de données - moins de stress pour le développeur !

Qu'est-ce qui vous semble le plus logique ? Existe-t-il un moyen efficace de récupérer rapidement un tableau de données ? Ou bien ma sale méthode est-elle probablement la meilleure idée ?

J'utilise Django 1.3. Je ne sais pas s'ils ont ajouté une façon plus agréable d'utiliser la fonction GROUP_BY récemment.

275voto

tback Points 3282

Django 1.10 et plus

Listes de documentation de Django extra como bientôt obsolète . (Merci de l'avoir signalé @seddonym, @Lucas03). J'ai ouvert un billet et c'est la solution que jarshwah a apportée.

from django.db.models.functions import TruncMonth
from django.db.models import Count

Sales.objects
    .annotate(month=TruncMonth('created'))  # 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'informations pour django >= 1.10

57voto

Rani Points 1312

Juste un petit complément à la réponse de @tback : Cela n'a pas fonctionné pour moi avec Django 1.10.6 et postgres. J'ai ajouté order_by() à la fin pour résoudre le problème.

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

14voto

Turtle Points 909

Une autre approche consiste à utiliser ExtractMonth . J'ai rencontré des problèmes lors de l'utilisation de TruncMonth car une seule valeur d'année de date était renvoyée. Par exemple, seuls les mois de 2009 étaient renvoyés. ExtractMonth a parfaitement résolu 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')

4voto

C.K. Points 134
    metrics = {
        'sales_sum': Sum('total'),
    }
    queryset = Order.objects.values('created__month')
                               .annotate(**metrics)
                               .order_by('created__month')

En queryset est une liste de commandes, une ligne par mois, combinant la somme des ventes : sales_sum

@Django 2.1.7

1voto

Oli Points 65050

Voici ma sale méthode. Elle 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 y a peut-être une meilleure façon de boucler les années/mois, mais ce n'est pas vraiment ce qui m'intéresse :)

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X