Django一分钟:lookupAPI详解,使用django orm生成高效的WHERE子句

一、Lookup API概述

Lookup API是Django用于构建数据库查询WHERE子句的API。

Lookup API的核心包含两部分:

  • RegisterLookupMixin:为子类提供注册lookup的方法
  • Query Expression API:一个接口,规定了可以被注册为lookup的类需要实现的方法。

在Django中有两个实现了Query Expression API的基类分别是:LookupTransform;前者用于声明查询条件,后者专注于对字段做转换。两者都实现了Query Expression API,都可以被注册为lookup,可以单独使用,也可以互相配合。

lookup是一个比较抽象的名词,它描述的是搜索和过滤条件,而不是查找这个动作本身。对字段注册lookup,我们就能构造查询表达语句,Django会将其编译为合适的SQLWHERE子句。

二、Lookup基类

前面提到实现Query Expression API的基类分别是LookupTransform。首先介绍Lookup类,它是我们构造查询语句的主力,我们从自定义一个Lookup的子类出发来了解它的工作原理。

1. 自定义Lookup

尝试实现这样一件事情,将User.objects.filter(username__is='Mario')转化为SQL:

sql 复制代码
"user"."username" IS 'mario'

当然SQL语句中不存在"IS",这样只是方便理解。实现这个功能需要两个步骤:

  1. 声明一个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
  1. 注册新声明的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查找类的方法有两种:

  1. 构造<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
  1. 使用创建实例的形式
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,但TransformLookup不同,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实现复杂的查询需求。
相关推荐
落落落sss26 分钟前
项目集成sharding-jdbc
android·java·数据库·spring·mybatis
尘浮生32 分钟前
Java项目实战II基于Java+Spring Boot+MySQL的酒店客房管理系统(源码+数据库+文档)
java·开发语言·数据库·spring boot·mysql·spring·intellij-idea
栀栀栀栀栀栀37 分钟前
Redis笔记(基本操作+Java实现)
java·数据库·mybatis
看山还是山,看水还是。42 分钟前
Web 渗透与防护
数据库·websocket·网络协议·web安全·网络安全·信息与通信·嵌入式实时数据库
Q186000000001 小时前
安装MySQL驱动程序笔记一
数据库·笔记·mysql
杨文同学2 小时前
MySQL13、事务&MVCC
数据库
王清欢Randy2 小时前
Apache Arrow IPC 消息格式
数据库·序列化·apache arrow·列存储引擎
shangtaobocdns3 小时前
django+vue
vue.js·python·django
qq_294481313 小时前
vscode 配置django
ide·vscode·django
天蓝蓝235283 小时前
Vue.js与Flask/Django后端配合
vue.js·django·flask