Django——QuerySet详解

QuerySet API 参考 | Django 文档 | Django (djangoproject.com)

Django 学习笔记之 Queryset 详解 - 时光飞逝,逝者如斯 - 博客园 (cnblogs.com)

Queryset 特性及高级查询技巧 | 大江狗的博客 (pythondjango.cn)

前言:Django ORM(对象关系映射)

Django ORM用到三个类:ManagerQuerySetModel

Manager定义表级方法(表级方法就是影响一条或多条记录的方法),可以以models.Manager为父类,定义自己的Manager,增加表级方法;

QuerySet是一个可遍历结构,包含一个或多个元素,每个元素都是一个Model实例,该元素的方法也是表级方法。一般来说没有自定义QuerySet类的必要,如果想要增加表级方法,可以自定义Manage类。

Model类,定义表的model时就是继承该类,该类的功能恒强大,通过自定义modelinstance可以获取外键实体等。它的方法都是记录级方法,不要在里面定义类方法,如统计记录总数、查看所有记录等,这些都应该放在自定义的Manager类中。

QuerySet简介

QuerySetDjango提供的强大的数据库接口(API)。通过该接口,可以使用filterexcludeget等方法进行查询,而不需要使用原始的SQL语言与数据库进行交互了。从数据库中查询出来的数据结果一般是一个集合,这个集合就叫做QuerySet

每个Model都有一个默认的Manager实例,名为ObjectsQuerySet有两种来源:通过Manager方法得到、通过QuerySet方法得到。

QuerySet时延迟获取的,只有用到这个QuerySet的时候,才会查询数据库求值。另外,查询到的QuerySet是缓存的,当再次使用同一个QuerySet时,不会再查询数据库,而是直接从缓存中取(有个别情况)。

一般而言,当对一个没有求值的QuerySet进行的运算,返回的是QuerySetValuesQuerySetValuesListQuerySetModel实例时,一般不会立即查询数据库;相反的,返回的不是这些类型时会查询数据库。

几种会导致QuerySet立即查询数据库的操作

  1. 遍历
1
2
3
4
a = Entry.objects.all()

for e in a:
print(e.headline)

​ 这里的每次遍历都要查询数据库,返回的a的每条记录只包含Entry表的字段值,不管Entrymodel里有没有onetooneonetomanymanytomany字段,都不会关联查询。遵循数据库最小读写原则。

​ 如果需要获得onetooneonetomany,那么需要使用select_related方法。而且通过select_related方法获取的关联对象时model instance,而不能是QuerySet

​ 对于onttomany的反向和manytomany,要用prefetch_related,返回的时多条关联记录,时QuerySet

  1. 使用步长分片
  2. 使用len()函数获取长度

​ 推荐使用QuerySet.count()方法,效率更高。

  1. 使用list()QuerySet转化成列表

  2. bool(),判断是否为空

1
2
3
if Entry.objects.filter(headline='Test'):

print("There is at least one Entry with the headline Test")

不建议使用该方法判断非空,应该使用QuerySet.exists(),效率更高。

  1. Pickle序列化/缓存
  2. 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
2
3
4
5
6
# 删除单条记录
Entry.objects.filter(blog=b).delete()

# 批量删除记录
blogs = Blog.objects.all()
blogs.delete()

update()

可以修改多个字段,返回修改的记录数。但是 update() 中的键值对的键只能是主表中的字段,不能是关联表字段。

如果要修改关联表中的字段,最好先 filter,查询出 QuerySet,再执行 QuerySet.update()。

例:

1
2
3
4
5
6
7
8
# 修改单个字段
Entry.objects.filter(pub_date__year=2010).update(comments_on=False)

# 修改多个字段
Entry.objects.filter(pub_date__year=2010).update(comments_on=False, headline="This is old")

# 修改相关模型上的列
Entry.objects.filter(blog__id=1).update(comments_on=True)

查询 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
3
post = Post.objects.get(id=1)
post.pv = F('pv') + 1
post.save()

2. Q 表达式(对应 AND/OR/NOT)

当使用 or 等逻辑关系,就使用 Q 类,filter 中的条件可以是 Q 表达式与非 Q 查询混合使用,但并不建议这么做。因为混合查询时 Q 表达式要放在前面,但是难免会忘记顺序而出错,所以在使用 Q 表达式的时候,就全部使用 Q 表达式。

Q 表达式解决 OR 查询的例子:

1
Post.objects.filter(Q(id=1) | Q(id=2))

或者也可以进行 AND 查询:

1
Post.objects.filter(Q(id=1) & Q(id=2))

进行NOT查询:

1
Post.objects.filter(~Q(director="Nolan"))

3. annotate() 无对应 SQL 关键字

详解 Django 的 annotate () 函数:对查询结果进行聚合 - Python 技术站 (pythonjishu.com)

给查询结果增加一个字段,用于统计、注解和计算后的结果。

示例:

模型定义如下:

1
2
3
4
5
class Comment(models.Model):
content = models.CharField(max_length=200)
pub_date = models.DateTimeField()
article = models.ForeignKey(Article, on_delete=models.CASCADE)
parent_comment = models.ForeignKey('self', on_delete=models.CASCADE, null=True, blank=True, related_name='child_comments')

若要查询每篇文章的评论数和每条评论的回复数,并按照评论数从高到低进行排序,那么可以这样:

1
2
3
4
5
6
from django.db.models import Count

articles = Article.objects.annotate(
comment_count = Count('comment'),
child_comment_count = Count('comment_child_comments')
).order_by('-comment_count')

