Django——QuerySet详解
QuerySet API 参考 | Django 文档 | Django (djangoproject.com)
前言:Django ORM(对象关系映射)
Django ORM
用到三个类:Manager
、QuerySet
、Model
。
Manager
定义表级方法(表级方法就是影响一条或多条记录的方法),可以以models.Manager
为父类,定义自己的Manager
,增加表级方法;
QuerySet
是一个可遍历结构,包含一个或多个元素,每个元素都是一个Model
实例,该元素的方法也是表级方法。一般来说没有自定义QuerySet
类的必要,如果想要增加表级方法,可以自定义Manage
类。
Model
类,定义表的model
时就是继承该类,该类的功能恒强大,通过自定义model
的instance
可以获取外键实体等。它的方法都是记录级方法,不要在里面定义类方法,如统计记录总数、查看所有记录等,这些都应该放在自定义的Manager
类中。
QuerySet
简介
QuerySet
是Django
提供的强大的数据库接口(API)。通过该接口,可以使用filter
、exclude
、get
等方法进行查询,而不需要使用原始的SQL
语言与数据库进行交互了。从数据库中查询出来的数据结果一般是一个集合,这个集合就叫做QuerySet
。
每个Model
都有一个默认的Manager
实例,名为Objects
,QuerySet
有两种来源:通过Manager
方法得到、通过QuerySet
方法得到。
QuerySet
时延迟获取的,只有用到这个QuerySet
的时候,才会查询数据库求值。另外,查询到的QuerySet
是缓存的,当再次使用同一个QuerySet
时,不会再查询数据库,而是直接从缓存中取(有个别情况)。
一般而言,当对一个没有求值的QuerySet
进行的运算,返回的是QuerySet
、ValuesQuerySet
、ValuesListQuerySet
、Model
实例时,一般不会立即查询数据库;相反的,返回的不是这些类型时会查询数据库。
几种会导致QuerySet
立即查询数据库的操作
- 遍历
1 |
|
这里的每次遍历都要查询数据库,返回的a
的每条记录只包含Entry
表的字段值,不管Entry
的model
里有没有onetoone
,onetomany
,manytomany
字段,都不会关联查询。遵循数据库最小读写原则。
如果需要获得onetoone
或onetomany
,那么需要使用select_related
方法。而且通过select_related
方法获取的关联对象时model instance
,而不能是QuerySet
。
对于onttomany
的反向和manytomany
,要用prefetch_related
,返回的时多条关联记录,时QuerySet
。
- 使用步长分片
- 使用
len()
函数获取长度
推荐使用QuerySet.count()
方法,效率更高。
使用
list()
将QuerySet
转化成列表bool()
,判断是否为空
1 |
|
不建议使用该方法判断非空,应该使用QuerySet.exists()
,效率更高。
- Pickle序列化/缓存。
repr()
QuerySet
方法
QuerySet
的方法主要涉及删、改、查。
删除 delete()
无返回值。
相当于 delete-from-where,delete-from-join-where。先 filter,然后对得到的 QuerySet 执行 delete() 方法。会同时删除关联的记录。
如删除表 1 里的 A 记录,表 2 中的 B 记录中有 A 的外键,同时也会删除 B 记录。对于 ManyToMany 关系,删除其中一方的记录时,会同时删除中间表的记录,也就是删除双方的关联关系。
而有些数据库如 sqlite 不支持 delete 与 limit 连用,因此在这些数据库对 QuerySet 的切片执行 delete() 会出错。
例:
1 |
|
改 update()
可以修改多个字段,返回修改的记录数。但是 update() 中的键值对的键只能是主表中的字段,不能是关联表字段。
如果要修改关联表中的字段,最好先 filter,查询出 QuerySet,再执行 QuerySet.update()。
例:
1 |
|
查询
filter(**kwargs)、exclude(**kwargs)、get(**kwargs)
相当于 select-from-where,select-from-join-where。返回一个新的QuerySet,filter() 的参数是便个数的键值对,不会出现 >,<,!=,这些符号分别用 __gt,__lt,~Q或exclude(),但是对于 != 建议使用 Q 查询,这样更不容易出错。
可以使用双下划线对 OneToOne、OneToMany、ManyToMany 进行关联查询和反向关联查询,而且方法都是一样的。
SQL 其他关键字在 Django 中的实现
1. F 表达式(无对应 SQL 关键字)
常用来执行数据库层面的计算,从而避免出现竞争状态。
例:
1 |
|
2. Q 表达式(对应 AND/OR/NOT)
当使用 or 等逻辑关系,就使用 Q 类,filter 中的条件可以是 Q 表达式与非 Q 查询混合使用,但并不建议这么做。因为混合查询时 Q 表达式要放在前面,但是难免会忘记顺序而出错,所以在使用 Q 表达式的时候,就全部使用 Q 表达式。
Q 表达式解决 OR 查询的例子:
1 |
|
或者也可以进行 AND 查询:
1 |
|
进行NOT查询:
1 |
|
3. annotate() 无对应 SQL 关键字
详解 Django 的 annotate () 函数:对查询结果进行聚合 - Python 技术站 (pythonjishu.com)
给查询结果增加一个字段,用于统计、注解和计算后的结果。
示例:
模型定义如下:
1 |
|
若要查询每篇文章的评论数和每条评论的回复数,并按照评论数从高到低进行排序,那么可以这样:
1 |
|
4. order_by 对应 order by
1 |
|
返回结果:QuerySet
可以通过指定列进行排序。
例:
1 |
|
这里的结果是把 pub_date 降序排列,然后再按照 headline
升序排列。“-pub_date”前面的符号表示降序,升序是默认的,如果要随机排序,则需要使用“?
”,如:
1 |
|
这里需要注意order_by("?")
的查询速度很慢,而且会有高昂的查询成本,虽然取决于使用的数据库。
5. distinct 对应 distinct
返回结果:QuerySet
原型:
1 |
|
该方法用来消除查询结果中的重复记录,在与 order_by 连用时需要注意可能会导致意外的结果,使原本重复的行看起来是不同的。
一般与 values()、values_list() 连用,这时候返回 ValuesQuerySet、ValuesListQuerySet。
6. values() 和 values_list() 对应' select 某几个字段'
返回结果:ValuesQuerySet、ValuesListQuerySet
values() 函数原型:
1 |
|
这两个是数据库查询的结果集,前者返回的是一个字典数组,其中的每个字典都是数据库里的一行记录,可以通过键来访问值。后者返回时的是元组的列表,无法修改其中的值,其中的每个元组都代表一个对象的字段值。两者通常用于在数据查询中返回特定字段,以提高性能。
其中 *fields 字段制定了 SELECT 应该被限制的字段名,也就是可以限制返回的列:
1 |
|
如果使用了 **expressions 参数,那么这些参数会被传递给 annotate():
1 |
|
values_list() 函数原型:
1 |
|
与values() 不同的地方在于该方法返回的是元组。
如果只需要一个字段的话,可以传递 flat 参数,其值为 True 的话,那么返回的结果将是单个值,不是 1 元组:
1 |
|
使用多个字段时,不可以传入 flat 参数,否则会出错。
当使用 named=True 参数时,会把结果作为 namedtuple() 返回:
1 |
|
7. select_related() 对应返回关联记录实体
返回结果:QuerySet
函数原型:
1 |
|
可以指定返回哪些关联表的模型实例。在使用时 filter() 与 select_related() 的顺序并不重要。select_related() 主要针对一对一和一对多关系进行优化。
1 |
|
8. prefetch_related(*field) 对应返回关联记录实体的集合
返回结果:QuerySet
函数原型:
1 |
|
该接口主要解决多对多的关联关系。当然其实 prefetch_related() 也可以做 select_related() 能做的事情,但是性能会低效些。所以还是建议在多对多的时候后 prefetch_related(),而一对一,一对多交给 select_related() 去做。
1 |
|
9. extra() 实现复杂的 where 子句
函数原型:
1 |
|
该方法可以直接写一些 SQL 语句,但是不同的数据库使用的 SQL 也是有差异的,所以还是不推荐使用,另外这是很老的 API,万不得已的情况下不推荐用。
10. aggregate(*args, **kwargs) 对应聚合函数
返回结果:对 QuerySet 计算的聚合值(平均值、总和等)的字典。
参数为聚合函数,最好用 **kwargs 的形式,每个参数起一个名字,每个参数都指定了一个将被包含在返回的字典中的值。
该函数与 annotate () 的区别是:annotate() 相当于 aggregate () 和 group_by 的结合,对每个 group 执行 aggregate () 函数。而单独的 aggregate () 并没有 group_by。
1 |
|
11. exists()、count()、len()
如果只是想知道一个 QuerySet 是否为空,而不想获取 QuerySet 中的每个元素,那就用 exists (),它要比 len ()、count ()、和直接进行 if 判断效率高。如果只想知道一个 QuerySet 有多大,而不想获取 QuerySet 中的每个元素,那就用 count ();如果已经从数据库获取到了 QuerySet,那就用 len ()
12. contains/startswith/endswith 对应 like
字段名加双下划线,除了它,还有 icontains,即 Case-insensitive contains,这个是大小写不敏感的,这需要相应数据库的支持。有些数据库需要设置
才能支持大小写敏感。
13. in 对应 in
字段名加双下划线
14. exclude(field__in=iterable) 对应 not in
iterable 是可迭代对象
15. gt/gte/lt/lte 对应于 >,>=,<,<=
字段名加双下划线
16. range 对应于 between and
字段名加双下划线,range 后面值是列表
17. isnull 对应于 is null
Entry.objects.filter (pub_date__isnull=True)
对应的 SQL
为 SELECT ... WHERE pub_date IS NULL;
18. QuerySet 切片 对应于 limit
QuerySet 的索引只能是非负整数,不支持负整数,所以 QuerySet [-1] 错误
1 |
|
执行 Entry.objects.all ()[5:8]
,对于不同的数据库,SQL
语句不同,Sqlite 的 SQL 语句为
select * from tablename limit 3 offset 5
; MySQL 的 SQL
语句为 select * from tablename limit 5,3