# Documentation Django 3.1.9.dev

# Agrégation

Le guide thématique sur l”API d’abstraction de base de données de Django décrit la façon dont les requêtes Django peuvent être utilisées pour créer, récupérer, mettre à jour et supprimer des objets individuels. Il est cependant parfois nécessaire de récupérer des valeurs dérivées du résumé ou de l”agrégation d’une collection d’objets. Ce guide thématique décrit la manière dont des valeurs agrégées peuvent être générées et renvoyées en utilisant des requêtes Django.

Tout au long de ce guide, nous nous référerons aux modèles suivants. Ces modèles sont utilisés pour gérer l’inventaire d’une série de bibliothèques en ligne :

    from django.db import models

    class Author(models.Model):
        name = models.CharField(max_length=100)
        age = models.IntegerField()

    class Publisher(models.Model):
        name = models.CharField(max_length=300)

    class Book(models.Model):
        name = models.CharField(max_length=300)
        pages = models.IntegerField()
        price = models.DecimalField(max_digits=10, decimal_places=2)
        rating = models.FloatField()
        authors = models.ManyToManyField(Author)
        publisher = models.ForeignKey(Publisher, on_delete=models.CASCADE)
        pubdate = models.DateField()

    class Store(models.Model):
        name = models.CharField(max_length=300)
        books = models.ManyToManyField(Book)

## Antisèche

Vous êtes pressé ? Voici comment effectuer des requêtes avec agrégation, sur la base des modèles ci-dessus :

    # Total number of books.
    >>> Book.objects.count()
    2452

    # Total number of books with publisher=BaloneyPress
    >>> Book.objects.filter(publisher__name='BaloneyPress').count()
    73

    # Average price across all books.
    >>> from django.db.models import Avg
    >>> Book.objects.all().aggregate(Avg('price'))
    {'price__avg': 34.35}

    # Max price across all books.
    >>> from django.db.models import Max
    >>> Book.objects.all().aggregate(Max('price'))
    {'price__max': Decimal('81.20')}

    # Difference between the highest priced book and the average price of all books.
    >>> from django.db.models import FloatField
    >>> Book.objects.aggregate(
    ...     price_diff=Max('price', output_field=FloatField()) - Avg('price'))
    {'price_diff': 46.85}

    # All the following queries involve traversing the Book<->Publisher
    # foreign key relationship backwards.

    # Each publisher, each with a count of books as a "num_books" attribute.
    >>> from django.db.models import Count
    >>> pubs = Publisher.objects.annotate(num_books=Count('book'))
    >>> pubs
    <QuerySet [<Publisher: BaloneyPress>, <Publisher: SalamiPress>, ...]>
    >>> pubs[0].num_books
    73

    # Each publisher, with a separate count of books with a rating above and below 5
    >>> from django.db.models import Q
    >>> above_5 = Count('book', filter=Q(book__rating__gt=5))
    >>> below_5 = Count('book', filter=Q(book__rating__lte=5))
    >>> pubs = Publisher.objects.annotate(below_5=below_5).annotate(above_5=above_5)
    >>> pubs[0].above_5
    23
    >>> pubs[0].below_5
    12

    # The top 5 publishers, in order by number of books.
    >>> pubs = Publisher.objects.annotate(num_books=Count('book')).order_by('-num_books')[:5]
    >>> pubs[0].num_books
    1323

## Génération de requêtes d’agrégation sur un objet `QuerySet`

Django propose deux manières de générer des agrégations. La première est de générer un résumé des valeurs d’un `QuerySet` entier. Par exemple, admettons que vous vouliez calculer le prix moyen de tous les livres disponibles à la vente. La syntaxe de requêtes de Django fournit une façon de décrire l’ensemble de tous les livres :

    >>> Book.objects.all()

Ce dont nous avons besoin, c’est d’une manière de calculer un résumé des valeurs de tous les objets appartenant à ce `QuerySet`. Cela se fait en ajoutant une clause `aggregate()` à la suite de l’objet `QuerySet`:

    >>> from django.db.models import Avg
    >>> Book.objects.all().aggregate(Avg('price'))
    {'price__avg': 34.35}