4. order_by 对应 order by

1
order_by(*fields)

返回结果:QuerySet

可以通过指定列进行排序。

例:

1
Entry.objects.filter(pub_date__year=2005).order_by("-pub_date", "headline")

这里的结果是把 pub_date 降序排列,然后再按照 headline 升序排列。“-pub_date”前面的符号表示降序,升序是默认的,如果要随机排序,则需要使用“?”,如:

1
Entry.objects.order_by("?")

这里需要注意order_by("?")的查询速度很慢,而且会有高昂的查询成本,虽然取决于使用的数据库。

5. distinct 对应 distinct

返回结果:QuerySet

原型:

1
distinct(*fields)

该方法用来消除查询结果中的重复记录,在与 order_by 连用时需要注意可能会导致意外的结果,使原本重复的行看起来是不同的。

一般与 values()、values_list() 连用,这时候返回 ValuesQuerySet、ValuesListQuerySet。

6. values() 和 values_list() 对应' select 某几个字段'

返回结果:ValuesQuerySet、ValuesListQuerySet

values() 函数原型:

1
values(*fields, **expressions)

这两个是数据库查询的结果集,前者返回的是一个字典数组,其中的每个字典都是数据库里的一行记录,可以通过键来访问值。后者返回时的是元组的列表,无法修改其中的值,其中的每个元组都代表一个对象的字段值。两者通常用于在数据查询中返回特定字段,以提高性能。

其中 *fields 字段制定了 SELECT 应该被限制的字段名,也就是可以限制返回的列:

1
2
3
4
>>> Blog.objects.values()
<QuerySet [{'id': 1, 'name': 'Beatles Blog', 'tagline': 'All the latest Beatles news.'}]>
>>> Blog.objects.values("id", "name")
<QuerySet [{'id': 1, 'name': 'Beatles Blog'}]>

如果使用了 **expressions 参数,那么这些参数会被传递给 annotate():

1
2
3
>>> from django.db.models.functions import Lower
>>> Blog.objects.values(lower_name=Lower("name"))
<QuerySet [{'lower_name': 'beatles blog'}]>

values_list() 函数原型:

1
values_list(*fields, flat=False, named=False)

与values() 不同的地方在于该方法返回的是元组。

如果只需要一个字段的话,可以传递 flat 参数,其值为 True 的话,那么返回的结果将是单个值,不是 1 元组:

1
2
3
4
5
>>> Entry.objects.values_list("id").order_by("id")
<QuerySet[(1,), (2,), (3,), ...]>

>>> Entry.objects.values_list("id", flat=True).order_by("id")
<QuerySet [1, 2, 3, ...]>

使用多个字段时,不可以传入 flat 参数,否则会出错。

当使用 named=True 参数时,会把结果作为 namedtuple() 返回:

1
2
>>> Entry.objects.values_list("id", "headline", named=True)
<QuerySet [Row(id=1, headline='First entry'), ...]>

返回结果:QuerySet

函数原型:

1
select_related(*fields)

可以指定返回哪些关联表的模型实例。在使用时 filter() 与 select_related() 的顺序并不重要。select_related() 主要针对一对一和一对多关系进行优化。

1
2
3
post = Post.objects.all().select_related('category')
for post in posts: # 产生数据库查询,category 数据也会一次性查询出来
print(post.category)

8. prefetch_related(*field) 对应返回关联记录实体的集合

返回结果:QuerySet

函数原型:

1
prefetch_related(*lookups)

该接口主要解决多对多的关联关系。当然其实 prefetch_related() 也可以做 select_related() 能做的事情,但是性能会低效些。所以还是建议在多对多的时候后 prefetch_related(),而一对一,一对多交给 select_related() 去做。

1
2
3
posts = Post.objects.all().prefetch_related('tag')
for post in posts: # 产生两条查询语句,分别查询 post 和 tag
print(post.tag.all())

9. extra() 实现复杂的 where 子句

函数原型:

1
extra(select=None, where=None, params=None, tables=None, order_by=None, select_params=None)

该方法可以直接写一些 SQL 语句,但是不同的数据库使用的 SQL 也是有差异的,所以还是不推荐使用,另外这是很老的 API,万不得已的情况下不推荐用。

10. aggregate(*args, **kwargs) 对应聚合函数

返回结果:对 QuerySet 计算的聚合值(平均值、总和等)的字典。

参数为聚合函数,最好用 **kwargs 的形式,每个参数起一个名字,每个参数都指定了一个将被包含在返回的字典中的值。

该函数与 annotate () 的区别是:annotate() 相当于 aggregate () 和 group_by 的结合,对每个 group 执行 aggregate () 函数。而单独的 aggregate () 并没有 group_by。

1
2
3
>>> q = Blog.objects.aggregate (Count ('entry')) #这是用 * args 的形式,最好不要这样用
>>> q = Blog.objects.aggregate (number_of_entries=Count ('entry')) #这是用 **kwargs 的形式
{'number_of_entries': 16}

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
2
a=Entry.objects.all()[5:10]
b=len(a)

执行 Entry.objects.all ()[5:8],对于不同的数据库,SQL 语句不同,Sqlite 的 SQL 语句为 select * from tablename limit 3 offset 5; MySQL 的 SQL 语句为 select * from tablename limit 5,3


Django——QuerySet详解
https://excelius.xyz/django——queryset详解/
作者
Ther
发布于
2024年8月29日
许可协议