进行查询 — Django 文档
进行查询
一旦您创建了 数据模型 ,Django 会自动为您提供一个数据库抽象 API,让您可以创建、检索、更新和删除对象。 本文档解释了如何使用此 API。 有关所有各种模型查找选项的完整详细信息,请参阅 数据模型参考 。
在本指南(以及参考资料)中,我们将参考以下模型,它们构成了一个 Weblog 应用程序:
创建对象
为了在 Python 对象中表示数据库表数据,Django 使用了一个直观的系统:模型类表示数据库表,该类的实例表示数据库表中的特定记录。
要创建对象,请使用模型类的关键字参数将其实例化,然后调用 save() 将其保存到数据库中。
假设模型存在于文件 mysite/blog/models.py
中,这是一个示例:
这会在幕后执行 INSERT
SQL 语句。 在您明确调用 save() 之前,Django 不会访问数据库。
save() 方法没有返回值。
保存对对象的更改
要保存对数据库中已有对象的更改,请使用 save()。
给定一个已经保存到数据库的 Blog
实例 b5
,这个例子改变了它的名字并更新了它在数据库中的记录:
这会在幕后执行 UPDATE
SQL 语句。 在您明确调用 save() 之前,Django 不会访问数据库。
保存 ForeignKey 和 ManyToManyField 字段
更新 ForeignKey 字段的工作方式与保存普通字段的方式完全相同——只需将正确类型的对象分配给相关字段即可。 此示例更新 Entry
实例 entry
的 blog
属性,假设 Entry
和 Blog
的适当实例已保存到数据库中(所以我们可以在下面检索它们):
更新 ManyToManyField 的工作方式略有不同 - 在字段上使用 add() 方法向关系添加记录。 本示例将 Author
实例 joe
添加到 entry
对象:
要将多条记录一次性添加到 ManyToManyField,请在对 add() 的调用中包含多个参数,如下所示:
如果您尝试分配或添加错误类型的对象,Django 会抱怨。
检索对象
要从数据库中检索对象,请通过模型类上的 Manager 构造 QuerySet。
QuerySet 表示数据库中的对象集合。 它可以有零个、一个或多个 过滤器 。 过滤器根据给定的参数缩小查询结果的范围。 在 SQL 术语中,QuerySet 等同于 SELECT
语句,过滤器是一个限制子句,例如 WHERE
或 LIMIT
。
您可以使用模型的 Manager 获得 QuerySet。 每个模型至少有一个Manager,默认叫做objects。 通过模型类直接访问它,如下所示:
笔记
Managers
只能通过模型类访问,而不是从模型实例访问,以强制分离“表级”操作和“记录级”操作。
Manager 是模型的 QuerySets
的主要来源。 例如,Blog.objects.all()
返回一个 QuerySet,其中包含数据库中的所有 Blog
对象。
使用过滤器检索特定对象
all() 返回的 QuerySet 描述了数据库表中的所有对象。 但是,通常您只需要选择完整对象集的一个子集。
要创建这样的子集,您需要优化初始 QuerySet,添加过滤条件。 优化 QuerySet 的两种最常见方法是:
filter(**kwargs)
- 返回一个新的 QuerySet 包含与给定查找参数匹配的对象。
exclude(**kwargs)
- 返回一个新的 QuerySet,其中包含 与 不匹配给定查找参数的对象。
查找参数(上述函数定义中的 **kwargs
)应采用以下 字段查找 中描述的格式。
例如,要获取 2006 年博客条目的 QuerySet,请使用 filter(),如下所示:
使用默认管理器类,它与:
链接过滤器
细化 QuerySet 的结果本身就是一个 QuerySet,因此可以将细化链接在一起。 例如:
这需要数据库中所有条目的初始 QuerySet,添加一个过滤器,然后是一个排除,然后是另一个过滤器。 最终结果是一个 QuerySet,其中包含所有标题以“What”开头的条目,这些条目在 2005 年 1 月 30 日和当天之间发布。
使用 get() 检索单个对象
filter() 总是会给你一个 QuerySet,即使只有一个对象与查询匹配 - 在这种情况下,它将是一个 QuerySet 包含一个元素。
如果您知道只有一个对象与您的查询匹配,您可以在直接返回对象的 Manager 上使用 get() 方法:
您可以将任何查询表达式与 get() 一起使用,就像使用 filter() 一样 - 再次参见下面的 字段查找 。
请注意,使用 get() 和使用带有 [0]
切片的 filter() 之间存在差异。 如果没有与查询匹配的结果,get() 将引发 DoesNotExist
异常。 这个异常是正在执行查询的模型类的一个属性——所以在上面的代码中,如果没有主键为 1 的 Entry
对象,Django 将引发 Entry.DoesNotExist
.
类似地,如果超过一项与 get() 查询匹配,Django 也会抱怨。 在这种情况下,它将引发 MultipleObjectsReturned,这也是模型类本身的一个属性。
其他 QuerySet 方法
大多数情况下,您会在需要时使用 all()、get()、filter() 和 exclude()从数据库中查找对象。 然而,这远非全部。 有关所有各种 QuerySet 方法的完整列表,请参阅 QuerySet API 参考 。
限制 QuerySets
使用 Python 的数组切片语法的子集将 QuerySet 限制为一定数量的结果。 这相当于 SQL 的 LIMIT
和 OFFSET
子句。
例如,这将返回前 5 个对象 (LIMIT 5
):
这将返回第六到第十个对象 (OFFSET 5 LIMIT 5
):
负索引(即 不支持 Entry.objects.all()[-1]
)。
通常,切片 QuerySet 会返回一个新的 QuerySet——它不会评估查询。 一个例外是,如果您使用 Python 切片语法的“step”参数。 例如,这实际上会执行查询以返回前 10 个每 秒 对象的列表:
由于其工作方式的模糊性,禁止对切片查询集进行进一步过滤或排序。
要检索 单个 对象而不是列表(例如 SELECT foo FROM bar LIMIT 1
),使用简单的索引而不是切片。 例如,在按标题按字母顺序排列条目后,这将返回数据库中的第一个 Entry
:
这大致相当于:
但是请注意,如果没有对象符合给定条件,第一个将引发 IndexError
而第二个将引发 DoesNotExist
。 有关更多详细信息,请参阅 get()。
字段查找
字段查找是您指定 SQL WHERE
子句内容的方式。 它们被指定为 QuerySet 方法 filter()、exclude() 和 get() 的关键字参数。
基本查找关键字参数采用 field__lookuptype=value
的形式。 (这是一个双下划线)。 例如:
转换(大致)为以下 SQL:
这怎么可能
Python 能够定义接受任意名称-值参数的函数,其名称和值在运行时进行评估。 更多信息参见Python官方教程中的tut-keywordargs。
查找中指定的字段必须是模型字段的名称。 但有一个例外,在 ForeignKey 的情况下,您可以指定后缀为 _id
的字段名称。 在这种情况下, value 参数应该包含外部模型主键的原始值。 例如:
如果传递无效的关键字参数,查找函数将引发 TypeError
。
数据库 API 支持大约二打查找类型; 完整的参考可以在 字段查找参考 中找到。 为了让您了解可用的内容,以下是您可能会使用的一些更常见的查找:
- :查找:`精确`
“精确”匹配。 例如:
将按照以下方式生成 SQL:
如果您不提供查找类型——也就是说,如果您的关键字参数不包含双下划线——则假定查找类型为
exact
。例如,以下两个语句是等效的:
这是为了方便起见,因为
exact
查找是常见情况。- :lookup:`iexact`
不区分大小写的匹配。 所以,查询:
将匹配标题为
"Beatles Blog"
、"beatles blog"
甚至"BeAtlES blOG"
的Blog
。- :查找:`包含`
区分大小写的包含测试。 例如:
大致翻译成这个 SQL:
请注意,这将匹配标题
'Today Lennon honored'
但不匹配'today lennon honored'
。还有一个不区分大小写的版本,:lookup:`icontains`。
- :lookup:`startswith`, :lookup:`endswith`
分别开始于和结束于搜索。 还有一些不区分大小写的版本,称为 :lookup:`istartswith` 和 :lookup:`iendswith`。
同样,这只会划伤表面。 完整的参考可以在 字段查找参考 中找到。
跨越关系的查找
Django 提供了一种强大而直观的方式来“跟踪”查找中的关系,在幕后自动为您处理 SQL JOIN
。 要跨越关系,只需使用跨模型的相关字段的字段名称,用双下划线分隔,直到到达所需的字段。
此示例检索所有具有 Blog
且 name
为 'Beatles Blog'
的 Entry
对象:
这个跨越可以像你想要的那样深。
它也向后工作。 要引用“反向”关系,只需使用模型的小写名称。
此示例检索所有 Blog
对象,这些对象至少有一个 Entry
,其 headline
包含 'Lennon'
:
如果你在多个关系之间进行过滤并且其中一个中间模型没有满足过滤条件的值,Django 会将其视为有空(所有值都是 NULL
),但有效,对象那里。 所有这一切都意味着不会引发任何错误。 例如,在这个过滤器中:
(如果有相关的 Author
模型),如果没有与条目关联的 author
,则将被视为没有附加 name
,而不是由于缺少 author
而引发错误。 通常这正是您希望发生的事情。 唯一可能令人困惑的情况是,如果您使用 :lookup:`isnull`。 因此:
将返回在 author
上有空 name
的 Blog
对象,以及在 entry
上有空 author
的对象。 如果你不想要那些后面的对象,你可以写:
跨越多值关系
当您根据 ManyToManyField 或反向 ForeignKey 过滤对象时,您可能会对两种不同类型的过滤器感兴趣。 考虑 Blog
/Entry
关系(Blog
到 Entry
是一对多关系)。 我们可能有兴趣查找标题中包含 “Lennon” 且发表于 2008 年的条目的博客。 或者,我们可能想要查找标题中包含 “Lennon” 条目以及 2008 年发布的条目的博客。 由于有多个条目与单个 Blog
相关联,因此这两种查询都是可能的,并且在某些情况下是有意义的。
ManyToManyField 也会出现相同类型的情况。 例如,如果 Entry
有一个名为 tags
的 ManyToManyField,我们可能想要找到链接到名为 “music” 和 的标签的条目]“bands” 或者我们可能想要一个包含名称为 “music” 和状态为 “public” 的标签的条目。
为了处理这两种情况,Django 有处理 filter() 调用的一致方式。 单个 filter() 调用中的所有内容都会同时应用以过滤出符合所有这些要求的项目。 连续的 filter() 调用进一步限制了对象集,但对于多值关系,它们适用于链接到主模型的任何对象,不一定是那些由较早的 过滤器选择的对象() 调用。
这可能听起来有点令人困惑,所以希望有一个例子可以澄清。 要选择标题中同时包含 “Lennon” 条目且发布于 2008 年的所有博客(满足两个条件的同一条目),我们将编写:
要选择标题中包含 “Lennon” 的条目以及 2008 年发布的条目的所有博客,我们将编写:
假设只有一个博客同时包含 “Lennon” 和 2008 年的条目,但 2008 年的条目都没有包含 “Lennon”。 第一个查询不会返回任何博客,但第二个查询会返回那个博客。
在第二个示例中,第一个过滤器将查询集限制为链接到标题中带有 “Lennon” 的条目的所有博客。 第二个过滤器将博客集 进一步 限制为那些也链接到 2008 年发布的条目的博客。 第二个过滤器选择的条目可能与第一个过滤器中的条目相同,也可能不同。 我们使用每个过滤器语句过滤 Blog
项,而不是 Entry
项。
过滤器可以引用模型上的字段
在到目前为止给出的示例中,我们构建了过滤器,将模型字段的值与常量进行比较。 但是,如果您想将模型字段的值与同一模型上的另一个字段的值进行比较,该怎么办?
Django 提供了 F 表达式 来允许这样的比较。 F()
的实例充当对查询中模型字段的引用。 然后可以在查询过滤器中使用这些引用来比较同一模型实例上两个不同字段的值。
例如,要查找评论多于 pingback 的所有博客条目的列表,我们构造一个 F()
对象来引用 pingback 计数,并在查询中使用该 F()
对象:
Django 支持对 F()
对象使用加法、减法、乘法、除法、取模和幂运算,包括常量和其他 F()
对象。 要查找评论数超过 pingback 的 倍 的所有博客条目,我们修改查询:
要查找条目评分小于 pingback 计数和评论计数之和的所有条目,我们将发出查询:
您还可以使用双下划线表示法来跨越 F()
对象中的关系。 带有双下划线的 F()
对象将引入访问相关对象所需的任何连接。 例如,要检索作者姓名与博客名称相同的所有条目,我们可以发出查询:
对于日期和日期/时间字段,您可以添加或减去 timedelta
对象。 以下将返回在发布后超过 3 天修改的所有条目:
F()
对象支持 .bitand()
、.bitor()
、.bitrightshift()
和 .bitleftshift()
的按位运算。 例如:
pk 查找快捷方式
为方便起见,Django 提供了一个 pk
查找快捷方式,它代表“主键”。
在示例 Blog
模型中,主键是 id
字段,因此这三个语句是等效的:
pk
的使用不限于 __exact
查询 - 任何查询词都可以与 pk
组合以对模型的主键执行查询:
pk
查找也适用于连接。 例如,这三个语句是等效的:
在 LIKE 语句中转义百分号和下划线
等同于 LIKE
SQL 语句的字段查找(iexact
、contains
、icontains
、startswith
、istartswith
、 endswith
和 iendswith
) 将自动转义 LIKE
语句中使用的两个特殊字符 - 百分号和下划线。 (在 LIKE
语句中,百分号表示多字符通配符,下划线表示单字符通配符。)
这意味着事情应该直观地工作,所以抽象不会泄漏。 例如,要检索包含百分号的所有条目,只需将百分号用作任何其他字符:
Django 会为您处理报价; 生成的 SQL 将如下所示:
下划线也是如此。 百分号和下划线都为您透明处理。
缓存和 QuerySets
每个 QuerySet 都包含一个缓存以最小化数据库访问。 了解它的工作原理将使您能够编写最有效的代码。
在新创建的 QuerySet 中,缓存为空。 第一次计算 QuerySet 时——因此,发生数据库查询——Django 将查询结果保存在 QuerySet 的缓存中并返回已明确请求的结果(例如,下一个元素,如果 QuerySet 正在迭代)。 QuerySet 的后续评估重用缓存的结果。
记住这种缓存行为,因为如果你没有正确使用你的 QuerySets,它可能会咬你。 例如,以下将创建两个 QuerySet ,评估它们,然后将它们丢弃:
这意味着相同的数据库查询将执行两次,有效地使您的数据库负载加倍。 此外,这两个列表可能不包含相同的数据库记录,因为 Entry
可能已在两个请求之间的瞬间添加或删除。
为了避免这个问题,只需保存 QuerySet 并重用它:
当 QuerySet 未缓存时
查询集并不总是缓存它们的结果。 当仅评估查询集的 part 时,会检查缓存,但如果未填充,则不会缓存后续查询返回的项目。 具体来说,这意味着 使用数组切片或索引限制查询集 不会填充缓存。
例如,重复获取查询集对象中的某个索引,每次都会查询数据库:
但是,如果已经评估了整个查询集,则会改为检查缓存:
以下是其他操作的一些示例,这些操作将导致评估整个查询集并因此填充缓存:
笔记
简单地打印查询集不会填充缓存。 这是因为对 __repr__()
的调用只返回整个查询集的一部分。
Q 对象的复杂查找
关键字参数查询 - 在 filter() 等中。 – 被“AND”在一起。 如果您需要执行更复杂的查询(例如,使用 OR
语句的查询),您可以使用 Q 对象 。
Q 对象 (django.db.models.Q
) 是用于封装关键字参数集合的对象。 这些关键字参数在上面的“字段查找”中指定。
例如,这个 Q
对象封装了单个 LIKE
查询:
Q
对象可以使用 &
和 |
运算符进行组合。 当一个运算符用于两个 Q
对象时,它会产生一个新的 Q
对象。
例如,此语句生成一个 Q
对象,表示两个 "question__startswith"
查询的“或”:
这等效于以下 SQL WHERE
子句:
您可以通过将 Q
对象与 &
和 |
运算符组合并使用括号分组来组合任意复杂的语句。 此外,Q
对象可以使用 ~
运算符进行否定,允许组合查找,将普通查询和否定 (NOT
) 查询结合起来:
每个采用关键字参数的查找函数(例如 filter(), exclude(), get()) 也可以传递一个或多个 Q
对象作为位置(未命名) ) 论点。 如果您向查找函数提供多个 Q
对象参数,则这些参数将被“与”在一起。 例如:
... 大致翻译成 SQL:
查找函数可以混合使用 Q
对象和关键字参数。 提供给查找函数的所有参数(无论是关键字参数还是 Q
对象)都被“AND”在一起。 但是,如果提供了 Q
对象,则它必须在任何关键字参数的定义之前。 例如:
... 将是一个有效的查询,相当于前面的例子; 但:
……无效。
比较对象
要比较两个模型实例,只需使用标准的 Python 比较运算符,即双等号:==
。 在幕后,它比较了两个模型的主键值。
使用上面的 Entry
示例,以下两个语句是等效的:
如果模型的主键不是 id
,没问题。 比较将始终使用主键,无论它叫什么。 例如,如果模型的主键字段称为 name
,则这两个语句是等效的:
删除对象
删除方法方便地命名为 delete()。 此方法立即删除对象并返回删除的对象数和包含每个对象类型删除数的字典。 例子:
您还可以批量删除对象。 每个 QuerySet 都有一个 delete() 方法,该方法删除该 QuerySet 的所有成员。
例如,这将删除 pub_date
年为 2005 的所有 Entry
对象:
请记住,只要有可能,这将完全在 SQL 中执行,因此在此过程中不一定会调用各个对象实例的 delete()
方法。 如果您在模型类上提供了自定义 delete()
方法并希望确保它被调用,您将需要“手动”删除该模型的实例(例如,通过迭代 QuerySet 并分别在每个对象上调用 delete()
),而不是使用 QuerySet 的批量 delete() 方法。
当 Django 删除一个对象时,默认情况下它会模拟 SQL 约束 ON DELETE CASCADE
的行为——换句话说,任何具有指向要删除的对象的外键的对象都将被删除。 例如:
此级联行为可通过 ForeignKey 的 on_delete 参数进行自定义。
请注意, delete() 是唯一未在 Manager 本身上公开的 QuerySet 方法。 这是一种安全机制,可防止您意外请求 Entry.objects.delete()
,并删除 all 条目。 如果您 do 想要删除所有对象,那么您必须明确请求一个完整的查询集:
复制模型实例
尽管没有用于复制模型实例的内置方法,但可以轻松地创建新实例并复制所有字段的值。 在最简单的情况下,您只需将 pk
设置为 None
。 使用我们的博客示例:
如果您使用继承,事情会变得更加复杂。 考虑 Blog
的子类:
由于继承的工作方式,您必须将 pk
和 id
都设置为 None:
此过程不会复制不属于模型数据库表的关系。 例如,Entry
有 ManyToManyField
到 Author
。 复制条目后,您必须为新条目设置多对多关系:
对于 OneToOneField
,您必须复制相关对象并将其分配给新对象的字段,以避免违反一对一唯一约束。 例如,假设 entry
已经如上复制:
一次更新多个对象
有时,您希望为 QuerySet 中的所有对象将字段设置为特定值。 您可以使用 update() 方法执行此操作。 例如:
使用此方法只能设置非关系字段和 ForeignKey 字段。 要更新非关系字段,请将新值作为常量提供。 要更新 ForeignKey 字段,请将新值设置为要指向的新模型实例。 例如:
update()
方法立即应用并返回查询匹配的行数(如果某些行已经具有新值,则可能不等于更新的行数)。 QuerySet 更新的唯一限制是它只能访问一个数据库表:模型的主表。 您可以根据相关字段进行过滤,但您只能更新模型主表中的列。 例子:
请注意,update()
方法直接转换为 SQL 语句。 这是直接更新的批量操作。 它不会在您的模型上运行任何 save() 方法,也不会发出 pre_save
或 post_save
信号(这是调用 save() 的结果) ),或使用 auto_now 字段选项。 如果您想将每个项目保存在 QuerySet 中并确保在每个实例上调用 save() 方法,您不需要任何特殊函数来处理它。 只需遍历它们并调用 save():
调用 update 还可以使用 F 表达式 根据模型中另一个字段的值更新一个字段。 这对于根据当前值递增计数器特别有用。 例如,要增加博客中每个条目的 pingback 计数:
但是,与 filter 和 exclude 子句中的 F()
对象不同,在更新中使用 F()
对象时不能引入连接——您只能引用正在更新的模型的本地字段。 如果您尝试使用 F()
对象引入连接,则会引发 FieldError
:
回退到原始 SQL
如果您发现自己需要编写一个 Django 的数据库映射器无法处理的过于复杂的 SQL 查询,您可以退回到手动编写 SQL。 Django 有几个用于编写原始 SQL 查询的选项; 请参阅 执行原始 SQL 查询 。
最后,重要的是要注意 Django 数据库层只是数据库的接口。 您可以通过其他工具、编程语言或数据库框架访问您的数据库; 您的数据库没有任何特定于 Django 的内容。