Dans cet exemple, `all()` est redondant, cela pourrait donc être simplifié en écrivant :

    >>> Book.objects.aggregate(Avg('price'))
    {'price__avg': 34.35}

Le paramètre de la clause `aggregate()` décrit la valeur d’agrégation que vous souhaitez calculer, dans ce cas la moyenne du champ `price` du modèle `Book`. Une liste des fonctions d’agrégation disponibles se trouve dans la référence de QuerySet.

`aggregate()` est la clause terminale d’un objet `QuerySet` qui, lorsqu’il est évalué, renvoie un dictionnaire de paires nom-valeur. Le nom est un identifiant de la valeur agrégée ; la valeur est le résultat du calcul d’agrégation. Le nom est généré automatiquement à partir du nom du champ et de la fonction d’agrégation. Si vous voulez indiquer manuellement le nom de la valeur agrégée, vous pouvez le faire en précisant ce nom dans la clause d’agrégation :

    >>> Book.objects.aggregate(average_price=Avg('price'))
    {'average_price': 34.35}

Si vous voulez générer plus d’une valeur agrégée, ajoutez un autre paramètre dans la clause `aggregate()`. Ainsi, si nous voulions aussi connaître les prix maximaux et minimaux de tous les livres, nous écririons la requête suivante :

    >>> from django.db.models import Avg, Max, Min
    >>> Book.objects.aggregate(Avg('price'), Max('price'), Min('price'))
    {'price__avg': 34.35, 'price__max': Decimal('81.20'), 'price__min': Decimal('12.99')}

## Génération de valeurs agrégées pour chaque élément d’un `QuerySet`

La deuxième manière de générer des valeurs récapitulatives est de générer un récapitulatif individuel pour chaque objet d’un `QuerySet`. Par exemple, si vous récupérez une liste de livres, il peut être utile de connaître le nombre d’auteurs ayant contribué à chaque livre. Chaque objet `Book` possède une relation plusieurs-à-plusieurs vers le modèle `Author`; nous voulons récapituler cette relation pour chaque livre de la requête.

Les récapitulatifs par objet peuvent être générés par la clause `annotate()`. Lorsque celle-ci est mentionnée, chaque objet de la requête est annoté avec les valeurs indiquées.

La syntaxe de ces annotations est identique à celle utilisée pour les clauses `aggregate()`. Chaque paramètre d”`annotate()` décrit une valeur agrégée à calculer. Par exemple, pour annoter les livres avec le nombre de leurs auteurs :

    # Build an annotated queryset
    >>> from django.db.models import Count
    >>> q = Book.objects.annotate(Count('authors'))
    # Interrogate the first object in the queryset
    >>> q[0]
    <Book: The Definitive Guide to Django>
    >>> q[0].authors__count
    2
    # Interrogate the second object in the queryset
    >>> q[1]
    <Book: Practical Django Projects>
    >>> q[1].authors__count
    1

Comme pour `aggregate()`, le nom de l’annotation est automatiquement dérivé du nom de la fonction d’agrégation et du nom du champ sur lequel se fait le calcul. Vous pouvez surcharger ce nom par défaut en fournissant un alias lors de la définition de l’annotation :

    >>> q = Book.objects.annotate(num_authors=Count('authors'))
    >>> q[0].num_authors
    2
    >>> q[1].num_authors
    1

Au contraire de `aggregate()`, `annotate()` n’est pas une clause terminale. Le résultat de la clause `annotate()` est un objet `QuerySet`. Cet objet peut très bien être modifié par une autre opération de type `QuerySet`, comme par exemple `filter()`, `order_by()` ou même d’autres appels à `annotate()`.

### Combinaison de plusieurs agrégations

La combinaison de plusieurs agrégations avec `annotate()` produit un mauvais résultat, car des jointures sont utilisées à la place de sous-requêtes :

    >>> book = Book.objects.first()
    >>> book.authors.count()
    2
    >>> book.store_set.count()
    3
    >>> q = Book.objects.annotate(Count('authors'), Count('store'))
    >>> q[0].authors__count
    6
    >>> q[0].store__count
    6

