Django中的Q查询和F查询

1 环境

Python3.7.3
Django==2.0.6

2. F()表达式

2.1 进行+1操作

  一个F()对象代表一个模型字段的值或注释列。使用它可以直接引用模型字段的值并执行数据库操作而不用把它们导入到python的内存中。相反,Django使用F()对象生成一个描述数据库级别所需操作的SQL表达式。

通过一个例子很容易理解。通常,有人会这样做:

reporter = this_models.objects.get(name='xionglihong')
reporter.field += 1
reporter.save()

  这里我们从数据库中取出reporter.field的值放入内存中并使用我们熟悉的python运算符操作它,然后把对象保存到数据库中。

但是我们还可以这样做:

from django.db.models import F

reporter = this_models.objects.get(name='xionglihong')
reporter.field = F('field') + 1
reporter.save()

  虽然field=F(‘field’) + 1看起来像常规的Python为实例属性赋值,但实际上它是一个描述数据库上操作的SQL结构。
当Django遇到要给F()实例,它会覆盖标准的Python运算符来创建一个封装的SQL表达式。

  在这个例子中,指示数据库增加由field表示的数据库字段,无论field的值曾经是什么,Python永远不需要知道,完全由数据库来处理。Python通过Django的F()类做的所有事情仅是参考某个字段创建SQL语法来描述操作。

像上面这样在单个实例中使用,F()可以通过updage()方法被用在QuerySets对象实例中。这样可以省略上面用到的两个查询–get()和save():

reporter = Reporters.objects.filter(name='Tintin')
reporter.update(stories_field=F('stories_field') + 1)

我们也可以使用update()来增加多个对象的字段值,这样比从数据中取出他们,通过循环挨个增加值,然后再保存回数据库中要更加快速:

Reporters.objects.all().update(stories_field=F('stories_field') + 1)

2.2 性能的提升

因此,F()可以通过以下方式提供性能优势:

  • 直接在数据库中操作而不是python
  • 减少一些操作所需的数据库查询次数

2.3 使用F()避免竞争关系

  使用F()的另一个好处是使用数据库来更新字段而不是用Python,以此避免竞争关系。

  如果有两个Python线程执行上面第一个示例,A线程可能在B线程从数据库检索数据后来检索、增加并保存某个字段值。那么B线程的保存将基于他最初检索到的原始值;而A线程的工作将会丢失。

  如果有数据库自身负责更新字段,这个过程将会更加健壮:在执行sasve()和update()时基于数据库中当前的字段值,而不是基于被检索出的实例的值。

2.4 F()操作在Model.save()后会持续存在

分配给模型字段的F()对象在模型实例保存后依然保持不变,并会应用于之后的每个save()。例如:

obj = models.Test.objects.get(name="cox")
obj.times = F(times) + 1
obj.save()
obj.save()
obj.save()

times如果初始值为1,那么最终值将会是4

2.5 F()再过滤(filter)中的使用

  F()在QuerySet过滤器中也非常有用,通过它可以实现基于自身字段值来过滤一组对象,而不是通过Python值。

  F()实例充当查询中一个模型字段的引用。这些引用在查询过滤器可以被用来比较同一模型实例的两个不同字段的值。

例如,我们要查找所有Blog记录中comment多于pingback的记录,我们构建一个F()对象来引用pingback的数量:

from django.db.models import F

Entry.objects.filter(n_commnets__gt=F('n_pingbacks'))

Django支持F()对象使用加、减、乘、除、取模和幂运算等算术操作,两个操作数可以是常数或F()对象。要查询blog记录中comment大于两本pingback的记录,修改查询为:

Entry.objects.filter(n_comments__gt=F('n_pingbacks') * 2)

为了查询rating 比pingback 和comment 数目总和要小的记录,我们将这样查询:

Entry.objects.filter(rating__lt=F('n_comments') + F('n_pingbacks'))

你还可以在F()对象中使用双下划线标记来跨越关联关系。 带有双下划线的F()对象将引入任何需要的join 操作以访问关联的对象。 例如,如要获取author 的名字与blog 名字相同的Entry,我们可以这样查询:

Entry.objects.filter(authors__name=F('blog__name'))

对于date 和date/time 字段,你可以给它们加上或减去一个timedelta对象。 下面的例子将返回发布超过3天后被修改的所有Entry:

from datetime import timedelta

Entry.objects.filter(mod_date__gt=F('pub_date') + timedelta(days=3))

