Las expresiones condicionales te permiten utilizar lógica if … elif … else dentro de los filtros, anotaciones, agregaciones y actualizaciones. Una expresión condicional evalúa una serie de condiciones para cada fila de una tabla y devuelve la expresión de resultado correspondiente. Las expresiones condicionales también pueden combinarse y anidarse como otras expresiones.
Usaremos el siguiente modelo en los ejemplos posteriores:
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,
)
Cuando¶Un objeto When() se utiliza para encapsular una condición y su resultado para utilizar en la expresión condicional. Utilizar un objeto When() es similar a utilizar el método filter(). La condición se puede especificar utilizando field lookups, objetos Q o objetos Expression que tienen un output_field que es una BooleanField. El resultado se proporciona utilizando el keyword then.
Algunos ejemplos:
>>> 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",
... )
Ten en cuenta que cada uno de estos valores puede ser una expresión.
Nota
Dado que el argumento del keyword then está reservado para el resultado del objeto When(), existe un conflicto potencial si un modelo Model tiene un campo llamado then. Esto se puede resolver de dos maneras:
>>> When(then__exact=0, then=1)
>>> When(Q(then=0), then=1)
Case¶Una expresión Case() es como el statement if … elif … else en Python. Cada condition en los objetos When() proporcionados se evalúa en orden, hasta que uno de ellos evalúa a un valor verdadero. La expresión del result del objeto When() correspondiente se devuelve.
Un ejemplo:
>>>
>>> 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() acepta cualquier número de objetos When() como argumentos individuales. Otras opciones se proporcionan utilizando keyword arguments. Si ninguna de las condiciones evalúa a VERDADERO, entonces la expresión dada con el keyword argumento default se devuelve. Si no se proporciona un argumento default, se utiliza None.
Si queríamos cambiar nuestra consulta anterior para obtener el descuento según cuánto tiempo ha estado con nosotros el cliente, podríamos hacerlo utilizando lookups:
>>> 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%')]>
Nota
Recuerda que las condiciones se evalúan en orden, por lo que en el ejemplo anterior obtenemos el resultado correcto aunque la segunda condición coincide tanto con Jane Doe como Jack Black. Esto funciona exactamente igual que un statement if … elif … else en Python.
También funciona Case() en una cláusula de filter(). Por ejemplo, para encontrar clientes de oro que se registraron hace más de un mes y clientes de platino que se registraron hace más de un año:
>>> 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')]>
Las expresiones condicionales se pueden utilizar en anotaciones, agregaciones, filtros, consultas y actualizaciones. También se pueden combinar y anidar con otras expresiones. Esto te permite realizar consultas condicionales potentes.
Cambiamos el account_type de nuestros clientes para que coincida con sus fechas de registro. Podemos hacer esto utilizando una expresión condicional y el método 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')]>
¿Qué sucede si queremos saber cuántos clientes hay para cada account_type? Puedes utilizar el argumento filter de las funciones de agregación <aggregation-functions> para lograr esto:
>>> # 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}
Esta agregación produce una consulta con la sintaxis de SQL 2003 FILTER WHERE en bases de datos que lo soporten:
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;
En otras bases de datos, esto se emula mediante una sentencia 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;
Los dos sentencias SQL son funcionalmente equivalentes, pero la más explícita FILTER puede realizar mejoras en el rendimiento.
Cuando una expresión condicional devuelve un valor booleano, es posible utilizarlo directamente en los filtros. Esto significa que no se agregará a las columnas SELECT, pero aún puedes usarlo para filtrar resultados:
>>> 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 términos de SQL, eso evalúa a:
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
)
may 31, 2026