- 小知识,大挑战!本文正在参与「程序员必备小知识」创作活动
通过 Q
对象完成复杂查询
多个过滤器逐个调用表示逻辑与关系,同sql
语句中where
部分的and
关键字。
eg:查询阅读量大于20,图书编号小于3的书。
Models.objects.filter(bread__gt=20,id__lt=3)
or
Models.objects.filter(bread__gt=20).filter(id__lt=3)
如果需要实现逻辑或or的查询,需要使用Q()对象结合|运算符.
在类似 filter()
中,查询使用的关键字参数是通过 "AND" 连接起来的。如果你要执行更复杂的查询(例如,由 OR
语句连接的查询),你可以使用 Q 对象
语法如下:
Q(属性名__运算符=值)
eg:查询阅读量大于20,图书编号小于3的书。
form django.db.models import Q
Models.objects.filter(Q(bread__gt=20) & Q(id__lt=3))
Q对象
可以使用&
、 |
表示逻辑与
和或
。
一个Q 对象
(django.db.models.Q
) 用于压缩关键字参数集合。这些关键字参数由前文 "Field lookups" 指定。
例如,该 Q
对象压缩了一个 LIKE
查询:
from django.db.models import Q
Q(question__startswith='What')
Q
对象能通过 &
和 |
操作符连接起来。当操作符被用于两个 Q
对象之间时会生成一个新的 Q
对象。
例如,该语句生成一个 Q
对象,表示两个 "question_startswith"
查询语句之间的 "OR" 关系:
Q(question__startswith='Who') | Q(question__startswith='What')
这等价于以下 SQL WHERE
字句:
WHERE question LIKE 'Who%' OR question LIKE 'What%'
你能通过 &
和 |
操作符和括号分组,组合任意复杂度的语句。当然, Q
对象也可通过 ~
操作符反转,允许在组合查询中组合普通查询或反向 (NOT
) 查询:
Q(question__startswith='Who') | ~Q(pub_date__year=2005)
每个接受关键字参数的查询函数 (例如 filter()
, exclude()
, get()
也同时接受一个或多个 Q
对象作为位置(未命名的)参数。若你为查询函数提供了多个 Q
对象参数,这些参数会通过 "AND" 连接。例子:
Poll.objects.get(
Q(question__startswith='Who'),
Q(pub_date=date(2005, 5, 2)) | Q(pub_date=date(2005, 5, 6))
)
...粗略地转为 SQL:
SELECT * from polls WHERE question LIKE 'Who%'
AND (pub_date = '2005-05-02' OR pub_date = '2005-05-06')
查询函数能混合使用 Q
对象和关键字参数。所有提供给查询函数的参数(即关键字参数或 Q
对象)均通过 "AND" 连接。然而,若提供了 Q
对象,那么它必须位于所有关键字参数之前。例子:
Poll.objects.get(
Q(pub_date=date(2005, 5, 2)) | Q(pub_date=date(2005, 5, 6)),
question__startswith='Who',
)
……会是一个有效的查询,等效于前文的例子;但是:
# INVALID QUERY
Poll.objects.get(
question__startswith='Who',
Q(pub_date=date(2005, 5, 2)) | Q(pub_date=date(2005, 5, 6))
)
……却是无效的。
F()
对象
- 可以使用模型的字段A与字段B进行比较,如果A写在了等号的左边,则B出现在等号的右边,需要通过F对象构造;
list.filter(bread__gte=F(‘bcommit’))
django支持对F()对象使用算数运算
list.filter(bread__gte=F(‘bcommit’)*2)
F()对象中还可以写作”模型类__列名”进行关联查询
list.filter(isDelete=F(‘heroinfo__isDelete’))
对于date/time字段,可与timedelta()进行运算
list.filter(bpub_date__lt=F(‘bpub_date’)+timedelta(days=1))
- class
F
AnF()
object represents the value of a model field, transformed value of a model field, or annotated column. It makes it possible to refer to model field values and perform database operations using them without actually having to pull them out of the database into Python memory.
取而代之的是,Django 使用 F()
对象来生成一个 SQL 表达式,在数据库层面描述所需的操作。
我们举个例子试试。通常情况下,我们可以这样做:
# Tintin filed a news story!
reporter = Reporters.objects.get(name='Tintin')
reporter.stories_filed += 1
reporter.save()
这里,我们从数据库中提取了 reporter.stories_filed
的值到内存中,并使用熟悉的 Python 操作符对其进行操作,然后将对象保存回数据库。但我们也可以这样做:
from django.db.models import F
reporter = Reporters.objects.get(name='Tintin')
reporter.stories_filed = F('stories_filed') + 1
reporter.save()
虽然 reporter.stories_filed = F('stories_filed') + 1
看起来像一个普通的 Python 赋值给一个实例属性,但实际上它是一个描述数据库操作的 SQL 结构。
当 Django 遇到 F()
的实例时,它会覆盖标准的 Python 运算符来创建一个封装的 SQL 表达式;在本例中,它指示数据库递增由 reporter.stories_filed
表示的数据库字段。
无论 reporter.stories_filed
上的值是多少,Python 永远不会知道它——它完全由数据库处理。通过 Django 的 F()
类,Python 所做的就是创建 SQL 语法来引用这个字段并描述操作。
要访问这样保存的新值,必须重新加载对象:
reporter = Reporters.objects.get(pk=reporter.pk)
# Or, more succinctly:
reporter.refresh_from_db()
F()
除了用于上述对单个实例的操作外,F()
还可以与 update()
一起用于对象实例的 QuerySets
。这就把我们上面使用的两个查询——get()
和 save()
减少到只有一个:
reporter = Reporters.objects.filter(name='Tintin')
reporter.update(stories_filed=F('stories_filed') + 1)
我们还可以使用 update()
来递增多个对象上的字段值——这可能比从数据库中把它们全部拉到 Python 中,在它们身上循环,递增每个对象的字段值,然后把每个对象保存回数据库要快得多:
Reporter.objects.all().update(stories_filed=F('stories_filed') + 1)
因此,F()
可以通过以下方式提供性能优势:
- 让数据库,而不是 Python 来完成工作
- 减少某些操作所需的查询次数
Changed in Django 3.2:
Support for transforms of the field was added.
使用 F()
避免竞争条件
F()
的另一个有用的好处是,让数据库——而不是 Python——更新一个字段的值,避免了 竞争条件。
如果两个 Python 线程执行上面第一个例子中的代码,一个线程可以在另一个线程从数据库中获取一个字段的值后,检索、递增并保存它。第二个线程保存的值将基于原始值,第一个线程的工作将丢失。
如果数据库负责更新字段,那么这个过程就比较稳健:它只会在执行 save()
或 update()
时,根据数据库中字段的值来更新字段,而不是根据检索实例时的值来更新。
F()
赋值在 Model.save()
之后持续存在
F()
分配给模型字段的对象在保存模型实例后会持续存在,并将应用于每个 save()
。例如:
reporter = Reporters.objects.get(name='Tintin')
reporter.stories_filed = F('stories_filed') + 1
reporter.save()
reporter.name = 'Tintin Jr.'
reporter.save()
在这种情况下,stories_filed
将被更新两次。如果最初是 1
,最终值将是 3
。这种持久性可以通过在保存模型对象后重新加载来避免,例如,使用 refresh_from_db()
。
在过滤器中使用 F()
F()
在 QuerySet
过滤器中也非常有用,它们可以根据对象的字段值而不是 Python 值的标准来过滤一组对象。
这在 在查询中使用 F() 表达式
中有所记载。
与注解一起使用 `F()``
F()
可用于通过将不同的字段与算术相结合,在你的模型上创建动态字段:
company = Company.objects.annotate(
chairs_needed=F('num_employees') - 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('manufacturer'))[0]
>> car.manufacturer
<Manufacturer: Toyota>
>> car.built_by
3
使用 F()
对空值进行排序
使用 F()
和 Expression.asc()
或 esc()
的关键词参数 nulls_first
或 nulls_last
来控制字段的空值的排序。默认情况下,排序取决于你的数据库。
例如,在已经联系过的公司之后,对尚未联系过的公司进行排序(last_contacted
为空):
from django.db.models import F
Company.objects.order_by(F('last_contacted').desc(nulls_last=True))