处理大型集合

默认情况下,relationship() 的行为是将集合的内容完全加载到内存中,这基于配置的 加载策略,该策略控制何时以及如何从数据库加载这些内容。相关集合可能会被加载到内存中,不仅在访问或急切加载时,而且在大多数情况下,当集合本身发生变化时,以及在工作单元系统要删除拥有对象的情况下,也需要填充。

当相关集合可能非常大时,在任何情况下都可能无法将此类集合填充到内存中,因为该操作可能会过度消耗时间、网络和内存资源。

本节包含旨在允许 relationship() 与大型集合一起使用同时保持足够性能的 API 特性。

只写关系

只写加载策略是配置 relationship() 的主要方法,该方法将保持可写,但不会将其内容加载到内存中。下面说明了现代类型注解声明式形式的只写 ORM 配置

>>> from decimal import Decimal
>>> from datetime import datetime

>>> from sqlalchemy import ForeignKey
>>> from sqlalchemy import func
>>> from sqlalchemy.orm import DeclarativeBase
>>> from sqlalchemy.orm import Mapped
>>> from sqlalchemy.orm import mapped_column
>>> from sqlalchemy.orm import relationship
>>> from sqlalchemy.orm import Session
>>> from sqlalchemy.orm import WriteOnlyMapped

>>> class Base(DeclarativeBase):
...     pass

>>> class Account(Base):
...     __tablename__ = "account"
...     id: Mapped[int] = mapped_column(primary_key=True)
...     identifier: Mapped[str]
...
...     account_transactions: WriteOnlyMapped["AccountTransaction"] = relationship(
...         cascade="all, delete-orphan",
...         passive_deletes=True,
...         order_by="AccountTransaction.timestamp",
...     )
...
...     def __repr__(self):
...         return f"Account(identifier={self.identifier!r})"

>>> class AccountTransaction(Base):
...     __tablename__ = "account_transaction"
...     id: Mapped[int] = mapped_column(primary_key=True)
...     account_id: Mapped[int] = mapped_column(
...         ForeignKey("account.id", ondelete="cascade")
...     )
...     description: Mapped[str]
...     amount: Mapped[Decimal]
...     timestamp: Mapped[datetime] = mapped_column(default=func.now())
...
...     def __repr__(self):
...         return (
...             f"AccountTransaction(amount={self.amount:.2f}, "
...             f"timestamp={self.timestamp.isoformat()!r})"
...         )
...
...     __mapper_args__ = {"eager_defaults": True}

上面,account_transactions 关系配置未使用普通的 Mapped 注解,而是使用了 WriteOnlyMapped 类型注解,它在运行时会将 lazy="write_only"加载策略 分配给目标 relationship()WriteOnlyMapped 注解是 Mapped 注解的另一种形式,它指示在对象的实例上使用 WriteOnlyCollection 集合类型。

上面的 relationship() 配置还包括几个特定于在删除 Account 对象以及从 account_transactions 集合中删除 AccountTransaction 对象时要采取的操作的元素。 这些元素是

2.0 版本新增:添加了“只写”关系加载器。

创建和持久化新的只写集合

只写集合允许直接分配整个集合,适用于 瞬态待定 对象。 根据上面的映射,这表明我们可以创建一个新的 Account 对象,其中包含一系列要添加到 SessionAccountTransaction 对象。 任何 Python 可迭代对象都可以用作要启动的对象源,下面我们使用 Python list

>>> new_account = Account(
...     identifier="account_01",
...     account_transactions=[
...         AccountTransaction(description="initial deposit", amount=Decimal("500.00")),
...         AccountTransaction(description="transfer", amount=Decimal("1000.00")),
...         AccountTransaction(description="withdrawal", amount=Decimal("-29.50")),
...     ],
... )

