关系加载技术

关于本文档

本节深入介绍如何加载相关对象。读者应熟悉 关系配置 和基本用法。

这里的大多数示例假设使用类似于 select 设置 中所示的“用户/地址”映射设置。

SQLAlchemy 的一大特点是提供对查询时如何加载相关对象的广泛控制。这里所说的“相关对象”指的是使用 relationship() 在映射器上配置的集合或标量关联。这种行为可以在映射器构建时通过使用 relationship.lazy 参数传递给 relationship() 函数来配置,也可以通过在 Select 结构中使用 ORM 加载选项 来配置。

关系加载分为三类:延迟加载、预加载不加载。延迟加载指的是从查询中返回的对象,其相关对象最初没有加载。当第一次访问特定对象上的给定集合或引用时,会发出一个额外的 SELECT 语句来加载请求的集合。

预加载指的是从查询中返回的对象,其相关集合或标量引用已在前端加载。ORM 通过以下两种方式实现这一点:要么通过在它通常发出的 SELECT 语句中添加 JOIN 来同时加载相关行,要么通过在主 SELECT 语句之后发出额外的 SELECT 语句来一次性加载集合或标量引用。

“不加载”指的是在给定关系上禁用加载,无论是属性为空并且从未加载,还是在访问属性时引发错误,以防止出现不必要的延迟加载。

关系加载风格概述

关系加载的主要形式有:

  • 延迟加载 - 通过 lazy='select'lazyload() 选项实现,这种加载形式在访问属性时会发出一个 SELECT 语句,以延迟加载单个对象上的相关引用。延迟加载是所有未明确指定 relationship.lazy 选项的 relationship() 结构的 默认加载风格。延迟加载在 延迟加载 中有详细介绍。

  • Select IN 加载 - 通过 lazy='selectin'selectinload() 选项实现,这种加载形式会发出第二个(或多个)SELECT 语句,将父对象的 主键标识符组装成 IN 子句,以便通过主键一次性加载相关集合/标量引用的所有成员。Select IN 加载在 Select IN 加载 中有详细介绍。

  • 连接式预加载 - 通过 lazy='joined'joinedload() 选项实现,这种加载形式会对给定的 SELECT 语句应用 JOIN,以便在同一结果集中加载相关行。连接式预加载在 连接式预加载 中有详细介绍。

  • 引发加载 - 通过 lazy='raise'lazy='raise_on_sql'raiseload() 选项实现,这种加载形式会在延迟加载通常发生的时候触发,但它会引发 ORM 异常,以防止应用程序进行不必要的延迟加载。关于引发加载的介绍请见 使用 raiseload 防止不必要的延迟加载

  • 子查询式预加载 - 通过 lazy='subquery'subqueryload() 选项实现,这种加载形式会发出第二个 SELECT 语句,其中重新陈述了嵌入在子查询中的原始查询,然后将该子查询与要加载的相关表进行 JOIN,以便一次性加载相关集合/标量引用的所有成员。子查询式预加载在 子查询式预加载 中有详细介绍。

  • 只写加载 - 通过 lazy='write_only' 或通过使用 WriteOnlyMapped 注解在 Relationship 对象的左侧实现。这种只加载集合的加载风格会生成一种替代的属性检测机制,该机制永远不会隐式从数据库中加载记录,而只会允许使用 WriteOnlyCollection.add()WriteOnlyCollection.add_all()WriteOnlyCollection.remove() 方法。查询集合是通过调用使用 WriteOnlyCollection.select() 方法构建的 SELECT 语句来完成的。关于只写加载的讨论请见 只写关系

  • 动态加载 - 通过 lazy='dynamic' 或通过使用 DynamicMapped 注解在 Relationship 对象的左侧实现。这是一种遗留的只加载集合的加载风格,它会在访问集合时生成一个 Query 对象,允许对集合内容发出自定义 SQL。然而,动态加载器会在各种情况下隐式地遍历底层集合,这使得它们不太适合管理真正的大型集合。动态加载器已被 “只写” 集合取代,后者会防止底层集合在任何情况下被隐式加载。关于动态加载器的讨论请见 动态关系加载器

在映射时配置加载策略

特定关系的加载策略可以在映射时配置,以在所有情况下生效,即加载映射类型的对象时,除非存在任何修改它的查询级选项。这使用 relationship.lazy 参数对 relationship() 进行配置;此参数的常见值包括 selectselectinjoined

以下示例说明了 一对多 中的关系示例,配置 Parent.children 关系以在发出 Parent 对象的 SELECT 语句时使用 Select IN 加载

from typing import List

from sqlalchemy import ForeignKey
from sqlalchemy.orm import DeclarativeBase
from sqlalchemy.orm import Mapped
from sqlalchemy.orm import mapped_column
from sqlalchemy.orm import relationship


class Base(DeclarativeBase):
    pass


class Parent(Base):
    __tablename__ = "parent"

    id: Mapped[int] = mapped_column(primary_key=True)
    children: Mapped[List["Child"]] = relationship(lazy="selectin")


class Child(Base):
    __tablename__ = "child"

    id: Mapped[int] = mapped_column(primary_key=True)
    parent_id: Mapped[int] = mapped_column(ForeignKey("parent.id"))

上面,每当加载一组 Parent 对象时,每个 Parentchildren 集合也将被填充,使用发出第二个查询的 "selectin" 加载策略。

relationship.lazy 参数的默认值为 "select",表示 延迟加载

使用加载器选项进行关系加载

配置加载策略的另一种,也可能是更常见的方式是在特定属性的每个查询的基础上使用 Select.options() 方法对其进行设置。使用加载器选项可以对关系加载进行非常详细的控制;最常见的选项有 joinedload()selectinload()lazyload()。该选项接受一个类绑定的属性,该属性引用应该被定位的特定类/属性。

from sqlalchemy import select
from sqlalchemy.orm import lazyload

# set children to load lazily
stmt = select(Parent).options(lazyload(Parent.children))

from sqlalchemy.orm import joinedload

# set children to load eagerly with a join
stmt = select(Parent).options(joinedload(Parent.children))

加载器选项还可以使用方法链来“链接”,以指定加载在更深层次上如何发生。

from sqlalchemy import select
from sqlalchemy.orm import joinedload

stmt = select(Parent).options(
    joinedload(Parent.children).subqueryload(Child.subelements)
)

链接的加载器选项可以应用于“延迟”加载的集合。这意味着,当集合或关联在访问时被延迟加载时,指定的选项将生效。

from sqlalchemy import select
from sqlalchemy.orm import lazyload

stmt = select(Parent).options(lazyload(Parent.children).subqueryload(Child.subelements))

上面,查询将返回 Parent 对象,而不加载 children 集合。当特定 Parent 对象上的 children 集合首次被访问时,它将延迟加载相关对象,但还会对 children 每个成员上的 subelements 集合应用急切加载。

向加载器选项添加条件

用于指示加载器选项的关系属性包括向创建的连接的 ON 子句或 WHERE 条件添加附加过滤条件的能力,具体取决于加载策略。这可以使用 PropComparator.and_() 方法实现,该方法将传递一个选项,使加载的结果限制在给定的过滤条件范围内。

from sqlalchemy import select
from sqlalchemy.orm import lazyload