Pour la plupart des agrégations, il n’existe pas de solution à ce problème. Cependant, l’agrégation `Count` possède un paramètre `distinct` qui peut être utile :

    >>> q = Book.objects.annotate(Count('authors', distinct=True), Count('store', distinct=True))
    >>> q[0].authors__count
    2
    >>> q[0].store__count
    3

En cas de doute, inspectez la requête SQL !

Afin de comprendre ce qui se produit dans la requête, il est possible d’examiner la propriété `query` de l’objet `QuerySet`.

## Jointures et agrégations

Jusqu’ici, nous avons eu affaire à des agrégations sur des champs appartenant au modèle sur lequel portait la requête. Cependant, la valeur à agréger peut parfois appartenir à un modèle qui est lié au modèle sur lequel porte la requête.

Lors de la définition du champ à agréger dans une fonction d’agrégation, Django permet d’utiliser la même notation de double soulignement qui est utilisée pour se référer aux champs liés dans les filtres. Django se charge ensuite des jointures de tables nécessaires pour récupérer et agréger la valeur liée.

Par exemple, pour trouver l’intervalle des prix pratiqués dans chaque magasin, vous pourriez utiliser l’annotation :

    >>> from django.db.models import Max, Min
    >>> Store.objects.annotate(min_price=Min('books__price'), max_price=Max('books__price'))

Ceci indique à Django de récupérer le modèle `Store`, de faire la jointure avec le modèle `Book` (par la relation plusieurs-à-plusieurs) et d’agréger sur le champ du prix du modèle `Book` pour produire les valeurs du minimum et du maximum.

Les mêmes règles s’appliquent à la clause `aggregate()`. Si vous vouliez connaître le prix le plus bas et le prix le plus haut de tous les livres mis en vente dans n’importe quel magasin, vous pourriez utiliser la fonction d’agrégation :

    >>> Store.objects.aggregate(min_price=Min('books__price'), max_price=Max('books__price'))

Les chaînes de jointure peuvent être aussi profondes que nécessaire. Par exemple, pour extraire l’âge du plus jeune auteur de tous les livres mis en vente, vous pourriez effectuer la requête :

    >>> Store.objects.aggregate(youngest_age=Min('books__authors__age'))

### Suivi des relations inverses

D’une manière similaire aux Recherches traversant les relations, les agrégations et les annotations sur les champs de modèles ou sur les modèles liés à celui sur lequel porte la requête peuvent contenir des relations « inverses » traversantes. Ici également, il est possible d’utiliser la syntaxe des noms en minuscules des modèles liés et des doubles soulignements.

Par exemple, il est possible d’extraire tous les éditeurs annotés avec leur décompte respectif de tous leurs livres publiés (notez la manière d’utiliser `'book'` pour définir la relation de clé étrangère inversée) :

    >>> from django.db.models import Avg, Count, Min, Sum
    >>> Publisher.objects.annotate(Count('book'))

(chaque `Publisher` du jeu de requête résultant possédera un attribut supplémentaire nommé `book__count`)

Une autre requête pourrait porter sur le plus ancien livre publié par chaque éditeur :

    >>> Publisher.objects.aggregate(oldest_pubdate=Min('book__pubdate'))

Le dictionnaire résultant possédera une clé nommée `'oldest_pubdate'`. Si l’alias n’avait pas été précisé, cette clé se serait appelée `'book__pubdate__min'`, ce qui est plutôt long.

Cela ne s’applique pas seulement aux clés étrangères, mais fonctionne aussi avec les relations plusieurs-à-plusieurs. Par exemple, nous pouvons rechercher tous les auteurs annotés avec le nombre total de pages de tous les livres pour lesquels ils ont été (co-)auteurs (notez la manière d’utiliser `'book'` pour définir la relation plusieurs-à-plusieurs inversée de `Author` vers `Book`)

    >>> Author.objects.annotate(total_pages=Sum('book__pages'))

Chaque `Author` du jeu de requête résultant possédera un attribut supplémentaire nommé `total_pages`. Si l’alias n’avait pas été précisé, cette clé se serait appelée `'book__pages__sum'`, ce qui est plutôt long.

Nous pourrions encore extraire la moyenne des notes de tous les livres écrits par les auteurs enregistrés :

    >>> Author.objects.aggregate(average_rating=Avg('book__rating'))