>>> with Session(engine) as session:
...     session.add(new_account)
...     session.commit()
BEGIN (implicit) INSERT INTO account (identifier) VALUES (?) [...] ('account_01',) INSERT INTO account_transaction (account_id, description, amount, timestamp) VALUES (?, ?, ?, CURRENT_TIMESTAMP) RETURNING id, timestamp [... (insertmanyvalues) 1/3 (ordered; batch not supported)] (1, 'initial deposit', 500.0) INSERT INTO account_transaction (account_id, description, amount, timestamp) VALUES (?, ?, ?, CURRENT_TIMESTAMP) RETURNING id, timestamp [insertmanyvalues 2/3 (ordered; batch not supported)] (1, 'transfer', 1000.0) INSERT INTO account_transaction (account_id, description, amount, timestamp) VALUES (?, ?, ?, CURRENT_TIMESTAMP) RETURNING id, timestamp [insertmanyvalues 3/3 (ordered; batch not supported)] (1, 'withdrawal', -29.5) COMMIT

一旦对象被数据库持久化(即处于 持久化分离 状态),集合就能够使用新项进行扩展,并且能够删除单个项。 但是,集合不能再使用完整的替换集合重新分配,因为此类操作需要将先前的集合完全加载到内存中,以便协调旧条目和新条目

>>> new_account.account_transactions = [
...     AccountTransaction(description="some transaction", amount=Decimal("10.00"))
... ]
Traceback (most recent call last):
...
sqlalchemy.exc.InvalidRequestError: Collection "Account.account_transactions" does not
support implicit iteration; collection replacement operations can't be used

向现有集合添加新项

对于持久对象的只写集合,使用 工作单元 进程对集合进行的修改只能通过使用 WriteOnlyCollection.add()WriteOnlyCollection.add_all()WriteOnlyCollection.remove() 方法进行

>>> from sqlalchemy import select
>>> session = Session(engine, expire_on_commit=False)
>>> existing_account = session.scalar(select(Account).filter_by(identifier="account_01"))
BEGIN (implicit) SELECT account.id, account.identifier FROM account WHERE account.identifier = ? [...] ('account_01',)
>>> existing_account.account_transactions.add_all( ... [ ... AccountTransaction(description="paycheck", amount=Decimal("2000.00")), ... AccountTransaction(description="rent", amount=Decimal("-800.00")), ... ] ... ) >>> session.commit()
INSERT INTO account_transaction (account_id, description, amount, timestamp) VALUES (?, ?, ?, CURRENT_TIMESTAMP) RETURNING id, timestamp [... (insertmanyvalues) 1/2 (ordered; batch not supported)] (1, 'paycheck', 2000.0) INSERT INTO account_transaction (account_id, description, amount, timestamp) VALUES (?, ?, ?, CURRENT_TIMESTAMP) RETURNING id, timestamp [insertmanyvalues 2/2 (ordered; batch not supported)] (1, 'rent', -800.0) COMMIT

上面添加的项保存在 Session 中的待处理队列中,直到下一次刷新,届时它们将被 INSERT 到数据库中,前提是添加的对象之前是 瞬态 的。

查询项

WriteOnlyCollection 在任何时候都不会存储对集合当前内容的引用,它也没有任何行为会直接向数据库发出 SELECT 以加载它们; 最重要的假设是,集合可能包含成千上万甚至数百万行,并且绝不应作为任何其他操作的副作用完全加载到内存中。

相反,WriteOnlyCollection 包括 SQL 生成助手,例如 WriteOnlyCollection.select(),它将生成一个预配置了当前父行的正确 WHERE / FROM 条件的 Select 构造,然后可以对其进行进一步修改,以 SELECT 任何所需范围的行,并且可以使用 服务器端游标 等功能进行调用,以便希望以内存高效的方式迭代整个集合的过程。

生成的语句如下所示。 请注意,它还包括 ORDER BY 条件,该条件在示例映射中由 relationship.order_by 参数指示 relationship(); 如果未配置该参数,则将省略此条件

>>> print(existing_account.account_transactions.select())
SELECT account_transaction.id, account_transaction.account_id, account_transaction.description, account_transaction.amount, account_transaction.timestamp FROM account_transaction WHERE :param_1 = account_transaction.account_id ORDER BY account_transaction.timestamp

我们可以将此 Select 构造与 Session 一起使用,以便查询 AccountTransaction 对象,最简单的方法是使用 Session.scalars() 方法,该方法将返回一个 Result,该结果直接产生 ORM 对象。 通常(但不是必需的)是,Select 将被进一步修改以限制返回的记录; 在下面的示例中,添加了额外的 WHERE 条件以仅加载“借记”帐户事务,以及 “LIMIT 10” 以仅检索前十行