stmt = select(A).options(lazyload(A.bs.and_(B.id > 5)))

使用限制条件时,如果特定集合已加载,则不会刷新它;为了确保新的条件生效,请应用 填充现有 执行选项。

from sqlalchemy import select
from sqlalchemy.orm import lazyload

stmt = (
    select(A)
    .options(lazyload(A.bs.and_(B.id > 5)))
    .execution_options(populate_existing=True)
)

为了向查询中的所有实体添加过滤条件,无论加载策略如何或其在加载过程中的位置如何,请参阅 with_loader_criteria() 函数。

版本 1.4 中的新增功能。

使用 Load.options() 指定子选项

使用方法链,路径中每个链接的加载器样式被明确声明。要在不更改特定属性的现有加载器样式的情况下沿着路径导航,可以使用 defaultload() 方法/函数。

from sqlalchemy import select
from sqlalchemy.orm import defaultload

stmt = select(A).options(defaultload(A.atob).joinedload(B.btoc))

可以使用类似的方法使用 Load.options() 方法同时指定多个子选项。

from sqlalchemy import select
from sqlalchemy.orm import defaultload
from sqlalchemy.orm import joinedload

stmt = select(A).options(
    defaultload(A.atob).options(joinedload(B.btoc), joinedload(B.btod))
)

另请参阅

在相关对象和集合上使用 load_only() - 说明了组合关系和面向列的加载器选项的示例。

注意

应用于对象的延迟加载集合的加载器选项对特定对象实例是“粘性”的,这意味着它们将在特定对象加载的集合上持续存在,只要该对象存在于内存中。例如,在前面的示例中

stmt = select(Parent).options(lazyload(Parent.children).subqueryload(Child.subelements))

如果特定 Parent 对象上的 children 集合由上面的查询加载并已过期(例如,当 Session 对象的事务提交或回滚时,或者使用 Session.expire_all() 时),当 Parent.children 集合下次被访问以重新加载它时,Child.subelements 集合将再次使用子查询急切加载进行加载。即使上面的 Parent 对象是从指定了不同选项集的后续查询中访问的,情况仍然如此。要更改现有对象上的选项而不将其清除和重新加载,必须使用 填充现有 执行选项一起显式设置它们。

# change the options on Parent objects that were already loaded
stmt = (
    select(Parent)
    .execution_options(populate_existing=True)
    .options(lazyload(Parent.children).lazyload(Child.subelements))
    .all()
)

如果上面加载的对象完全从 Session 中清除,例如由于垃圾收集或使用 Session.expunge_all(),则“粘性”选项也将消失,并且如果新创建的对象再次加载,它们将使用新的选项。

未来的 SQLAlchemy 版本可能会添加更多用于操作已加载对象上的加载器选项的替代方案。

延迟加载

默认情况下,所有对象间关系都是延迟加载。与 relationship() 关联的标量或集合属性包含一个触发器,该触发器会在第一次访问该属性时触发。此触发器通常在访问时发出 SQL 调用以加载相关对象或对象。

>>> spongebob.addresses
SELECT addresses.id AS addresses_id, addresses.email_address AS addresses_email_address, addresses.user_id AS addresses_user_id FROM addresses WHERE ? = addresses.user_id [5]
[<Address(u'[email protected]')>, <Address(u'[email protected]')>]

唯一没有发出 SQL 的情况是简单的多对一关系,当相关对象可以仅通过其主键来识别,并且该对象已经存在于当前 Session 中时。因此,虽然延迟加载对于相关集合可能很昂贵,但在加载大量具有针对相对较小的目标对象集的简单多对一关系的对象的情况下,延迟加载可能能够本地引用这些对象,而无需发出与父对象一样多的 SELECT 语句。

这种“在属性访问时加载”的默认行为被称为“延迟”或“选择”加载 - 名称“选择”是因为当第一次访问该属性时,通常会发出“SELECT”语句。

可以使用 lazyload() 加载器选项为通常以其他方式配置的给定属性启用延迟加载。

from sqlalchemy import select
from sqlalchemy.orm import lazyload

# force lazy loading for an attribute that is set to
# load some other way normally
stmt = select(User).options(lazyload(User.addresses))

使用 raiseload 阻止不必要的延迟加载

使用 lazyload() 策略会导致对象关系映射中最常见的问题之一:N+1 问题,即对于加载的任何 N 个对象,访问其延迟加载的属性将导致发出 N+1 个 SELECT 语句。在 SQLAlchemy 中,解决 N+1 问题的常见方法是使用其功能强大的急切加载系统。但是,急切加载要求在 Select 中预先指定要加载的属性。对于可能访问未急切加载的其他属性的代码,如果不需要延迟加载,可以使用 raiseload() 策略;此加载器策略将延迟加载的行为替换为引发信息性错误。

from sqlalchemy import select
from sqlalchemy.orm import raiseload

stmt = select(User).options(raiseload(User.addresses))

上面,从上述查询加载的 User 对象将不会加载 .addresses 集合;如果后面的某些代码尝试访问此属性,则会引发 ORM 异常。

raiseload() 可以与所谓的“通配符”说明符一起使用,以指示所有关系应使用此策略。例如,要将一个属性设置为急切加载,并将所有其他属性设置为 raise

from sqlalchemy import select
from sqlalchemy.orm import joinedload
from sqlalchemy.orm import raiseload

stmt = select(Order).options(joinedload(Order.items), raiseload("*"))

上面的通配符将应用于所有关系,而不仅仅是 Order 上的 items 之外的关系,也包括 Item 对象上的所有关系。要为 Order 对象设置 raiseload(),请使用 Load 指定完整的路径

from sqlalchemy import select
from sqlalchemy.orm import joinedload
from sqlalchemy.orm import Load

stmt = select(Order).options(joinedload(Order.items), Load(Order).raiseload("*"))

相反,要为 Item 对象设置 raise

stmt = select(Order).options(joinedload(Order.items).raiseload("*"))

raiseload() 选项仅适用于关系属性。对于面向列的属性,defer() 选项支持 defer.raiseload 选项,其工作原理相同。

提示

“raiseload”策略不适用于 工作单元 刷新过程。这意味着如果 Session.flush() 过程需要加载集合才能完成其工作,它将在绕过任何 raiseload() 指令的情况下进行加载。

联接急切加载

联接急切加载是 SQLAlchemy ORM 中包含的最早的急切加载样式。它的工作原理是将 JOIN(默认情况下为 LEFT OUTER join)连接到发出的 SELECT 语句,并从与父级相同的 result set 中填充目标标量/集合。

在映射级别,这看起来像

class Address(Base):
    # ...

    user: Mapped[User] = relationship(lazy="joined")

联接急切加载通常用作查询的选项,而不是作为映射上的默认加载选项,尤其是在用于集合而不是多对一引用时。这可以通过使用 joinedload() 加载器选项来实现

>>> from sqlalchemy import select
>>> from sqlalchemy.orm import joinedload
>>> stmt = select(User).options(joinedload(User.addresses)).filter_by(name="spongebob")
>>> spongebob = session.scalars(stmt).unique().all()
SELECT addresses_1.id AS addresses_1_id, addresses_1.email_address AS addresses_1_email_address, addresses_1.user_id AS addresses_1_user_id, users.id AS users_id, users.name AS users_name, users.fullname AS users_fullname, users.nickname AS users_nickname FROM users LEFT OUTER JOIN addresses AS addresses_1 ON users.id = addresses_1.user_id WHERE users.name = ? ['spongebob']