Le dictionnaire résultant possédera une clé nommée `'average_rating'`. Si l’alias n’avait pas été précisé, cette clé se serait appelée `'book__rating__avg'`, ce qui est plutôt long.

## Agrégations et autres clauses de `QuerySet`

### `filter()` et `exclude()`

Les agrégations peuvent aussi intervenir dans les filtres. Toute méthode `filter()` (ou `exclude()`) appliquée à des champs de modèle normaux fera l’effet de restreindre les objets concernés par l’agrégation.

Lorsqu’il est combiné à une clause `annotate()`, un filtre fait l’effet de restreindre les objets sur lesquels l’annotation est calculée. Par exemple, vous pouvez générer une liste annotée de tous les livres ayant leur titre commençant par « Django » à l’aide de la requête :

    >>> from django.db.models import Avg, Count
    >>> Book.objects.filter(name__startswith="Django").annotate(num_authors=Count('authors'))

Lorsqu’il est combiné à une clause `aggregate()`, un filtre fait l’effet de restreindre les objets sur lesquels l’agrégation est calculée. Par exemple, vous pouvez générer le prix moyen de tous les livres ayant leur titre commençant par « Django » à l’aide de la requête :

    >>> Book.objects.filter(name__startswith="Django").aggregate(Avg('price'))

#### Filtrage sur les annotations

Les valeurs annotées peuvent également être filtrées. L’alias de l’annotation peut être utilisé dans les clauses `filter()` et `exclude()` de la même manière que pour n’importe quel autre champ de modèle.

Par exemple, pour générer une liste de livres ayant plus d’un auteur, vous pouvez écrire la requête :

    >>> Book.objects.annotate(num_authors=Count('authors')).filter(num_authors__gt=1)

Cette requête génère un jeu de requête annoté, puis filtre le résultat sur la base de l’annotation.

Si vous avez besoin de deux annotations avec deux filtres séparés, vous pouvez utiliser le paramètre `filter` avec n’importe quel agrégat. Par exemple, pour générer une liste d’auteurs avec le décompte des livres bien notés

    >>> highly_rated = Count('book', filter=Q(book__rating__gte=7))
    >>> Author.objects.annotate(num_books=Count('book'), highly_rated_books=highly_rated)

Chaque `Author` dans le jeu de résultats possédera les deux attributs `num_books` et `highly_rated_books`.

Choix entre `filter` et `QuerySet.filter()`

Évitez d’utiliser le paramètre `filter` avec une seule annotation ou agrégation. Il est plus efficace d’utiliser `QuerySet.filter()` pour exclure des lignes. Le paramètre d’agrégation `filter` n’est utile que lorsqu’on utilise au moins deux agrégations sur les mêmes relations avec des conditions différentes.

#### Ordre des clauses `annotate()` et `filter()`

Lors de la rédaction de requête complexe impliquant à la fois des clauses `annotate()` et `filter()`, une attention particulière doit être donnée à l’ordre dans lequel les clauses sont appliquées à l’objet `QuerySet`.

Lorsqu’une clause `annotate()` est appliquée à une requête, l’annotation est calculée sur l’état de la requête au point exact où l’annotation est demandée. L’implication pratique de ceci est que `filter()` et `annotate()` ne sont pas des opérations commutatives.

Étant donné :

- Un « Publisher » A possède deux livres avec notations 4 et 5.
- Un « Publisher » B possède deux livres avec notations 1 et 4.
- Un « Publisher » A possède un livre avec notation 1.

Voici un exemple avec l’agrégation `Count`:

    >>> a, b = Publisher.objects.annotate(num_books=Count('book', distinct=True)).filter(book__rating__gt=3.0)
    >>> a, a.num_books
    (<Publisher: A>, 2)
    >>> b, b.num_books
    (<Publisher: B>, 2)

    >>> a, b = Publisher.objects.filter(book__rating__gt=3.0).annotate(num_books=Count('book'))
    >>> a, a.num_books
    (<Publisher: A>, 2)
    >>> b, b.num_books
    (<Publisher: B>, 1)