>>> account_transactions = session.scalars(
...     existing_account.account_transactions.select()
...     .where(AccountTransaction.amount < 0)
...     .limit(10)
... ).all()
BEGIN (implicit) SELECT account_transaction.id, account_transaction.account_id, account_transaction.description, account_transaction.amount, account_transaction.timestamp FROM account_transaction WHERE ? = account_transaction.account_id AND account_transaction.amount < ? ORDER BY account_transaction.timestamp LIMIT ? OFFSET ? [...] (1, 0, 10, 0)
>>> print(account_transactions) [AccountTransaction(amount=-29.50, timestamp='...'), AccountTransaction(amount=-800.00, timestamp='...')]

移除项

针对当前 Session持久化 状态加载的单个项可以使用 WriteOnlyCollection.remove() 方法标记为从集合中删除。 刷新过程将隐式地认为对象已经是集合的一部分,当操作进行时。 下面的示例说明了删除单个 AccountTransaction 项,根据 级联 设置,这将导致删除该行

>>> existing_transaction = account_transactions[0]
>>> existing_account.account_transactions.remove(existing_transaction)
>>> session.commit()
DELETE FROM account_transaction WHERE account_transaction.id = ? [...] (3,) COMMIT

与任何 ORM 映射的集合一样,对象删除可以继续进行,要么将对象从集合中取消关联,同时将对象保留在数据库中,要么根据 delete-orphan 配置发出对其行的 DELETE relationship()

不删除集合的移除涉及为 一对多 关系将外键列设置为 NULL,或者为 多对多 关系删除相应的关联行。

批量插入新项

WriteOnlyCollection 可以生成 DML 构造,例如 Insert 对象,这些对象可以在 ORM 上下文中使用以产生批量插入行为。 有关 ORM 批量插入的概述,请参阅 ORM 批量 INSERT 语句 部分。

一对多集合

仅对于常规一对多集合WriteOnlyCollection.insert() 方法将生成一个 Insert 构造,该构造预先使用与父对象对应的 VALUES 条件建立。 由于此 VALUES 条件完全针对相关表,因此该语句可用于 INSERT 新行,这些新行同时将成为相关集合中的新记录

>>> session.execute(
...     existing_account.account_transactions.insert(),
...     [
...         {"description": "transaction 1", "amount": Decimal("47.50")},
...         {"description": "transaction 2", "amount": Decimal("-501.25")},
...         {"description": "transaction 3", "amount": Decimal("1800.00")},
...         {"description": "transaction 4", "amount": Decimal("-300.00")},
...     ],
... )
BEGIN (implicit) INSERT INTO account_transaction (account_id, description, amount, timestamp) VALUES (?, ?, ?, CURRENT_TIMESTAMP) [...] [(1, 'transaction 1', 47.5), (1, 'transaction 2', -501.25), (1, 'transaction 3', 1800.0), (1, 'transaction 4', -300.0)] <...>
>>> session.commit() COMMIT

多对多集合

对于多对多集合,两个类之间的关系涉及使用 relationship.secondary 参数配置的第三个表 relationship。 要使用 WriteOnlyCollection 将行批量插入到这种类型的集合中,可以首先分别批量插入新记录,使用 RETURNING 检索,然后将这些记录传递给 WriteOnlyCollection.add_all() 方法,工作单元进程将在其中继续将它们持久化为集合的一部分。

假设一个类 BankAudit 使用多对多表引用了许多 AccountTransaction 记录

>>> from sqlalchemy import Table, Column
>>> audit_to_transaction = Table(
...     "audit_transaction",
...     Base.metadata,
...     Column("audit_id", ForeignKey("audit.id", ondelete="CASCADE"), primary_key=True),
...     Column(
...         "transaction_id",
...         ForeignKey("account_transaction.id", ondelete="CASCADE"),
...         primary_key=True,
...     ),
... )
>>> class BankAudit(Base):
...     __tablename__ = "audit"
...     id: Mapped[int] = mapped_column(primary_key=True)
...     account_transactions: WriteOnlyMapped["AccountTransaction"] = relationship(
...         secondary=audit_to_transaction, passive_deletes=True
...     )

