写点什么

Django 笔记二十三之 case、when 操作条件表达式搜索、更新等操作

作者:Hunter熊
  • 2023-05-07
    北京
  • 本文字数:3655 字

    阅读完需:约 12 分钟

Django笔记二十三之case、when操作条件表达式搜索、更新等操作

本文首发于公众号:Hunter 后端

原文链接:Django笔记二十三之条件表达式搜索、更新等操作


这一篇笔记将介绍条件表达式,就是如何在 model 的使用中根据不同的条件筛选数据返回。


这个操作类似于数据库中 if elif else 的逻辑。


以下是本篇笔记的目录:


  1. model 和数据准备

  2. When 和 Case 操作新增字段返回

  3. 条件搜索

  4. 条件更新

  5. 条件聚合

1、model 和数据准备

这篇笔记我们用到的 model 是 Client,放在 blog/models.py 下


以下是 Client 的 model 定义:


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,    )
复制代码


其中 choices 的操作在前面字段类型中都有介绍到,这里不再赘述。


然后 migrate 相关操作这里不多说了,接下来插入一些数据,在 shell 中执行:


from blog.models import Client
Client.objects.create(name="client_1", registered_on="2020-01-01", account_type="R")Client.objects.create(name="client_2", registered_on="2021-07-12", account_type="G")Client.objects.create(name="client_3", registered_on="2022-09-20", account_type="P")Client.objects.create(name="client_4", registered_on="2022-12-07", account_type="P")
复制代码


接下来介绍我们操作的知识点。

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 字段的月份区间来得到一个新的字段:


from django.db.models import Case, Value, Whenfrom 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") ) )
复制代码


在上面的代码中,我们通过 annotate() 来新建一个 season 字段,这个字段的值是根据 registered_on 的月份所在区间来为 season 赋值。


Case() 函数内包含了四种 When 的可能性,然后会有一个 default 默认值


在每一个 When() 函数里,前一个是个表达式,可以是这种形式,也可以是 Q() 操作的语句,then= 表示如果满足前面的表达式,那么值的内容将会是后面的值。


在值的定义里,我们这里用到了 Value() 函数,Value() 表示其值是一个字符串。


获取字段值


如果该字段取值是获取某个字段的内容,比如 Client 里的 name 字段,就不需要 Value() 函数来操作,可以直接使用:


When(registered_on__month__in=[1,2,3], then="name")
复制代码


或者通过 F() 函数来取字段值:


from django.db.models import FWhen(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 Clientfrom datetime import date, timedeltafrom 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)  ))
复制代码


一个例子


之前我在工作中遇到这样一种需求,假设有一个 TestModel,有一个 field_1 的字段,他的值被有 A, B, C 三种或者还有其他的值,但其他的值我们不关心


现在需要将数据按照 B,C,A 的顺序返回结果,那么这里用到 Case 和 When 的处理方法就可以,我们可以通过条件得出一个新的字段 priority,然后 order_by("priority") 即可


处理如下:


TestModel.objects.annotate(  priority=Case(      When(field_1="B", then=1),      When(field_1="C", then=2),      When(field_1="A", then=3),      default=4  )).order_by("priority")
复制代码

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}
复制代码


这个操作对应于 MySQL 中的语句如下:


select count(CASE WHEN account_type='R' THEN id ELSE null end) as regular,       count(CASE WHEN account_type='G' THEN id ELSE null end) as gold,       count(CASE WHEN account_type='P' THEN id ELSE null end) as platinumFROM blog_client;
复制代码


我们也可以根据另一种方式来获取各自的总数数据,但是返回的结构是不一样的:


Client.objects.values("account_type").annotate(count=Count("account_type"))
复制代码


返回的结果形式为:


<QuerySet [{'account_type': 'P', 'count': 3}, {'account_type': 'R', 'count': 1}]>
复制代码


以上就是本篇笔记关于条件表达式的全部内容,在接下来几篇笔记中将会介绍 model 的数据库函数,大致的内容会是比较和转换函数、日期函数、数据公式、文本函数等。

发布于: 2023-05-07阅读数: 15
用户头像

Hunter熊

关注

公众号:Hunter后端 2018-09-17 加入

Python后端工程师,欢迎互相沟通交流

评论

发布
暂无评论
Django笔记二十三之case、when操作条件表达式搜索、更新等操作_Python_Hunter熊_InfoQ写作社区