使用相关对象

优质
小牛编辑
135浏览
2023-12-01

SQLAlchemy 1.4 / 2.0 Tutorial

此页是 SQLAlchemy 1.4/2.0教程 .

上一页: 使用ORM进行数据操作 |下一步: |next|

使用相关对象

在本节中,我们将介绍一个更重要的ORM概念,即ORM如何与引用其他对象的映射类交互。在本节中 声明映射类 ,映射的类示例使用了一个名为 relationship() . 这个构造定义了两个不同的映射类之间的链接,或者从一个映射类到它自身的链接,后者称为 self-referential 关系。

描述 relationship() ,首先我们将以简短的形式查看映射,省略 Column 映射和其他指令:

from sqlalchemy.orm import relationship
class User(Base):
    __tablename__ = 'user_account'

    # ... Column mappings

    addresses = relationship("Address", back_populates="user")


class Address(Base):
    __tablename__ = 'address'

    # ... Column mappings

    user = relationship("User", back_populates="addresses")

上图是 User 类现在具有一个属性 User.addresses 以及 Address 类具有一个属性 Address.user 。这个 relationship() 构造将用于检查 Table 映射到 UserAddress 上课。作为 Table 对象,该对象表示 address 表中有一个 ForeignKeyConstraint 它指的是 user_account 表中的 relationship() 可以毫不含糊地确定存在 one to many 关系来自 User.addressesUser ;中的某一特定行 user_account 表可以由 address 桌子。

所有一对多的关系自然对应于 many to one 另一个方向的关系,在本例中 Address.user . 这个 relationship.back_populates 参数,如上所示 relationship() 对象引用另一个名称,确定这两个 relationship() 构造应该被认为是相互补充的;我们将在下一节中看到这一点。

持久化和加载关系

我们可以先说明 relationship() 对对象实例执行操作。如果我们做一个新的 User 对象时,我们可以注意到,当我们访问 .addresses 元素:

>>> u1 = User(name='pkrabs', fullname='Pearl Krabs')
>>> u1.addresses
[]

这个对象是Python的SQLAlchemy特定版本 list 它有能力跟踪和响应对它所做的更改。当我们访问属性时,集合也会自动出现,即使我们从未将其分配给对象。这与 使用ORM插入行 我们观察到,基于列的属性(我们没有显式地为其赋值)也显示为 None 自动地,而不是 AttributeError 就像Python通常的行为一样。

作为 u1 物体静止不动 transient 以及 list 我们从那里得到的 u1.addresses 还没有被改变(即附加或扩展),它还没有真正与对象相关联,但是当我们对它进行更改时,它将成为 User 对象。

集合是特定于 Address 类,它是唯一可以在其中持久化的Python对象类型。使用 list.append() 方法我们可以添加 Address 对象:

>>> a1 = Address(email_address="pearl.krabs@gmail.com")
>>> u1.addresses.append(a1)

在这一点上, u1.addresses 集合中包含新的 Address 对象:

>>> u1.addresses
[Address(id=None, email_address='pearl.krabs@gmail.com')]

当我们把 Address 对象与 User.addresses 收集 u1 实例中,还发生了另一个行为,即 User.addresses 关系本身与 Address.user 这样我们不仅可以从 User 对象到 Address 对象,我们也可以从 Address 对象返回“父对象” User 对象:

>>> a1.user
User(id=None, name='pkrabs', fullname='Pearl Krabs')

此同步是由于我们使用 relationship.back_populates 两者之间的参数 relationship() 物体。此参数命名另一个参数 relationship() 其中互补属性赋值/列表变异应该发生。它在另一个方向也同样有效,那就是如果我们创造另一个 Address 对象并将其指定给 Address.user 属性,那个 Address 成为 User.addresses 关于那个的收集 User 对象:

>>> a2 = Address(email_address="pearl@aol.com", user=u1)
>>> u1.addresses
[Address(id=None, email_address='pearl.krabs@gmail.com'), Address(id=None, email_address='pearl@aol.com')]

