# Documentation Django 6.0.7

# Expressions conditionnelles

Les expressions conditionnelles permettent d’utiliser de la logique `if` … `elif` … `else` à l’intérieur des filtres, des annotations, des agrégations et des mises à jour. Une expression conditionnelle évalue une série de conditions pour chaque ligne d’une table et renvoie l’expression résultante correspondante. Les expressions conditionnelles peuvent également être combinées et imbriquées comme toute autre expression.

## Les classes d’expressions conditionnelles

Nous allons utiliser le modèle suivant dans les exemples qui suivront :

    from django.db import models

    class Client(models.Model):
        REGULAR = "R"
        GOLD = "G"
        PLATINUM = "P"
        ACCOUNT_TYPE_CHOICES = {
            REGULAR: "Regular",
            GOLD: "Gold",
            PLATINUM: "Platinum",
        }
        name = models.CharField(max_length=50)
        registered_on = models.DateField()
        account_type = models.CharField(
            max_length=1,
            choices=ACCOUNT_TYPE_CHOICES,
            default=REGULAR,
        )

### `When`

class When(condition=None, then=None, **lookups)

Un objet `When()` est utilisé pour englober une condition et ses résultats pour leur exploitation dans une expression conditionnelle. L’emploi d’un objet `When()` est semblable à celui d’une méthode `filter()`. La condition peut être indiquée en utilisant des objets de recherche de champ, des objets `Q` ou des objets `Expression` dont le résultat `output_field` est un `BooleanField`. Le résultat est fourni en utilisant le mot-clé `then`.

Quelques exemples :

    >>> from django.db.models import F, Q, When
    >>> # String arguments refer to fields; the following two examples are equivalent:
    >>> When(account_type=Client.GOLD, then="name")
    >>> When(account_type=Client.GOLD, then=F("name"))
    >>> # You can use field lookups in the condition
    >>> from datetime import date
    >>> When(
    ...     registered_on__gt=date(2014, 1, 1),
    ...     registered_on__lt=date(2015, 1, 1),
    ...     then="account_type",
    ... )
    >>> # Complex conditions can be created using Q objects
    >>> When(Q(name__startswith="John") | Q(name__startswith="Paul"), then="name")
    >>> # Condition can be created using boolean expressions.
    >>> from django.db.models import Exists, OuterRef
    >>> non_unique_account_type = (
    ...     Client.objects.filter(
    ...         account_type=OuterRef("account_type"),
    ...     )
    ...     .exclude(pk=OuterRef("pk"))
    ...     .values("pk")
    ... )
    >>> When(Exists(non_unique_account_type), then=Value("non unique"))
    >>> # Condition can be created using lookup expressions.
    >>> from django.db.models.lookups import GreaterThan, LessThan
    >>> When(
    ...     GreaterThan(F("registered_on"), date(2014, 1, 1))
    ...     & LessThan(F("registered_on"), date(2015, 1, 1)),
    ...     then="account_type",
    ... )

N’oubliez pas que chacune de ces valeurs peut être elle-même une expression.

Note

Comme le paramètre nommé `then` est réservé au résultat de l’expression `When()`, un conflit potentiel existe si un `Model` possède un champ nommé `then`. Ceci peut être résolu de deux manières :

    >>> When(then__exact=0, then=1)
    >>> When(Q(then=0), then=1)

### `Case`

class Case(*cases, **extra)

Une expression `Case()` est semblable à une instruction `if` … `elif` … `else` en Python. Chaque `condition` dans les objets `When()` fournis est évaluée dans l’ordre, jusqu’à ce que l’une d’elle résulte en une valeur vraie. L’expression `result` de l’objet `When()` correspondant est renvoyée.

Un exemple :

    >>>
    >>> from datetime import date, timedelta
    >>> from django.db.models import Case, Value, When
    >>> Client.objects.create(
    ...     name="Jane Doe",
    ...     account_type=Client.REGULAR,
    ...     registered_on=date.today() - timedelta(days=36),
    ... )
    >>> Client.objects.create(
    ...     name="James Smith",
    ...     account_type=Client.GOLD,
    ...     registered_on=date.today() - timedelta(days=5),
    ... )
    >>> Client.objects.create(
    ...     name="Jack Black",
    ...     account_type=Client.PLATINUM,
    ...     registered_on=date.today() - timedelta(days=10 * 365),
    ... )
    >>> # Get the discount for each Client based on the account type
    >>> Client.objects.annotate(
    ...     discount=Case(
    ...         When(account_type=Client.GOLD, then=Value("5%")),
    ...         When(account_type=Client.PLATINUM, then=Value("10%")),
    ...         default=Value("0%"),
    ...     ),
    ... ).values_list("name", "discount")
    <QuerySet [('Jane Doe', '0%'), ('James Smith', '5%'), ('Jack Black', '10%')]>

`Case()` accepte un nombre indéfini d’objets `When()` comme paramètres individuels. D’autres options sont fournies sous forme de paramètres nommés. Si aucune des conditions évaluées ne résulte en une valeur vraie, c’est alors l’expression indiquée dans le paramètre nommé `default` qui est renvoyée. Si aucun paramètre `default` n’est fourni, c’est `None` qui est utilisé.