提示

在包含 joinedload() 来引用一对多或多对多集合时,必须将 Result.unique() 方法应用于返回的结果,这将通过主键对传入的行进行唯一化,否则这些行会因联接而被乘以。如果不存在此方法,ORM 将引发错误。

这在现代 SQLAlchemy 中不是自动的,因为它会更改 result set 的行为,使其返回比语句通常返回的行数更少的 ORM 对象。因此,SQLAlchemy 始终明确使用 Result.unique(),这样就不会对返回的对象是通过主键进行唯一化产生歧义。

默认情况下发出的 JOIN 是 LEFT OUTER JOIN,以便允许引导对象不引用相关行。对于保证有元素的属性,例如指向相关对象的非空外键的多对一引用,可以通过使用内部联接来提高查询效率;这可以在映射级别通过 relationship.innerjoin 标志来实现

class Address(Base):
    # ...

    user_id: Mapped[int] = mapped_column(ForeignKey("users.id"))
    user: Mapped[User] = relationship(lazy="joined", innerjoin=True)

在查询选项级别,通过 joinedload.innerjoin 标志

from sqlalchemy import select
from sqlalchemy.orm import joinedload

stmt = select(Address).options(joinedload(Address.user, innerjoin=True))

当应用于包含 OUTER JOIN 的链时,JOIN 会向右嵌套自身

>>> from sqlalchemy import select
>>> from sqlalchemy.orm import joinedload
>>> stmt = select(User).options(
...     joinedload(User.addresses).joinedload(Address.widgets, innerjoin=True)
... )
>>> results = session.scalars(stmt).unique().all()
SELECT widgets_1.id AS widgets_1_id, widgets_1.name AS widgets_1_name, addresses_1.id AS addresses_1_id, addresses_1.email_address AS addresses_1_email_address, addresses_1.user_id AS addresses_1_user_id, users.id AS users_id, users.name AS users_name, users.fullname AS users_fullname, users.nickname AS users_nickname FROM users LEFT OUTER JOIN ( addresses AS addresses_1 JOIN widgets AS widgets_1 ON addresses_1.widget_id = widgets_1.id ) ON users.id = addresses_1.user_id

提示

如果在发出 SELECT 时使用数据库行锁定技术,这意味着正在使用 Select.with_for_update() 方法来发出 SELECT..FOR UPDATE,那么联接的表也可能会被锁定,具体取决于所用后端的行为。由于此原因,不建议在 SELECT..FOR UPDATE 的同时使用联接急切加载。

联接急切加载的真谛

由于联接急切加载似乎与 Select.join() 的使用有很多相似之处,因此经常会让人混淆何时以及如何使用它。必须理解,虽然 Select.join() 用于更改查询的结果,但 joinedload() 会尽最大努力更改查询的结果,而是隐藏渲染联接的影响,只允许存在相关对象。

加载器策略背后的理念是,任何一组加载方案都可以应用于特定的查询,并且结果不会改变 - 只有完全加载相关对象和集合所需的 SQL 语句数量会改变。特定的查询可能一开始使用所有延迟加载。在上下文中使用它之后,可能会发现特定属性或集合总是被访问,并且更改这些属性的加载器策略将更有效率。可以更改策略而无需对查询进行其他修改,结果将保持相同,但会发出更少的 SQL 语句。理论上(实际上也几乎如此),无论对 Select 进行哪些操作,都不会基于加载器策略的更改而加载不同的主对象或相关对象集。

joinedload() 特别是如何实现不影响以任何方式返回的实体行的结果,是因为它为添加到查询中的联接创建了一个匿名别名,这样其他部分的查询就无法引用它。例如,下面的查询使用 joinedload()usersaddresses 创建一个 LEFT OUTER JOIN,但添加到 Address.email_addressORDER BY 无效 - Address 实体未在查询中命名

>>> from sqlalchemy import select
>>> from sqlalchemy.orm import joinedload
>>> stmt = (
...     select(User)
...     .options(joinedload(User.addresses))
...     .filter(User.name == "spongebob")
...     .order_by(Address.email_address)
... )
>>> result = session.scalars(stmt).unique().all()
SELECT addresses_1.id AS addresses_1_id, addresses_1.email_address AS addresses_1_email_address, addresses_1.user_id AS addresses_1_user_id, users.id AS users_id, users.name AS users_name, users.fullname AS users_fullname, users.nickname AS users_nickname FROM users LEFT OUTER JOIN addresses AS addresses_1 ON users.id = addresses_1.user_id WHERE users.name = ? ORDER BY addresses.email_address <-- this part is wrong ! ['spongebob']

上面,ORDER BY addresses.email_address 无效,因为 addresses 不在 FROM 列表中。加载 User 记录并按电子邮件地址排序的正确方法是使用 Select.join()

>>> from sqlalchemy import select
>>> stmt = (
...     select(User)
...     .join(User.addresses)
...     .filter(User.name == "spongebob")
...     .order_by(Address.email_address)
... )
>>> result = session.scalars(stmt).unique().all()
SELECT users.id AS users_id, users.name AS users_name, users.fullname AS users_fullname, users.nickname AS users_nickname FROM users JOIN addresses ON users.id = addresses.user_id WHERE users.name = ? ORDER BY addresses.email_address ['spongebob']

当然,上面的语句与之前的语句不同,因为来自 addresses 的列根本没有包含在结果中。我们可以添加 joinedload() 回来,这样就有两个连接 - 一个是我们用来排序的连接,另一个用于匿名加载 User.addresses 集合的内容。

>>> stmt = (
...     select(User)
...     .join(User.addresses)
...     .options(joinedload(User.addresses))
...     .filter(User.name == "spongebob")
...     .order_by(Address.email_address)
... )
>>> result = session.scalars(stmt).unique().all()
SELECT addresses_1.id AS addresses_1_id, addresses_1.email_address AS addresses_1_email_address, addresses_1.user_id AS addresses_1_user_id, users.id AS users_id, users.name AS users_name, users.fullname AS users_fullname, users.nickname AS users_nickname FROM users JOIN addresses ON users.id = addresses.user_id LEFT OUTER JOIN addresses AS addresses_1 ON users.id = addresses_1.user_id WHERE users.name = ? ORDER BY addresses.email_address ['spongebob']

我们上面看到的是,我们对 Select.join() 的使用是为了提供我们希望在后续查询条件中使用的 JOIN 子句,而我们对 joinedload() 的使用只关心加载 User.addresses 集合,针对结果中的每个 User。在这种情况下,这两个连接很可能显得冗余 - 实际上它们确实是冗余的。如果我们只想使用一个 JOIN 来进行集合加载和排序,我们可以使用 contains_eager() 选项,下面将在 将显式 JOIN/语句路由到急切加载的集合 中介绍。但为了了解为什么 joinedload() 会执行它所执行的操作,请考虑如果我们对特定的 Address 进行 **过滤**。