我们实际上利用了 user 参数作为关键字参数在 Address 构造函数,它就像在 Address 班级。它等效于将 Address.user 事后属性::

# equivalent effect as a2 = Address(user=u1)
>>> a2.user = u1

将对象级联到会话中

我们现在有一个 User 还有两个 Address 与内存中的双向结构相关联,但如前面在 使用ORM插入行 ,这些对象被称为 transient 状态,直到它们与 Session 对象。

我们利用 Session 这仍在进行中,请注意,当我们应用 Session.add() 引导方法 User 对象,相关 Address 对象也被添加到相同的 Session ::

>>> session.add(u1)
>>> u1 in session
True
>>> a1 in session
True
>>> a2 in session
True

上述行为,其中 Session 收到 User 然后沿着 User.addresses 查找相关 Address 对象,称为 save-update cascade 并在ORM参考文档中进行了详细讨论 级联 .

这三个对象现在位于 pending 状态;这意味着它们已准备好成为插入操作的主题,但此操作尚未进行;所有三个对象都尚未分配主键,此外, a1a2 对象有一个名为 user_id 它指的是 Column 它有一个 ForeignKeyConstraint 指的是 user_account.id 列;这些也是 None 由于对象尚未与实际数据库行相关联::

>>> print(u1.id)
None
>>> print(a1.user_id)
None

在这个阶段,我们可以看到工作单元过程提供的非常大的实用程序;回想一下 INSERT通常自动生成“VALUES”子句 ,行被插入到 user_accountaddress 表使用一些复杂的语法来自动关联 address.user_id 包含 user_account 排。另外,我们有必要为 user_account 排在第一排之前 address ,因为 address依赖的 在他们的父行 user_account 为了他们的价值 user_id 列。

当使用 Session ,所有这些单调乏味的事情都可以为我们处理,即使是最顽固的SQL纯粹主义者也可以从INSERT、UPDATE和DELETE语句的自动化中获益。当我们 Session.commit() 事务所有步骤都以正确的顺序调用,而且还调用 user_account 行应用于 address.user_id 适当列示:

>>> session.commit()
INSERT INTO user_account (name, fullname) VALUES (?, ?)
[...] ('pkrabs', 'Pearl Krabs')
INSERT INTO address (email_address, user_id) VALUES (?, ?)
[...] ('pearl.krabs@gmail.com', 6)
INSERT INTO address (email_address, user_id) VALUES (?, ?)
[...] ('pearl@aol.com', 6)
COMMIT

加载关系

在最后一步,我们打了电话 Session.commit() 它为事务发出一个提交,然后根据 Session.commit.expire_on_commit 使所有对象过期,以便为下一个事务刷新。

当我们下一次访问这些对象的属性时,我们将看到为该行的主属性发出的SELECT,例如当我们查看 u1 对象:

>>> u1.id
BEGIN (implicit)
SELECT user_account.id AS user_account_id, user_account.name AS user_account_name,
user_account.fullname AS user_account_fullname
FROM user_account
WHERE user_account.id = ?
[...] (6,)
6

这个 u1 User 对象现在有一个持久集合 User.addresses 我们也可以进入。因为此集合由来自 address 表,当我们访问此集合时,我们再次看到 lazy load 为了检索对象而发射:

>>> u1.addresses
SELECT address.id AS address_id, address.email_address AS address_email_address,
address.user_id AS address_user_id
FROM address
WHERE ? = address.user_id
[...] (6,)
[Address(id=4, email_address='pearl.krabs@gmail.com'), Address(id=5, email_address='pearl@aol.com')]

SQLAlchemy ORM中的集合和相关属性在内存中是持久的;一旦集合或属性被填充,SQL将不再被发出,直到该集合或属性被填充 expired . 我们可以进入 u1.addresses 以及添加或删除项,这将不会引发任何新的SQL调用:

>>> u1.addresses
[Address(id=4, email_address='pearl.krabs@gmail.com'), Address(id=5, email_address='pearl@aol.com')]

