Django 知识库:QuerySet查询

4267 views, 2020/05/22 updated   Go to Comments

以博客文章模型为例:

# models.py

class Post(models.Model):
    owner = models.ForeignKey(User, ..., related_name='posts')
    title = models.CharField(...)
    body = models.TextField(...)
    created = models.DateTimeField(...)

要从模型中检索对象,首先要构建一个管理器。默认情况下的管理器是这个:

>>> Post.objects
<django.db.models.manager.Manager object at ...>

>>> p = Post(title='...')
>>> p.objects
Traceback:
    ...
AttributeError: "Manager isn't accessible via Post instances."

管理器只能通过模型类获得,而不能通过实例获得。

最常用的检索就是获取所有对象了:

>>> all_posts = Post.objects.all()
>>> all_posts
<QuerySet [<Post: ...>, <Post: ...>, <Post: ...>]>

得到的结果是满足检索要求的集合,这就是通常说的 QuerySet

检索特定对象用 filter()

# 获取 2019 年发布的文章
Post.objects.filter(created__year=2019)

如果你要排除某些特定对象,可以用 exclude()

# 获取 2019 年以外时间发布的文章
Post.objects.exclude(created__year=2019)

也可以做链式查询:

Post.objects.filter(created__year=2019).exclude(title__startswith='Foo')

链多少层都可以,只要你喜欢。

此外,查询集在执行时不会原地修改,每次都会返回一个全新的子集:

a = Post.objects.all()
b = a.exclude(created__year=2019)

上面代码中的 ab 都是独立分开的查询集,互不影响。

QuerySet 在创建时是懒惰的,即并不会涉及数据库的操作:

>>> a = Post.objects.all()
>>> b = a.exclude(created__year=2019)
>>> c = b.filter(title__startswith='Foo')
>>> print(c)

上面的代码看起来像是对数据库查询了三次,但实际上只是在 print(c) 时才会真正执行查询。

稍有不同的是切片:

>>> d = Post.objects.all()[1:10]

上面这句不会执行查询,道理相同。

下面这句就不一样了:

>>> e = Post.objects.all()[:10:2]

这句是会执行查询的,Django 需要查询数据库以便返回带有间隔的列表。换句话说,带有步长的切片就会触发查询。

有一些管理器方法并不返回 QuerySet ,而是返回一个模型对象或者变量。比如 get() 方法可以取得单个确定的对象:

>>> Post.objects.get(id=1)

.create().first()last()count()exists()都属于这一类。

跨关系的查找也可以:

>>> Post.objects.get(owner__username='dusai')

如果 get() 返回了多个或者零个结果会报错。

我们已经多次用到如 owner__username 这种参数形式了。其实这里的 __ 你理解成 . 就好了,因为语法规则里关键字不能用 . ,所以就用 __ 来代替了。

还有 title__startswith='Foo' 这类用法,startswith 显然不是模型字段,而是指查找以 Foo 打头的 title 字段的相关数据。类似的查询方法还有:

exact # 完全匹配
iexact # 不区分大小写的完全匹配
contains # 包含
icontains # 不区分大小写的包含
in # 被包含在给定的集合中,如 Post.objects.filter(id__in=[2, 3, 4])
gt # 大于,如 Post.objects.filter(id__gt=3)
gte # 大于或等于
lt # 小于
lte # 小于或等于
startswith # 以 xx 开头
istartswith # 不区分大小写的以 xx 开头
endswith
iendswith
range # 在范围内
date # 日期字段使用,如 Post.objects.filter(created__date=datetime.date(2020, 1, 1))
year, month, day...
isnull
regex # 匹配正则
iregex

有这么多,够你折腾了。




本文作者: 杜赛
发布时间: 2020年05月22日 - 09:24
最后更新: 2020年05月22日 - 09:24
转载请保留原文链接及作者