>>> stmt = (
...     select(User)
...     .join(User.addresses)
...     .options(joinedload(User.addresses))
...     .filter(User.name == "spongebob")
...     .filter(Address.email_address == "[email protected]")
... )
>>> result = session.scalars(stmt).unique().all()
SELECT addresses_1.id AS addresses_1_id, addresses_1.email_address AS addresses_1_email_address, addresses_1.user_id AS addresses_1_user_id, users.id AS users_id, users.name AS users_name, users.fullname AS users_fullname, users.nickname AS users_nickname FROM users JOIN addresses ON users.id = addresses.user_id LEFT OUTER JOIN addresses AS addresses_1 ON users.id = addresses_1.user_id WHERE users.name = ? AND addresses.email_address = ? ['spongebob', '[email protected]']

上面,我们可以看到这两个 JOIN 扮演着截然不同的角色。一个将精确匹配一行,即 UserAddress 的连接,其中 Address.email_address=='[email protected]'。另一个 LEFT OUTER JOIN 将匹配与 User 相关的所有 Address 行,并且仅用于填充 User.addresses 集合,针对返回的那些 User 对象。

通过将 joinedload() 的使用更改为另一种加载风格,我们可以完全独立于用于检索我们想要的实际 User 行的 SQL 来更改集合的加载方式。下面我们将 joinedload() 更改为 selectinload()

>>> stmt = (
...     select(User)
...     .join(User.addresses)
...     .options(selectinload(User.addresses))
...     .filter(User.name == "spongebob")
...     .filter(Address.email_address == "[email protected]")
... )
>>> result = session.scalars(stmt).all()
SELECT users.id AS users_id, users.name AS users_name, users.fullname AS users_fullname, users.nickname AS users_nickname FROM users JOIN addresses ON users.id = addresses.user_id WHERE users.name = ? AND addresses.email_address = ? ['spongebob', '[email protected]'] # ... selectinload() emits a SELECT in order # to load all address records ...

当使用连接急切加载时,如果查询包含影响外部返回行的修饰符,例如使用 DISTINCT、LIMIT、OFFSET 或等效项时,完成的语句首先会包装在子查询中,并且专门用于连接急切加载的连接会应用于子查询。SQLAlchemy 的连接急切加载会更进一步,然后又会更进一步,以绝对确保它不会影响查询的最终结果,只会影响集合和相关对象的加载方式,无论查询的格式是什么。

Select IN 加载

在大多数情况下,Select IN 加载是急切加载对象集合最简单、最有效的方式。Select IN 急切加载唯一不可行的情况是当模型使用复合主键时,并且后端数据库不支持使用 IN 的元组,目前包括 SQL Server。

“Select IN” 急切加载是使用 "selectin" 参数传递给 relationship.lazy 或使用 selectinload() 加载器选项来提供的。这种加载风格会发出一个 SELECT 语句,该语句引用父对象的 主键值,或者在多对一关系的情况下,引用子对象的那些主键值,在一个 IN 子句内,以便加载相关的关联。

>>> from sqlalchemy import select
>>> from sqlalchemy.orm import selectinload
>>> stmt = (
...     select(User)
...     .options(selectinload(User.addresses))
...     .filter(or_(User.name == "spongebob", User.name == "ed"))
... )
>>> result = session.scalars(stmt).all()
SELECT users.id AS users_id, users.name AS users_name, users.fullname AS users_fullname, users.nickname AS users_nickname FROM users WHERE users.name = ? OR users.name = ? ('spongebob', 'ed') SELECT addresses.id AS addresses_id, addresses.email_address AS addresses_email_address, addresses.user_id AS addresses_user_id FROM addresses WHERE addresses.user_id IN (?, ?) (5, 7)

上面,第二个 SELECT 引用了 addresses.user_id IN (5, 7),其中“5”和“7”是前面两个加载的 User 对象的主键值;在完全加载一批对象之后,它们的主键值会被注入到第二个 SELECT 的 IN 子句中。因为 UserAddress 之间的关系具有一个简单的 主键连接条件,并且提供可以从 Address.user_id 中推导出 User 的主键值,所以该语句根本没有 JOIN 或子查询。

对于简单 的多对一加载,也不需要 JOIN,因为会使用父对象的外键值。

>>> from sqlalchemy import select
>>> from sqlalchemy.orm import selectinload
>>> stmt = select(Address).options(selectinload(Address.user))
>>> result = session.scalars(stmt).all()
SELECT addresses.id AS addresses_id, addresses.email_address AS addresses_email_address, addresses.user_id AS addresses_user_id FROM addresses SELECT users.id AS users_id, users.name AS users_name, users.fullname AS users_fullname, users.nickname AS users_nickname FROM users WHERE users.id IN (?, ?) (1, 2)

提示

我们所说的“简单”是指,relationship.primaryjoin 条件表达了“一”侧的主键与“多”侧的直接外键之间的相等比较,没有其他任何条件。

Select IN 加载也支持 多对多关系,它目前将跨越所有三个表进行 JOIN 以匹配从一侧到另一侧的行。

关于这种加载需要注意以下几点:

  • 该策略会一次为最多 500 个父主键值发出一个 SELECT,因为主键被渲染成 SQL 语句中的一个大型 IN 表达式。像 Oracle 这样的某些数据库对 IN 表达式的大小有严格的限制,并且总体而言,SQL 字符串的大小不应该任意大。

  • 由于“selectin”加载依赖于 IN,所以对于使用复合主键的映射,它必须使用 IN 的“元组”形式,看起来像 WHERE (table.column_a, table.column_b) IN ((?, ?), (?, ?), (?, ?))。此语法目前在 SQL Server 上不支持,并且对于 SQLite 来说需要至少版本 3.15。SQLAlchemy 中没有特殊的逻辑来提前检查哪些平台支持这种语法,哪些平台不支持;如果在不支持的平台上运行,数据库会立即返回错误。SQLAlchemy 只是直接运行 SQL 使其失败的优点是,如果某个特定的数据库开始支持这种语法,那么它将无需对 SQLAlchemy 进行任何更改即可工作(就像 SQLite 的情况一样)。

子查询急切加载

遗留功能

subqueryload() 急切加载器目前大多是遗留的,被 selectinload() 策略所取代,该策略的设计更加简单,在功能方面(例如 每项产量)更加灵活,并且在大多数情况下会发出更高效的 SQL 语句。由于 subqueryload() 依赖于重新解释原始的 SELECT 语句,因此在提供非常复杂的源查询时,它可能无法有效地工作。

subqueryload() 可能会继续适用于使用复合主键的对象的急切加载集合的特定情况,在 Microsoft SQL Server 后端上,该后端仍然不支持“元组 IN”语法。

子查询加载在操作上类似于 Select IN 急切加载,但是发出的 SELECT 语句是从原始语句派生的,并且比 Select IN 急切加载具有更复杂的查询结构。

子查询急切加载是使用 "subquery" 参数传递给 relationship.lazy 或使用 subqueryload() 加载器选项来提供的。

子查询急切加载的操作是针对每个要加载的关系发出一个第二个 SELECT 语句,一次针对所有结果对象。这个 SELECT 语句引用原始的 SELECT 语句,并将其包装在子查询中,以便我们检索返回的主对象的相同主键列表,然后将其链接到所有集合成员的总和,以便一次性加载它们。