如果我们不采取明确的步骤来优化延迟加载,那么延迟加载所产生的负载可能会很快变得昂贵,但是惰性加载的网络至少经过了相当好的优化,不执行冗余的工作;正如 u1.addresses 集合已刷新,根据 identity map 这些其实是一样的 Address 实例作为 a1a2 我们已经处理过的对象,所以我们已经加载了这个特定对象图中的所有属性:

>>> a1
Address(id=4, email_address='pearl.krabs@gmail.com')
>>> a2
Address(id=5, email_address='pearl@aol.com')

关系如何加载,或不加载,是一个完整的主题本身。有关这些概念的其他介绍将在本节后面的 装载机策略 .

在查询中使用关系

上一节介绍了 relationship() 使用时构造 映射类的实例 ,上面是 u1a1a2 的实例 UserAddress 类。在本节中,我们将介绍 relationship() 因为它适用于 映射类的类级行为 ,它以多种方式帮助自动构建SQL查询。

使用关系加入

章节 显式FROM子句和连接设置ON子句 介绍了 Select.join()Select.join_from() 方法来组合SQL联接子句。为了描述如何在表之间联接,这些方法 推断 ON子句基于存在一个明确的 ForeignKeyConstraint 对象在链接两个表的表元数据结构中,或者我们可以提供一个显式的SQL表达式构造来指示特定的ON子句。

当使用ORM实体时,还有一种附加的机制可以帮助我们设置连接的ON子句,即利用 relationship() 我们在用户映射中设置的对象,如在 声明映射类 . 类绑定属性对应于 relationship() 可作为 单参数Select.join() ,其中它同时指示连接的右侧以及ON子句:

>>> print(
...     select(Address.email_address).
...     select_from(User).
...     join(User.addresses)
... )
SELECT address.email_address
FROM user_account JOIN address ON user_account.id = address.user_id

ORM的存在 relationship() 映射上的 Select.join()Select.join_from() 如果我们不具体说明,那就是 不用于ON子句推理 . 这意味着,如果我们从 UserAddress 如果没有ON子句,它的工作原理是 ForeignKeyConstraint 两者之间的映射 Table 对象,不是因为 relationship() 上的对象 UserAddress 班级:

>>> print(
...    select(Address.email_address).
...    join_from(User, Address)
... )
SELECT address.email_address
FROM user_account JOIN address ON user_account.id = address.user_id

在别名目标之间连接

在本节中 ORM实体别名 我们介绍了 aliased() 构造,用于将SQL别名应用于ORM实体。当使用 relationship() 为了帮助构造SQL连接,在这个用例中,连接的目标是 aliased() 通过利用 PropComparator.of_type() 修改器。为了演示我们将构造 ORM实体别名 使用 relationship() 要加入的属性::

>>> print(
...        select(User).
...        join(User.addresses.of_type(address_alias_1)).
...        where(address_alias_1.email_address == 'patrick@aol.com').
...        join(User.addresses.of_type(address_alias_2)).
...        where(address_alias_2.email_address == 'patrick@gmail.com')
...    )
SELECT user_account.id, user_account.name, user_account.fullname
FROM user_account
JOIN address AS address_1 ON user_account.id = address_1.user_id
JOIN address AS address_2 ON user_account.id = address_2.user_id
WHERE address_1.email_address = :email_address_1
AND address_2.email_address = :email_address_2

利用 relationship() 构造连接 from 作为别名实体,属性可从 aliased() 直接施工:

>>> user_alias_1 = aliased(User)
>>> print(
...     select(user_alias_1.name).
...     join(user_alias_1.addresses)
... )
SELECT user_account_1.name
FROM user_account AS user_account_1
JOIN address ON user_account_1.id = address.user_id

扩充ON准则

relationship() 构造也可以用额外的标准进行扩充。这对于快速限制关系路径上特定连接的范围非常有用,对于配置加载程序策略等用例也很有用,下面在 装载机策略 . 这个 PropComparator.and_() 方法以位置方式接受一系列SQL表达式,这些表达式将通过AND联接到JOIN的ON子句。例如,如果我们想从 UserAddress 但也将“打开”条件仅限于某些电子邮件地址:

