Django【执行查询】(一)
官方Django3.2 文档:https://docs.djangoproject.com/en/3.2/topics/db/queries/
本文大部分内容参考官方3.2版本文档撰写,仅供学习使用
官方PDF下载链接:https://media.readthedocs.org/pdf/django/3.2.x/django.pdf
Django 执行查询(一)
(本文中涉及的到的代码,建议复制粘贴的会以可折叠代码块展示,其他代码块建议手动练习。如果你对Models没有初步认识,欢迎去看看官方文档-Models或者其他文章。)
初步准备
将参考以下模型,它们构成了一个 Weblog 应用程序,文章中所有查询操作都基于该模型。
点击查看代码
from django.db import models
class Blog(models.Model):
name = models.CharField(max_length=100)
tagline = models.TextField()
def __str__(self):
return self.name
class Author(models.Model):
name = models.CharField(max_length=200)
email = models.EmailField()
def __str__(self):
return self.name
class Entry(models.Model):
blog = models.ForeignKey(Blog, on_delete=models.CASCADE)
headline = models.CharField(max_length=255)
body_text = models.TextField()
pub_date = models.DateField()
mod_date = models.DateField()
authors = models.ManyToManyField(Author)
number_of_comments = models.IntegerField()
number_of_pingbacks = models.IntegerField()
rating = models.IntegerField()
def __str__(self):
return self.headline
将修改保存到对象
使用模型类的关键字参数对其进行实例化,然后调用save()以将其保存到数据库中:
>>> from blog.models import Blog
>>> b = Blog(name='Beatles Blog', tagline='All the latest Beatles news.')
>>> b.save()
检索对象
如果你想查看Queryset相关API,可以点击这个链接。
为了从数据库中检索对象,通过你的模型类上的Manager(管理器)构造一个QuerySet。过滤器根据给定的参数缩小查询结果的范围。在SQL术语中,一个QuerySet相当于一个SELECT语句,而过滤器是一个限制性子句,如WHERE或LIMIT。
您可以使用模型的 Manager 获得一个 QuerySet。每个模型至少有一个Manager(管理器),默认称为objects。通过模型类直接访问它,如下所示:
>>> Blog.objects
<django.db.models.manager.Manager object at ...>
>>> b = Blog(name='Foo', tagline='Bar')
>>> b.objects
Traceback:
...
AttributeError: "Manager isn't accessible via Blog instances."
【注意】
管理器(manger)只能通过模型类而不是
模型实例来访问,以强制区分“表级”操作和“记录级”操作。
Manager(管理器) 是模型的 QuerySet 的主要来源。例如,Blog.objects.all() 返回一个包含数据库中所有 Blog 对象的 QuerySet。
检索所有对象
从表中检索对象的最简单方法是获取所有对象。为此,请在 Manager 上使用 all() 方法:
>>> all_entries = Entry.objects.all()
all() 方法返回了数据库中所有对象的 QuerySet。
使用过滤器检索特定对象
all() 返回的 QuerySet 描述了数据库表中的所有对象。但是,通常您只需要选择完整对象集的一个子集。
要创建这样的子集,您需要优化初始 QuerySet,添加过滤条件。细化 QuerySet 的两种最常见的方法是:
filter(**kwargs)
返回一个新的 QuerySet,其中包含与给定查找参数匹配的对象。
exclude(**kwargs)
返回一个新的 QuerySet,其中包含与给定查找参数匹配的对象。
返回一个新的QuerySet,包含不符合给定查找参数的对象。
查询参数(上述函数定义中的**kwargs)应该采用下面字段查询中描述的格式。
例如,要包含获取 2006 年的博客条目(entries blog)的 QuerySet,像这样使用 filter():
Entry.objects.filter(pub_date__year=2006)
通过默认管理器类也一样:
Entry.objects.all().filter(pub_date__year=2006)
链式过滤器
细化QuerySet 的结果本身还是一个 QuerySet,所以能串联多个细化过程。例子:
>>> Entry.objects.filter(
... headline__startswith='What'
... ).exclude(
... pub_date__gte=datetime.date.today()
... ).filter(
... pub_date__gte=datetime.date(2005, 1, 30)
... )
这个例子先获取包含数据库所有条目(entry)的 QuerySet,添加一个过滤器,然后排除一些,再进入另一个过滤器。最终的 QuerySet 包含标题以 "What" 开头的,发布日期介于 2005 年 1 月 30 日与今天之间的所有条目。
每个Queryset都是唯一的
每次精炼(细化)一个 QuerySet,你就会获得一个全新的 QuerySet,后者与前者毫无关联。每次精炼都会创建一个单独的、不同的 QuerySet,能被存储,使用和复用。
>>> q1 = Entry.objects.filter(headline__startswith="What")
>>> q2 = q1.exclude(pub_date__gte=datetime.date.today())
>>> q3 = q1.filter(pub_date__gte=datetime.date.today())
这三个 QuerySets 是独立的。第一个是基础 QuerySet,包含了所有标题以 "What" 开头的条目。第二个是第一个的子集,带有额外条件,排除了 pub_date 是今天和今天之后的所有记录。第三个是第一个的子集,带有额外条件,只筛选 pub_date 是今天或未来的所有记录。最初的 QuerySet (q1) 不受筛选操作影响。
QuerySet 是惰性的
QuerySet 是惰性的 —— 创建 QuerySet 并不会引发任何数据库活动。你可以将一整天的过滤器都堆积在一起,Django 只会在 QuerySet 被计算时执行查询操作。来瞄一眼这个例子:
>>> q = Entry.objects.filter(headline__startswith="What")
>>> q = q.filter(pub_date__lte=datetime.date.today())
>>> q = q.exclude(body_text__icontains="food")
>>> print(q)
虽然这看起来像是三次数据库操作,实际上只在最后一行 (print(q)) 做了一次。一般来说, QuerySet 的结果直到你 “要使用” 时才会从数据库中拿出。当你要用时,才通过数据库计算出 QuerySet。
用 get() 检索单个对象
filter() 总是返回一个 QuerySet,即便只有一个对象满足查询条件 —— 这种情况下,QuerySet 只包含了一个元素。
若你知道只会有一个对象满足查询条件,你可以在 Manager 上使用 get() 方法,它会直接返回这个对象:
>>> one_entry = Entry.objects.get(pk=1)
【注意】
使用切片 [0] 时的 get() 和 filter() 有点不同。如果没有满足查询条件的结果, get() 会抛出一个 DoesNotExist 异常。该异常是执行查询的模型类的一个属性 —— 所有,上述代码中,若没有哪个 Entry 对象的主键是 1,Django 会抛出 Entry.DoesNotExist。
类似了,Django 会在有不止一个记录满足 get() 查询条件时发出警告。这时,Django 会抛出 MultipleObjectsReturned,这同样也是模型类的一个属性。
所以说get()方法的使用中无论不存在或者存在多条都会报错,抛出的异常是可以被合理应用的
其它 QuerySet 方法
大多数情况下,你会在需要从数据库中检索对象时使用 all(), get(), filter() 和 exclude()。然而,这样远远不够;完整的各种 QuerySet 方法请参阅 QuerySet API reference 。
限制Queryset条目数
总结一下,可以切边,切边后的结果不能再排序or过滤。而且不能使用副索引
利用 Python 的数组切片语法将 QuerySet 切成指定长度。这等价于 SQL 的 LIMIT 和 OFFSET 子句。
例如,这将返回前 5 个对象 (LIMIT 5):
>>> Entry.objects.all()[:5]
这会返回第 6 至第 10 个对象 (OFFSET 5 LIMIT 5):
>>> Entry.objects.all()[5:10]
不支持负索引 (例如 Entry.objects.all()[-1])
一般情况下, QuerySet 的切片返回一个新的 QuerySet —— 其并未执行查询。一个特殊情况是使用了的 Python 切片语法的 “步长”。例如,这将会实际的执行查询命令,为了获取从前 10 个对象中,每隔一个抽取的对象组成的列表:
>>> Entry.objects.all()[:10:2]
由于对 queryset 切片工作方式的模糊性,禁止对其进行进一步的排序或过滤。
要检索 单个 对象而不是一个列表时(例如 SELECT foo FROM bar LIMIT 1),请使用索引,而不是切片。例如,这会返回按标题字母排序后的第一个 Entry:
>>> Entry.objects.order_by('headline')[0]
这大致等价于:
>>> Entry.objects.order_by('headline')[0:1].get()
然而,注意一下,若没有对象满足给定条件,前者会抛出 IndexError,而后者会抛出 DoesNotExist。参考 get() 获取更多细节。
字段查询
字段查询即你如何制定 SQL WHERE 子句。它们以关键字参数的形式传递给 QuerySet 方法 filter(), exclude() 和 get()。
基本的查询关键字参数遵照 field__lookuptype=value。(有个双下划线)。例如:
>>> Entry.objects.filter(pub_date__lte='2006-01-01')
转换为 SQL 语句大致如下:
SELECT * FROM blog_entry WHERE pub_date <= '2006-01-01';
查询子句中指定的字段必须是模型的一个字段名。不过也有个例外,在 ForeignKey 中,你可以指定以 _id 为后缀的字段名。这种情况下,value 参数需要包含 foreign 模型的主键的原始值。例子:
(点击此连接 ForeignKey 官方详解(多对一关系))
>>> Entry.objects.filter(blog_id=4)
若你传入了无效的关键字参数,查询函数会抛出 TypeError。
数据库 API 支持两套查询类型;完整参考文档位于 QuerySet API reference 字段查询参考。为了让你了解能干啥,以下是一些常见的查询:
exact
完全匹配。如果提供的比较值是 None,它将被解释为 SQL NULL (详见 isnull)。
一个 "exact" 匹配的例子:
>>> Entry.objects.get(headline__exact="Cat bites dog")
官方翻译:
若你没有提供查询类型 —— 也就说,若关键字参数未包含双下划线 —— 查询类型会被指定为 exact。
google翻译:
如果您不提供查找类型(即,如果您的关键字参数不包含双下划线),则假定查找类型是exact(精确)。
(下图是官方翻译,我是觉得有点问题)
iexact
不区分大小写的完全匹配。如果提供的比较值是 None,它将被解释为 SQL NULL (详见 isnull)。
>>> Blog.objects.get(name__iexact="beatles blog")
会匹配标题为 "Beatles Blog", "beatles blog", 甚至 "BeAtlES blOG" 的 Blog。
contains
区分大小写的包含测试。
举例:
Entry.objects.get(headline__contains='Lennon')
SQL 等价于:
SELECT ... WHERE headline LIKE '%Lennon%';
icontains
不区分大小写的包含测试。
举例:
Entry.objects.get(headline__icontains='Lennon')
SQL 等价于:
SELECT ... WHERE headline ILIKE '%Lennon%';
startswith, endswith
区分大小写的开头为;区分大小写的结尾为.
以……开头和以……结尾的查找。当然也有大小写不敏感的版本,名为 istartswith 和 iendswith。
(官方文档中对 field 查询参考 没有再详细介绍,对更多字段查询感兴趣可以查看我google翻译的文章,点击这段话即可)
跨关系查询
Django 提供了一种强大而直观的方式来“追踪”查询中的关系,在幕后自动为你处理 SQL JOIN 关系。为了跨越关系,跨模型使用关联字段名,字段名由双下划线分割,直到拿到想要的字段。
本例检索出所有的 Entry 对象,其 Blog 的 name 为 'Beatles Blog' :
>>> Entry.objects.filter(blog__name='Beatles Blog')
这个跨度可以是你想要的深度。
它也可以向后工作。虽然它可以被定制,但默认情况下,你在查询中使用模型的小写名称来引用 "反向 "关系。
本例检索的所有 Blog 对象均拥有少一个 标题 含有 'Lennon' 的条目:
>>> Blog.objects.filter(entry__headline__contains='Lennon')