2.6 在annotate中使用F()

F()可以使用算术运算组合不同字段在模型中创建动态字段。

company = Company.objects.annotate(chairs_needed=F('num_employee') - F('num_chairs'))

如果组合的是不同类型的字段,你需要告诉Django返回值是哪种字段类型。由于F()不直接支持output_field,所以需要使用ExpressionWrapper来封装表达式:

from django.db.models import DatetimeField, ExpressionWrapper, F
Ticket.objects.annotate(
    expires=ExpressionWrapper(
        F('active_at') + F('duration'), output_field=DateTimeField()
    )
)

当引用关联字段(如ForeignKey)时,F()返回的主键值而不是一个模型实例:

>>> car = Company.objects.annotate(built_by=F('manufacturrer'))[0]
>>> car.manufacturer
<Manufacturer: Toyota>
>>> car.built_by
3

2.7 使用F()对null值排序

  使用F()和传递nulls_first或nulls_last参数给Expression.asc()或desc()来控制字段的null值的排序。默认情况下这个排序取决于你的数据库。

例如,要将未联系过(last_contacted为null)的公司排在已联系过的公司后面:

from django.db.models import F

Company.objects.order_by(F('last_contacted').desc(nulls_last=True))

3 Q()表达式

3.1 “且”或者“或”

  当我们在查询的条件中需要组合条件时(例如两个条件“且”或者“或”)时。我们可以使用Q()查询对象。

  这样就生成了一个Q()对象,我们可以使用符号&或者|将多个Q()对象组合起来传递给filter(),exclude(),get()等函数。当多个Q()对象组合起来时,Django会自动生成一个新的Q()。例如下面代码就将两个条件组合成了一个

例如下面的代码

from django.db.models import Q

this_q=Q(name="cox") | Q(name="Tom") # 或
models.Author.objects.filter(this_q) # 获取在Author表中,name等于cox和name等于cox的所有数据

this_q=Q(name="cox") & Q(age=12) # 且
models.Author.objects.filter(Q(name="cox") & Q(age=12))# 获取在Author表中,name等于cox并且age等于12的所有数据

3.2 非

Q(question__startswith='Who') | ~Q(pub_date__year=2005)
models.Author.objects.filter(this_q) # 获取在Author表中,question__startswith等于Who,或者 pub_date__year不低于2005的所有数据

这样我们可以使用 “&”或者“|”还有括号来对条件进行分组从而组合成更加复杂的查询逻辑。

3.3 多个Q表达式作为查询条件

  可以传递多个Q()对象给查询函数

例如下面代码:

News.objects.get(Q(question__startswith='Who'),Q(pub_date=date(2005, 5, 2)) | Q(pub_date=date(2005, 5, 6)))

3.4 Q表达式与普通表达式一起使用

  Q()对象可以结合关键字参数一起传递给查询函数,不过需要注意的是要将Q()对象放在关键字参数的前面

看下面代码

News.objects.get(Q(pub_date=date(2005, 5, 2)) | Q(pub_date=date(2005, 5, 6)),question__startswith='Who')

提醒下,Q()表达式结合关键字参数一起传递给查询函数,Q表达式一定要在前面

下面是错误的实例(重要的事情说三次,这是错误的实例,这是错误的实例,这是错误的实例)

News.objects.get(question__startswith='Who',Q(pub_date=date(2005, 5, 2)) | Q(pub_date=date(2005, 5, 6)))

3.5 Q()传入条件查询

实例一:

q1 = Q()
q1.connector = 'OR'
q1.children.append(('name', "cox"))
q1.children.append(('name', "Tom"))
q1.children.append(('name', "Jeck"))
    
models.Author.objects.filter(q1) # 在Author表中,name等于cox/Tom/Jeck的所有数据

实例二:

con = Q()

q1 = Q()
q1.connector = 'OR'
q1.children.append(('name', "cox"))
q1.children.append(('name', "Tom"))
q1.children.append(('name', "Jeck"))

q2 = Q()
q2.connector = 'OR'
q2.children.append(('age', 12))

con.add(q1, 'AND')
con.add(q2, 'AND')

models.Author.objects.filter(con) # 在Author表中,name等于cox/Tom/Jeck的 并且 满足age等于12 的所有数据

3.6~Q() 表达式

在Q()语句中,~代表非

models.Author.objects.filter(~Q(name="cox")) # 获取在Author表中,name不等于cox的所有数据
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值