>>> stmt = (
...   select(User.fullname).
...   join(User.addresses.and_(Address.email_address == 'pearl.krabs@gmail.com'))
... )
>>> session.execute(stmt).all()
SELECT user_account.fullname
FROM user_account
JOIN address ON user_account.id = address.user_id AND address.email_address = ?
[...] ('pearl.krabs@gmail.com',)
[('Pearl Krabs',)]

EXISTS forms:has()/any()

在本节中 存在的子查询 ,我们介绍了 Exists 对象,该对象将SQL EXISTS关键字与标量子查询一起提供。这个 relationship() 构造提供了一些helper方法,这些方法可以用来根据关系生成一些常见的EXISTS查询样式。

对于一对多的关系,比如 User.addresses ,一个针对 address 表,该表与 user_account 可以使用 PropComparator.any() . 此方法接受可选的WHERE条件来限制子查询匹配的行:

>>> stmt = (
...   select(User.fullname).
...   where(User.addresses.any(Address.email_address == 'pearl.krabs@gmail.com'))
... )
>>> session.execute(stmt).all()
SELECT user_account.fullname
FROM user_account
WHERE EXISTS (SELECT 1
FROM address
WHERE user_account.id = address.user_id AND address.email_address = ?)
[...] ('pearl.krabs@gmail.com',)
[('Pearl Krabs',)]

由于EXISTS对于负查找的效率更高,一个常见的查询是定位不存在相关实体的实体。这是一个简洁的短语,如 ~User.addresses.any() ,选择 User 没有关联的实体 Address 排:

>>> stmt = (
...   select(User.fullname).
...   where(~User.addresses.any())
... )
>>> session.execute(stmt).all()
SELECT user_account.fullname
FROM user_account
WHERE NOT (EXISTS (SELECT 1
FROM address
WHERE user_account.id = address.user_id))
[...] ()
[('Patrick McStar',), ('Squidward Tentacles',), ('Eugene H. Krabs',)]

这个 PropComparator.has() 方法的工作方式与 PropComparator.any() ,但它用于多对一关系,例如如果我们想定位所有 Address 属于“珍珠”的物品:

>>> stmt = (
...   select(Address.email_address).
...   where(Address.user.has(User.name=="pkrabs"))
... )
>>> session.execute(stmt).all()
SELECT address.email_address
FROM address
WHERE EXISTS (SELECT 1
FROM user_account
WHERE user_account.id = address.user_id AND user_account.name = ?)
[...] ('pkrabs',)
[('pearl.krabs@gmail.com',), ('pearl@aol.com',)]

公共关系运算符

附带了一些其他种类的SQL生成助手 relationship() ,包括:

  • 多对一等于比较 -可以将特定对象实例与多对一关系进行比较,以选择目标实体的外键与给定对象的主键值匹配的行:

    >>> print(select(Address).where(Address.user == u1))
    SELECT address.id, address.email_address, address.user_id
    FROM address
    WHERE :param_1 = address.user_id
    
  • 多对一不等于比较 -也可以使用不等于运算符::

    >>> print(select(Address).where(Address.user != u1))
    SELECT address.id, address.email_address, address.user_id
    FROM address
    WHERE address.user_id != :user_id_1 OR address.user_id IS NULL
    
  • object is contained in a one-to-many collection -这本质上是“等于”比较的一对多版本,选择主键等于相关对象中外键值的行:

    >>> print(select(User).where(User.addresses.contains(a1)))
    SELECT user_account.id, user_account.name, user_account.fullname
    FROM user_account
    WHERE user_account.id = :param_1
    
  • An object has a particular parent from a one-to-many perspective - with_parent() 函数生成一个比较,返回给定父级引用的行,这与使用 == 多对一边的运算符:

    >>> from sqlalchemy.orm import with_parent
    >>> print(select(Address).where(with_parent(u1, User.addresses)))
    SELECT address.id, address.email_address, address.user_id
    FROM address
    WHERE :param_1 = address.user_id
    

装载机策略

在本节中 加载关系 我们引入了这样一个概念:当我们处理映射对象的实例时,使用 relationship() 在默认情况下,将发出 lazy load 当未填充集合以加载此集合中应存在的对象时。

