进行查询

一旦你创建了你的 data models ,django自动为您提供一个数据库抽象API,允许您创建、检索、更新和删除对象。本文档解释如何使用此API。参考 data model reference 有关所有各种模型查找选项的完整详细信息。

在本指南(以及参考资料)中,我们将引用以下模型,它们构成了一个博客应用程序:

from datetime import date

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(default=date.today)
    authors = models.ManyToManyField(Author)
    number_of_comments = models.IntegerField(default=0)
    number_of_pingbacks = models.IntegerField(default=0)
    rating = models.IntegerField(default=5)

    def __str__(self):
        return self.headline

创建对象

为了在Python对象中表示数据库表数据,Django使用了一个直观的系统:一个模型类表示一个数据库表,该类的一个实例表示数据库表中的一个特定记录。

若要创建对象,请使用模型类的关键字参数将其实例化,然后调用 save() 将其保存到数据库。

假设模特位于一个文件中 mysite/blog/models.py ,下面是一个例子:

>>> from blog.models import Blog
>>> b = Blog(name="Beatles Blog", tagline="All the latest Beatles news.")
>>> b.save()

这将执行 INSERT 后台的SQL语句。在您显式调用之前,Django不会命中数据库 save() .

这个 save() 方法没有返回值。

参见

save() 采用了许多这里没有描述的高级选项。参见文档 save() 有关完整的详细信息。

要在单个步骤中创建和保存对象,请使用 create() 方法。

保存对对象的更改

要保存对数据库中已有对象的更改,请使用 save() .

给出了一个 Blog 实例 b5 已保存到数据库的,则此示例更改其名称并更新其在数据库中的记录:

>>> b5.name = "New name"
>>> b5.save()

这将执行 UPDATE 后台的SQL语句。在您显式调用之前,Django不会命中数据库 save() .

储蓄 ForeignKeyManyToManyField 领域

正在更新 ForeignKey 字段的工作方式与保存普通字段的方式完全相同--将正确类型的对象分配给相关的字段。此示例更新 blog 对象的属性 Entry 实例 entry ,假设适当的 EntryBlog 已经保存到数据库中(这样我们可以在下面检索它们):

>>> from blog.models import Blog, Entry
>>> entry = Entry.objects.get(pk=1)
>>> cheese_blog = Blog.objects.get(name="Cheddar Talk")
>>> entry.blog = cheese_blog
>>> entry.save()

正在更新 ManyToManyField 工作方式略有不同--使用 add() 方法将记录添加到关系中。此示例将 Author 实例 joe 发送到 entry 对象:

>>> from blog.models import Author
>>> joe = Author.objects.create(name="Joe")
>>> entry.authors.add(joe)

将多条记录添加到 ManyToManyField 在调用中一次性包含多个参数 add() ,如下所示:

>>> john = Author.objects.create(name="John")
>>> paul = Author.objects.create(name="Paul")
>>> george = Author.objects.create(name="George")
>>> ringo = Author.objects.create(name="Ringo")
>>> entry.authors.add(john, paul, george, ringo)

如果您试图分配或添加错误类型的对象,Django会抱怨。

检索对象

要从数据库中检索对象,请构造 QuerySet 通过A Manager 在你的模型课上。

A QuerySet 表示数据库中的对象集合。它可以有零个,一个或多个 过滤器 . 过滤器根据给定的参数缩小查询结果的范围。在SQL术语中,A QuerySet 等同于 SELECT 语句,过滤器是限制子句,例如 WHERELIMIT .

你会得到一个 QuerySet 通过使用您的模型的 Manager 。每种型号至少有一个 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."

备注

Managers 只能通过模型类而不是模型实例来访问,以在“表级”操作和“记录级”操作之间实现分离。

这个 ManagerQuerySets 对于一个模型。例如, Blog.objects.all() 返回A QuerySet 包含所有 Blog 数据库中的对象。

正在检索所有对象

从表中检索对象的最简单方法是获取所有对象。为此,请使用 all() 对象上的方法 Manager

>>> all_entries = Entry.objects.all()

这个 all() 方法返回 QuerySet 数据库中的所有对象。

使用筛选器检索特定对象

这个 QuerySet 返回的 all() 描述数据库表中的所有对象。不过,通常只需要选择完整对象集的一个子集。

要创建这样一个子集,需要细化初始值 QuerySet ,添加筛选条件。两种最常用的方法来改进 QuerySet 是:

filter(**kwargs)

返回新的 QuerySet 包含与给定查找参数匹配的对象。

exclude(**kwargs)

返回新的 QuerySet 包含执行此操作的对象 not 匹配给定的查找参数。

查找参数 (**kwargs 在上述函数定义中)应采用 Field lookups 下面。

例如,要获得 QuerySet 在2006年的博客条目中,使用 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))

这需要首字母 QuerySet 在数据库中的所有条目中,添加一个筛选器,然后添加一个排除,然后添加另一个筛选器。最终结果是 QuerySet 包含2005年1月30日至当日发布的标题以“what”开头的所有条目。

过滤的 QuerySet 是独一无二的

每次你精炼 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 是懒惰的