Les deux requêtes renvoient une liste d’objets « Publisher » ayant au moins un livre avec une note dépassant 3.0, ce qui fait que Publisher C est exclu.

Dans la première requête, l’annotation précède le filtre, ce qui fait que le filtre n’a aucun effet sur l’annotation. `distinct=True` est obligatoire pour éviter une anomalie de requête.

La seconde requête compte le nombre de livres ayant une note dépassant 3.0 pour chaque Publisher. Le filtre précède l’annotation, ce qui fait que le filtre limite les objets pris en compte lors du calcul de l’annotation.

Voici un autre exemple avec l’agrégation `Avg`:

    >>> a, b = Publisher.objects.annotate(avg_rating=Avg('book__rating')).filter(book__rating__gt=3.0)
    >>> a, a.avg_rating
    (<Publisher: A>, 4.5)  # (5+4)/2
    >>> b, b.avg_rating
    (<Publisher: B>, 2.5)  # (1+4)/2

    >>> a, b = Publisher.objects.filter(book__rating__gt=3.0).annotate(avg_rating=Avg('book__rating'))
    >>> a, a.avg_rating
    (<Publisher: A>, 4.5)  # (5+4)/2
    >>> b, b.avg_rating
    (<Publisher: B>, 4.0)  # 4/1 (book with rating 1 excluded)

La première requête calcule la note moyenne de tous les livres de chaque éditeur pour les éditeurs ayant au moins un livre avec une note dépassant 3.0. La seconde requête calcule la moyenne des notes des livres d’un éditeur pour tous les livres dont la note dépasse 3.0.

Il est difficile de deviner comment l’ORM va traduire des jeux de requêtes complexes en instructions SQL ; en cas de doute, inspectez le code SQL produit avec `str(queryset.query)` et écrivez de nombreux tests.

### `order_by()`

Les annotations peuvent être utilisées comme base de tri. Lorsque vous définissez une clause `order_by()`, les agrégations que vous indiquez peuvent référencer n’importe quel alias défini dans le cadre d’une clause `annotate()` de la requête.

Par exemple, pour trier un jeu de requête de livres par le nombre d’auteurs ayant contribué au livre, vous pourriez écrire la requête suivante :

    >>> Book.objects.annotate(num_authors=Count('authors')).order_by('num_authors')

### `values()`

En principe, les annotations sont générées pour chaque objet ; un jeu de requête annoté renvoie un résultat par objet du jeu de requête original. Cependant, lorsqu’une clause `values()` est utilisée pour restreindre les colonnes renvoyées dans le jeu de requête, la méthode d’évaluation des annotations est légèrement différente. Au lieu de renvoyer un résultat annoté pour chaque résultat du jeu de requête original, les résultats d’origine sont groupés selon les combinaisons uniques des champs indiqués dans la clause `values()`. Puis l’annotation est fournie pour chaque groupe unique ; l’annotation est donc calculée sur tous les membres du groupe.

Par exemple, considérons une requête sur les auteurs cherchant à trouver la moyenne des notes des livres écrits par chaque auteur :

    >>> Author.objects.annotate(average_rating=Avg('book__rating'))

Ceci renvoie un résultat pour chaque auteur de la base de données, annoté par la note moyenne de leurs livres.

Cependant, le résultat sera légèrement différent si vous utilisez une clause `values()`:

    >>> Author.objects.values('name').annotate(average_rating=Avg('book__rating'))

Dans cet exemple, les auteurs sont groupés par nom, vous allez donc obtenir un résultat annoté pour chaque nom d’auteur unique. Cela signifie que si vous avez deux auteurs de même nom, leurs résultats seront fusionnés en un seul dans la requête résultante ; la moyenne sera calculée sur tous les livres écrits par les deux auteurs.

#### Ordre des clauses `annotate()` et `values()`

Comme pour la clause `filter()`, l’ordre d’apparition des clauses `annotate()` et `values()` dans une requête est important. Si la clause `values()` précède `annotate()`, l’annotation est calculée par rapport aux groupements définis par la clause `values()`.

Cependant, si la clause `annotate()` précède la clause `values()`, les annotations sont générées sur la totalité du jeu de requête. Dans ce cas, la clause `values()` ne restreint que les champs dans le résultat final.