为了说明这两个操作,我们使用批量插入添加更多 AccountTransaction 对象,我们通过将 returning(AccountTransaction) 添加到批量 INSERT 语句来使用 RETURNING 检索(请注意,我们可以同样轻松地使用现有的 AccountTransaction 对象)

>>> new_transactions = session.scalars(
...     existing_account.account_transactions.insert().returning(AccountTransaction),
...     [
...         {"description": "odd trans 1", "amount": Decimal("50000.00")},
...         {"description": "odd trans 2", "amount": Decimal("25000.00")},
...         {"description": "odd trans 3", "amount": Decimal("45.00")},
...     ],
... ).all()
BEGIN (implicit) INSERT INTO account_transaction (account_id, description, amount, timestamp) VALUES (?, ?, ?, CURRENT_TIMESTAMP), (?, ?, ?, CURRENT_TIMESTAMP), (?, ?, ?, CURRENT_TIMESTAMP) RETURNING id, account_id, description, amount, timestamp [...] (1, 'odd trans 1', 50000.0, 1, 'odd trans 2', 25000.0, 1, 'odd trans 3', 45.0)

准备好 AccountTransaction 对象列表后,WriteOnlyCollection.add_all() 方法用于将多行一次与新的 BankAudit 对象关联

>>> bank_audit = BankAudit()
>>> session.add(bank_audit)
>>> bank_audit.account_transactions.add_all(new_transactions)
>>> session.commit()
INSERT INTO audit DEFAULT VALUES [...] () INSERT INTO audit_transaction (audit_id, transaction_id) VALUES (?, ?) [...] [(1, 10), (1, 11), (1, 12)] COMMIT

批量更新和删除项

WriteOnlyCollection 可以生成预先建立 WHERE 条件的 Select 构造的方式类似,它还可以使用相同的 WHERE 条件生成 UpdateDelete 构造,以允许针对大型集合中的元素进行面向条件的 UPDATE 和 DELETE 语句。

一对多集合

与 INSERT 的情况一样,此功能对于一对多集合最直接。

在下面的示例中,WriteOnlyCollection.update() 方法用于生成针对集合中元素发出的 UPDATE 语句,定位 “amount” 等于 -800 的行,并将 200 的金额添加到其中

>>> session.execute(
...     existing_account.account_transactions.update()
...     .values(amount=AccountTransaction.amount + 200)
...     .where(AccountTransaction.amount == -800),
... )
BEGIN (implicit) UPDATE account_transaction SET amount=(account_transaction.amount + ?) WHERE ? = account_transaction.account_id AND account_transaction.amount = ? [...] (200, 1, -800)
<...>

以类似的方式,WriteOnlyCollection.delete() 将生成以相同方式调用的 DELETE 语句

>>> session.execute(
...     existing_account.account_transactions.delete().where(
...         AccountTransaction.amount.between(0, 30)
...     ),
... )
DELETE FROM account_transaction WHERE ? = account_transaction.account_id AND account_transaction.amount BETWEEN ? AND ? RETURNING id [...] (1, 0, 30) <...>

多对多集合

提示

这里的技术涉及多表 UPDATE 表达式,这些表达式稍微高级一些。

对于多对多集合的批量 UPDATE 和 DELETE,为了使 UPDATE 或 DELETE 语句与父对象的主键相关联,关联表必须显式成为 UPDATE/DELETE 语句的一部分,这需要后端包含对非标准 SQL 语法的支持,或者在构造 UPDATE 或 DELETE 语句时需要额外的显式步骤。

对于支持多表版本的 UPDATE 的后端,WriteOnlyCollection.update() 方法应该适用于多对多集合,无需额外的步骤,如下例所示,其中针对多对多 BankAudit.account_transactions 集合中的 AccountTransaction 对象发出 UPDATE

>>> session.execute(
...     bank_audit.account_transactions.update().values(
...         description=AccountTransaction.description + " (audited)"
...     )
... )
UPDATE account_transaction SET description=(account_transaction.description || ?) FROM audit_transaction WHERE ? = audit_transaction.audit_id AND account_transaction.id = audit_transaction.transaction_id RETURNING id [...] (' (audited)', 1)
<...>

