SqlAlchemy 2.0 中文文档(十三)(4)https://developer.aliyun.com/article/1562963
批量更新和删除项目
类似于WriteOnlyCollection
可以生成带有预先建立 WHERE 条件的Select
构造,它也可以生成具有相同 WHERE 条件的Update
和Delete
构造,以允许针对大型集合中的元素的基于条件的 UPDATE 和 DELETE 语句。
一对多集合
和插入一样,这个特性在一对多集合中最为直接。
在下面的例子中,使用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 语句中,这要求后端包含对非标准 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) <...>
上面的语句自动使用了“UPDATE…FROM”语法,由 SQLite 和其他后端支持,在 WHERE 子句中命名附加的audit_transaction
表。
要更新或删除多对多集合,其中多表语法不可用,多对多条件可能会移到 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) <...>
一对多集合
就像在 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) <...>
上述语句自动使用“UPDATE…FROM”语法,在 SQLite 和其他支持的数据库中,在 WHERE 子句中命名附加的audit_transaction
表。
要更新或删除多对多集合,其中多表语法不可用,多对多条件可以移动到 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 映射属性类型。 |
class sqlalchemy.orm.WriteOnlyCollection
只写集合,可以将更改同步到属性事件系统。
使用relationship()
的"write_only"
延迟加载策略在映射中使用WriteOnlyCollection
。有关此配置的背景,请参阅只写关系。
新版本 2.0 中新增。
参见
只写关系
成员
add(), add_all(), delete(), insert(), remove(), select(), update()
类签名
类sqlalchemy.orm.WriteOnlyCollection
(sqlalchemy.orm.writeonly.AbstractCollectionWriter
)
method add(item: _T) → None
向此WriteOnlyCollection
添加项目。
下一个刷新时,给定的项目将以父实例集合的形式持久化到数据库中。
method add_all(iterator: Iterable[_T]) → None
向此WriteOnlyCollection
添加项目的可迭代项。
给定的项目将以父实例集合的形式在下一个刷新时持久化到数据库中。
method delete() → Delete
生成一个Delete
,该删除将以此实例本地的WriteOnlyCollection
来引用行。
method insert() → Insert
对于一对多集合,生成一个Insert
,该插入将以此实例本地的WriteOnlyCollection
来插入新行。
该构造仅支持不包括relationship.secondary
参数的Relationship
。对于引用多对多表的关系,请使用普通的批量插入技术来生成新对象,然后使用AbstractCollectionWriter.add_all()
将它们与集合关联起来。
method remove(item: _T) → None
从此WriteOnlyCollection
中移除一个项目。
下一个刷新时,给定的项目将从父实例的集合中移除。
method select() → Select[Tuple[_T]]
生成一个表示此实例本地WriteOnlyCollection
内行的Select
构造。
method update() → Update
生成一个Update
,该更新将以此实例本地的WriteOnlyCollection
来引用行。
class 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
上启用了“自动刷新”,这将在每次集合准备发出查询时自动发生。
动态关系加载器 - API
对象名称 | 描述 |
AppenderQuery | 支持基本集合存储操作的动态查询。 |
DynamicMapped | 代表“动态”关系的 ORM 映射属性类型。 |
class sqlalchemy.orm.AppenderQuery
支持基本集合存储操作的动态查询。
AppenderQuery
上的方法包括 Query
的所有方法,以及用于集合持久化的其他方法。
成员
add(), add_all(), append(), count(), extend(), remove()
类签名
类 sqlalchemy.orm.AppenderQuery
(sqlalchemy.orm.dynamic.AppenderMixin
, sqlalchemy.orm.Query
)
method add(item: _T) → None
继承自 AppenderMixin.add()
方法的 AppenderMixin
向此 AppenderQuery
添加一个项目。
给定的项目将在下一次提交时以父实例集合的形式持久化到数据库中。
提供此方法是为了帮助实现与 WriteOnlyCollection
集合类的向前兼容。
版本 2.0 中的新功能。
method add_all(iterator: Iterable[_T]) → None
继承自 AppenderMixin.add_all()
方法的 AppenderMixin
向此 AppenderQuery
添加一个项目的可迭代对象。
给定的项目将在下一次提交时以父实例集合的形式持久化到数据库中。
提供此方法是为了帮助实现与 WriteOnlyCollection
集合类的向前兼容。
版本 2.0 中的新功能。
method append(item: _T) → None
继承自 AppenderMixin.append()
方法的 AppenderMixin
将一个项目追加到此 AppenderQuery
中。
给定的项目将在下一个 flush 时以父实例集合的形式持久化到数据库中。
method count() → int
继承自 AppenderMixin.count()
方法的 AppenderMixin
返回此 Query
生成的 SQL 所返回的行数。
这将生成此查询的 SQL 如下:
SELECT count(1) AS count_1 FROM ( SELECT <rest of query follows...> ) AS anon_1
上述 SQL 返回单行,该行是 count 函数的聚合值;然后 Query.count()
方法返回该单个整数值。
警告
需要注意的是,count() 返回的值与此查询从 .all()
方法等返回的 ORM 对象数量不同。当 Query
对象被要求返回完整实体时,将基于主键去重,这意味着如果相同的主键值会在结果中出现多次,那么只会有一个该主键的对象存在。这不适用于针对单个列的查询。
另请参阅
我的查询结果与 query.count() 告诉我的对象数量不同 - 为什么?
要对特定列进行精细化控制以进行计数,跳过子查询的使用或以其他方式控制 FROM 子句,或者使用 Session.query()
与 expression.func
表达式结合使用,例如:
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)))
另请参阅
2.0 迁移 - ORM 用法
method extend(iterator: Iterable[_T]) → None
继承自 AppenderMixin.extend()
方法的 AppenderMixin
将一个项目可迭代的添加到此 AppenderQuery
中。
给定的项目将在下一个 flush 时以父实例集合的形式持久化到数据库中。
method remove(item: _T) → None
继承自 AppenderMixin.remove()
方法的 AppenderMixin
从此 AppenderQuery
中删除一个项目。
给定的项目将在下一个 flush 时从父实例的集合中移除。
class sqlalchemy.orm.DynamicMapped
代表“动态”关系的 ORM 映射属性类型。
DynamicMapped
类型注释可用于 Annotated Declarative Table 映射中,指示对特定 relationship()
使用 lazy="dynamic"
加载策略。
传统特性
“动态”延迟加载策略是现在在 Write Only Relationships 部分中描述的 “write_only” 策略的传统形式。
例如:
class User(Base): __tablename__ = "user" id: Mapped[int] = mapped_column(primary_key=True) addresses: DynamicMapped[Address] = relationship( cascade="all,delete-orphan" )
参见 Dynamic Relationship Loaders 部分的背景信息。
2.0 版本中新增。
另请参见
Dynamic Relationship Loaders - 完整背景信息
WriteOnlyMapped
- 完全符合 2.0 版本风格
类签名
类 sqlalchemy.orm.DynamicMapped
(sqlalchemy.orm.base._MappedAnnotationBase
)
动态关系加载器 - API
对象名称 | 描述 |
AppenderQuery | 支持基本集合存储操作的动态查询。 |
DynamicMapped | 代表 “动态” 关系的 ORM 映射属性类型。 |
class sqlalchemy.orm.AppenderQuery
支持基本集合存储操作的动态查询。
AppenderQuery
上的方法包括 Query
的所有方法,以及用于集合持久性的额外方法。
成员
add(), add_all(), append(), count(), extend(), remove()
类签名
类 sqlalchemy.orm.AppenderQuery
(sqlalchemy.orm.dynamic.AppenderMixin
, sqlalchemy.orm.Query
) 的签名
method add(item: _T) → None
继承自 AppenderMixin.add()
方法的 AppenderMixin
将项添加到此 AppenderQuery
中。
给定的项将以父实例集合的形式在下一个 flush 中持久化到数据库中。
提供此方法是为了帮助与 WriteOnlyCollection
集合类保持向前兼容。
2.0 版本中新增。
method add_all(iterator: Iterable[_T]) → None
继承自 AppenderMixin.add_all()
方法的 AppenderMixin
将项的可迭代对象添加到此 AppenderQuery
中。
下一个 flush 时,给定的项将以父实例集合的形式持久化到数据库中。
提供此方法是为了帮助与 WriteOnlyCollection
集合类保持向前兼容。
2.0 版本中新增。
method append(item: _T) → None
继承自 AppenderMixin.append()
方法的 AppenderMixin
将项目附加到此 AppenderQuery
。
给定的项目将在下一次刷新时以父实例集合的形式持久化到数据库中。
method count() → int
继承自 AppenderMixin.count()
方法的 AppenderMixin
返回此 Query
形成的 SQL 将返回的行数计数。
这将为此查询生成以下 SQL:
SELECT count(1) AS count_1 FROM ( SELECT <rest of query follows...> ) AS anon_1
上述 SQL 返回单行,该行是计数函数的聚合值;然后 Query.count()
方法返回该单个整数值。
警告
重要的是要注意,count() 返回的值 不同于此查询从 .all() 方法等返回的 ORM 对象数。当 Query
对象被要求返回完整实体时,将 基于主键去重 条目,这意味着如果相同的主键值会出现在结果中超过一次,则该主键的对象只会出现一次。这不适用于针对单个列的查询。
另请参阅
我的查询的对象数与 query.count() 告诉我的不一样 - 为什么?
若要对特定列进行精细控制以计数,跳过子查询的使用或以其他方式控制 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)))
另请参阅
2.0 迁移 - ORM 用法
method extend(iterator: Iterable[_T]) → None
继承自 AppenderMixin.extend()
方法的 AppenderMixin
将项目的可迭代项添加到此 AppenderQuery
中。
给定的项目将在下一次刷新时以父实例集合的形式持久化到数据库中。
method remove(item: _T) → None
继承自 AppenderMixin.remove()
方法的 AppenderMixin
从此 AppenderQuery
中删除项目。
给定的项目将在下一次刷新时从父实例的集合中移除。
class sqlalchemy.orm.DynamicMapped
代表“动态”关系的 ORM 映射属性类型。
DynamicMapped
类型注释可在注释的声明性表映射中使用,以指示应该为特定 relationship()
使用 lazy="dynamic"
加载器策略。
传统特性
“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”策略,如果访问了未加载的属性,将立即引发未加载的属性。也可以在查询选项基础上使用 raiseload()
加载器选项。
另请参阅
使用 raiseload 防止不需要的延迟加载
使用被动删除
SQLAlchemy 中集合管理的一个重要方面是,当引用集合的对象被删除时,SQLAlchemy 需要考虑到位于该集合内的对象。这些对象将需要从父对象中取消关联,对于一对多集合,这意味着外键列将被设置为 NULL,或者根据 级联 设置,可能希望对这些行发出 DELETE。
工作单元过程仅仅考虑逐行对象,这意味着 DELETE 操作意味着集合中的所有行必须在刷新过程中完全加载到内存中。对于大型集合来说,这是不可行的,因此我们转而依赖数据库自身的能力来使用外键 ON DELETE 规则自动更新或删除行,指示工作单元放弃实际需要加载这些行以处理它们。可以通过在relationship()
构造上配置relationship.passive_deletes
来指示工作单元以这种方式工作;使用的外键约束也必须正确配置。
有关完整“被动删除”配置的更多详细信息,请参阅使用 ORM 关系的外键 ON DELETE 级联部分。