Skip to content

连接和子查询

要将一个表连接到另一个表,以便还加载相关模型,您可以使用以下方法。

  • select_related(related: Union[List, str]) -> QuerySet

  • select_all(follow: bool = True) -> QuerySet

  • prefetch_related(related: Union[List, str]) -> QuerySet

  • 模型

    • Model.load()方法
    • 查询集代理

    • QuerysetProxy.select_related(related: Union[List, str]) 方法

    • QuerysetProxy.select_all(follow: bool=True) 方法
    • QuerysetProxy.prefetch_related(related: Union[List, str]) 方法

选择相关

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 实例,以便您可以将它们链接在一起

1
2
3
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 为了不陷入无限循环,因为相关模型也保持与父模型访问模型集的关系。

1
2
3
4
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)

要选择具有所有分支机构和地址的所有公司,您只需查询:

1
2
3
4
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 实例,以便您可以将它们链接在一起

1
2
3
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 个子对象。如下所示:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
                     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 部分