>>> from sqlalchemy import select
>>> from sqlalchemy.orm import subqueryload
>>> stmt = select(User).options(subqueryload(User.addresses)).filter_by(name="spongebob")
>>> results = session.scalars(stmt).all()
SELECT users.id AS users_id, users.name AS users_name, users.fullname AS users_fullname, users.nickname AS users_nickname FROM users WHERE users.name = ? ('spongebob',) SELECT addresses.id AS addresses_id, addresses.email_address AS addresses_email_address, addresses.user_id AS addresses_user_id, anon_1.users_id AS anon_1_users_id FROM ( SELECT users.id AS users_id FROM users WHERE users.name = ?) AS anon_1 JOIN addresses ON anon_1.users_id = addresses.user_id ORDER BY anon_1.users_id, addresses.id ('spongebob',)

关于这种加载需要注意以下几点:

  • “subquery”加载器策略发出的 SELECT 语句与“selectin”不同,它需要子查询,并且会继承原始查询中存在的任何性能限制。子查询本身也可能会因所用数据库的具体情况而产生性能损失。

  • “子查询”加载为了正常工作,需要一些特殊的排序要求。使用 subqueryload() 以及限制修饰符,例如 Select.limit()Select.offset() 的查询,应该 **始终** 包括针对唯一列(例如主键)的 Select.order_by(),以便由 subqueryload() 发出的附加查询包含与父查询中使用的相同的排序。没有它,内部查询有可能返回错误的行

    # incorrect, no ORDER BY
    stmt = select(User).options(subqueryload(User.addresses).limit(1))
    
    # incorrect if User.name is not unique
    stmt = select(User).options(subqueryload(User.addresses)).order_by(User.name).limit(1)
    
    # correct
    stmt = (
        select(User)
        .options(subqueryload(User.addresses))
        .order_by(User.name, User.id)
        .limit(1)
    )
  • “子查询”加载在用于多级深层预加载时还会遇到额外的性能/复杂性问题,因为子查询将被重复嵌套。

  • “子查询”加载与 每行返回 提供的“批处理”加载不兼容,对于集合和标量关系都是如此。

出于上述原因,应该优先使用“选择加载”策略而不是“子查询”策略。

另请参阅

Select IN 加载

使用哪种加载方式?

使用哪种类型的加载通常归结于优化 SQL 执行次数、SQL 发出的复杂性和获取的数据量之间的权衡。

一对多/多对多集合 - selectinload() 通常是最好的加载策略。它发出一个额外的 SELECT 语句,该语句使用尽可能少的表,使原始语句保持不变,并且对于任何类型的原始查询都最灵活。它唯一的重大限制是当在一个具有复合主键的表上使用一个不支持“元组 IN”的后端时,当前包括 SQL Server 和非常旧的 SQLite 版本;所有其他包含的后端都支持它。

多对一 - joinedload() 策略是最通用的策略。在特殊情况下,immediateload() 策略可能也很有用,如果潜在的相关值很少,因为此策略将在没有发出任何 SQL 的情况下从本地 Session 中获取对象,如果相关对象已经存在。

多态预加载

支持在每个预加载基础上指定多态选项。有关 PropComparator.of_type() 方法与 with_polymorphic() 函数结合的示例,请参见 多态子类型的预加载 部分。

通配符加载策略

每个 joinedload()subqueryload()lazyload()selectinload()raiseload() 都可以用来为特定查询设置 relationship() 加载的默认样式,影响所有未在语句中另行指定 relationship() 映射的属性。此功能可通过将字符串 '*' 作为参数传递给这些选项中的任何一个来使用

from sqlalchemy import select
from sqlalchemy.orm import lazyload

stmt = select(MyClass).options(lazyload("*"))

上面,lazyload('*') 选项将取代所有 relationship() 构造中使用的 lazy 设置,除了使用 lazy='write_only'lazy='dynamic' 的那些。

例如,如果某些关系指定 lazy='joined'lazy='selectin',使用 lazyload('*') 将单方面导致所有这些关系使用 'select' 加载,例如在访问每个属性时发出 SELECT 语句。

该选项不会取代查询中说明的加载器选项,例如 joinedload()selectinload() 等。下面的查询将继续对 widget 关系使用连接加载

from sqlalchemy import select
from sqlalchemy.orm import lazyload
from sqlalchemy.orm import joinedload

stmt = select(MyClass).options(lazyload("*"), joinedload(MyClass.widget))

虽然对 joinedload() 的说明将在 lazyload() 选项之前或之后出现的情况下发生,如果传递了多个每个都包含 "*" 的选项,则最后一个选项将生效。

每个实体的通配符加载策略

通配符加载器策略的变体是能够在每个实体的基础上设置策略。例如,如果查询 UserAddress,我们可以指示 Address 上的所有关系使用延迟加载,同时保持 User 的加载器策略不受影响,方法是首先应用 Load 对象,然后将 * 指定为链接选项

from sqlalchemy import select
from sqlalchemy.orm import Load

stmt = select(User, Address).options(Load(Address).lazyload("*"))

上面,Address 上的所有关系将被设置为延迟加载。

将显式连接/语句路由到预加载的集合

joinedload() 的行为是自动创建连接,使用匿名别名作为目标,其结果将被路由到加载对象上的集合和标量引用中。通常情况下,查询已经包含代表特定集合或标量引用的必要连接,并且由 joinedload 功能添加的连接是冗余的 - 然而,您仍然希望填充集合/引用。

为此,SQLAlchemy 提供了 contains_eager() 选项。此选项的使用方式与 joinedload() 选项相同,只是假定 Select 对象将明确包含适当的连接,通常使用诸如 Select.join() 之类的

from sqlalchemy.orm import contains_eager

stmt = select(User).join(User.addresses).options(contains_eager(User.addresses))

如果语句的“eager”部分是“aliased”,则应使用 PropComparator.of_type() 指定路径,该方法允许传递特定的 aliased() 结构。

# use an alias of the Address entity
adalias = aliased(Address)

# construct a statement which expects the "addresses" results

stmt = (
    select(User)
    .outerjoin(User.addresses.of_type(adalias))
    .options(contains_eager(User.addresses.of_type(adalias)))
)

# get results normally
r = session.scalars(stmt).unique().all()
SELECT users.user_id AS users_user_id, users.user_name AS users_user_name, adalias.address_id AS adalias_address_id, adalias.user_id AS adalias_user_id, adalias.email_address AS adalias_email_address, (...other columns...) FROM users LEFT OUTER JOIN email_addresses AS email_addresses_1 ON users.user_id = email_addresses_1.user_id

传递给 contains_eager() 的路径需要是起始实体的完整路径。例如,如果我们要加载 Users->orders->Order->items->Item,则选项将用作

stmt = select(User).options(contains_eager(User.orders).contains_eager(Order.items))

使用 contains_eager() 加载自定义过滤的集合结果

当我们使用 contains_eager() 时,我们正在构建用于填充集合的 SQL。由此自然而然地得出,我们可以通过编写 SQL 来加载集合或标量属性的子集,选择**修改**集合将要存储的值。

提示