Par exemple, si nous inversons l’ordre des clauses `values()` et `annotate()` dans notre exemple précédent :

    >>> Author.objects.annotate(average_rating=Avg('book__rating')).values('name', 'average_rating')

Cela produit maintenant un résultat unique pour chaque auteur ; cependant, seuls le nom de l’auteur et l’annotation `average_rating` sont renvoyés dans les données de résultat.

Remarquez également que `average_rating` a été explicitement inclus dans la liste des valeurs à renvoyer. C’est obligatoire à cause de l’ordre des clauses `values()` et `annotate()`.

Si la clause `values()` précède la clause `annotate()`, toute annotation sera automatiquement ajoutée au résultat de la requête. Toutefois, si la clause `values()` est appliquée après la clause `annotate()`, vous devez inclure explicitement la colonne agrégée.

#### Interaction avec le tri par défaut ou `order_by()`

Les champs mentionnés dans la partie `order_by()` d’une requête (ou ceux faisant partie du tri par défaut d’un modèle) sont utilisés lors de la sélection des données du résultat, même s’ils ne sont pas spécifiés explicitement dans l’appel à `values()`. Ces champs supplémentaires sont aussi pris en compte pour regrouper les résultats similaires et peuvent faire apparaître de manière distincte des lignes qui seraient regroupées sans la présence de ces champs. C’est particulièrement flagrant lorsqu’il s’agit de compter des objets.

Pour fournir un exemple, supposons que vous ayez un modèle comme celui-ci :

    from django.db import models

    class Item(models.Model):
        name = models.CharField(max_length=10)
        data = models.IntegerField()

        class Meta:
            ordering = ["name"]

L’élément important est ici le tri par défaut sur le champ `name`. Si vous souhaitez compter le nombre d’apparitions distinctes de la valeur `data`, vous pourriez écrire ceci :

    # Warning: not quite correct!
    Item.objects.values("data").annotate(Count("id"))

…ce qui regroupe les objets `Item` par leurs valeurs `data` communes, puis compte le nombre de valeurs `id` dans chaque groupe. Sauf que ça ne marchera pas comme prévu. Le tri par défaut selon `name` intervient aussi dans le regroupement, ce qui fait que la requête regroupe sur les paires distinctes `(data, name)`, ce qui ne correspond pas à l’intention de départ. Voici en réalité ce qu’il faut écrire pour obtenir la requête espérée :

    Item.objects.values("data").annotate(Count("id")).order_by()

…effaçant ainsi tout ordre de tri dans la requête. Il serait aussi possible de trier sur le champ `data` sans conséquence néfaste, dans la mesure où ce champ est déjà impliqué dans la requête.

Ce comportement est le même que ce qui est noté dans la documentation des requêtes concernant `distinct()`, la règle générale étant identique : il n’est normalement pas souhaité que des colonnes supplémentaires soient inclues dans le résultat, il faut donc effacer l’ordre de tri ou en tout cas s’assurer que les champs de tri sont déjà impliqués dans l’appel à `values()`.

Note

Vous pourriez raisonnablement vous demander pourquoi Django ne supprime pas ces colonnes parasites à notre place. La raison principale est de conserver la cohérence avec `distinct()` et d’autres endroits : Django ne supprime jamais les contraintes de tri que vous avez indiquées (et nous ne pouvons pas modifier le comportement de ces autres méthodes car cela trahirait notre politique de Stabilité de l’API).

### Agrégation des annotations

Vous pouvez aussi générer une agrégation des résultats d’une annotation. Lorsque vous définissez une clause `aggregate()`, cette agrégation peut se référer à n’importe quel alias défini dans une clause `annotate()` de la même requête.

Par exemple, si vous souhaitiez calculer le nombre moyen d’auteurs par livre, vous annoteriez premièrement les livres avec le nombre d’auteurs, puis vous agrégeriez ce nombre en vous référant au champ annoté :

    >>> from django.db.models import Avg, Count
    >>> Book.objects.annotate(num_authors=Count('authors')).aggregate(Avg('num_authors'))
    {'num_authors__avg': 1.66}