QuerySets 是懒惰的--创造一个 QuerySet 不涉及任何数据库活动。您可以将过滤器堆叠在一起一整天,Django实际上不会运行查询,直到 QuerySetevaluated 。看一下这个例子:

>>> 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评价的 通过访问数据库。有关评估确切时间的详细信息,请参阅 什么时候? QuerySet s被评估 .

检索单个对象 get()

filter() 总会给你一个 QuerySet ,即使只有一个对象与查询匹配-在本例中,它将是 QuerySet 包含单个元素。

如果知道只有一个对象与查询匹配,则可以使用 get() 对象上的方法 Manager 它直接返回对象:

>>> one_entry = Entry.objects.get(pk=1)

可以将任何查询表达式用于 get() 就像和 filter() 再次看到 Field lookups 下面。

注意,使用 get() 并使用 filter() 用一片 [0] . 如果没有与查询匹配的结果, get() 将提高 DoesNotExist 例外。这个异常是正在执行查询的模型类的一个属性,所以在上面的代码中,如果没有 Entry 主键为1的对象,Django将提升 Entry.DoesNotExist .

同样,如果多个项目与 get() 查询。在这种情况下,它会上升 MultipleObjectsReturned 这也是模型类本身的一个属性。

其他 QuerySet 方法

大部分时间你会用到 all()get()filter()exclude() 当需要从数据库中查找对象时。然而,这还远远不够;看 QuerySet API Reference 要获得所有 QuerySet 方法。

限制 QuerySet 的S

使用python数组切片语法的子集来限制 QuerySet 一定数量的结果。这相当于SQL的 LIMITOFFSET 条款。

例如,这将返回前5个对象 (LIMIT 5 ):

>>> Entry.objects.all()[:5]

这将返回第六个到第十个对象 (OFFSET 5 LIMIT 5 ):

>>> Entry.objects.all()[5:10]

负索引(即 Entry.objects.all()[-1] )不支持。

一般而言,切分 QuerySet 返回一个新的 QuerySet --它不对查询求值。一个例外情况是,如果您使用的是PythonSlice语法的“Step”参数。例如,这实际上会执行查询,以便返回每个 second 前10名的对象:

>>> Entry.objects.all()[:10:2]

由于切片查询集的工作方式不明确,因此禁止对其进行进一步筛选或排序。