SQLAlchemy 现在提供了一种更简单的方法来实现这一点,允许将 WHERE 条件直接添加到诸如 joinedload()selectinload() 的加载器选项中,使用 PropComparator.and_()。有关示例,请参阅部分 向加载器选项添加条件

如果相关集合要使用比简单 WHERE 子句更复杂的 SQL 条件或修饰符查询,则此处描述的技术仍然适用。

例如,我们可以加载一个 User 对象,并通过过滤联接数据,使用 contains_eager() 仅将特定地址加载到其 .addresses 集合中,还可以使用 填充现有 确保覆盖任何已加载的集合。

stmt = (
    select(User)
    .join(User.addresses)
    .filter(Address.email_address.like("%@aol.com"))
    .options(contains_eager(User.addresses))
    .execution_options(populate_existing=True)
)

上面的查询将只加载包含至少一个 Address 对象的 User 对象,该对象在其 email 字段中包含子字符串 'aol.com'User.addresses 集合将包含这些 Address 条目,不会包含实际上与集合关联的任何其他 Address 条目。

提示

在所有情况下,除非被告知这样做,否则 SQLAlchemy ORM 不会覆盖已加载的属性和集合。由于正在使用 身份映射,因此 ORM 查询通常返回实际上已存在且已加载到内存中的对象。因此,当使用 contains_eager() 以替代方式填充集合时,通常最好使用 填充现有,如上所示,以便使用新数据刷新已加载的集合。选项 populate_existing 将重置所有已存在的属性,包括挂起的更改,因此请确保在使用它之前将所有数据刷新。使用默认行为为 自动刷新Session 就足够了。

注意

我们使用 contains_eager() 加载的自定义集合不是“粘性的”;也就是说,下次加载此集合时,它将以其通常的默认内容加载。如果对象已过期,集合将被重新加载,这会在使用 Session.commit()Session.rollback() 方法(假设使用默认会话设置)或使用 Session.expire_all()Session.expire() 方法时发生。

另请参阅

向加载器选项添加条件 - 允许在任何关系加载器选项中直接使用 WHERE 条件的现代 API

关系加载器 API

对象名称 描述

contains_eager(*keys, **kw)

指示应从查询中手动声明的列中急切加载给定的属性。

defaultload(*keys)

指示属性应使用其预定义的加载器样式加载。

immediateload(*keys, [recursion_depth])

指示应使用带有每个属性 SELECT 语句的立即加载来加载给定的属性。

joinedload(*keys, **kw)

指示应使用联接急切加载来加载给定的属性。

lazyload(*keys)

指示应使用“延迟”加载来加载给定的属性。

Load

表示加载器选项,这些选项修改启用 ORM 的 Select 或传统 Query 的状态,以影响各种映射属性的加载方式。

noload(*keys)

指示给定的关系属性应保持未加载状态。

raiseload(*keys, **kw)

指示如果访问给定的属性,则应引发错误。

selectinload(*keys, [recursion_depth])

指示应使用 SELECT IN 急切加载来加载给定的属性。

subqueryload(*keys)

指示应使用子查询急切加载来加载给定的属性。

function sqlalchemy.orm.contains_eager(*keys: Literal['*'] | QueryableAttribute[Any], **kw: Any) _AbstractLoad

指示应从查询中手动声明的列中急切加载给定的属性。

此函数是 Load 接口的一部分,支持方法链和独立操作。

该选项与加载所需行的显式联接一起使用,即

sess.query(Order).join(Order.user).options(
    contains_eager(Order.user)
)

上面的查询将从 Order 实体联接到其相关的 User 实体,返回的 Order 对象将具有已预先填充的 Order.user 属性。

它也可以用于自定义急切加载的集合中的条目;查询通常希望使用 填充现有 执行选项,假设父对象的父级集合可能已加载。

sess.query(User).join(User.addresses).filter(
    Address.email_address.like("%@aol.com")
).options(contains_eager(User.addresses)).populate_existing()

有关完整使用详细信息,请参阅部分 将显式联接/语句路由到急切加载的集合中

function sqlalchemy.orm.defaultload(*keys: Literal['*'] | QueryableAttribute[Any]) _AbstractLoad

指示属性应使用其预定义的加载器样式加载。

此加载选项的行为是不改变属性的当前加载方式,这意味着将使用先前配置的加载方式,或者如果未选择先前加载方式,则将使用默认加载方式。

此方法用于将其他加载选项链接到一系列属性的链中,而不会更改链中链接的加载器样式。例如,要为元素的元素设置联接急切加载

session.query(MyClass).options(
    defaultload(MyClass.someattribute).joinedload(
        MyOtherClass.someotherattribute
    )
)

defaultload() 也用于在相关类上设置列级选项,即 defer()undefer()

session.scalars(
    select(MyClass).options(
        defaultload(MyClass.someattribute)
        .defer("some_column")
        .undefer("some_other_column")
    )
)
function sqlalchemy.orm.immediateload(*keys: Literal['*'] | QueryableAttribute[Any], recursion_depth: int | None = None) _AbstractLoad

指示应使用带有每个属性 SELECT 语句的立即加载来加载给定的属性。

加载使用“lazyloader”策略完成,不会触发任何额外的急切加载器。

immediateload() 选项通常被 selectinload() 选项取代,后者通过为所有加载的物体发出 SELECT 来更有效地执行相同的任务。

此函数是 Load 接口的一部分,支持方法链和独立操作。

参数:

recursion_depth

可选的 int;当与自引用关系一起设置为正整数时,表示“selectin”加载将自动继续到该深度,直到没有找到任何元素。

注意

immediateload.recursion_depth 选项目前只支持自引用关系。还没有选项可以自动遍历涉及多个关系的递归结构。

警告

此参数是新的且实验性的,应被视为“alpha”状态

版本 2.0 中的新功能: 添加了 immediateload.recursion_depth

function sqlalchemy.orm.joinedload(*keys: Literal['*'] | QueryableAttribute[Any], **kw: Any) _AbstractLoad

指示应使用联接急切加载来加载给定的属性。

此函数是 Load 接口的一部分,支持方法链和独立操作。

示例

# joined-load the "orders" collection on "User"
select(User).options(joinedload(User.orders))

# joined-load Order.items and then Item.keywords
select(Order).options(
    joinedload(Order.items).joinedload(Item.keywords)
)

# lazily load Order.items, but when Items are loaded,
# joined-load the keywords collection
select(Order).options(
    lazyload(Order.items).joinedload(Item.keywords)
)
参数:

innerjoin

如果为 True,则表示联接急切加载应使用内部联接,而不是默认的左外联接。

select(Order).options(joinedload(Order.user, innerjoin=True))

为了将多个急切联接链接在一起,其中一些可能是 OUTER 而另一些是 INNER,可以使用右嵌套联接将它们链接起来

select(A).options(
    joinedload(A.bs, innerjoin=False).joinedload(
        B.cs, innerjoin=True
    )
)

上面的查询通过“outer”联接链接 A.bs,通过“inner”联接链接 B.cs,将联接呈现为“a LEFT OUTER JOIN (b JOIN c)”。当使用旧版本的 SQLite (< 3.7.16) 时,这种形式的 JOIN 被转换为使用完整的子查询,因为这种语法在其他情况下并不直接支持。