上面的语句自动使用 SQLite 和其他数据库支持的 “UPDATE..FROM” 语法,以在 WHERE 子句中命名额外的 audit_transaction 表。

要 UPDATE 或 DELETE 多表语法不可用的多对多集合,可以将多对多条件移动到 SELECT 中,例如,SELECT 可以与 IN 组合以匹配行。 WriteOnlyCollection 仍然在这里帮助我们,因为我们使用 WriteOnlyCollection.select() 方法为我们生成此 SELECT,利用 Select.with_only_columns() 方法生成 标量子查询

>>> from sqlalchemy import update
>>> subq = bank_audit.account_transactions.select().with_only_columns(AccountTransaction.id)
>>> session.execute(
...     update(AccountTransaction)
...     .values(description=AccountTransaction.description + " (audited)")
...     .where(AccountTransaction.id.in_(subq))
... )
UPDATE account_transaction SET description=(account_transaction.description || ?) WHERE account_transaction.id IN (SELECT account_transaction.id FROM audit_transaction WHERE ? = audit_transaction.audit_id AND account_transaction.id = audit_transaction.transaction_id) RETURNING id [...] (' (audited)', 1) <...>

只写集合 - API 文档

对象名称 描述

WriteOnlyCollection

只写集合,可以将更改同步到属性事件系统中。

WriteOnlyMapped

表示“只写”关系的 ORM 映射属性类型。

sqlalchemy.orm.WriteOnlyCollection

只写集合,可以将更改同步到属性事件系统中。

WriteOnlyCollection 通过将 "write_only" 延迟加载策略与 relationship() 一起使用,用于映射。 有关此配置的背景信息,请参阅 只写关系

2.0 版本新增。

另请参阅

只写关系

类签名

class sqlalchemy.orm.WriteOnlyCollection (sqlalchemy.orm.writeonly.AbstractCollectionWriter)

方法 sqlalchemy.orm.WriteOnlyCollection.add(item: _T) None

向此 WriteOnlyCollection 添加一个项。

给定的项将在下一次刷新时根据父实例的集合持久化到数据库中。

方法 sqlalchemy.orm.WriteOnlyCollection.add_all(iterator: Iterable[_T]) None

向此 WriteOnlyCollection 添加一个项的可迭代对象。

给定的项将在下一次刷新时根据父实例的集合持久化到数据库中。

方法 sqlalchemy.orm.WriteOnlyCollection.delete() Delete

生成一个 Delete,它将根据此实例本地 WriteOnlyCollection 引用行。

方法 sqlalchemy.orm.WriteOnlyCollection.insert() Insert

对于一对多集合,生成一个 Insert,它将根据此实例本地 WriteOnlyCollection 插入新行。

此构造仅支持包含 relationship.secondary 参数的 Relationship。 对于引用多对多表的关系,请使用普通的批量插入技术来生成新对象,然后使用 AbstractCollectionWriter.add_all() 将它们与集合关联。

方法 sqlalchemy.orm.WriteOnlyCollection.remove(item: _T) None

从此 WriteOnlyCollection 中删除一个项。

给定的项将在下一次刷新时从父实例的集合中删除。

方法 sqlalchemy.orm.WriteOnlyCollection.select() Select[Tuple[_T]]

生成一个 Select 构造,表示此实例本地 WriteOnlyCollection 中的行。

方法 sqlalchemy.orm.WriteOnlyCollection.update() Update

生成一个 Update,它将根据此实例本地 WriteOnlyCollection 引用行。

sqlalchemy.orm.WriteOnlyMapped

表示“只写”关系的 ORM 映射属性类型。

WriteOnlyMapped 类型注解可以在 带注解的声明式表 映射中使用,以指示对于特定的 relationship() 应该使用 lazy="write_only" 加载器策略。

例如:

class User(Base):
    __tablename__ = "user"
    id: Mapped[int] = mapped_column(primary_key=True)
    addresses: WriteOnlyMapped[Address] = relationship(
        cascade="all,delete-orphan"
    )

有关背景信息,请参阅 只写关系 部分。

2.0 版本新增。

另请参阅

只写关系 - 完整背景

DynamicMapped - 包括旧式的 Query 支持

类签名

sqlalchemy.orm.WriteOnlyMapped (sqlalchemy.orm.base._MappedAnnotationBase)