要检索一个 single 对象而不是列表(例如 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';

这是怎么可能的

python能够定义接受名称和值在运行时计算的任意名称值参数的函数。有关详细信息,请参阅 Keyword Arguments 在官方的python教程中。

查找中指定的字段必须是模型字段的名称。但是有一个例外,在 ForeignKey 可以指定后缀为 _id . 在这种情况下,value参数应该包含外部模型主键的原始值。例如:

>>> Entry.objects.filter(blog_id=4)

如果传递的关键字参数无效,则查找函数将引发 TypeError .

数据库API支持大约24种查找类型;可以在 field lookup reference . 为了让您了解可用的功能,下面是一些您可能会使用的常见查找:

exact

“完全”匹配。例如:

>>> Entry.objects.get(headline__exact="Cat bites dog")

将沿着这些行生成SQL:

SELECT ... WHERE headline = 'Cat bites dog';

如果不提供查找类型——也就是说,如果关键字参数不包含双下划线——则假定查找类型为 exact .

例如,以下两条语句是等价的:

>>> Blog.objects.get(id__exact=14)  # Explicit form
>>> Blog.objects.get(id=14)  # __exact is implied

这是为了方便,因为 exact 查找是常见的情况。

iexact

不区分大小写的匹配。因此,查询如下:

>>> Blog.objects.get(name__iexact="beatles blog")

将匹配 Blog 有头衔的 "Beatles Blog""beatles blog" ,甚至 "BeAtlES blOG" .

contains

区分大小写的安全壳测试。例如::

Entry.objects.get(headline__contains="Lennon")

大致翻译成这个SQL:

SELECT ... WHERE headline LIKE '%Lennon%';

注意这将与标题匹配 'Today Lennon honored' 但不是 'today lennon honored' .

还有一个不区分大小写的版本, icontains .

startswith, endswith

分别从搜索开始和结束。还有一些不区分大小写的版本称为 istartswithiendswith .

同样,这只会划伤表面。完整的参考资料可在 field lookup reference .

跨越关系的查找

Django提供了一种强大而直观的方式来“跟踪”查找中的关系,处理SQL JOIN 在幕后自动给你。若要跨越关系,请跨模型使用相关字段的字段名,用双下划线分隔,直到到达所需的字段为止。

此示例检索所有 Entry 具有 Blog 谁的 name'Beatles Blog'

>>> Entry.objects.filter(blog__name="Beatles Blog")

这个跨度可以像你想的那样深。

它也是逆向的。当它 can be customized ,默认情况下,使用模型的小写名称在查找中引用“反向”关系。

此示例检索所有 Blog 至少具有一个 Entry 谁的 headline'Lennon'

>>> Blog.objects.filter(entry__headline__contains="Lennon")

如果您正在跨多个关系进行筛选,并且其中一个中间模型没有满足筛选条件的值,Django会将其视为存在空值(所有值都是 NULL ,但有效,对象在那里。这意味着不会出现错误。例如,在此筛选器中:

Blog.objects.filter(entry__authors__name="Lennon")

(如果有相关的 Author 模型),如果没有 author 与一个条目关联,它将被视为 name 附加,而不是由于缺少 author . 通常这正是你想要发生的事情。唯一可能令人困惑的情况是,如果您使用 isnull . 因此:

Blog.objects.filter(entry__authors__name__isnull=True)

将返回 Blog 具有空的对象 nameauthor 还有那些空的 authorentry . 如果不需要后一个对象,可以编写:

Blog.objects.filter(entry__authors__isnull=False, entry__authors__name__isnull=True)

跨越多值关系

当跨越 ManyToManyField 或者相反 ForeignKey (例如来自 BlogEntry ),对多个属性进行过滤会引发是否要求每个属性在同一相关对象中重合的问题。我们可能会搜索包含2008年词条的博客 “Lennon” 在它的标题中,或者我们可能会搜索只有2008年的条目以及一些较新或较旧的条目的博客 “Lennon” 在它的标题中。

选择包含至少一个2008年条目的所有博客 "Lennon" 在标题中(满足这两个条件的同一条目),我们将写道:

Blog.objects.filter(entry__headline__contains="Lennon", entry__pub_date__year=2008)

否则,要执行更有权限的查询,只需使用 some 输入方式为 "Lennon" 在它的标题和 some 从2008年开始,我们会这样写:

Blog.objects.filter(entry__headline__contains="Lennon").filter(
    entry__pub_date__year=2008
)

假设只有一个博客的两个条目都包含 "Lennon" 和2008年的条目,但2008年的条目中没有一个包含 "Lennon" 。第一个查询不会返回任何博客,但第二个查询将返回那个博客。(这是因为由第二过滤器选择的条目可能与第一过滤器中的条目相同,也可能不同。我们正在过滤 Blog 项,而不是 Entry 项目。)简而言之,如果每个条件都需要匹配相同的相关对象,那么每个条件都应该包含在一个 filter() 打电话。

备注

当第二个(更允许的)查询链接多个筛选器时,它会对主要模型执行多个联接,从而可能会产生重复项。

>>> from datetime import date
>>> beatles = Blog.objects.create(name="Beatles Blog")
>>> pop = Blog.objects.create(name="Pop Music Blog")
>>> Entry.objects.create(
...     blog=beatles,
...     headline="New Lennon Biography",
...     pub_date=date(2008, 6, 1),
... )
<Entry: New Lennon Biography>
>>> Entry.objects.create(
...     blog=beatles,
...     headline="New Lennon Biography in Paperback",
...     pub_date=date(2009, 6, 1),
... )
<Entry: New Lennon Biography in Paperback>
>>> Entry.objects.create(
...     blog=pop,
...     headline="Best Albums of 2008",
...     pub_date=date(2008, 12, 15),
... )
<Entry: Best Albums of 2008>
>>> Entry.objects.create(
...     blog=pop,
...     headline="Lennon Would Have Loved Hip Hop",
...     pub_date=date(2020, 4, 1),
... )
<Entry: Lennon Would Have Loved Hip Hop>
>>> Blog.objects.filter(
...     entry__headline__contains="Lennon",
...     entry__pub_date__year=2008,
... )
<QuerySet [<Blog: Beatles Blog>]>
>>> Blog.objects.filter(
...     entry__headline__contains="Lennon",
... ).filter(
...     entry__pub_date__year=2008,
... )
<QuerySet [<Blog: Beatles Blog>, <Blog: Beatles Blog>, <Blog: Pop Music Blog]>

备注

行为 filter() 对于跨越多值关系的查询,如上所述,其实现方式与 exclude() . 相反,条件是 exclude() 调用不一定引用同一项。

例如,以下查询将排除包含 both 条目与 “列侬” 在标题中 and 2008年出版的作品:

Blog.objects.exclude(
    entry__headline__contains="Lennon",
    entry__pub_date__year=2008,
)

但是,与使用时的行为不同 filter() ,这不会基于满足这两个条件的条目限制博客。为了做到这一点,即选择所有不包含与 “列侬” 这是2008年发布的,您需要进行两个查询:

Blog.objects.exclude(
    entry__in=Entry.objects.filter(
        headline__contains="Lennon",
        pub_date__year=2008,
    ),
)

过滤器可以引用模型上的字段

在目前给出的示例中,我们构建了过滤器,将模型字段的值与常量进行比较。但是,如果您想将一个模型字段的值与同一个模型上的另一个字段进行比较呢?

Django提供 F expressions 允许这样的比较。实例 F() 作为对查询中模型字段的引用。然后,可以在查询筛选器中使用这些引用来比较同一模型实例上两个不同字段的值。

例如,要查找评论多于Pingback的所有博客条目的列表,我们构造一个 F() 对象引用ping back计数,并使用该 F() 查询中的对象:

>>> from django.db.models import F
>>> Entry.objects.filter(number_of_comments__gt=F("number_of_pingbacks"))

Django支持使用加法、减法、乘法、除法、模运算和幂运算 F() 对象,既有常量又有其他 F() 物体。要查找所有包含超过 twice 与Pingback一样多的评论,我们修改查询:

>>> Entry.objects.filter(number_of_comments__gt=F("number_of_pingbacks") * 2)

要查找条目的评级小于ping back计数和评论计数之和的所有条目,我们将发出查询:

>>> Entry.objects.filter(rating__lt=F("number_of_comments") + F("number_of_pingbacks"))

还可以使用双下划线表示法在 F() 对象。一个 F() 带有双下划线的对象将引入访问相关对象所需的任何联接。例如,要检索作者的姓名与博客名称相同的所有条目,我们可以发出查询:

>>> Entry.objects.filter(authors__name=F("blog__name"))

对于日期和日期/时间字段,可以添加或减去 timedelta 对象。下面将返回在发布后3天以上修改的所有条目:

>>> from datetime import timedelta
>>> Entry.objects.filter(mod_date__gt=F("pub_date") + timedelta(days=3))

这个 F() 对象通过以下方式支持按位操作 .bitand().bitor().bitxor().bitrightshift() ,以及 .bitleftshift() 。例如:

>>> F("somefield").bitand(16)

甲骨文公司

Oracle不支持按位异或操作。

表达式可以引用转换

Django支持在表达式中使用转换。

例如,要查找所有 Entry 与上次修改同年发布的对象:

>>> from django.db.models import F
>>> Entry.objects.filter(pub_date__year=F("mod_date__year"))

要查找条目发布的最早年份,我们可以发出以下查询:

>>> from django.db.models import Min
>>> Entry.objects.aggregate(first_published_year=Min("pub_date__year"))

此示例查找评分最高的条目的值以及每年所有条目上的评论总数:

>>> from django.db.models import OuterRef, Subquery, Sum
>>> Entry.objects.values("pub_date__year").annotate(
...     top_rating=Subquery(
...         Entry.objects.filter(
...             pub_date__year=OuterRef("pub_date__year"),
...         )
...         .order_by("-rating")
...         .values("rating")[:1]
...     ),
...     total_comments=Sum("number_of_comments"),
... )

这个 pk 查找快捷方式

为了方便起见,Django提供了 pk 查找快捷方式,代表“主键”。

在该示例中 Blog 模型中,主键是 id 字段,所以这三个语句是等价的:

>>> Blog.objects.get(id__exact=14)  # Explicit form
>>> Blog.objects.get(id=14)  # __exact is implied
>>> Blog.objects.get(pk=14)  # pk implies id__exact

对.的使用 pk 并不限于 __exact 查询--任何查询术语都可以与 pk 要查询模型的主键,请执行以下操作:

# Get blogs entries with id 1, 4 and 7
>>> Blog.objects.filter(pk__in=[1, 4, 7])

# Get all blog entries with id > 14
>>> Blog.objects.filter(pk__gt=14)

pk 查找也可以跨连接工作。例如,以下三个语句是等价的:

>>> Entry.objects.filter(blog__id__exact=3)  # Explicit form
>>> Entry.objects.filter(blog__id=3)  # __exact is implied
>>> Entry.objects.filter(blog__pk=3)  # __pk implies __id__exact

在中转义百分号和下划线 LIKE 声明

相当于 LIKE SQL语句 (iexactcontainsicontainsstartswithistartswithendswithiendswith )将自动转义中使用的两个特殊字符 LIKE 语句——百分号和下划线。(在A中) LIKE 语句中,百分号表示多字符通配符,下划线表示单字符通配符。)

这意味着事情应该凭直觉工作,这样抽象就不会泄露。例如,要检索包含百分号的所有条目,请将百分号用作任何其他字符:

>>> Entry.objects.filter(headline__contains="%")

Django负责为您报价;得到的SQL如下所示:

SELECT ... WHERE headline LIKE '%\%%';

下划线也是如此。百分比符号和下划线都是透明地为您处理的。

缓存和 QuerySet 的S

QuerySet 包含用于最小化数据库访问的缓存。了解它的工作方式将使您能够编写最有效的代码。

在新创建的 QuerySet ,缓存为空。第一次A QuerySet 被计算——因此,会发生数据库查询——django将查询结果保存在 QuerySet 的缓存并返回已显式请求的结果(例如,下一个元素,如果 QuerySet 正在迭代)。后续评估 QuerySet 重用缓存的结果。

请记住这种缓存行为,因为如果您不使用 QuerySet S说得对。例如,以下代码将创建两个 QuerySet S,评估一下,然后扔掉:

>>> print([e.headline for e in Entry.objects.all()])
>>> print([e.pub_date for e in Entry.objects.all()])

这意味着相同的数据库查询将执行两次,有效地将数据库负载加倍。另外,两个列表可能不包含相同的数据库记录,因为 Entry 可能是在两个请求之间的分隔秒内添加或删除的。

若要避免此问题,请将 QuerySet 并重复使用它:

>>> queryset = Entry.objects.all()
>>> print([p.headline for p in queryset])  # Evaluate the query set.
>>> print([p.pub_date for p in queryset])  # Reuse the cache from the evaluation.

什么时候? QuerySet S未缓存

查询集并不总是缓存它们的结果。仅在评估时 part 对于查询集,将检查缓存,但如果未填充缓存,则不会缓存后续查询返回的项。具体来说,这意味着 limiting the queryset 使用数组切片或索引不会填充缓存。

例如,重复获取queryset对象中的某个索引将在每次查询数据库:

>>> queryset = Entry.objects.all()
>>> print(queryset[5])  # Queries the database
>>> print(queryset[5])  # Queries the database again

但是,如果已经评估了整个查询集,则将改为检查缓存:

>>> queryset = Entry.objects.all()
>>> [entry for entry in queryset]  # Queries the database
>>> print(queryset[5])  # Uses cache
>>> print(queryset[5])  # Uses cache

以下是一些其他操作的示例,这些操作将导致对整个查询集进行评估,从而填充缓存:

>>> [entry for entry in queryset]
>>> bool(queryset)
>>> entry in queryset
>>> list(queryset)

备注

只打印查询集不会填充缓存。这是因为 __repr__() 只返回整个查询集的一个切片。

异步查询

如果您正在编写异步视图或代码,则不能以我们上面描述的方式使用ORM进行查询,因为您不能调用 blocking 来自异步代码的同步代码--它会阻塞事件循环(或者,更有可能的是,Django会注意到并引发 SynchronousOnlyOperation 以阻止这种情况发生)。

幸运的是,您可以使用Django的异步查询API执行许多查询。所有可能被阻止的方法,例如 get()delete() -有一个异步变量 (aget()adelete() ),当您迭代结果时,您可以使用异步迭代 (async for )相反。

查询迭代

迭代查询的默认方式-使用 for -当Django在迭代时加载结果时,将导致在后台阻止数据库查询。要解决此问题,您可以切换到 async for **

async for entry in Authors.objects.filter(name__startswith="A"):
    ...

请注意,您还不能执行其他可能遍历查询集的操作,例如包装 list() 以强制其求值(您可以使用 async for 在理解中,如果你想要的话)。

因为 QuerySet 方法,如 filter()exclude() 不要实际运行查询-他们将查询集设置为在迭代时运行-您可以在异步代码中自由使用这些查询集。有关哪些方法可以像这样继续使用以及哪些方法具有异步版本的指南,请阅读下一节。

QuerySet 和管理器方法

关于管理器和类查询集的几种方法 get()first() -强制执行查询集,并且正在阻塞。有些,比如 filter()exclude() ,不强制执行,因此可以安全地从异步代码运行。但你该如何区分这两者呢?

你可以四处看看,看看有没有 a -方法的前缀版本(例如,我们有 aget() 但不是 afilter() ),有一种更符合逻辑的方法--在 QuerySet reference

在那里,您将发现QuerySet上的方法分为两个部分:

  • Methods that return new querysets :这些是非阻塞的,没有异步版本。您可以在任何情况下自由使用这些工具,但请阅读 defer()only() 在你使用它们之前。

  • Methods that do not return querysets :这些是阻塞版本,并且具有异步版本--每个版本的异步名称都在其文档中注明,尽管我们的标准模式是添加一个 a 前缀。

使用这种区别,您可以计算出何时需要使用异步版本,以及何时不需要。例如,下面是一个有效的异步查询:

user = await User.objects.filter(username=my_input).afirst()

filter() 返回一个查询集,所以在一个异步环境中继续链接它是很好的,而 first() 计算并返回模型实例-因此,我们更改为 afirst() ,并使用 await 在整个表达式的前面,以便以异步友好的方式调用它。

备注

如果您忘记将 await 部分内容,您可能会看到如下错误 "coroutine object has no attribute x""<coroutine …>" 字符串代替您的模型实例。如果你曾经看到过这些,你就会错过一个 await 某处可以将这种协同程序转化为真正的价值。

交易记录

交易记录为 not 当前支持异步查询和更新。你会发现尝试使用它会提高 SynchronousOnlyOperation

如果您希望使用事务,我们建议您在单独的同步函数中编写ORM代码,然后使用 sync_to_async -请参见 异步支持 想要更多。

查询 JSONField

查找实现与 JSONField ,主要是由于密钥变换的存在。为了演示,我们将使用以下示例模型:

from django.db import models


class Dog(models.Model):
    name = models.CharField(max_length=200)
    data = models.JSONField(null=True)

    def __str__(self):
        return self.name

存储和查询 None

与其他字段一样,存储 None 因为该字段的值会将其存储为SQL NULL 。虽然不推荐,但可以存储JSON标量 null 而不是SQL NULL 通过使用 Value(None, JSONField())

从数据库中检索时,无论存储哪个值,JSON标量的Python表示 null 与SQL相同 NULL ,即 None . 因此,很难区分它们。

这只适用于 None 作为字段的顶级值。如果 None 在一个 listdict ,它将始终被解释为JSON null .

在查询时, None 值将始终被解释为JSON null 。查询SQL的步骤 NULL ,使用 isnull

>>> Dog.objects.create(name="Max", data=None)  # SQL NULL.
<Dog: Max>
>>> Dog.objects.create(name="Archie", data=Value(None, JSONField()))  # JSON null.
<Dog: Archie>
>>> Dog.objects.filter(data=None)
<QuerySet [<Dog: Archie>]>
>>> Dog.objects.filter(data=Value(None, JSONField()))
<QuerySet [<Dog: Archie>]>
>>> Dog.objects.filter(data__isnull=True)
<QuerySet [<Dog: Max>]>
>>> Dog.objects.filter(data__isnull=False)
<QuerySet [<Dog: Archie>]>

除非您确定希望使用SQL NULL 值,考虑设置 null=False 并为空值提供适当的默认值,例如 default=dict .

备注

存储JSON标量 null 不违反 null=False .

键、索引和路径转换

要基于给定的字典关键字进行查询,请使用该关键字作为查找名称:

>>> Dog.objects.create(
...     name="Rufus",
...     data={
...         "breed": "labrador",
...         "owner": {
...             "name": "Bob",
...             "other_pets": [
...                 {
...                     "name": "Fishy",
...                 }
...             ],
...         },
...     },
... )
<Dog: Rufus>
>>> Dog.objects.create(name="Meg", data={"breed": "collie", "owner": None})
<Dog: Meg>
>>> Dog.objects.filter(data__breed="collie")
<QuerySet [<Dog: Meg>]>

可以将多个密钥链接在一起以形成路径查找:

>>> Dog.objects.filter(data__owner__name="Bob")
<QuerySet [<Dog: Rufus>]>

如果键是整数,它将被解释为数组中的索引转换:

>>> Dog.objects.filter(data__owner__other_pets__0__name="Fishy")
<QuerySet [<Dog: Rufus>]>

如果要查询的键与另一个查找的名称冲突,请使用 contains 查找相反。

若要查询缺少的键,请使用 isnull 查找:

>>> Dog.objects.create(name="Shep", data={"breed": "collie"})
<Dog: Shep>
>>> Dog.objects.filter(data__owner__isnull=True)
<QuerySet [<Dog: Shep>]>

备注

上面给出的查找示例隐式使用 exact 查找。键、索引和路径变换也可以链接到: icontainsendswithiendswithiexactregexiregexstartswithistartswithltltegtgte ,以及 控制和关键查找 .

KT() 表达式

class KT(lookup)

的键、索引或路径转换的文本值。 JSONField 。中可以使用双下划线表示法 lookup 链接字典键和索引转换。

例如:

>>> from django.db.models.fields.json import KT
>>> Dog.objects.create(
...     name="Shep",
...     data={
...         "owner": {"name": "Bob"},
...         "breed": ["collie", "lhasa apso"],
...     },
... )
<Dog: Shep>
>>> Dogs.objects.annotate(
...     first_breed=KT("data__breed__1"), owner_name=KT("data__owner__name")
... ).filter(first_breed__startswith="lhasa", owner_name="Bob")
<QuerySet [<Dog: Shep>]>

备注

由于密钥路径查询的工作方式, exclude()filter() 不能保证生产出详尽的设备。如果要包括没有路径的对象,请添加 isnull 查找。

警告

由于任何字符串都可以是JSON对象中的键,因此除下面列出的字符串以外的任何查找都将被解释为键查找。未引发任何错误。要格外小心打字错误,并且总是检查你的查询是否按你的意愿工作。

MariaDB和Oracle用户

使用 order_by() on key、index或path转换将使用值的字符串表示对对象进行排序。这是因为MariaDB和Oracle数据库没有提供将JSON值转换为等效SQL值的函数。

Oracle用户

在Oracle数据库上,使用 None 作为中的查找值 exclude() 查询将返回没有 null 作为给定路径的值,包括不具有该路径的对象。在其他数据库后端,查询将返回具有路径而值不是的对象 null .

PostgreSQL用户

在PostgreSQL上,如果只使用一个键或索引,则SQL运算符 -> 使用。如果使用多个运算符,则 #> 使用运算符。

SQLite用户

在SQLite上, "true""false" ,以及 "null" 字符串值将始终被解释为 TrueFalse 和JSON null 分别进行了分析。

控制和关键查找

contains

这个 contains 上的查找被覆盖 JSONField 。返回的对象是那些给定的 dict 的键-值对都包含在该字段的顶级中。例如:

>>> Dog.objects.create(name="Rufus", data={"breed": "labrador", "owner": "Bob"})
<Dog: Rufus>
>>> Dog.objects.create(name="Meg", data={"breed": "collie", "owner": "Bob"})
<Dog: Meg>
>>> Dog.objects.create(name="Fred", data={})
<Dog: Fred>
>>> Dog.objects.filter(data__contains={"owner": "Bob"})
<QuerySet [<Dog: Rufus>, <Dog: Meg>]>
>>> Dog.objects.filter(data__contains={"breed": "collie"})
<QuerySet [<Dog: Meg>]>

Oracle和SQLite

contains Oracle和SQLite不支持。

contained_by

这是与 contains 查找-返回的对象将是其中对象上的键-值对是传递的值中的键-值对的子集的那些对象。例如:

>>> Dog.objects.create(name="Rufus", data={"breed": "labrador", "owner": "Bob"})
<Dog: Rufus>
>>> Dog.objects.create(name="Meg", data={"breed": "collie", "owner": "Bob"})
<Dog: Meg>
>>> Dog.objects.create(name="Fred", data={})
<Dog: Fred>
>>> Dog.objects.filter(data__contained_by={"breed": "collie", "owner": "Bob"})
<QuerySet [<Dog: Meg>, <Dog: Fred>]>
>>> Dog.objects.filter(data__contained_by={"breed": "collie"})
<QuerySet [<Dog: Fred>]>

Oracle和SQLite

contained_by Oracle和SQLite不支持。

has_key

返回给定键位于数据顶级的对象。例如:

>>> Dog.objects.create(name="Rufus", data={"breed": "labrador"})
<Dog: Rufus>
>>> Dog.objects.create(name="Meg", data={"breed": "collie", "owner": "Bob"})
<Dog: Meg>
>>> Dog.objects.filter(data__has_key="owner")
<QuerySet [<Dog: Meg>]>

has_keys

返回所有给定键都位于数据顶级的对象。例如:

>>> Dog.objects.create(name="Rufus", data={"breed": "labrador"})
<Dog: Rufus>
>>> Dog.objects.create(name="Meg", data={"breed": "collie", "owner": "Bob"})
<Dog: Meg>
>>> Dog.objects.filter(data__has_keys=["breed", "owner"])
<QuerySet [<Dog: Meg>]>

has_any_keys

返回任意给定键位于数据顶级的对象。例如:

>>> Dog.objects.create(name="Rufus", data={"breed": "labrador"})
<Dog: Rufus>
>>> Dog.objects.create(name="Meg", data={"owner": "Bob"})
<Dog: Meg>
>>> Dog.objects.filter(data__has_any_keys=["owner", "breed"])
<QuerySet [<Dog: Rufus>, <Dog: Meg>]>

复杂查找 Q 对象

关键字参数查询--在 filter() 等等——是“和”合在一起的。如果需要执行更复杂的查询(例如, OR 语句),您可以使用 Q objects .

A Q object (django.db.models.Q )是用于封装关键字参数集合的对象。这些关键字参数在上面的“字段查找”中指定。

例如,这个 Q 对象封装单个 LIKE 查询:

from django.db.models import Q

Q(question__startswith="What")

Q 对象可以使用 &| ,以及 ^ 操作员。当一个运算符用于两个 Q 对象,它会产生一个新的 Q 对象。

例如,此语句生成一个 Q 表示两个对象的“或”的对象 "question__startswith" 查询:

Q(question__startswith="Who") | Q(question__startswith="What")

这相当于下面的SQL WHERE 条款:

WHERE question LIKE 'Who%' OR question LIKE 'What%'

您可以通过组合以下命令来编写任意复杂的语句 Q 对象的 &| ,以及 ^ 运算符,并使用括号分组。另外, Q 对象可以使用 ~ 运算符,允许组合查询以组合正常查询和否定查询 (NOT )查询::

Q(question__startswith="Who") | ~Q(pub_date__year=2005)

采用关键字参数的每个查找函数(例如 filter()exclude()get() )也可以通过一个或多个 Q 对象作为位置(未命名)参数。如果您提供多个 Q 对象参数到查找函数,参数将“和”一起使用。例如::

Poll.objects.get(
    Q(question__startswith="Who"),
    Q(pub_date=date(2005, 5, 2)) | Q(pub_date=date(2005, 5, 6)),
)

... 大致翻译成SQL:

SELECT * from polls WHERE question LIKE 'Who%'
    AND (pub_date = '2005-05-02' OR pub_date = '2005-05-06')

查找函数可以混合使用 Q 对象和关键字参数。为查找函数提供的所有参数(无论是关键字参数还是 Q 对象)被“和”组合在一起。但是,如果 Q 提供了对象,它必须位于任何关键字参数的定义之前。例如::

Poll.objects.get(
    Q(pub_date=date(2005, 5, 2)) | Q(pub_date=date(2005, 5, 6)),
    question__startswith="Who",
)

…将是一个有效的查询,相当于前面的示例;但是:

# INVALID QUERY
Poll.objects.get(
    question__startswith="Who",
    Q(pub_date=date(2005, 5, 2)) | Q(pub_date=date(2005, 5, 6)),
)

…无效。

参见

这个 OR lookups examples 在Django的单元测试中显示了 Q .

比较对象

要比较两个模型实例,请使用标准的Python比较运算符双等号: == . 在幕后,比较两个模型的主键值。

使用 Entry 上面的示例中,以下两个语句是等价的:

>>> some_entry == other_entry
>>> some_entry.id == other_entry.id

如果没有调用模型的主键 id ,没问题。比较将始终使用主键,不管它叫什么。例如,如果模型的主键字段被调用 name ,这两种说法是等价的:

>>> some_obj == other_obj
>>> some_obj.name == other_obj.name

删除对象

为了方便起见,将删除方法命名为 delete() 。此方法立即删除对象,并返回删除的对象数和每种对象类型的删除数的字典。示例:

>>> e.delete()
(1, {'blog.Entry': 1})

还可以批量删除对象。每个 QuerySet 有一个 delete() 方法,删除该方法的所有成员 QuerySet .

例如,这将删除所有 Entry 具有 pub_date 2005年:

>>> Entry.objects.filter(pub_date__year=2005).delete()
(5, {'webapp.Entry': 5})

请记住,只要可能,这将完全在SQL中执行,因此 delete() 在此过程中,不必调用单个对象实例的方法。如果您提供了自定义 delete() 方法,如果要确保调用该方法,则需要“手动”删除该模型的实例(例如,通过迭代 QuerySet 呼唤 delete() 在每个对象上)而不是使用批量 delete() A方法 QuerySet .

当Django删除对象时,默认情况下它模拟SQL约束的行为 ON DELETE CASCADE --换句话说,任何具有指向要删除的对象的外键的对象都将与它一起删除。例如::

b = Blog.objects.get(pk=1)
# This will delete the Blog and all of its Entry objects.
b.delete()

这个级联行为可以通过 on_delete 论据 ForeignKey .

注意 delete() 是唯一的 QuerySet 未在上公开的方法 Manager 本身。这是一个安全机制,防止您意外请求 Entry.objects.delete() 删除 all 参赛作品。如果你 do 要删除所有对象,则必须显式请求完整的查询集::

Entry.objects.all().delete()

复制模型实例

尽管没有用于复制模型实例的内置方法,但可以轻松地创建复制了所有字段值的新实例。在最简单的情况下,您可以设置 pkNone_state.addingTrue 。以我们的博客为例:

blog = Blog(name="My blog", tagline="Blogging is easy")
blog.save()  # blog.pk == 1

blog.pk = None
blog._state.adding = True
blog.save()  # blog.pk == 2

如果使用继承,事情会变得更复杂。考虑的子类 Blog ::

class ThemeBlog(Blog):
    theme = models.CharField(max_length=200)


django_blog = ThemeBlog(name="Django", tagline="Django is easy", theme="python")
django_blog.save()  # django_blog.pk == 3

由于继承的工作方式,您必须同时设置 pkidNone ,以及 _state.addingTrue **

django_blog.pk = None
django_blog.id = None
django_blog._state.adding = True
django_blog.save()  # django_blog.pk == 4

此过程不会复制不属于模型数据库表的关系。例如, Entry 有一个 ManyToManyFieldAuthor . 复制条目后,必须为新条目设置多对多关系:

entry = Entry.objects.all()[0]  # some previous entry
old_authors = entry.authors.all()
entry.pk = None
entry._state.adding = True
entry.save()
entry.authors.set(old_authors)

对于一个 OneToOneField ,必须复制相关对象并将其分配给新对象的字段,以避免违反一对一的唯一约束。例如,假设 entry 已如上所述复制::

detail = EntryDetail.objects.all()[0]
detail.pk = None
detail._state.adding = True
detail.entry = entry
detail.save()

一次更新多个对象

有时,您希望为 QuerySet . 你可以用 update() 方法。例如::

# Update all the headlines with pub_date in 2007.
Entry.objects.filter(pub_date__year=2007).update(headline="Everything is the same")

您只能设置非关系字段和 ForeignKey 使用此方法的字段。要更新非关系字段,请将新值作为常量提供。要更新 ForeignKey 字段中,将新值设置为要指向的新模型实例。例如:

>>> b = Blog.objects.get(pk=1)

# Change every Entry so that it belongs to this Blog.
>>> Entry.objects.update(blog=b)

这个 update() 方法立即应用,并返回查询匹配的行数(如果某些行已经具有新值,则可能不等于更新的行数)。唯一的限制是 QuerySet 更新的是,它只能访问一个数据库表:模型的主表。您可以基于相关字段进行筛选,但只能更新模型主表中的列。示例:

>>> b = Blog.objects.get(pk=1)

# Update all the headlines belonging to this Blog.
>>> Entry.objects.filter(blog=b).update(headline="Everything is the same")

请注意 update() 方法直接转换为SQL语句。它是用于直接更新的批量操作。它一点也不运转 save() 方法,或发出 pre_savepost_save 信号(是呼叫的结果 save() 或荣誉 auto_now 字段选项。如果要保存 QuerySet 并确保 save() 方法在每个实例上调用,不需要任何特殊函数来处理。绕过去打电话 save() ::

for item in my_queryset:
    item.save()

对更新的调用还可以使用 F expressions 若要基于模型中另一个字段的值更新一个字段,请执行以下操作。这对于根据计数器的当前值递增计数器特别有用。例如,要增加博客中每个条目的ping back计数,请执行以下操作:

>>> Entry.objects.update(number_of_pingbacks=F("number_of_pingbacks") + 1)

然而,不同于 F() 对象,则不能在使用 F() 更新中的对象--您只能引用要更新的模型的本地字段。如果您尝试引入与 F() 对象,则为 FieldError 将被提出:

# This will raise a FieldError
>>> Entry.objects.update(headline=F("blog__name"))

返回原始SQL

如果您发现自己需要编写一个对Django的数据库映射器来说太复杂的SQL查询,那么您可以手工编写SQL。Django有几个用于编写原始SQL查询的选项;请参见 执行原始SQL查询 .

最后,需要注意的是,django数据库层只是到数据库的接口。您可以通过其他工具、编程语言或数据库框架访问数据库;您的数据库没有任何Django特定的内容。