Introduction
L'ORM de Django est connu pour sa puissance et sa simplicité... Mais il offre aussi des fonctionnalités avancées. Ces dernières permettent de simplifier les opérations complexes.
Dans cet article, deux méthodes nous intéressent :
- Aggregate
- Annotate
Ces deux méthodes peuvent être sources de confusion.
Comment faire le bon choix ?
Pour expliquer ces deux concepts, nous allons partir sur des exemples simples. Tout d'abord, nous allons définir deux modèles dans notre projet Django :
from django.db import models
class Customer(models.Model):
name = models.CharField(max_length=50)
def __str__(self):
return self.name
class Project(models.Model):
customer = models.ForeignKey(Customer, on_delete=models.CASCADE)
time_spent = models.IntegerField(verbose_name="Temps passé")
def __str__(self):
return f"{self.customer}, temps passé : {self.time_spent}"
Aggregate : Calculer une valeur unique pour tout le queryset
La méthode aggregate permet de réaliser des calculs globaux sur un queryset.
Cela signifie que vous pouvez obtenir une valeur unique (comme une somme, une moyenne ou un maximum) sans parcourir chaque élément individuellement.
Reprenons notre exemple, où nous calculons le temps total passé sur tous les projets en une seule requête :
from django.db.models import Sum
result = Project.objects.aggregate(total_time_spent=Sum('time_spent'))
print(result)
# On pourrait très bien modifier le queryset de cette façon sans soucis :
# resultat = Project.objects.filter(customer__name="PyGabDev").aggregate(total_time_spent=Sum('time_spent'))
Dans ce cas, nous calculons la somme de la colonne time_spent pour toutes les instances du queryset.
aggregate retourne un dictionnaire contenant la valeur calculée, avec une clé qui correspond au nom que nous avons défini, ici, total_time_spent.
Prenons un exemple concret. Voici les instances de Project en base de données :
Si nous affichons le contenu de result, voici ce que nous obtenons :
Nous avons bien un dictionnaire, avec en clé total_time_spent et en valeur 24, c'est-à-dire le total des heures de tous les projets cumulés.
Nous pouvons donc facilement accéder à cette valeur :
Evidemment, aggregate ne se limite pas à Sum, on pourrait par exemple très bien utiliser :
- Avg, calcul de la moyenne
- Max, donne la valeur maximale
- Min, donne la valeur minimale
Utilisez aggregate pour calculer une valeur unique sur votre queryset.
Annotate : Appliquer des calculs à chaque élément du queryset
La méthode annotate permet d'ajouter des calculs à chaque élément du queryset, contrairement à aggregate, qui retourne une valeur unique pour l'ensemble du queryset.
Avec un exemple simple :
from django.db.models import Sum
result = Customer.objects.annotate(total_time_spent=Sum('project__time_spent'))
for customer in result:
print(f"Client: {customer.name}, Temps total : {customer.total_time_spent}")
Dans cet exemple, nous calculons le temps total passé sur les projets de chaque client.
project__time_spent, ici nous accédons au champ time_spent du modèle Project grâce à la relation entre Customer et Project.
Sum('project__time_spent'), cela calcule la somme du temps passé pour chaque client.
Grâce à annotate, un nouvel attribut total_time_spent est ajouté à chaque client.
Comme avec aggregate, vous pouvez utiliser des fonctions comme Count, Avg...
Utilisez annotate pour calculer et ajouter de nouvelles informations à chaque objet du queryset.
Le mot de la fin
Les méthodes annotate et aggregate se complètent et visent chacune des objectifs distincts.
Pour aller plus loin, vous pouvez les associer à l'objet F, qui permet d'effectuer des calculs directement dans la base de données, sans passer par un chargement en mémoire Python, améliorant ainsi les performances de vos opérations.
Par ailleurs, j'ai également publié une vidéo sur YouTube.
Retour