web-dev-qa-db-fra.com

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 mensuelle de:

  • Combien de ventes y a-t-il eu en un mois (COUNT)
  • La valeur combinée (SUM)

Je ne sais pas quelle est la meilleure façon d'attaquer cela. J'ai vu des requêtes extra-sélectives assez effrayantes, mais mon esprit simple me dit que je ferais peut-être mieux d'itérer des chiffres, à partir d'un début d'année/mois arbitraire et en comptant jusqu'à ce que j'atteigne le mois en cours, en jetant simplement filtrage des requêtes pour ce mois. Plus de travail de base de données - moins de stress pour les développeurs!

Qu'est-ce qui vous semble le plus logique? Existe-t-il une bonne façon de retirer une table de données rapide? Ou ma sale méthode est-elle probablement la meilleure idée?

J'utilise Django 1.3. Je ne sais pas s'ils ont ajouté un moyen plus agréable à GROUP_BY récemment.

74
Oli

Django 1.10 et supérieur

La documentation de Django répertorie extra comme bientôt déconseillé. (Merci d'avoir souligné @seddonym, @ Lucas03). J'ai ouvert un ticket et c'est la solution fournie par 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 

Versions antérieures

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

  • Nombre ajouté
  • Ajout d'informations pour Django> = 1.10
200
tback

Juste un petit ajout à la réponse @tback: Cela n'a pas fonctionné pour moi avec Django 1.10.6 et postgres. J'ai ajouté order_by () à la fin pour le 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()
22
Rani

Une autre approche consiste à utiliser ExtractMonth. J'ai rencontré des problèmes avec TruncMonth en raison du retour d'une seule valeur datetime year. Par exemple, seuls les mois de 2009 ont été 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')  
6
Turtle
    metrics = {
        'sales_sum': Sum('total'),
    }
    queryset = Order.objects.values('created__month')
                               .annotate(**metrics)
                               .order_by('created__month')

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

@ Django 2.1.7

0
C.K.

Voici comment grouper les données par périodes arbitraires:

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))
0
Max Malysh

Voici ma sale méthode. C'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 des années/mois, mais ce n'est pas vraiment ce qui m'importe :)

0
Oli