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

一、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",这样只是方便理解。实现这个功能需要两个步骤:

  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,但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实现复杂的查询需求。
相关推荐
爱喝水的鱼丶1 小时前
SAP-ABAP:S/4 HANA ABAP 新特性入门:与 ECC ABAP 的核心差异、架构优势与学习路径规划
运维·数据库·性能优化·sap·abap·hana·开发交流
余槐i8 小时前
PostgreSQL 函数索引报错 must be immutable:3种绕法与代价
数据库·后端·sql·postgresql·性能优化
数据库小学妹8 小时前
PostgreSQL表膨胀怎么办?autovacuum清不动的根因与排查命令
数据库·postgresql·表膨胀·mvcc·数据库运维
mister_guo8 小时前
MySQL-1267错误:排序规则冲突的原因与解决方案
数据库·mysql
骇客野人9 小时前
在麒麟操作系统上 分别安装 jdk1.8, 达梦数据库,redis ,memcache 的操作实施方案和步骤
数据库·redis·memcached
美狐美颜SDK开放平台10 小时前
直播APP如何实现美白滤镜功能?视频美颜SDK技术开发方案详解
前端·数据库·人工智能·音视频·美颜sdk·直播美颜sdk·视频美颜sdk
花间相见12 小时前
【计算基础|MySQL01】【上手】安装连接、数据类型与建库建表
数据库·后端
花间相见12 小时前
【计算基础|MySQL02】【上手】SQL 增删改查
数据库·后端
ITxiaobing202312 小时前
IP 库与 AppsFlyer 国家/地区数据不一致,如何系统地对账?
数据库·网络协议·tcp/ip
这个DBA有点耶12 小时前
从时间到语义:时序数据库与向量数据库的核心机制、选型与多模融合趋势
数据库·mysql·架构