一、Lookup API概述
Lookup API是Django用于构建数据库查询WHERE
子句的API。
Lookup API的核心包含两部分:
RegisterLookupMixin
:为子类提供注册lookup的方法Query Expression API
:一个接口,规定了可以被注册为lookup的类需要实现的方法。
在Django中有两个实现了Query Expression API
的基类分别是:Lookup
和Transform
;前者用于声明查询条件,后者专注于对字段做转换。两者都实现了Query Expression API
,都可以被注册为lookup,可以单独使用,也可以互相配合。
lookup是一个比较抽象的名词,它描述的是搜索和过滤条件,而不是查找这个动作本身。对字段注册lookup,我们就能构造查询表达语句,Django会将其编译为合适的SQLWHERE
子句。
二、Lookup基类
前面提到实现Query Expression API
的基类分别是Lookup
和Transform
。首先介绍Lookup
类,它是我们构造查询语句的主力,我们从自定义一个Lookup
的子类出发来了解它的工作原理。
1. 自定义Lookup
尝试实现这样一件事情,将User.objects.filter(username__is='Mario')
转化为SQL:
sql
"user"."username" IS 'mario'
当然SQL语句中不存在"IS",这样只是方便理解。实现这个功能需要两个步骤:
- 声明一个
Lookup
的子类,并重写as_sql()
方法:
python
from django.db.models import Lookup
class Is(Lookup):
lookup_name = "is"
def as_sql(self, compiler, connection):
lhs, lhs_params = self.process_lhs(compiler, connection)
rhs, rhs_params = self.process_rhs(compiler, connection)
params = lhs_params + rhs_params
return "%s IS %s" % (lhs, rhs), params
- 注册新声明的lookup类
Is
:
需要lookup在某个字段上生效,就需要调用这个字段上的register_lookup
方法进行注册。Filed
继承了RegisterLookupMixin
类,因此可以使用其提供的register_lookup
方法注册lookup。为Field
注册lookup,其所有子类就都可以使用此lookup:
python
from django.db.models import Field
Field.register_lookup(Is)
Filed
提供了装饰器方法注册lookup:
python
from django.db.models import Field
@Field.register_lookup
class Is(Lookup): ...
使用新定义的Is
查找类的方法有两种:
- 构造
<lhs>__<lookup_name>=<rhs>
形式的查询表达语句:
python
from django.contrib.auth.models import User
q = User.objects.filter(username__is="mairo")
q.query
print(q.query)
编译的SQL语句:
SQL
SELECT ...略 FROM "auth_user" WHERE "auth_user"."username" IS mairo
- 使用创建实例的形式
python
q = User.objects.filter(username__is="mairo")
from django.db.models import F
q2 = User.objects.filter(Is(F('username'), 'mario'))
编译的SQL语句与上面的方法完全一致,两种方法是等效的。
2. Django内置的lookup
Django内置了很多lookup,在django.db.models.lookups
目录下你可以找到它们:
python
...
@Field.register_lookup
class GreaterThan(FieldGetDbPrepValueMixin, BuiltinLookup):
lookup_name = "gt"
@Field.register_lookup
class GreaterThanOrEqual(FieldGetDbPrepValueMixin, BuiltinLookup):
lookup_name = "gte"
...
介绍两种常用的内置lookup:
- exact
默认情况下当你没有提供查找类型的时候默认使用exact
:
python
Entry.objects.get(id=14)
Entry.objects.get(id__exact=14)
# 上面两个完全一致,准确来说django将id=14默认视为id__exact=14
等效SQL语句:
sql
SELECT ... WHERE id = 14;
- in
python
Entry.objects.filter(id__in=[1, 3, 4])
等效SQL语句:
SELECT ... WHERE id IN (1, 3, 4);
三、Transform基类
与Lookup
类相同Transform
类及其子类也可以被注册为lookup,但Transform
与Lookup
不同,Transform 更加关注的是如何改变字段值,而不是直接进行值的比较。通过使用 Transform
,你可以对数据库中的数据执行诸如转换为小写、截取字符串的一部分、日期时间的格式化等操作。通常会和Lookup
配合使用,它主要做预处理工作。它是数据库级别的转换,具有很好的性能。
1. 自定义Transform
python
from django.db.models import Transform
from django.db.models import IntegerField
class AbsoluteValue(Transform):
lookup_name = "abs"
function = "ABS"
IntegerField.register_lookup(AbsoluteValue)
Transform
在源码中继承了两个类class Transform(RegisterLookupMixin, Func)
,可以说它是一个特殊的Func
,在声明时就规定好了用于转换的function
,因此只接收一个参数。
- 使用示例1:
python
Experiment.objects.filter(change__abs=27)
生成的SQL:
sql
SELECT ... WHERE ABS("experiments"."change") = 27
注意这里django实际上将change__abs=27
视作 change__abs__exact=27
。
- 使用示例2:
python
Experiment.objects.filter(change__abs__lt=27)
生成的SQL:
sql
SELECT ... WHERE ABS("experiments"."change") < 27
2. 内置的Transform
- year
python
Blog.objects.filter(time__year__gt=2023)
生成的SQL
sql
SELECT ... FROM "snippets_blog" WHERE "snippets_blog"."time" > 2023-12-31 23:59:59.999999+00:00
Django内置了很多中方便的transform,这里不做赘述。
四、lookup表达语句的构成
一个lookup表达语句可以由三部分构成,组合使用可以构造强大高效的查询语句。
- field:如
Book.objects.filter(author__best_friends__first_name...)
- Transform:如
__lower__first3chars__reversed
- lookup:如
__icontains
默认__exact
五、总结
- lookup API是Django中一个很重要的功能,它的主要工作就是构造查询的
WHERE
子句。 - Django已经内置了很多使用的lookups帮助我们快速构造查询语句。
- Django允许我们自定义lookups实现复杂的查询需求。