Conditional expressions let you use if ... elif ...
else logic within filters, annotations, aggregations, and updates. A
conditional expression evaluates a series of conditions for each row of a table
and returns the matching result expression. Conditional expressions can also be
combined and nested like other expressions.
在后续的例子中,我们将使用以下模型:
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, )
WhenWhen() 对象用于封装一个条件及其结果,以便在条件表达式中使用。使用 When() 对象类似于使用 filter() 方法。可以使用 字段查找、 Q 对象或 Expression 对象来指定条件,这些对象的 output_field 是 BooleanField。结果是用 then 关键字提供的。
一些示例:
>>> 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", ... )
请记住,每个值都可以是一个表达式。
Note
由于 then 关键字参数用于 When() 的结果,如果一个 Model 具有一个名为 then 的字段,可能会存在潜在冲突。这可以通过以下两种方式解决:
>>> When(then__exact=0, then=1) >>> When(Q(then=0), then=1)
CaseCase() 表达式就像 Python 中的 if... elif... else 语句。在提供的 When() 对象中的每个 condition 按顺序执行,直到执行出一个对的值。从匹配的 When() 对象中返回 result 表达式。
例子:
>>> >>> 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")
Case() 接受任意数量的 When() 对象作为单个参数。其他选项是通过关键字参数提供的。如果没有一个条件的值是 TRUE,那么将返回用 default 关键字参数给出的表达式。如果没有提供 default 参数,则使用 None。
如果我们想要根据客户与我们的关系时间来更改之前的查询以获取折扣,我们可以使用查找操作来实现:
>>> 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")
Note
请记住,条件是按顺序计算的,所以在上面的例子中,尽管第二个条件同时符合 Jane Doe 和 Jack Black,我们还是得到了正确的结果。这就像在 Python 中的 if... elif... else 语句一样。
Case() 也可以在 filter() 子句中使用。例如,要查找在一个月前注册的金牌客户和在一年前注册的铂金客户:
>>> 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")
条件表达式可用于注解、聚合、过滤器、查找和更新中。它们还可以与其他表达式组合和嵌套。这使你可以进行强大的条件查询。
假设我们想要根据客户的注册日期更改其 account_type。我们可以使用条件表达式和 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")
如果我们想要找出每种 account_type 有多少客户,我们可以使用 聚合函数 的 filter 参数来实现:
>>> # 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}
在支持 SQL 2003 FILTER WHERE 语法的数据库上,这个聚合产生一个查询。
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;
在其他数据库中,这是用 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;
这两条 SQL 语句在功能上是等同的,但更明确的 FILTER 可能表现得更好。
当条件表达式返回布尔值时,可以直接在过滤器中使用它。这意味着它不会被添加到 SELECT 列中,但您仍然可以使用它来过滤结果:
>>> 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))
用 SQL 术语来说,它的值是:
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 )
上一篇:Django参考文档:模型类参考
下一篇:Django参考文档:约束参考