1.介绍
- model 和数据准备
- When 和 Case 操作新增字段返回
- 条件搜索
- 条件更新
- 条件聚合
2. When 和 Case 操作新增字段返回
我们使用的条件表达式使用的 When 和 Case 的函数,这个其实就对应于 SQL 里的 CASE 和 WHEN 函数。
我们先来说一下需求,我们在获取 Client 数据的时候,想要知道这条数据 registered_on 日期字段所在的季节,比如 1月就是 Spring,7月就是 Autumn。
怎么处理呢?
很简单,先获取 Client 数据,然后根据 registered_on 字段判断月份,比如在 1,2,3 之间就是 Spring。
这种方法是可行的,但是如果我们有另一个需求,比如说想要筛选出所有季节为 Spring 的数据呢
(这个例子其实不太恰当,因为这种操作,我们可以直接通过 filter(registered_on__month__in=[1,2,3])来筛选,但是这里我们强行要求使用filter(季节=‘Spring’)的这种形式来操作)
那么这时候就可以用上我们的 When Case 的用法了。
在下面的操作中,我们通过判断 registered_on 字段的月份区间来得到一个新的字段:
在上面的代码中,我们通过 annotate() 来新建一个 season 字段,这个字段的值是根据 registered_on 的月份所在区间来为 season 赋值。
Case() 函数内包含了四种 When 的可能性,然后会有一个 default 默认值
在每一个 When() 函数里,前一个是个表达式,可以是这种形式,也可以是 Q() 操作的语句,then= 表示如果满足前面的表达式,那么值的内容将会是后面的值。
在值的定义里,我们这里用到了 Value() 函数,Value() 表示其值是一个字符串。
from django.db.models import Case, Value, When
from blog.models import Client
results = Client.objects.annotate(
season=
Case(
When(registered_on__month__in=[1,2,3], then=Value("Spring")),
When(registered_on__month__in=[4,5,6], then=Value("Summer")),
When(registered_on__month__in=[7,8,9], then=Value("Autumn")),
When(registered_on__month__in=[10,11,12], then=Value("Winter")),
default=Value("Spring")
)
)
获取字段值
如果该字段取值是获取某个字段的内容,比如 Client 里的 name 字段,就不需要 Value() 函数来操作,可以直接使用:
When(registered_on__month__in=[1,2,3], then="name")
或者通过 F() 函数来取字段值:
在不需要对字段内容进行操作的情况下,上面两条命令的作用是一样的
from django.db.models import F
When(registered_on__month__in=[1,2,3], then=F("name"))
3、条件搜索
还是前面的需求,我们需要对 Client 的数据进行数据筛选,筛选出 season 为 Spring 的数据,可以在上面的操作中接着 filter():
results = Client.objects.annotate(
season=
Case(
When(registered_on__month__in=[1,2,3], then=Value("Spring")),
When(registered_on__month__in=[4,5,6], then=Value("Summer")),
When(registered_on__month__in=[7,8,9], then=Value("Autumn")),
When(registered_on__month__in=[10,11,12], then=Value("Winter")),
default=Value("Spring")
)
).filter(season="Spring")
根据条件进行filter
对于 Client 这个 model,我们想实现这样的搜索条件:
如果 account_type 的值为 Client.GOLD,则 registered_on 字段搜索一个月以前的数据
如果 account_type 的值为 Client.PLATINUM,则 registered_on 字段搜索一年前的数据
对于这个需求,在之前我们怎么做?
使用 Q() 语法来连接:
from blog.models import Client
from datetime import date, timedelta
from django.db.models import Q
a_month_ago = date.today() - timedelta(days=30)
a_year_ago = date.today() - timedelta(days=365)
condition = (Q(account_type=Client.GOLD) & Q(registered_on__lte=a_month_ago))| \
(Q(account_type= Client.PLATINUM) & Q(registered_on__lte= a_year_ago))
Client.objects.filter(condition)
在这里,如果用到我们的 Case 和 When 的函数也是可以的:
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)
)
)
4、条件更新
除了前面对数据进行条件的筛选,我们还可以根据条件来对数据进行更新
假设现在需求是对 registered_on 字段的年份进行条件更新:
年份为 2020年的 account_type 字段内容变为 Client.PLATINUM
年份为 2021年的 account_type 字段内容变为 Client.REGULAR
那么相应的代码应该如下:
Client.objects.update(
account_type=Case(
When(registered_on__year=2020, then=Value(Client.PLATINUM)),
When(registered_on__year=2021, then=Value(Client.REGULAR)),
default=Value(Client.GOLD)
)
)
需要注意的是,在上面的代码中我们没有针对数据进行 filter() 操作,所以作用的是全表数据,其他非 2020 和 2021 年份的数据也会被更新,如果仅希望操作 2020 和 2021 年的数据,可以加上 filter() 的条件限制:
Client.objects.filter(registered_on__year__in=[2020, 2021]).update(
account_type=Case(
When(registered_on__year=2020, then=Value(Client.PLATINUM)),
When(registered_on__year=2021, then=Value(Client.REGULAR)),
default=Value(Client.GOLD)
)
)
5、条件聚合
我们现在需要对数据根据条件进行聚合操作,比如 Client 这个 model,我们对其按照 account_type 分组,获取各自的总数。
代码如下:
from django.db.models import Count, Q
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': 1, 'gold': 0, 'platinum': 3}