动态关系加载器

旧式特性

“动态”延迟加载器策略是旧式形式,现在是 只写关系 部分中描述的“write_only”策略。

“动态”策略从相关集合生成旧式的 Query 对象。但是,“动态”关系的一个主要缺点是,在某些情况下,集合会完全迭代,其中一些情况并不明显,只能通过仔细的编程和针对具体情况的测试来避免。因此,对于真正的大型集合管理,应首选 WriteOnlyCollection

动态加载器也不兼容 异步 I/O (asyncio) 扩展。它可以与一些限制一起使用,如 Asyncio 动态指南 中所示,但同样,应首选与 asyncio 完全兼容的 WriteOnlyCollection

动态关系策略允许配置 relationship(),当在实例上访问时,它将返回旧式的 Query 对象,而不是集合。Query 然后可以进一步修改,以便可以根据过滤条件迭代数据库集合。返回的 Query 对象是 AppenderQuery 的一个实例,它结合了 Query 的加载和迭代行为,以及基本的集合变更方法,例如 AppenderQuery.append()AppenderQuery.remove()

可以使用 DynamicMapped 注解类,通过类型注解声明式形式配置“动态”加载器策略

from sqlalchemy.orm import DynamicMapped


class User(Base):
    __tablename__ = "user"

    id: Mapped[int] = mapped_column(primary_key=True)
    posts: DynamicMapped[Post] = relationship()

在上面,单个 User 对象上的 User.posts 集合将返回 AppenderQuery 对象,它是 Query 的子类,也支持基本的集合变更操作

jack = session.get(User, id)

# filter Jack's blog posts
posts = jack.posts.filter(Post.headline == "this is a post")

# apply array slices
posts = jack.posts[5:20]

动态关系通过 AppenderQuery.append()AppenderQuery.remove() 方法支持有限的写入操作

oldpost = jack.posts.filter(Post.headline == "old post").one()
jack.posts.remove(oldpost)

jack.posts.append(Post("new post"))

由于动态关系的读取端始终查询数据库,因此对底层集合的更改在刷新数据之前不会可见。但是,只要在使用中的 Session 上启用了“autoflush”,每次集合即将发出查询时,都会自动发生这种情况。

动态关系加载器 - API

对象名称 描述

AppenderQuery

支持基本集合存储操作的动态查询。

DynamicMapped

表示“动态”关系的 ORM 映射属性类型。

sqlalchemy.orm.AppenderQuery

支持基本集合存储操作的动态查询。

AppenderQuery 上的方法包括 Query 的所有方法,以及用于集合持久性的其他方法。

类签名

sqlalchemy.orm.AppenderQuery (sqlalchemy.orm.dynamic.AppenderMixin, sqlalchemy.orm.Query)

方法 sqlalchemy.orm.AppenderQuery.add(item: _T) None

继承自 AppenderMixin.add() 方法,来自 AppenderMixin

向此 AppenderQuery 添加一个项目。

给定的项将在下一次刷新时根据父实例的集合持久化到数据库中。

提供此方法是为了帮助实现与 WriteOnlyCollection 集合类的向前兼容性。

2.0 版本新增。

方法 sqlalchemy.orm.AppenderQuery.add_all(iterator: Iterable[_T]) None

继承自 AppenderMixin.add_all() 方法,来自 AppenderMixin

向此 AppenderQuery 添加一个项目迭代器。

给定的项将在下一次刷新时根据父实例的集合持久化到数据库中。

提供此方法是为了帮助实现与 WriteOnlyCollection 集合类的向前兼容性。

2.0 版本新增。

方法 sqlalchemy.orm.AppenderQuery.append(item: _T) None

继承自 AppenderMixin.append() 方法,来自 AppenderMixin

向此 AppenderQuery 追加一个项目。

给定的项将在下一次刷新时根据父实例的集合持久化到数据库中。

方法 sqlalchemy.orm.AppenderQuery.count() int

继承自 AppenderMixin.count() 方法,来自 AppenderMixin

返回此 Query 形成的 SQL 将返回的行数。

这将为此 Query 生成如下 SQL

SELECT count(1) AS count_1 FROM (
    SELECT <rest of query follows...>
) AS anon_1

