连接和子查询
要将一个表连接到另一个表,以便还加载相关模型,您可以使用以下方法。
-
select_related(related: Union[List, str]) -> QuerySet
-
select_all(follow: bool = True) -> QuerySet
-
prefetch_related(related: Union[List, str]) -> QuerySet
-
模型
选择相关
select_related(related: Union[List, str]) -> QuerySet
允许在同一查询期间预取相关模型。
使用 select_lated 时,始终只对数据库运行一个查询,这意味着会生成一个(有时很复杂)连接,并在 python 中处理稍后的嵌套模型。
要获取相关模型,请使用外键名称。
要链接相关模型关系,请在名称之间使用双下划线。
!!!注意如果您来自 django,请注意 django 中的 ormarselect_lated 与 -> 不同,您只能选择单个关系类型,而在 ormar 中,您可以跨外键关系选择相关,外键的反面(因此虚拟自动生成的键)和ManyToMany 字段(因此当前版本的所有关系)。
!!!tip 要控制选择哪些模型字段,请使用 fields() 和 except_fields()QuerySet 方法。
!!!tip 要控制模型的顺序(主模型或嵌套模型),请使用 order_by() 方法。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24 | class Album(ormar.Model):
ormar_config = base_ormar_config.copy()
id: int = ormar.Integer(primary_key=True)
name: str = ormar.String(max_length=100)
is_best_seller: bool = ormar.Boolean(default=False)
class Track(ormar.Model):
ormar_config = base_ormar_config.copy()
id: int = ormar.Integer(primary_key=True)
album: Optional[Album] = ormar.ForeignKey(Album)
title: str = ormar.String(max_length=100)
position: int = ormar.Integer()
play_count: int = ormar.Integer(nullable=True)
# Django style
album = await Album.objects.select_related("tracks").all()
# Python style
album = await Album.objects.select_related(Album.tracks).all()
# will return album with all columns tracks
|
您可以提供一个字符串或字符串列表(或一个字段/字段列表)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43 | class SchoolClass(ormar.Model):
ormar_config = base_ormar_config.copy(tablename="schoolclasses")
id: int = ormar.Integer(primary_key=True)
name: str = ormar.String(max_length=100)
department: Optional[Department] = ormar.ForeignKey(Department, nullable=False)
class Category(ormar.Model):
ormar_config = base_ormar_config.copy(tablename="categories")
id: int = ormar.Integer(primary_key=True)
name: str = ormar.String(max_length=100)
class Student(ormar.Model):
ormar_config = base_ormar_config.copy()
id: int = ormar.Integer(primary_key=True)
name: str = ormar.String(max_length=100)
schoolclass: Optional[SchoolClass] = ormar.ForeignKey(SchoolClass)
category: Optional[Category] = ormar.ForeignKey(Category, nullable=True)
class Teacher(ormar.Model):
ormar_config = base_ormar_config.copy()
id: int = ormar.Integer(primary_key=True)
name: str = ormar.String(max_length=100)
schoolclass: Optional[SchoolClass] = ormar.ForeignKey(SchoolClass)
category: Optional[Category] = ormar.ForeignKey(Category, nullable=True)
# Django style
classes = await SchoolClass.objects.select_related(
["teachers__category", "students"]).all()
# Python style
classes = await SchoolClass.objects.select_related(
[SchoolClass.teachers.category, SchoolClass.students]).all()
# will return classes with teachers and teachers categories
# as well as classes students
|
Many2Many 字段的行为完全相同,您可以在其中输入 Many2Many 字段的名称,并为您获取最终的模型。
!!!警告如果您在所有查询期间将ForeignKey 字段设置为不可为空(因此必需),则不可为空的模型将自动预取,即使您不将它们包含在select_lated 中。
!!!注意所有不返回行的方法显式返回一个 QuerySet 实例,以便您可以将它们链接在一起
| So operations like `filter()`, `select_related()`, `limit()` and `offset()` etc. can be chained.
Something like `Track.object.select_related("album").filter(album__name="Malibu").offset(1).limit(1).all()`
|
全选
select_all(follow: bool = False) -> QuerySet
默认情况下,当您选择 all() 时,不会加载任何关系,同样,当使用 select_lated() 时,您需要显式指定应加载的所有关系。如果您还想包括嵌套关系,这可能会很麻烦。
这就是引入 select_all() 的原因,因此默认情况下加载模型的所有关系(与 all() 方法相反)。
默认情况下,仅添加父模型(从中运行查询)直接相关的模型。
如果设置 follow=True,它还会添加相关模型的相关模型。
!!!info 为了不陷入无限循环,因为相关模型也保持与父模型访问模型集的关系。
| That way already visited models that are nested are loaded, but the load do not
follow them inside. So Model A -> Model B -> Model C -> Model A -> Model X
will load second Model A but will never follow into Model X.
Nested relations of those kind need to be loaded manually.
|
样本日期如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31 | base_ormar_config = OrmarConfig(
database=databases.Database(DATABASE_URL, force_rollback=True),
metadata=sqlalchemy.MetaData(),
)
class Address(ormar.Model):
ormar_config = base_ormar_config.copy(tablename="addresses")
id: int = ormar.Integer(primary_key=True)
street: str = ormar.String(max_length=100, nullable=False)
number: int = ormar.Integer(nullable=False)
post_code: str = ormar.String(max_length=20, nullable=False)
class Branch(ormar.Model):
ormar_config = base_ormar_config.copy(tablename="branches")
id: int = ormar.Integer(primary_key=True)
name: str = ormar.String(max_length=100, nullable=False)
address = ormar.ForeignKey(Address)
class Company(ormar.Model):
ormar_config = base_ormar_config.copy(tablename="companies")
id: int = ormar.Integer(primary_key=True)
name: str = ormar.String(max_length=100, nullable=False, name="company_name")
founded: int = ormar.Integer(nullable=True)
branches = ormar.ManyToMany(Branch)
|
要选择具有所有分支机构和地址的所有公司,您只需查询:
| companies = await Company.objects.select_all(follow=True).all()
# which is equivalent to:
companies = await Company.objects.select_related('branches__address').all()
|
当然,在这种情况下,在 select_lated 中发出显式关系名称非常容易,但是当您有多个关系时, select_all() 的好处就会显现出来。
例如,如果 Company 有 3 个关系,并且所有这 3 个关系都有它自己的 3 个关系,则您必须向 select_lated 发出 9 个关系字符串,select_all() 也无法更改关系名称。
!!!note 请注意,您可以将 select_all() 与其他 QuerySet 方法(如 filter、exclude_fields 等)链接起来。要排除关系,请使用带有要排除的关系名称(也是嵌套的)的 except_fields() 调用。
预取相关
prefetch_related(related: Union[List, str]) -> QuerySet
允许在查询期间预取相关模型 - 但与 select_lated 相反,每个后续模型都是在单独的数据库查询中获取的。
使用 prefetch_lated 时,每个模型都会针对数据库运行一个查询,这意味着您将一个接一个地执行多个查询。
要获取相关模型,请使用外键名称。
要链接相关模型关系,请在名称之间使用双下划线。
!!!tip 要控制选择哪些模型字段,请使用 fields() 和 except_fields()QuerySet 方法。
!!!tip 要控制模型的顺序(主模型或嵌套模型),请使用 order_by() 方法。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25 | class Album(ormar.Model):
ormar_config = base_ormar_config.copy()
id: int = ormar.Integer(primary_key=True)
name: str = ormar.String(max_length=100)
is_best_seller: bool = ormar.Boolean(default=False)
class Track(ormar.Model):
ormar_config = base_ormar_config.copy()
id: int = ormar.Integer(primary_key=True)
album: Optional[Album] = ormar.ForeignKey(Album)
title: str = ormar.String(max_length=100)
position: int = ormar.Integer()
play_count: int = ormar.Integer(nullable=True)
# Django style
album = await Album.objects.prefetch_related("tracks").all()
# Python style
album = await Album.objects.prefetch_related(Album.tracks).all()
# will return album will all columns tracks
|
您可以提供一个字符串或字符串列表
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42 | class SchoolClass(ormar.Model):
ormar_config = base_ormar_config.copy(tablename="schoolclasses")
id: int = ormar.Integer(primary_key=True)
name: str = ormar.String(max_length=100)
department: Optional[Department] = ormar.ForeignKey(Department, nullable=False)
class Category(ormar.Model):
ormar_config = base_ormar_config.copy(tablename="categories")
id: int = ormar.Integer(primary_key=True)
name: str = ormar.String(max_length=100)
class Student(ormar.Model):
ormar_config = base_ormar_config.copy()
id: int = ormar.Integer(primary_key=True)
name: str = ormar.String(max_length=100)
schoolclass: Optional[SchoolClass] = ormar.ForeignKey(SchoolClass)
category: Optional[Category] = ormar.ForeignKey(Category, nullable=True)
class Teacher(ormar.Model):
ormar_config = base_ormar_config.copy()
id: int = ormar.Integer(primary_key=True)
name: str = ormar.String(max_length=100)
schoolclass: Optional[SchoolClass] = ormar.ForeignKey(SchoolClass)
category: Optional[Category] = ormar.ForeignKey(Category, nullable=True)
# Django style
classes = await SchoolClass.objects.prefetch_related(
["teachers__category", "students"]).all()
# Python style
classes = await SchoolClass.objects.prefetch_related(
[SchoolClass.teachers.category, SchoolClass.students]).all()
# will return classes with teachers and teachers categories
# as well as classes students
|
Many2Many 字段的行为完全相同,您可以在其中输入 Many2Many 字段的名称,并为您获取最终的模型。
!!!警告如果您在所有查询期间将ForeignKey 字段设置为不可为空(因此必需),则不可为空的模型将自动预取,即使您不将它们包含在select_lated 中。
!!!注意所有不返回行的方法显式返回一个 QuerySet 实例,以便您可以将它们链接在一起
| So operations like `filter()`, `select_related()`, `limit()` and `offset()` etc. can be chained.
Something like `Track.object.select_related("album").filter(album__name="Malibu").offset(1).limit(1).all()`
|
选择相关与预取相关
您应该使用 -> select_lated 或 prefetch_lated 中的哪一个?
嗯,这实际上取决于您的数据。最好的答案是亲自尝试一下,看看哪一个在您的系统限制下表现得更快/更好。
要记住什么:
表现
查询数量:select_lated 始终对数据库执行一个查询,而 prefetch_lated 则执行多个查询。通常,查询 (I/O) 操作是最慢的操作,但并非必须如此。
行数:假设一个表 A 中有 10 000 个对象,每个对象在表 B 中有 3 个子对象,随后表 B 中的每个对象在表 C 中有 2 个子对象。如下所示:
| Model C
/
Model B - Model C
/
Model A - Model B - Model C
\ \
\ Model C
\
Model B - Model C
\
Model C
|
这意味着 select_lated 将始终返回 60 000 行 (10 000 * 3 * 2),稍后压缩为 10 000 个模型。
有多少行将返回 prefetch_lated?
好吧,这取决于,如果模型 B 和 C 中的每一个都是唯一的,那么它将在第一个查询中返回 10 000 行,在第二个查询中返回 30 000 行(表 B 中 A 的 3 个子项中的每一个都是唯一的),以及 60 000 行(表 B 中的每个子项都是唯一的)。表 C 中模型 B 的 2 个子级在第三个查询中是唯一的)。
在这种情况下,select_lated 似乎是一个更好的选择,与 3 个 prefetch_lated 相比,它不仅会运行一个查询,而且与 100 000 个 prefetch_lated (10+30+60k) 相比,它还会返回 60 000 行。
但是,如果每个模型 A 都有完全相同的 3 个模型 B,并且每个模型 C 都有完全相同的模型 C,该怎么办? select_lated 仍将返回 60 000 行,而 prefetch_lated 将为模型 A 返回 10 000 行,为模型 B 返回 3 行,为模型 C 返回 2 行。因此总共 10 006 行。现在,根据模型的结构(即,如果它具有长 Text() 字段等), prefetch_lated 可能会更快,尽管它需要执行三个单独的查询而不是一个。
记忆
ormar 不保留已加载模型的注册表。
这意味着在上面的 select_lated 示例中,您将始终拥有 10 000 个模型 A、30 000 个模型 B(即使数据库中的唯一行数为 3 - select_lated 的处理会为每个父模型生成新的子模型)。和 60 000 个模型 C.
如果第 1、10、100 行等共享同一个模型 B,并且您更新其中之一,则共享同一子项的其余行将不会立即更新。如果将更改保存到数据库中,则更改仅在重新加载后才可用(单独加载每个子项或再次加载整个查询)。这意味着 select_lated 将使用更多内存,因为每个子对象都被实例化为新对象
!!!注意如果我们决定引入缓存,这可能会在未来版本中发生变化。
!!!警告默认情况下,所有子项(或相同模型加载 2 次以上的事件)都是完全独立的、不同的 python 对象,尽管它们代表数据库中的同一行。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22 | They will evaluate to True when compared, so in example above:
```python
# will return True if child1 of both rows is the same child db row
row1.child1 == row100.child1
# same here:
model1 = await Model.get(pk=1)
model2 = await Model.get(pk=1) # same pk = same row in db
# will return `True`
model1 == model2
```
but
```python
# will return False (note that id is a python `builtin` function not ormar one).
id(row1.child1) == id(ro100.child1)
# from above - will also return False
id(model1) == id(model2)
```
|
相反,使用 prefetch_lated,每个唯一的不同子模型仅实例化一次,并且相同的子模型在所有父模型之间共享。这意味着在上面的 prefetch_lated 示例中,如果表 B 中有 3 个不同的模型,表 C 中有 2 个不同的模型,则所有模型 A 实例之间将仅共享 5 个子嵌套模型。这也意味着,如果您更新任何属性,则所有父对象都会更新该属性,因为它们共享相同的子对象。
模型方法
每个模型实例都有一组方法来保存、更新或加载自身。
加载
您可以通过调用load()方法加载ForeignKey相关模型。
load() 可用于从数据库刷新模型(如果它被其他进程更改)。
!!!tip 阅读有关模型方法中的 load() 方法的更多信息
QuerysetProxy 方法
当直接访问相关的ManyToMany字段以及ReverseForeignKey时,返回相关模型的列表。
但同时它公开了 QuerySet API 的子集,因此您可以直接从父模型过滤、创建、选择相关模型等。
选择相关
与上面的 select_lated 函数完全相同,但允许您从关系的另一端获取相关对象。
!!!tip 要了解有关 QuerysetProxy 的更多信息,请访问 querysetproxy 部分
全选
与上面的 select_all 函数完全相同,但允许您从关系的另一端获取相关对象。
!!!tip 要了解有关 QuerysetProxy 的更多信息,请访问 querysetproxy 部分
预取相关
与上面的 prefetch_lated 函数完全相同,但允许您从关系的另一端获取相关对象。
!!!tip 要了解有关 QuerysetProxy 的更多信息,请访问 querysetproxy 部分