Si nous voulions modifier notre requête précédente pour obtenir le rabais sur la base de la fidélité de `Client`, nous pourrions le faire à l’aide d’expressions de recherche :

    >>> a_month_ago = date.today() - timedelta(days=30)
    >>> a_year_ago = date.today() - timedelta(days=365)
    >>> # Get the discount for each Client based on the registration date
    >>> Client.objects.annotate(
    ...     discount=Case(
    ...         When(registered_on__lte=a_year_ago, then=Value("10%")),
    ...         When(registered_on__lte=a_month_ago, then=Value("5%")),
    ...         default=Value("0%"),
    ...     )
    ... ).values_list("name", "discount")
    <QuerySet [('Jane Doe', '5%'), ('James Smith', '0%'), ('Jack Black', '10%')]>

Note

Rappelez-vous que les conditions sont évaluées dans l’ordre, ce qui fait que dans l’exemple ci-dessus, nous obtenons le résultat correct même si la seconde condition correspondait à la fois à Jane Doe et à Jack Black. Cela fonctionne exactement de la même manière qu’avec l’instruction `if` … `elif` … `else` en Python.

`Case()` fonctionne aussi dans une clause `filter()`. Par exemple, pour trouver tous les clients « gold » qui se sont inscrits il y a plus d’un mois et les clients « platinum » qui se sont inscrits il y a plus d’un an :

    >>> a_month_ago = date.today() - timedelta(days=30)
    >>> a_year_ago = date.today() - timedelta(days=365)
    >>> Client.objects.filter(
    ...     registered_on__lte=Case(
    ...         When(account_type=Client.GOLD, then=a_month_ago),
    ...         When(account_type=Client.PLATINUM, then=a_year_ago),
    ...     ),
    ... ).values_list("name", "account_type")
    <QuerySet [('Jack Black', 'P')]>

## Requêtes avancées

Les expressions conditionnelles peuvent être utilisées dans les annotations, les agrégations, les filtres, les recherches et les mises à jour. Elles peuvent également être combinées et imbriquées avec d’autres expressions. Cela permet d’effectuer des requêtes conditionnelles puissantes.

### Mise à jour conditionnelle

Admettons que nous voulions modifier le type de compte `account_type` de nos clients pour qu’il corresponde aux dates d’enregistrement. Nous pouvons le faire à l’aide d’une expression conditionnelle et de la méthode `update()`:

    >>> a_month_ago = date.today() - timedelta(days=30)
    >>> a_year_ago = date.today() - timedelta(days=365)
    >>> # Update the account_type for each Client from the registration date
    >>> Client.objects.update(
    ...     account_type=Case(
    ...         When(registered_on__lte=a_year_ago, then=Value(Client.PLATINUM)),
    ...         When(registered_on__lte=a_month_ago, then=Value(Client.GOLD)),
    ...         default=Value(Client.REGULAR),
    ...     ),
    ... )
    >>> Client.objects.values_list("name", "account_type")
    <QuerySet [('Jane Doe', 'G'), ('James Smith', 'R'), ('Jack Black', 'P')]>

### Agrégation conditionnelle

Et si nous voulions savoir combien de clients existent pour chaque type de compte ? Nous pouvons utiliser le paramètre `filter` des fonctions d’agrégat pour pouvoir faire cela :

    >>> # Create some more Clients first so we can have something to count
    >>> Client.objects.create(
    ...     name="Jean Grey", account_type=Client.REGULAR, registered_on=date.today()
    ... )
    >>> Client.objects.create(
    ...     name="James Bond", account_type=Client.PLATINUM, registered_on=date.today()
    ... )
    >>> Client.objects.create(
    ...     name="Jane Porter", account_type=Client.PLATINUM, registered_on=date.today()
    ... )
    >>> # Get counts for each value of account_type
    >>> from django.db.models import Count
    >>> Client.objects.aggregate(
    ...     regular=Count("pk", filter=Q(account_type=Client.REGULAR)),
    ...     gold=Count("pk", filter=Q(account_type=Client.GOLD)),
    ...     platinum=Count("pk", filter=Q(account_type=Client.PLATINUM)),
    ... )
    {'regular': 2, 'gold': 1, 'platinum': 3}

Cet agrégat produit une requête avec la syntaxe SQL 2003 `FILTER WHERE` pour les bases de données qui la prennent en charge :

    SELECT count('id') FILTER (WHERE account_type=1) as regular,
           count('id') FILTER (WHERE account_type=2) as gold,
           count('id') FILTER (WHERE account_type=3) as platinum
    FROM clients;

Pour les autres bases de données, cet effet est émulé à l’aide d’une instruction `CASE`:

    SELECT count(CASE WHEN account_type=1 THEN id ELSE null) as regular,
           count(CASE WHEN account_type=2 THEN id ELSE null) as gold,
           count(CASE WHEN account_type=3 THEN id ELSE null) as platinum
    FROM clients;

Les deux instructions SQL sont fonctionnellement équivalentes mais la variante `FILTER` plus explicite peut donner de meilleures performances.

### Filtre conditionnel

Lorsqu’une expression conditionnelle renvoie une valeur booléenne, il est possible de l’utiliser directement dans des filtres. Cela signifie qu’elle ne sera pas ajoutée aux colonnes `SELECT`, mais qu’il est quand même possible de l’utiliser pour filtrer les résultats :

    >>> non_unique_account_type = (
    ...     Client.objects.filter(
    ...         account_type=OuterRef("account_type"),
    ...     )
    ...     .exclude(pk=OuterRef("pk"))
    ...     .values("pk")
    ... )
    >>> Client.objects.filter(~Exists(non_unique_account_type))

En termes SQL, cela donne :

    SELECT ...
    FROM client c0
    WHERE NOT EXISTS (
      SELECT c1.id
      FROM client c1
      WHERE c1.account_type = c0.account_type AND NOT c1.id = c0.id
    )