草庐IT

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

hunterxiong 2023-05-15 原文

本文首发于公众号: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, 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")
		)
	)

在上面的代码中,我们通过 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 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)
	)
)

一个例子

之前我在工作中遇到这样一种需求,假设有一个 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 platinum
FROM blog_client;

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

Client.objects.values("account_type").annotate(count=Count("account_type"))

返回的结果形式为:

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

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

如果想获取更多后端相关文章,可扫码关注阅读:

有关Django笔记二十三之case、when操作条件表达式搜索、更新等操作的更多相关文章

  1. ruby-on-rails - rails : "missing partial" when calling 'render' in RSpec test - 2

    我正在尝试测试是否存在表单。我是Rails新手。我的new.html.erb_spec.rb文件的内容是:require'spec_helper'describe"messages/new.html.erb"doit"shouldrendertheform"dorender'/messages/new.html.erb'reponse.shouldhave_form_putting_to(@message)with_submit_buttonendendView本身,new.html.erb,有代码:当我运行rspec时,它失败了:1)messages/new.html.erbshou

  2. ruby-on-rails - 如何验证 update_all 是否实际在 Rails 中更新 - 2

    给定这段代码defcreate@upgrades=User.update_all(["role=?","upgraded"],:id=>params[:upgrade])redirect_toadmin_upgrades_path,:notice=>"Successfullyupgradeduser."end我如何在该操作中实际验证它们是否已保存或未重定向到适当的页面和消息? 最佳答案 在Rails3中,update_all不返回任何有意义的信息,除了已更新的记录数(这可能取决于您的DBMS是否返回该信息)。http://ar.ru

  3. ruby - 主要 :Object when running build from sublime 的未定义方法 `require_relative' - 2

    我已经从我的命令行中获得了一切,所以我可以运行rubymyfile并且它可以正常工作。但是当我尝试从sublime中运行它时,我得到了undefinedmethod`require_relative'formain:Object有人知道我的sublime设置中缺少什么吗?我正在使用OSX并安装了rvm。 最佳答案 或者,您可以只使用“require”,它应该可以正常工作。我认为“require_relative”仅适用于ruby​​1.9+ 关于ruby-主要:Objectwhenrun

  4. ruby-on-rails - 使用 rails 4 设计而不更新用户 - 2

    我将应用程序升级到Rails4,一切正常。我可以登录并转到我的编辑页面。也更新了观点。使用标准View时,用户会更新。但是当我添加例如字段:name时,它​​不会在表单中更新。使用devise3.1.1和gem'protected_attributes'我需要在设备或数据库上运行某种更新命令吗?我也搜索过这个地方,找到了许多不同的解决方案,但没有一个会更新我的用户字段。我没有添加任何自定义字段。 最佳答案 如果您想允许额外的参数,您可以在ApplicationController中使用beforefilter,因为Rails4将参数

  5. Ruby 元类 : why three when defined singleton methods? - 2

    让我们计算MRI范围内的类别:defcount_classesObjectSpace.count_objects[:T_CLASS]endk=count_classes用类方法定义类:classAdefself.foonilendend然后运行:putscount_classes-k#=>3请解释一下,为什么是三个? 最佳答案 查看MRI代码,每次你创建一个Class时,在Ruby中它是Class类型的对象,ruby会自动为这个新类创建“元类”类,这是另一个单例类型的Class对象。C函数调用(class.c)是:rb_define

  6. ruby - 如何根据特征实现 FactoryGirl 的条件行为 - 2

    我有一个用户工厂。我希望默认情况下确认用户。但是鉴于unconfirmed特征,我不希望它们被确认。虽然我有一个基于实现细节而不是抽象的工作实现,但我想知道如何正确地做到这一点。factory:userdoafter(:create)do|user,evaluator|#unwantedimplementationdetailshereunlessFactoryGirl.factories[:user].defined_traits.map(&:name).include?(:unconfirmed)user.confirm!endendtrait:unconfirmeddoenden

  7. ruby - 在 Ruby 中有条件地定义函数 - 2

    我有一些代码在几个不同的位置之一运行:作为具有调试输出的命令行工具,作为不接受任何输出的更大程序的一部分,以及在Rails环境中。有时我需要根据代码的位置对代码进行细微的更改,我意识到以下样式似乎可行:print"Testingnestedfunctionsdefined\n"CLI=trueifCLIdeftest_printprint"CommandLineVersion\n"endelsedeftest_printprint"ReleaseVersion\n"endendtest_print()这导致:TestingnestedfunctionsdefinedCommandLin

  8. ruby - 定义方法参数的条件 - 2

    我有一个只接受一个参数的方法:defmy_method(number)end如果使用number调用方法,我该如何引发错误??通常,我如何定义方法参数的条件?比如我想在调用的时候报错:my_method(1) 最佳答案 您可以添加guard在函数的开头,如果参数无效则引发异常。例如:defmy_method(number)failArgumentError,"Inputshouldbegreaterthanorequalto2"ifnumbereputse.messageend#=>Inputshouldbegreaterthano

  9. ruby-on-rails - Nokogiri:使用 XPath 搜索 <div> - 2

    我使用Nokogiri(Rubygem)css搜索寻找某些在我的html里面。看起来Nokogiri的css搜索不喜欢正则表达式。我想切换到Nokogiri的xpath搜索,因为这似乎支持搜索字符串中的正则表达式。如何在xpath搜索中实现下面提到的(伪)css搜索?require'rubygems'require'nokogiri'value=Nokogiri::HTML.parse(ABBlaCD3"HTML_END#my_blockisgivenmy_bl="1"#my_eqcorrespondstothisregexmy_eq="\/[0-9]+\/"#FIXMEThefoll

  10. ruby - 怎么来的(a_method || :other) returns :other only when assigning to a var called a_method? - 2

    给定以下方法:defsome_method:valueend以下语句按我的预期工作:some_method||:other#=>:valuex=some_method||:other#=>:value但是下面语句的行为让我感到困惑:some_method=some_method||:other#=>:other它按预期创建了一个名为some_method的局部变量,随后对some_method的调用返回该局部变量的值。但为什么它分配:other而不是:value呢?我知道这可能不是一件明智的事情,并且可以看出它可能有多么模棱两可,但我认为应该在考虑作业之前评估作业的右侧...我已经在R

随机推荐