innerjoin 标志也可以用术语 "unnested" 表示。这表示应使用内部联接,除非联接链接到左侧的左外联接,在这种情况下它将呈现为左外联接。例如,假设 A.bs 是外联接

select(A).options(
    joinedload(A.bs).joinedload(B.cs, innerjoin="unnested")
)

上面的联接将呈现为“a LEFT OUTER JOIN b LEFT OUTER JOIN c”,而不是“a LEFT OUTER JOIN (b JOIN c)”。

注意

“unnested” 标志**不会**影响从多对多关联表(例如,配置为 relationship.secondary 的表)到目标表的生成的联接;为了结果的正确性,这些联接始终是 INNER,因此如果链接到 OUTER 联接,则它们始终是右嵌套的。

注意

joinedload() 生成的联接是匿名别名。联接进行的标准无法修改,ORM 启用的 Select 或旧版 Query 无法以任何方式引用这些联接,包括排序。有关详细信息,请参阅联接急切加载的真谛

要生成一个可用的特定 SQL JOIN,请使用 Select.join()Query.join()。要将显式 JOIN 与集合的急切加载结合起来,请使用 contains_eager();请参阅 将显式 JOIN/语句路由到急切加载的集合

function sqlalchemy.orm.lazyload(*keys: Literal['*'] | QueryableAttribute[Any]) _AbstractLoad

指示应使用“延迟”加载来加载给定的属性。

此函数是 Load 接口的一部分,支持方法链和独立操作。

class sqlalchemy.orm.Load

表示加载器选项,这些选项修改启用 ORM 的 Select 或传统 Query 的状态,以影响各种映射属性的加载方式。

Load 对象在大多数情况下是在幕后隐式使用的,当使用查询选项(如 joinedload()defer() 或类似选项)时。它通常不会直接实例化,除非在某些非常特殊的情况下。

另请参阅

每个实体通配符加载策略 - 说明了直接使用 Load 可能有用的示例。

类签名

class sqlalchemy.orm.Load (sqlalchemy.orm.strategy_options._AbstractLoad)

method sqlalchemy.orm.Load.contains_eager(attr: _AttrType, alias: _FromClauseArgument | None = None, _is_chain: bool = False, _propagate_to_loaders: bool = False) Self

继承自 sqlalchemy.orm.strategy_options._AbstractLoad.contains_eager 方法 of sqlalchemy.orm.strategy_options._AbstractLoad

生成一个新的 Load 对象,应用了 contains_eager() 选项。

有关使用示例,请参见 contains_eager()

method sqlalchemy.orm.Load.defaultload(attr: Literal['*'] | QueryableAttribute[Any]) Self

继承自 sqlalchemy.orm.strategy_options._AbstractLoad.defaultload 方法 of sqlalchemy.orm.strategy_options._AbstractLoad

生成一个新的 Load 对象,应用了 defaultload() 选项。

有关使用示例,请参见 defaultload()

method sqlalchemy.orm.Load.defer(key: Literal['*'] | QueryableAttribute[Any], raiseload: bool = False) Self

继承自 sqlalchemy.orm.strategy_options._AbstractLoad.defer 方法 of sqlalchemy.orm.strategy_options._AbstractLoad

生成一个新的 Load 对象,应用了 defer() 选项。

有关使用示例,请参见 defer()

method sqlalchemy.orm.Load.get_children(*, omit_attrs: Tuple[str, ...] = (), **kw: Any) Iterable[HasTraverseInternals]

继承自 HasTraverseInternals.get_children() 方法 of HasTraverseInternals

返回此 HasTraverseInternals 的直接子 HasTraverseInternals 元素。

这用于访问遍历。

**kw 可能包含更改返回集合的标志,例如返回项目的子集以减少较大的遍历,或返回来自不同上下文(如架构级集合而不是子句级)的子项。

method sqlalchemy.orm.Load.immediateload(attr: Literal['*'] | QueryableAttribute[Any], recursion_depth: int | None = None) Self

继承自 sqlalchemy.orm.strategy_options._AbstractLoad.immediateload 方法 of sqlalchemy.orm.strategy_options._AbstractLoad

生成一个新的 Load 对象,应用了 immediateload() 选项。

有关使用示例,请参见 immediateload()

attribute sqlalchemy.orm.Load.inherit_cache: bool | None = None

继承自 HasCacheKey.inherit_cache 属性 HasCacheKey

指示此 HasCacheKey 实例是否应该使用其直接父类使用的缓存键生成方案。

该属性默认为 None,这意味着一个结构还没有考虑它是否适合参与缓存;这在功能上等同于将值设置为 False,除了还会发出警告。

如果与对象对应的 SQL 不会根据此类局部属性(而不是其父类)而更改,则可以将此标志设置为特定类的 True

另请参阅

为自定义结构启用缓存支持 - 为第三方或用户定义的 SQL 结构设置 HasCacheKey.inherit_cache 属性的一般指南。

method sqlalchemy.orm.Load.joinedload(attr: Literal['*'] | QueryableAttribute[Any], innerjoin: bool | None = None) Self

继承自 sqlalchemy.orm.strategy_options._AbstractLoad.joinedload 方法 sqlalchemy.orm.strategy_options._AbstractLoad

生成一个新的 Load 对象,其中应用了 joinedload() 选项。

请参阅 joinedload() 以获取用法示例。

method sqlalchemy.orm.Load.lazyload(attr: Literal['*'] | QueryableAttribute[Any]) Self

继承自 sqlalchemy.orm.strategy_options._AbstractLoad.lazyload 方法 sqlalchemy.orm.strategy_options._AbstractLoad

生成一个新的 Load 对象,其中应用了 lazyload() 选项。

请参阅 lazyload() 以获取用法示例。

method sqlalchemy.orm.Load.load_only(*attrs: Literal['*'] | QueryableAttribute[Any], raiseload: bool = False) Self

继承自 sqlalchemy.orm.strategy_options._AbstractLoad.load_only 方法 sqlalchemy.orm.strategy_options._AbstractLoad

生成一个新的 Load 对象,其中应用了 load_only() 选项。

请参阅 load_only() 以获取用法示例。

method sqlalchemy.orm.Load.noload(attr: Literal['*'] | QueryableAttribute[Any]) Self

继承自 sqlalchemy.orm.strategy_options._AbstractLoad.noload 方法 sqlalchemy.orm.strategy_options._AbstractLoad

生成一个新的 Load 对象,其中应用了 noload() 选项。

请参阅 noload() 以获取用法示例。

method sqlalchemy.orm.Load.options(*opts: _AbstractLoad) Self

将一系列选项作为子选项应用于此 Load 对象。

例如:

query = session.query(Author)
query = query.options(
            joinedload(Author.book).options(
                load_only(Book.summary, Book.excerpt),
                joinedload(Book.citations).options(
                    joinedload(Citation.author)
                )
            )
        )
参数:

*opts – 一系列加载器选项对象(最终是 Load 对象),应该应用于此 Load 对象指定的路径。

1.3.6 版新增。

method sqlalchemy.orm.Load.process_compile_state(compile_state: ORMCompileState) None

继承自 sqlalchemy.orm.strategy_options._AbstractLoad.process_compile_state 方法 sqlalchemy.orm.strategy_options._AbstractLoad

对给定的 ORMCompileState 应用修改。