延迟加载是最著名的ORM模式之一,也是最具争议的模式之一。当内存中的几十个ORM对象每个都引用少数几个未加载的属性时,对这些对象的例程操作可能会产生许多额外的查询,这些查询可能会相加(也称为 N plus one problem ),更糟糕的是,它们是隐式排放的。这些隐式查询可能不会被注意到,当在不再有可用的数据库事务之后尝试它们时,或者在使用其他并发模式(如 asyncio ,它们实际上根本不会起作用。

同时,如果lazy-loading与所使用的并发方法兼容并且不会引起问题,那么lazy-loading是一种非常流行和有用的模式。基于这些原因,SQLAlchemy的ORM非常强调能够控制和优化这种加载行为。

最重要的是,有效使用ORM延迟加载的第一步是 测试应用程序,打开SQL回显,并观察发出的SQL . 如果似乎有许多冗余的SELECT语句看起来非常像是可以更有效地将它们卷进一个语句中,如果有不适当地为已被 detached 从他们的 Session ,这是研究使用 装载机策略 .

加载程序策略表示为对象,这些对象可以使用 Select.options() 方法,例如:

for user_obj in session.execute(
    select(User).options(selectinload(User.addresses))
).scalars():
    user_obj.addresses  # access addresses collection already loaded

它们也可以配置为 relationship() 使用 relationship.lazy 选项,例如:

from sqlalchemy.orm import relationship
class User(Base):
    __tablename__ = 'user_account'

    addresses = relationship("Address", back_populates="user", lazy="selectin")

每个装入器策略对象都会向语句中添加一些信息,这些信息将由 Session 当它决定各种属性在被访问时应该如何加载和/或表现。

下面几节将介绍一些最常用的装载机策略。

参见

中的两个部分 关系加载技术

选择负荷

现代SQLAlchemy中最有用的加载程序是 selectinload() 装载机选项。此选项解决了“N+1”问题的最常见形式,即引用相关集合的一组对象。 selectinload() 将确保使用单个查询预先加载完整系列对象的特定集合。它使用一个SELECT表单来实现这一点,在大多数情况下,可以针对相关表单独发出,而不引入联接或子查询,并且只对那些尚未加载集合的父对象执行查询。下面我们举例说明 selectinload() 通过加载所有 User 对象及其所有相关的 Address 对象;当我们调用 Session.execute() 只有一次,给一个 select() 构造,当访问数据库时,实际上会发出两个SELECT语句,第二个语句用于获取相关的 Address 物体:

>>> from sqlalchemy.orm import selectinload
>>> stmt = (
...   select(User).options(selectinload(User.addresses)).order_by(User.id)
... )
>>> for row in session.execute(stmt):
...     print(f"{row.User.name}  ({', '.join(a.email_address for a in row.User.addresses)})")
SELECT user_account.id, user_account.name, user_account.fullname
FROM user_account ORDER BY user_account.id
[...] ()
SELECT address.user_id AS address_user_id, address.id AS address_id,
address.email_address AS address_email_address
FROM address
WHERE address.user_id IN (?, ?, ?, ?, ?, ?)
[...] (1, 2, 3, 4, 5, 6)
spongebob  (spongebob@sqlalchemy.org)
sandy  (sandy@sqlalchemy.org, sandy@squirrelpower.org)
patrick  ()
squidward  ()
ehkrabs  ()
pkrabs  (pearl.krabs@gmail.com, pearl@aol.com)

参见

加载时选择 - in 关系加载技术

连接荷载

这个 joinedload() 急切加载策略是SQLAlChemy中最古老的急切加载程序,它使用连接(根据选项,可能是外部连接或内部连接)来扩充传递给数据库的SELECT语句,然后可以加载相关对象。

这个 joinedload() 策略最适合于加载相关的多对一对象,因为这只需要向在任何情况下都会读取的主实体行添加额外的列。为了提高效率,它还接受一个选项 joinedload.innerjoin 因此,内联接而不是外联接可用于如下情况,其中我们知道所有 Address 对象具有关联的 User

>>> from sqlalchemy.orm import joinedload
>>> stmt = (
...   select(Address).options(joinedload(Address.user, innerjoin=True)).order_by(Address.id)
... )
>>> for row in session.execute(stmt):
...     print(f"{row.Address.email_address} {row.Address.user.name}")
SELECT address.id, address.email_address, address.user_id, user_account_1.id AS id_1,
user_account_1.name, user_account_1.fullname
FROM address
JOIN user_account AS user_account_1 ON user_account_1.id = address.user_id
ORDER BY address.id
[...] ()
spongebob@sqlalchemy.org spongebob
sandy@sqlalchemy.org sandy
sandy@squirrelpower.org sandy
pearl.krabs@gmail.com pkrabs
pearl@aol.com pkrabs

joinedload() 也适用于集合,这意味着一对多的关系,但是它的作用是以递归方式将每个相关项的主行数相乘,从而使嵌套集合和/或更大集合的结果集发送的数据量增加一个数量级,因此它的使用与另一个选项(如 selectinload() 应根据具体情况进行评估。

需要注意的是,随附的WHERE和ORDER BY标准 Select 声明 不要将joinedload()呈现的表作为目标 。在上面的SQL中可以看到,一个 匿名别名 应用于 user_account 表,以便在查询中不能直接寻址。这一概念将在一节中更详细地讨论。 加入渴望装载的禅宗

ON子句由 joinedload() 使用 PropComparator.and_() 前面描述的方法 扩充ON准则 ;下面将进一步介绍此技术与加载程序策略的示例 扩充装载机策略路径 . 然而,更一般地说,“联合急切加载”可以应用于 Select 使用的 Select.join() 使用下一节中描述的方法, 显式连接+立即加载 .

小技巧

需要注意的是,多对一的紧急加载通常不是必需的,因为“N+1”问题在普通情况下不太常见。当多个对象都引用相同的相关对象时,如多个 Address 对象,每个对象都引用相同的 User ,则只会为此发出一次SQL User 对象使用正常的延迟加载。惰性加载例程将通过当前 Session 而不会在可能的情况下发出任何SQL。

参见

加入的热切装载 - in 关系加载技术

显式连接+立即加载

如果我们要装上 Address 连接到 user_account 表中使用一种方法,如 Select.join() 要呈现连接,我们还可以利用该连接来急切地加载 Address.user 属性在每个 Address 返回了对象。本质上,我们使用的是“连接的急切加载”,但是我们自己呈现连接。这种常见用例是通过使用 contains_eager() 选项。此选项非常类似于 joinedload() ,除非它假定我们已经自己设置了联接,并且它只指示COLUMNS子句中的其他列应该加载到每个返回对象的相关属性中,例如:

>>> from sqlalchemy.orm import contains_eager
>>> stmt = (
...   select(Address).
...   join(Address.user).
...   where(User.name == 'pkrabs').
...   options(contains_eager(Address.user)).order_by(Address.id)
... )
>>> for row in session.execute(stmt):
...     print(f"{row.Address.email_address} {row.Address.user.name}")
SELECT user_account.id, user_account.name, user_account.fullname,
address.id AS id_1, address.email_address, address.user_id
FROM address JOIN user_account ON user_account.id = address.user_id
WHERE user_account.name = ? ORDER BY address.id
[...] ('pkrabs',)
pearl.krabs@gmail.com pkrabs
pearl@aol.com pkrabs

在上面,我们都过滤了行 user_account.name 还从中加载行 user_account 进入 Address.user 返回行的属性。如果我们申请的话 joinedload() 另外,我们将得到一个不必要地连接两次的SQL查询:

>>> stmt = (
...   select(Address).
...   join(Address.user).
...   where(User.name == 'pkrabs').
...   options(joinedload(Address.user)).order_by(Address.id)
... )
>>> print(stmt)  # SELECT has a JOIN and LEFT OUTER JOIN unnecessarily
SELECT address.id, address.email_address, address.user_id,
user_account_1.id AS id_1, user_account_1.name, user_account_1.fullname
FROM address JOIN user_account ON user_account.id = address.user_id
LEFT OUTER JOIN user_account AS user_account_1 ON user_account_1.id = address.user_id
WHERE user_account.name = :name_1 ORDER BY address.id

参见

中的两个部分 关系加载技术

扩充装载机策略路径

扩充ON准则 我们演示了如何将任意条件添加到使用 relationship() 在ON子句中也包括附加条件。这个 PropComparator.and_() 方法实际上通常可用于大多数加载程序选项。例如,如果我们想重新加载用户名和他们的电子邮件地址,但是使用 sqlalchemy.org 域,我们可以申请 PropComparator.and_() 传递给的参数 selectinload() 要限制此标准:

>>> from sqlalchemy.orm import selectinload
>>> stmt = (
...   select(User).
...   options(
...       selectinload(
...           User.addresses.and_(
...             ~Address.email_address.endswith("sqlalchemy.org")
...           )
...       )
...   ).
...   order_by(User.id).
...   execution_options(populate_existing=True)
... )
>>> for row in session.execute(stmt):
...     print(f"{row.User.name}  ({', '.join(a.email_address for a in row.User.addresses)})")
SELECT user_account.id, user_account.name, user_account.fullname
FROM user_account ORDER BY user_account.id
[...] ()
SELECT address.user_id AS address_user_id, address.id AS address_id,
address.email_address AS address_email_address
FROM address
WHERE address.user_id IN (?, ?, ?, ?, ?, ?)
AND (address.email_address NOT LIKE '%' || ?)
[...] (1, 2, 3, 4, 5, 6, 'sqlalchemy.org')
spongebob  ()
sandy  (sandy@squirrelpower.org)
patrick  ()
squidward  ()
ehkrabs  ()
pkrabs  (pearl.krabs@gmail.com, pearl@aol.com)

上面需要注意的一件非常重要的事情是,添加了一个特殊选项 .execution_options(populate_existing=True) . 此选项在获取行时生效,它指示我们使用的加载程序选项应该 代替 对象上集合的现有内容(如果已加载)。因为我们正在处理一个 Session 反复地说,我们在上面看到加载的对象与在本教程的ORM部分开始时首次持久化的那些对象是相同的。

参见

向加载程序选项添加条件 - in 关系加载技术

填充现有 - in ORM查询指南

拉塞洛德

另一个值得一提的装载机策略是 raiseload() . 此选项用于完全阻止应用程序具有 N plus one 导致通常是延迟加载的内容引发错误,从而导致根本问题。它是通过两个变体控制的 raiseload.sql_only 选项阻止需要SQL的延迟加载,而不是所有“加载”操作,包括那些只需要查询当前 Session .

一种使用方法 raiseload() 是配置它 relationship() 通过设置 relationship.lazy 价值观 "raise_on_sql" ,以便对于特定映射,某个关系将永远不会尝试发出SQL:

class User(Base):
    __tablename__ = 'user_account'

    # ... Column mappings

    addresses = relationship("Address", back_populates="user", lazy="raise_on_sql")


class Address(Base):
    __tablename__ = 'address'

    # ... Column mappings

    user = relationship("User", back_populates="addresses", lazy="raise_on_sql")

使用这种映射,应用程序将被阻止延迟加载,这表明特定查询需要指定加载程序策略:

u1 = s.execute(select(User)).scalars().first()
u1.addresses
sqlalchemy.exc.InvalidRequestError: 'User.addresses' is not available due to lazy='raise_on_sql'

例外情况表示应该预先加载此集合:

u1 = s.execute(select(User).options(selectinload(User.addresses))).scalars().first()

这个 lazy="raise_on_sql" option也试图在多对一关系中保持聪明;在上面,如果 Address.user AN属性 Address 对象未加载,但 User 对象在同一个 Session ,则“raiseload”策略不会引起错误。

参见

使用raiseload防止不需要的懒惰负载 - in 关系加载技术

SQLAlchemy 1.4 / 2.0 Tutorial

下一个教程部分: 进一步阅读