上面的 SQL 返回单行,即计数函数的聚合值;然后 Query.count() 方法返回该单个整数值。

警告

重要的是要注意,count() 返回的值与此 Query 从诸如 .all() 方法之类的方法返回的 ORM 对象数量不同Query 对象在被要求返回完整实体时,将基于主键对条目进行去重,这意味着如果同一个主键值在结果中出现多次,则只会存在该主键的一个对象。这不适用于针对单个列的查询。

为了精细控制要计数的特定列,跳过子查询的使用或以其他方式控制 FROM 子句,或使用其他聚合函数,请结合使用 expression.func 表达式和 Session.query(),例如:

from sqlalchemy import func

# count User records, without
# using a subquery.
session.query(func.count(User.id))

# return count of user "id" grouped
# by "name"
session.query(func.count(User.id)).group_by(User.name)

from sqlalchemy import distinct

# count distinct "name" values
session.query(func.count(distinct(User.name)))
方法 sqlalchemy.orm.AppenderQuery.extend(iterator: Iterable[_T]) None

继承自 AppenderMixin.extend() 方法,来自 AppenderMixin

向此 AppenderQuery 添加一个项目迭代器。

给定的项将在下一次刷新时根据父实例的集合持久化到数据库中。

方法 sqlalchemy.orm.AppenderQuery.remove(item: _T) None

继承自 AppenderMixin.remove() 方法,来自 AppenderMixin

从此 AppenderQuery 中删除一个项目。

给定的项将在下一次刷新时从父实例的集合中删除。

sqlalchemy.orm.DynamicMapped

表示“动态”关系的 ORM 映射属性类型。

DynamicMapped 类型注解可以在 带注解的声明式表 映射中使用,以指示对于特定的 relationship() 应该使用 lazy="dynamic" 加载器策略。

旧式特性

“动态”延迟加载器策略是旧式形式,现在是 只写关系 部分中描述的“write_only”策略。

例如:

class User(Base):
    __tablename__ = "user"
    id: Mapped[int] = mapped_column(primary_key=True)
    addresses: DynamicMapped[Address] = relationship(
        cascade="all,delete-orphan"
    )

有关背景信息,请参阅 动态关系加载器 部分。

2.0 版本新增。

另请参阅

动态关系加载器 - 完整背景

WriteOnlyMapped - 完全 2.0 风格版本

类签名

sqlalchemy.orm.DynamicMapped (sqlalchemy.orm.base._MappedAnnotationBase)

设置 RaiseLoad

“raise”加载的关系将在属性通常会发出延迟加载的地方引发 InvalidRequestError

class MyClass(Base):
    __tablename__ = "some_table"

    # ...

    children: Mapped[List[MyRelatedClass]] = relationship(lazy="raise")

在上面,如果 children 集合的属性访问之前未填充,则会引发异常。这包括读取访问,但对于集合也会影响写入访问,因为在未先加载集合的情况下无法对其进行修改。这样做的理由是确保应用程序在特定上下文中不会发出任何意外的延迟加载。与其必须通读 SQL 日志来确定所有必需的属性都已急切加载,不如使用“raise”策略,如果访问未加载的属性,将立即引发异常。“raise”策略也可通过使用 raiseload() 加载器选项在查询选项的基础上使用。

使用被动删除

SQLAlchemy 中集合管理的一个重要方面是,当删除引用集合的对象时,SQLAlchemy 需要考虑此集合内部的对象。这些对象需要与父对象解除关联,对于一对多集合,这意味着外键列设置为 NULL,或者根据 级联 设置,可能反而希望为这些行发出 DELETE。

工作单元 进程仅在逐行的基础上考虑对象,这意味着 DELETE 操作意味着集合中的所有行都必须在刷新过程中完全加载到内存中。对于大型集合,这是不可行的,因此我们转而依靠数据库自身的能力,使用外键 ON DELETE 规则自动更新或删除行,指示工作单元放弃实际需要加载这些行才能处理它们。可以通过在 relationship() 构造上配置 relationship.passive_deletes 来指示工作单元以此方式工作;使用中的外键约束也必须正确配置。

有关完整“被动删除”配置的更多详细信息,请参阅 将外键 ON DELETE 级联与 ORM 关系一起使用 部分。