此方法是特定 CompileStateOption 实现的一部分,仅在编译 ORM 查询时在内部调用。

method sqlalchemy.orm.Load.process_compile_state_replaced_entities(compile_state: ORMCompileState, mapper_entities: Sequence[_MapperEntity]) None

继承自 sqlalchemy.orm.strategy_options._AbstractLoad.process_compile_state_replaced_entities 方法 sqlalchemy.orm.strategy_options._AbstractLoad

对给定的 ORMCompileState 应用修改,其中包含被 with_only_columns() 或 with_entities() 替换的实体。

此方法是特定 CompileStateOption 实现的一部分,仅在编译 ORM 查询时在内部调用。

新添加于版本 1.4.19。

attribute sqlalchemy.orm.Load.propagate_to_loaders: bool

继承自 sqlalchemy.orm.strategy_options._AbstractLoad.propagate_to_loaders 属性 sqlalchemy.orm.strategy_options._AbstractLoad

如果为 True,则表示此选项应该被传递到关系延迟加载器以及属性加载/刷新操作中发生的“次要” SELECT 语句。

method sqlalchemy.orm.Load.raiseload(attr: Literal['*'] | QueryableAttribute[Any], sql_only: bool = False) Self

继承自 sqlalchemy.orm.strategy_options._AbstractLoad.raiseload 方法 sqlalchemy.orm.strategy_options._AbstractLoad

生成一个新的 Load 对象,并应用了 raiseload() 选项。

有关用法示例,请参阅 raiseload()

method sqlalchemy.orm.Load.selectin_polymorphic(classes: Iterable[Type[Any]]) Self

继承自 sqlalchemy.orm.strategy_options._AbstractLoad.selectin_polymorphic 方法 sqlalchemy.orm.strategy_options._AbstractLoad

生成一个新的 Load 对象,并应用了 selectin_polymorphic() 选项。

有关用法示例,请参阅 selectin_polymorphic()

method sqlalchemy.orm.Load.selectinload(attr: Literal['*'] | QueryableAttribute[Any], recursion_depth: int | None = None) Self

继承自 sqlalchemy.orm.strategy_options._AbstractLoad.selectinload 方法 sqlalchemy.orm.strategy_options._AbstractLoad

生成一个新的 Load 对象,并应用了 selectinload() 选项。

有关用法示例,请参阅 selectinload()

method sqlalchemy.orm.Load.subqueryload(attr: Literal['*'] | QueryableAttribute[Any]) Self

继承自 sqlalchemy.orm.strategy_options._AbstractLoad.subqueryload 方法 sqlalchemy.orm.strategy_options._AbstractLoad

生成一个新的 Load 对象,并应用了 subqueryload() 选项。

有关用法示例,请参阅 subqueryload()

method sqlalchemy.orm.Load.undefer(key: Literal['*'] | QueryableAttribute[Any]) Self

继承自 sqlalchemy.orm.strategy_options._AbstractLoad.undefer 方法 sqlalchemy.orm.strategy_options._AbstractLoad

生成一个新的 Load 对象,并应用了 undefer() 选项。

有关用法示例,请参阅 undefer()

method sqlalchemy.orm.Load.undefer_group(name: str) Self

继承自 sqlalchemy.orm.strategy_options._AbstractLoad.undefer_group 方法的 sqlalchemy.orm.strategy_options._AbstractLoad

生成一个新的 Load 对象,并应用 undefer_group() 选项。

有关用法示例,请参见 undefer_group()

method sqlalchemy.orm.Load.with_expression(key: _AttrType, expression: _ColumnExpressionArgument[Any]) Self

继承自 sqlalchemy.orm.strategy_options._AbstractLoad.with_expression 方法的 sqlalchemy.orm.strategy_options._AbstractLoad

生成一个新的 Load 对象,并应用 with_expression() 选项。

有关用法示例,请参见 with_expression()

function sqlalchemy.orm.noload(*keys: Literal['*'] | QueryableAttribute[Any]) _AbstractLoad

指示给定的关系属性应保持未加载状态。

关系属性在访问时将返回 None,而不会产生任何加载效果。

此函数是 Load 接口的一部分,支持方法链和独立操作。

noload() 仅适用于 relationship() 属性。

遗留功能

noload() 选项为遗留。因为它强制集合为空,这必然会导致非直观且难以预测的结果。在现代 SQLAlchemy 中,此选项没有合法用途。

另请参阅

关系加载技术

function sqlalchemy.orm.raiseload(*keys: Literal['*'] | QueryableAttribute[Any], **kw: Any) _AbstractLoad

指示如果访问给定的属性,则应引发错误。

使用 raiseload() 配置的关系属性将在访问时引发 InvalidRequestError。此方法通常在应用程序尝试确保在特定上下文中访问的所有关系属性已通过急切加载进行加载时很有用。这样一来,就不必阅读 SQL 日志来确保不会发生延迟加载,这种策略将导致它们立即引发异常。

raiseload() 仅适用于 relationship() 属性。要将 raise-on-SQL 行为应用于基于列的属性,请在 defer() 加载程序选项上使用 defer.raiseload 参数。

参数:

sql_only – 如果为 True,则仅在延迟加载将发出 SQL 时引发异常,而不会在仅检查标识映射或确定由于缺少键而相关值应为 None 时引发异常。如果为 False,则策略将针对所有类型的关系加载引发异常。

此函数是 Load 接口的一部分,支持方法链和独立操作。

function sqlalchemy.orm.selectinload(*keys: Literal['*'] | QueryableAttribute[Any], recursion_depth: int | None = None) _AbstractLoad

指示应使用 SELECT IN 急切加载来加载给定的属性。

此函数是 Load 接口的一部分,支持方法链和独立操作。

示例

# selectin-load the "orders" collection on "User"
select(User).options(selectinload(User.orders))

# selectin-load Order.items and then Item.keywords
select(Order).options(
    selectinload(Order.items).selectinload(Item.keywords)
)

# lazily load Order.items, but when Items are loaded,
# selectin-load the keywords collection
select(Order).options(
    lazyload(Order.items).selectinload(Item.keywords)
)
参数:

recursion_depth

可选的 int;当与自引用关系一起设置为正整数时,表示“selectin”加载将自动继续到该深度,直到没有找到任何元素。

注意

selectinload.recursion_depth 选项目前仅支持自引用关系。还没有选项可以自动遍历涉及多个关系的递归结构。

此外,selectinload.recursion_depth 参数是新的并且处于实验阶段,应被视为 2.0 系列的“alpha”状态。

2.0 版新增: 添加了 selectinload.recursion_depth

function sqlalchemy.orm.subqueryload(*keys: Literal['*'] | QueryableAttribute[Any]) _AbstractLoad

指示应使用子查询急切加载来加载给定的属性。

此函数是 Load 接口的一部分,支持方法链和独立操作。

示例

# subquery-load the "orders" collection on "User"
select(User).options(subqueryload(User.orders))

# subquery-load Order.items and then Item.keywords
select(Order).options(
    subqueryload(Order.items).subqueryload(Item.keywords)
)

# lazily load Order.items, but when Items are loaded,
# subquery-load the keywords collection
select(Order).options(
    lazyload(Order.items).subqueryload(Item.keywords)
)