支持 ORM 的 INSERT、UPDATE 和 DELETE 语句

关于本文档

本节使用在 SQLAlchemy 统一教程 中首次说明的 ORM 映射,在 声明映射类 部分中展示,以及在 映射类继承层次结构 部分中展示的继承映射。

查看本页的 ORM 设置.

Session.execute() 方法除了处理支持 ORM 的 Select 对象之外,还可以以各种方式处理支持 ORM 的 InsertUpdateDelete 对象,这些方式用于一次性 INSERT、UPDATE 或 DELETE 许多数据库行。还有一些特定于方言的支持,用于支持 ORM 的“upsert”,即 INSERT 语句,它会自动对已存在的行使用 UPDATE。

下表总结了本文档中讨论的调用形式

ORM 用例

使用的 DML 结构

数据使用…传递

支持 RETURNING 吗?

支持多表映射吗?

ORM 批量 INSERT 语句

insert()

传递到 Session.execute.params 的字典列表

使用 SQL 表达式的 ORM 批量 INSERT

insert()

Session.execute.paramsInsert.values()

使用每行 SQL 表达式的 ORM 批量 INSERT

insert()

传递到 Insert.values() 的字典列表

ORM “upsert” 语句

insert()

传递到 Insert.values() 的字典列表

ORM 按主键进行批量 UPDATE

update()

传递到 Session.execute.params 的字典列表

使用自定义 WHERE 条件的 ORM UPDATE 和 DELETE

update()delete()

传递到 Update.values() 的关键字

部分,使用手动步骤

ORM 批量 INSERT 语句

可以根据 ORM 类构建 insert() 结构并传递给 Session.execute() 方法。传递到 Session.execute.params 参数的字典列表(与 Insert 对象本身分离)将为语句调用**批量 INSERT 模式**,这实际上意味着操作将尽可能优化以处理多行

>>> from sqlalchemy import insert
>>> session.execute(
...     insert(User),
...     [
...         {"name": "spongebob", "fullname": "Spongebob Squarepants"},
...         {"name": "sandy", "fullname": "Sandy Cheeks"},
...         {"name": "patrick", "fullname": "Patrick Star"},
...         {"name": "squidward", "fullname": "Squidward Tentacles"},
...         {"name": "ehkrabs", "fullname": "Eugene H. Krabs"},
...     ],
... )
INSERT INTO user_account (name, fullname) VALUES (?, ?) [...] [('spongebob', 'Spongebob Squarepants'), ('sandy', 'Sandy Cheeks'), ('patrick', 'Patrick Star'), ('squidward', 'Squidward Tentacles'), ('ehkrabs', 'Eugene H. Krabs')]
<...>

参数字典包含键值对,这些键值对可能对应于 ORM 映射属性,这些属性与映射的 Columnmapped_column() 声明相匹配,以及与 复合 声明相匹配。键应与**ORM 映射属性名称**匹配,而不是实际的数据库列名称,如果这两个名称恰好不同。

版本 2.0 中变更: Insert 结构传递给 Session.execute() 方法现在会调用“批量插入”,它使用与传统 Session.bulk_insert_mappings() 方法相同的函数。与 1.x 版本相比,这是一个行为变化,在 1.x 版本中,Insert 将以 Core 为中心的方式解释,使用列名称作为值键;现在接受 ORM 属性键。可以通过将执行选项 {"dml_strategy": "raw"} 传递给 Session.execution_options 参数(Session.execute() 的参数)来使用 Core 风格的功能。

使用 RETURNING 获取新对象

批量 ORM 插入功能支持某些后端上的 INSERT..RETURNING,它可以返回 Result 对象,该对象可以生成单个列以及与新生成记录相对应的完全构造的 ORM 对象。INSERT..RETURNING 要求使用支持 SQL RETURNING 语法以及支持 executemany 与 RETURNING 的后端;此功能适用于所有 SQLAlchemy 包含的 后端,除了 MySQL(MariaDB 包含在内)。

例如,我们可以运行与之前相同的语句,添加使用 UpdateBase.returning() 方法,将完整的 User 实体作为我们想要返回的内容传递。 Session.scalars() 用于允许迭代 User 对象

>>> users = session.scalars(
...     insert(User).returning(User),
...     [
...         {"name": "spongebob", "fullname": "Spongebob Squarepants"},
...         {"name": "sandy", "fullname": "Sandy Cheeks"},
...         {"name": "patrick", "fullname": "Patrick Star"},
...         {"name": "squidward", "fullname": "Squidward Tentacles"},
...         {"name": "ehkrabs", "fullname": "Eugene H. Krabs"},
...     ],
... )
INSERT INTO user_account (name, fullname) VALUES (?, ?), (?, ?), (?, ?), (?, ?), (?, ?) RETURNING id, name, fullname, species [...] ('spongebob', 'Spongebob Squarepants', 'sandy', 'Sandy Cheeks', 'patrick', 'Patrick Star', 'squidward', 'Squidward Tentacles', 'ehkrabs', 'Eugene H. Krabs')
>>> print(users.all()) [User(name='spongebob', fullname='Spongebob Squarepants'), User(name='sandy', fullname='Sandy Cheeks'), User(name='patrick', fullname='Patrick Star'), User(name='squidward', fullname='Squidward Tentacles'), User(name='ehkrabs', fullname='Eugene H. Krabs')]

在上面的示例中,渲染的 SQL 采用由 insertmanyvalues 特性使用的形式,如 SQLite 后端所要求的,其中单个参数字典内联到单个 INSERT 语句中,以便可以使用 RETURNING。

在版本 2.0 中更改: ORM Session 现在解释来自 InsertUpdate 甚至 Delete 结构的 ORM 上下文中的 RETURNING 子句,这意味着可以将列表达式和 ORM 映射实体的混合传递给 Insert.returning() 方法,然后以 ORM 结果从诸如 Select 这样的结构中传递的方式传递,包括映射实体将作为 ORM 映射对象在结果中传递。对 ORM 加载器选项(如 load_only()selectinload())的支持也存在。

将 RETURNING 记录与输入数据顺序相关联

在使用批量 INSERT 与 RETURNING 时,重要的是要注意,大多数数据库后端没有提供关于 RETURNING 中返回记录顺序的正式保证,包括没有保证它们的顺序将与输入记录的顺序相对应。对于需要确保 RETURNING 记录可以与输入数据关联的应用程序,可以指定附加参数 Insert.returning.sort_by_parameter_order,根据后端,它可能会使用保持令牌的特殊 INSERT 表单,用于适当地重新排序返回的行,或者在某些情况下,例如在下面使用 SQLite 后端,该操作将一次插入一行

>>> data = [
...     {"name": "pearl", "fullname": "Pearl Krabs"},
...     {"name": "plankton", "fullname": "Plankton"},
...     {"name": "gary", "fullname": "Gary"},
... ]
>>> user_ids = session.scalars(
...     insert(User).returning(User.id, sort_by_parameter_order=True), data
... )
INSERT INTO user_account (name, fullname) VALUES (?, ?) RETURNING id [... (insertmanyvalues) 1/3 (ordered; batch not supported)] ('pearl', 'Pearl Krabs') INSERT INTO user_account (name, fullname) VALUES (?, ?) RETURNING id [insertmanyvalues 2/3 (ordered; batch not supported)] ('plankton', 'Plankton') INSERT INTO user_account (name, fullname) VALUES (?, ?) RETURNING id [insertmanyvalues 3/3 (ordered; batch not supported)] ('gary', 'Gary')
>>> for user_id, input_record in zip(user_ids, data): ... input_record["id"] = user_id >>> print(data) [{'name': 'pearl', 'fullname': 'Pearl Krabs', 'id': 6}, {'name': 'plankton', 'fullname': 'Plankton', 'id': 7}, {'name': 'gary', 'fullname': 'Gary', 'id': 8}]

在版本 2.0.10 中新增: 添加了 Insert.returning.sort_by_parameter_order,它是在 insertmanyvalues 架构中实现的。

另请参见

将 RETURNING 行与参数集相关联 - 关于在不显著降低性能的情况下保证输入数据和结果行之间对应关系的方法的背景信息

使用异构参数字典

ORM 批量插入功能支持“异构”的参数字典列表,这基本上意味着“单个字典可以具有不同的键”。当检测到这种情况时,ORM 会将参数字典分解为对应于每组键的组,并相应地批处理到单独的 INSERT 语句中

>>> users = session.scalars(
...     insert(User).returning(User),
...     [
...         {
...             "name": "spongebob",
...             "fullname": "Spongebob Squarepants",
...             "species": "Sea Sponge",
...         },
...         {"name": "sandy", "fullname": "Sandy Cheeks", "species": "Squirrel"},
...         {"name": "patrick", "species": "Starfish"},
...         {
...             "name": "squidward",
...             "fullname": "Squidward Tentacles",
...             "species": "Squid",
...         },
...         {"name": "ehkrabs", "fullname": "Eugene H. Krabs", "species": "Crab"},
...     ],
... )
INSERT INTO user_account (name, fullname, species) VALUES (?, ?, ?), (?, ?, ?) RETURNING id, name, fullname, species [... (insertmanyvalues) 1/1 (unordered)] ('spongebob', 'Spongebob Squarepants', 'Sea Sponge', 'sandy', 'Sandy Cheeks', 'Squirrel') INSERT INTO user_account (name, species) VALUES (?, ?) RETURNING id, name, fullname, species [...] ('patrick', 'Starfish') INSERT INTO user_account (name, fullname, species) VALUES (?, ?, ?), (?, ?, ?) RETURNING id, name, fullname, species [... (insertmanyvalues) 1/1 (unordered)] ('squidward', 'Squidward Tentacles', 'Squid', 'ehkrabs', 'Eugene H. Krabs', 'Crab')

在上面的示例中,传递的五个参数字典转换为三个 INSERT 语句,按每个字典中特定键集分组,同时仍然保持行顺序,即 ("name", "fullname", "species")("name", "species")("name","fullname", "species")

在 ORM 批量 INSERT 语句中发送 NULL 值

批量 ORM 插入功能利用了传统“批量”插入行为以及 ORM 工作单元中也存在的行为,即包含 NULL 值的行使用不引用这些列的语句插入;这里的理由是,包含服务器端 INSERT 默认值的后端和模式可能对 NULL 值的存在与否敏感,会按预期生成服务器端值。这种默认行为会将批量插入批次分解成更多行数更少的批次

>>> session.execute(
...     insert(User),
...     [
...         {
...             "name": "name_a",
...             "fullname": "Employee A",
...             "species": "Squid",
...         },
...         {
...             "name": "name_b",
...             "fullname": "Employee B",
...             "species": "Squirrel",
...         },
...         {
...             "name": "name_c",
...             "fullname": "Employee C",
...             "species": None,
...         },
...         {
...             "name": "name_d",
...             "fullname": "Employee D",
...             "species": "Bluefish",
...         },
...     ],
... )
INSERT INTO user_account (name, fullname, species) VALUES (?, ?, ?) [...] [('name_a', 'Employee A', 'Squid'), ('name_b', 'Employee B', 'Squirrel')] INSERT INTO user_account (name, fullname) VALUES (?, ?) [...] ('name_c', 'Employee C') INSERT INTO user_account (name, fullname, species) VALUES (?, ?, ?) [...] ('name_d', 'Employee D', 'Bluefish') ...

上面,四行的批量 INSERT 被分解成三个单独的语句,第二个语句被重新格式化为不引用包含 None 值的单个参数字典的 NULL 列。当数据集中的许多行包含随机 NULL 值时,这种默认行为可能不理想,因为它会导致“executemany”操作分解成更多数量更小的操作;特别是当依赖于 insertmanyvalues 来减少语句总数时,这可能会对性能产生更大的影响。

要禁用参数中 None 值的处理到单独的批次,请传递执行选项 render_nulls=True;这将导致所有参数字典被同等对待,假设每个字典中都具有相同的键集

>>> session.execute(
...     insert(User).execution_options(render_nulls=True),
...     [
...         {
...             "name": "name_a",
...             "fullname": "Employee A",
...             "species": "Squid",
...         },
...         {
...             "name": "name_b",
...             "fullname": "Employee B",
...             "species": "Squirrel",
...         },
...         {
...             "name": "name_c",
...             "fullname": "Employee C",
...             "species": None,
...         },
...         {
...             "name": "name_d",
...             "fullname": "Employee D",
...             "species": "Bluefish",
...         },
...     ],
... )
INSERT INTO user_account (name, fullname, species) VALUES (?, ?, ?) [...] [('name_a', 'Employee A', 'Squid'), ('name_b', 'Employee B', 'Squirrel'), ('name_c', 'Employee C', None), ('name_d', 'Employee D', 'Bluefish')] ...

上面,所有参数字典都以单个 INSERT 批次发送,包括第三个参数字典中存在的 None 值。

在版本 2.0.23 中新增: 添加了 render_nulls 执行选项,它反映了传统 Session.bulk_insert_mappings.render_nulls 参数的行为。

用于连接表继承的批量 INSERT

ORM 批量插入基于传统 工作单元 系统使用的内部系统以发出 INSERT 语句。这意味着对于映射到多个表的 ORM 实体(通常是使用 连接表继承 映射的实体),批量 INSERT 操作将为映射表示的每个表发出 INSERT 语句,并将服务器生成的 主键值正确传输到依赖它们的表行。RETURNING 功能在此处也受支持,ORM 将为执行的每个 INSERT 语句接收 Result 对象,然后将它们“水平拼接”在一起,以便返回的行包含所有插入的列的值

>>> managers = session.scalars(
...     insert(Manager).returning(Manager),
...     [
...         {"name": "sandy", "manager_name": "Sandy Cheeks"},
...         {"name": "ehkrabs", "manager_name": "Eugene H. Krabs"},
...     ],
... )
INSERT INTO employee (name, type) VALUES (?, ?) RETURNING id, name, type [... (insertmanyvalues) 1/2 (ordered; batch not supported)] ('sandy', 'manager') INSERT INTO employee (name, type) VALUES (?, ?) RETURNING id, name, type [insertmanyvalues 2/2 (ordered; batch not supported)] ('ehkrabs', 'manager') INSERT INTO manager (id, manager_name) VALUES (?, ?), (?, ?) RETURNING id, manager_name, id AS id__1 [... (insertmanyvalues) 1/1 (ordered)] (1, 'Sandy Cheeks', 2, 'Eugene H. Krabs')

小贴士

连接表继承映射的批量 INSERT 需要 ORM 内部使用 Insert.returning.sort_by_parameter_order 参数,以便它可以将基表的 RETURNING 行中的主键值关联到用于插入“子”表的参数集中,这就是为什么上面说明的 SQLite 后端透明地降级为使用非批处理语句的原因。此功能的背景信息位于 将 RETURNING 行与参数集相关联 中。

使用 SQL 表达式的 ORM 批量插入

ORM 批量插入功能支持添加一组固定的参数,这些参数可能包括要应用于每个目标行的 SQL 表达式。要实现这一点,请将使用 Insert.values() 方法(传递将应用于所有行的参数字典)与通常的批量调用形式结合起来,在调用 Session.execute() 时包含包含单个行值的参数字典列表。

例如,给定一个 ORM 映射,其中包含一个“timestamp”列

import datetime


class LogRecord(Base):
    __tablename__ = "log_record"
    id: Mapped[int] = mapped_column(primary_key=True)
    message: Mapped[str]
    code: Mapped[str]
    timestamp: Mapped[datetime.datetime]

如果我们想要插入一系列的LogRecord元素,每个元素都有一个唯一的message字段,但我们希望对所有行应用SQL函数now(),我们可以将timestamp传递给Insert.values(),然后使用“批量”模式传递其他记录。

>>> from sqlalchemy import func
>>> log_record_result = session.scalars(
...     insert(LogRecord).values(code="SQLA", timestamp=func.now()).returning(LogRecord),
...     [
...         {"message": "log message #1"},
...         {"message": "log message #2"},
...         {"message": "log message #3"},
...         {"message": "log message #4"},
...     ],
... )
INSERT INTO log_record (message, code, timestamp) VALUES (?, ?, CURRENT_TIMESTAMP), (?, ?, CURRENT_TIMESTAMP), (?, ?, CURRENT_TIMESTAMP), (?, ?, CURRENT_TIMESTAMP) RETURNING id, message, code, timestamp [... (insertmanyvalues) 1/1 (unordered)] ('log message #1', 'SQLA', 'log message #2', 'SQLA', 'log message #3', 'SQLA', 'log message #4', 'SQLA')
>>> print(log_record_result.all()) [LogRecord('log message #1', 'SQLA', datetime.datetime(...)), LogRecord('log message #2', 'SQLA', datetime.datetime(...)), LogRecord('log message #3', 'SQLA', datetime.datetime(...)), LogRecord('log message #4', 'SQLA', datetime.datetime(...))]

ORM 批量插入和每行 SQL 表达式

Insert.values() 方法本身接受一个参数字典列表。当以这种方式使用 Insert 结构,而没有将任何参数字典列表传递给 Session.execute.params 参数时,不会使用批量 ORM 插入模式,而是完全按照给定方式呈现 INSERT 语句,并只调用一次。这种操作模式对于按行传递 SQL 表达式的情况可能有用,并且在使用 ORM 的“upsert”语句时也会使用,本章后面将在 ORM “upsert” 语句 中介绍。

下面是一个嵌入每行 SQL 表达式的 INSERT 的虚构示例,还演示了此形式中的 Insert.returning()

>>> from sqlalchemy import select
>>> address_result = session.scalars(
...     insert(Address)
...     .values(
...         [
...             {
...                 "user_id": select(User.id).where(User.name == "sandy"),
...                 "email_address": "[email protected]",
...             },
...             {
...                 "user_id": select(User.id).where(User.name == "spongebob"),
...                 "email_address": "[email protected]",
...             },
...             {
...                 "user_id": select(User.id).where(User.name == "patrick"),
...                 "email_address": "[email protected]",
...             },
...         ]
...     )
...     .returning(Address),
... )
INSERT INTO address (user_id, email_address) VALUES ((SELECT user_account.id FROM user_account WHERE user_account.name = ?), ?), ((SELECT user_account.id FROM user_account WHERE user_account.name = ?), ?), ((SELECT user_account.id FROM user_account WHERE user_account.name = ?), ?) RETURNING id, user_id, email_address [...] ('sandy', '[email protected]', 'spongebob', '[email protected]', 'patrick', '[email protected]')
>>> print(address_result.all()) [Address(email_address='[email protected]'), Address(email_address='[email protected]'), Address(email_address='[email protected]')]

由于上面没有使用批量 ORM 插入模式,因此以下功能不存在。

  • 连接表继承 或其他多表映射不受支持,因为这将需要多个 INSERT 语句。

  • 异构参数集 不受支持 - VALUES 集中的每个元素必须具有相同的列。

  • 核心级别的扩展优化,例如 insertmanyvalues 提供的批处理不可用;语句需要确保参数总数不超过后端数据库施加的限制。

由于上述原因,通常不建议对 ORM INSERT 语句使用 Insert.values() 的多个参数集,除非有明确的理由,要么是正在使用“upsert”,要么是需要在每个参数集中嵌入每行 SQL 表达式。

遗留 Session 批量 INSERT 方法

Session 包含用于执行“批量”INSERT 和 UPDATE 语句的遗留方法。这些方法与 SQLAlchemy 2.0 版本的这些功能共享实现,这些功能在 ORM 批量 INSERT 语句ORM 按主键批量 UPDATE 中进行了描述,但缺少许多功能,即 RETURNING 支持以及对会话同步的支持。

例如,使用 Session.bulk_insert_mappings() 的代码可以按如下方式移植代码,从以下映射示例开始

session.bulk_insert_mappings(User, [{"name": "u1"}, {"name": "u2"}, {"name": "u3"}])

上面使用新的 API 表达为

from sqlalchemy import insert

session.execute(insert(User), [{"name": "u1"}, {"name": "u2"}, {"name": "u3"}])

ORM “upsert” 语句

使用 SQLAlchemy 的选定后端可能包括特定于方言的 Insert 结构,这些结构还具有执行“upsert”或 INSERT 的功能,其中参数集中的现有行会转换为 UPDATE 语句的近似值。对于“现有行”,这可能意味着共享相同主键值的行,也可能指代行中被认为是唯一的其他索引列;这取决于所用后端的功能。

包含 SQLAlchemy 的特定于方言的“upsert” API 功能的方言包括

用户应该回顾上述部分以了解有关这些对象正确构造的背景信息;特别是,“upsert”方法通常需要参考原始语句,因此语句通常在两个单独的步骤中构造。

第三方后端,例如在 外部方言 中提到的那些后端,也可能具有类似的构造。

虽然 SQLAlchemy 还没有后端无关的 upsert 结构,但上述 Insert 变体仍然与 ORM 兼容,因为它们可以在与 Insert 结构本身相同的 ORM 批量插入和每行 SQL 表达式 中使用,即通过将要 INSERT 的所需行嵌入到 Insert.values() 方法中。在下面的示例中,SQLite insert() 函数用于生成一个 Insert 结构,该结构包含“ON CONFLICT DO UPDATE”支持。然后将语句传递给 Session.execute(),它会正常执行,但具有附加特性,即传递给 Insert.values() 的参数字典被解释为 ORM 映射的属性键,而不是列名。

>>> from sqlalchemy.dialects.sqlite import insert as sqlite_upsert
>>> stmt = sqlite_upsert(User).values(
...     [
...         {"name": "spongebob", "fullname": "Spongebob Squarepants"},
...         {"name": "sandy", "fullname": "Sandy Cheeks"},
...         {"name": "patrick", "fullname": "Patrick Star"},
...         {"name": "squidward", "fullname": "Squidward Tentacles"},
...         {"name": "ehkrabs", "fullname": "Eugene H. Krabs"},
...     ]
... )
>>> stmt = stmt.on_conflict_do_update(
...     index_elements=[User.name], set_=dict(fullname=stmt.excluded.fullname)
... )
>>> session.execute(stmt)
INSERT INTO user_account (name, fullname) VALUES (?, ?), (?, ?), (?, ?), (?, ?), (?, ?) ON CONFLICT (name) DO UPDATE SET fullname = excluded.fullname [...] ('spongebob', 'Spongebob Squarepants', 'sandy', 'Sandy Cheeks', 'patrick', 'Patrick Star', 'squidward', 'Squidward Tentacles', 'ehkrabs', 'Eugene H. Krabs')
<...>

在 upsert 语句中使用 RETURNING

从 SQLAlchemy ORM 的角度来看,upsert 语句看起来像普通的 Insert 结构,包括 Insert.returning() 与 upsert 语句以与 ORM 批量插入和每行 SQL 表达式 中演示的方式相同的方式工作,因此可以传递任何列表达式或相关的 ORM 实体类。从上一节的示例继续

>>> result = session.scalars(
...     stmt.returning(User), execution_options={"populate_existing": True}
... )
INSERT INTO user_account (name, fullname) VALUES (?, ?), (?, ?), (?, ?), (?, ?), (?, ?) ON CONFLICT (name) DO UPDATE SET fullname = excluded.fullname RETURNING id, name, fullname, species [...] ('spongebob', 'Spongebob Squarepants', 'sandy', 'Sandy Cheeks', 'patrick', 'Patrick Star', 'squidward', 'Squidward Tentacles', 'ehkrabs', 'Eugene H. Krabs')
>>> print(result.all()) [User(name='spongebob', fullname='Spongebob Squarepants'), User(name='sandy', fullname='Sandy Cheeks'), User(name='patrick', fullname='Patrick Star'), User(name='squidward', fullname='Squidward Tentacles'), User(name='ehkrabs', fullname='Eugene H. Krabs')]

上面的示例使用 RETURNING 返回由语句插入或更新的每一行的 ORM 对象。示例还添加了对 填充现有 执行选项的使用。此选项表示,对于已存在的行,已存在于 Session 中的 User 对象应使用新行中的数据刷新。对于纯 Insert 语句,此选项并不重要,因为生成的每一行都是一个全新的主键标识。但是,当 Insert 还包含“upsert”选项时,它也可能会产生来自已存在行且因此可能已在 Session 对象的 身份映射 中表示主键标识的结果。

另请参见

填充现有

通过主键进行 ORM 批量 UPDATE

Update 结构可以与 Session.execute() 一起使用,方式类似于 Insert 语句的使用方式,如 ORM 批量 INSERT 语句 中所述,传递一个包含许多参数字典的列表,每个字典代表一个与单个主键值对应的单个行。此用法不应与使用 ORM 使用 Update 语句的更常见方式混淆,后者使用显式 WHERE 子句,这在 ORM UPDATE 和 DELETE 使用自定义 WHERE 条件 中有介绍。

对于 UPDATE 的“批量”版本,update() 结构是根据 ORM 类创建的,并传递给 Session.execute() 方法;生成的 Update 对象不应包含任何值,通常也不应包含 WHERE 条件,也就是说,Update.values() 方法没有使用,并且 Update.where()通常不使用,但在需要添加其他过滤条件的罕见情况下可以使用。

Update 结构以及一个参数字典列表(每个字典都包含一个完整的主键值)一起传递,将为语句调用通过主键进行批量 UPDATE 模式,生成匹配每个行的适当 WHERE 条件,并使用 executemany 对 UPDATE 语句运行每个参数集。

>>> from sqlalchemy import update
>>> session.execute(
...     update(User),
...     [
...         {"id": 1, "fullname": "Spongebob Squarepants"},
...         {"id": 3, "fullname": "Patrick Star"},
...         {"id": 5, "fullname": "Eugene H. Krabs"},
...     ],
... )
UPDATE user_account SET fullname=? WHERE user_account.id = ? [...] [('Spongebob Squarepants', 1), ('Patrick Star', 3), ('Eugene H. Krabs', 5)]
<...>

请注意,每个参数字典都必须包含每个记录的完整主键,否则将引发错误。

与批量 INSERT 功能一样,这里也支持异构参数列表,其中参数将被分组到 UPDATE 运行的子批次中。

在版本 2.0.11 中更改: 可以使用 Update.where() 方法添加其他条件,将其他 WHERE 条件与 通过主键进行 ORM 批量 UPDATE 结合使用。但是,此条件始终是除了已存在的 WHERE 条件以外的,其中包含主键值。

使用“通过主键进行批量 UPDATE”功能时,RETURNING 功能不可用;多个参数字典的列表必然会使用 DBAPI executemany,而它通常不支持结果行。

在版本 2.0 中更改: Update 结构以及参数字典列表一起传递给 Session.execute() 方法现在将调用“批量更新”,它使用与旧的 Session.bulk_update_mappings() 方法相同的函数。与 1.x 系列相比,这是一个行为变化,在 1.x 系列中,Update 只能与显式 WHERE 条件和内联 VALUES 一起使用。

为包含多个参数集的 UPDATE 语句禁用通过主键进行的批量 ORM 更新

通过主键进行 ORM 批量更新功能(对每个记录运行一个 UPDATE 语句,其中包含每个主键值的 WHERE 条件)会在以下情况下自动使用:

  1. 给定的 UPDATE 语句针对 ORM 实体

  2. Session 用于执行语句,而不是 Core Connection

  3. 传递的参数是字典列表

为了调用不使用“通过主键进行的 ORM 批量更新”的 UPDATE 语句,请使用 Session.connection() 方法直接针对 Connection 调用语句,以获取当前 Connection 进行事务。

>>> from sqlalchemy import bindparam
>>> session.connection().execute(
...     update(User).where(User.name == bindparam("u_name")),
...     [
...         {"u_name": "spongebob", "fullname": "Spongebob Squarepants"},
...         {"u_name": "patrick", "fullname": "Patrick Star"},
...     ],
... )
UPDATE user_account SET fullname=? WHERE user_account.name = ? [...] [('Spongebob Squarepants', 'spongebob'), ('Patrick Star', 'patrick')]
<...>

用于连接表继承的通过主键进行批量 UPDATE

使用具有连接表继承的映射时,ORM 批量更新与 ORM 批量插入具有类似的行为;如 用于连接表继承的批量 INSERT 中所述,批量 UPDATE 操作将为映射中表示的每个表发出一个 UPDATE 语句,对于这些表,给定参数包含要更新的值(未受影响的表将被跳过)。

示例

>>> session.execute(
...     update(Manager),
...     [
...         {
...             "id": 1,
...             "name": "scheeks",
...             "manager_name": "Sandy Cheeks, President",
...         },
...         {
...             "id": 2,
...             "name": "eugene",
...             "manager_name": "Eugene H. Krabs, VP Marketing",
...         },
...     ],
... )
UPDATE employee SET name=? WHERE employee.id = ? [...] [('scheeks', 1), ('eugene', 2)] UPDATE manager SET manager_name=? WHERE manager.id = ? [...] [('Sandy Cheeks, President', 1), ('Eugene H. Krabs, VP Marketing', 2)]
<...>

旧的 Session 批量 UPDATE 方法

旧的 Session 批量 INSERT 方法 中所述,Session.bulk_update_mappings() 方法是 Session 的旧批量更新形式,当 ORM 解释包含主键参数的 update() 语句时,ORM 会在内部使用它;但是,当使用旧版本时,不包括会话同步等功能。

以下示例

session.bulk_update_mappings(
    User,
    [
        {"id": 1, "name": "scheeks", "manager_name": "Sandy Cheeks, President"},
        {"id": 2, "name": "eugene", "manager_name": "Eugene H. Krabs, VP Marketing"},
    ],
)

使用新的 API 表示为

from sqlalchemy import update

session.execute(
    update(User),
    [
        {"id": 1, "name": "scheeks", "manager_name": "Sandy Cheeks, President"},
        {"id": 2, "name": "eugene", "manager_name": "Eugene H. Krabs, VP Marketing"},
    ],
)

使用自定义 WHERE 条件进行 ORM UPDATE 和 DELETE

当使用自定义 WHERE 条件构造 UpdateDelete 时(即使用 Update.where()Delete.where() 方法),可以通过将它们传递给 Session.execute() 在 ORM 上下文中调用它们,而无需使用 Session.execute.params 参数。对于 Update,要更新的值应使用 Update.values() 传递。

这种使用模式与之前在 ORM 批量 UPDATE 按主键 中描述的功能不同,因为 ORM 会原样使用给定的 WHERE 子句,而不是将 WHERE 子句固定为按主键。这意味着单个 UPDATE 或 DELETE 语句可以一次影响多行。

例如,下面发出一个影响多行“fullname”字段的 UPDATE。

>>> from sqlalchemy import update
>>> stmt = (
...     update(User)
...     .where(User.name.in_(["squidward", "sandy"]))
...     .values(fullname="Name starts with S")
... )
>>> session.execute(stmt)
UPDATE user_account SET fullname=? WHERE user_account.name IN (?, ?) [...] ('Name starts with S', 'squidward', 'sandy')
<...>

对于 DELETE,一个基于条件删除行的示例。

>>> from sqlalchemy import delete
>>> stmt = delete(User).where(User.name.in_(["squidward", "sandy"]))
>>> session.execute(stmt)
DELETE FROM user_account WHERE user_account.name IN (?, ?) [...] ('squidward', 'sandy')
<...>

警告

请阅读以下部分 ORM 启用的更新和删除的重要注意事项和注意事项,了解有关 ORM 启用的 UPDATE 和 DELETE 功能如何偏离 ORM 工作单元 特性的重要注意事项,例如使用 Session.delete() 方法删除单个对象。

ORM 启用的更新和删除的重要注意事项和注意事项

ORM 启用的 UPDATE 和 DELETE 功能绕过了 ORM 工作单元 自动化,有利于能够发出一个 UPDATE 或 DELETE 语句,该语句可以一次匹配多行而不会产生复杂性。

  • 这些操作不提供 Python 中关系的级联 - 假设任何需要它的外键引用都配置了 ON UPDATE CASCADE 和/或 ON DELETE CASCADE,否则如果外键引用正在被强制执行,数据库可能会发出完整性违规。有关一些示例,请参见 将外键 ON DELETE cascade 与 ORM 关系一起使用 中的说明。

  • 在 UPDATE 或 DELETE 之后,Session 中受相关表上的 ON UPDATE CASCADE 或 ON DELETE CASCADE 影响的依赖对象,特别是引用现在已删除行的对象的依赖对象,可能仍然引用这些对象。此问题会在 Session 到期后解决,这通常在 Session.commit() 时发生,也可以通过使用 Session.expire_all() 强制进行。

  • ORM 启用的 UPDATE 和 DELETE 不会自动处理连接表继承。有关如何处理连接继承映射的说明,请参见 具有自定义 WHERE 条件的 UPDATE/DELETE 用于连接表继承 部分。

  • 为了将多态标识限制为单表继承映射的特定子类,所需的 WHERE 条件将**自动包含**。这仅适用于没有自己表的子类映射器。

  • with_loader_criteria() 选项**受** ORM 更新和删除操作的支持;这里的条件将添加到正在发出的 UPDATE 或 DELETE 语句的条件中,并且在“同步”过程中也会考虑到这一点。

  • 为了使用事件处理程序拦截 ORM 启用的 UPDATE 和 DELETE 操作,请使用 SessionEvents.do_orm_execute() 事件。

选择同步策略

当将 update()delete() 与使用 Session.execute() 的 ORM 启用的执行结合使用时,会存在其他 ORM 特定的功能,这些功能将**同步**语句更改的状态与当前存在于 身份映射 中的对象的状态 Session。通过“同步”,我们的意思是 UPDATEd 属性将使用新值刷新,或者至少过期,以便它们将在下次访问时使用新值重新填充,而 DELETEd 对象将被移入已删除状态。

此同步可以通过“同步策略”进行控制,该策略作为字符串 ORM 执行选项传递,通常通过使用 Session.execute.execution_options 字典

>>> from sqlalchemy import update
>>> stmt = (
...     update(User).where(User.name == "squidward").values(fullname="Squidward Tentacles")
... )
>>> session.execute(stmt, execution_options={"synchronize_session": False})
UPDATE user_account SET fullname=? WHERE user_account.name = ? [...] ('Squidward Tentacles', 'squidward')
<...>

执行选项也可以使用 Executable.execution_options() 方法与语句本身捆绑在一起

>>> from sqlalchemy import update
>>> stmt = (
...     update(User)
...     .where(User.name == "squidward")
...     .values(fullname="Squidward Tentacles")
...     .execution_options(synchronize_session=False)
... )
>>> session.execute(stmt)
UPDATE user_account SET fullname=? WHERE user_account.name = ? [...] ('Squidward Tentacles', 'squidward')
<...>

支持以下 synchronize_session

  • 'auto' - 这是默认值。在支持 RETURNING 的后端上将使用 'fetch' 策略,其中包括除 MySQL 之外的所有 SQLAlchemy 原生驱动程序。如果 RETURNING 不受支持,则将改用 'evaluate' 策略。

  • 'fetch' - 通过在 UPDATE 或 DELETE 之前执行 SELECT,或通过使用 RETURNING(如果数据库支持它)来检索受影响行的主键标识,以便受操作影响的内存中对象可以使用新值(更新)刷新或从 Session 中清除(删除)。即使给定的 update()delete() 构造显式地使用 UpdateBase.returning() 指定实体或列,也可以使用此同步策略。

    在版本 2.0 中更改: 在使用具有 WHERE 条件的 ORM 启用的 UPDATE 和 DELETE 时,可以将显式 UpdateBase.returning()'fetch' 同步策略结合使用。实际语句将包含 'fetch' 策略所需列与已请求列之间的并集。

  • 'evaluate' - 这表示在 Python 中评估 UPDATE 或 DELETE 语句中给定的 WHERE 条件,以在 Session 中找到匹配的对象。此方法不会向操作添加任何 SQL 往返行程,并且在没有 RETURNING 支持的情况下,可能效率更高。对于具有复杂条件的 UPDATE 或 DELETE 语句,'evaluate' 策略可能无法在 Python 中评估表达式,并将引发错误。如果发生这种情况,请改为对操作使用 'fetch' 策略。

    小贴士

    如果 SQL 表达式使用 Operators.op()custom_op 功能使用自定义运算符,则 Operators.op.python_impl 参数可用于指示 "evaluate" 同步策略将使用的 Python 函数。

    版本 2.0 中的新功能。

    警告

    如果要在包含大量已过期对象的 Session 上运行 UPDATE 操作,则应避免使用 "evaluate" 策略,因为它需要刷新对象以针对给定的 WHERE 条件进行测试,这将为每个对象发出一个 SELECT。在这种情况下,尤其是当后端支持 RETURNING 时,应优先使用 "fetch" 策略。

  • False - 不同步会话。此选项可能对不支持 RETURNING 的后端有用,在这些后端中无法使用 "evaluate" 策略。在这种情况下,Session 中对象的狀態保持不变,并且不会自动与发出的 UPDATE 或 DELETE 语句相对应,即使存在通常与匹配的行相对应的此类对象。

使用 RETURNING 与 UPDATE/DELETE 和自定义 WHERE 条件

UpdateBase.returning() 方法与支持 WHERE 条件的 ORM 启用的 UPDATE 和 DELETE 完全兼容。可以为 RETURNING 指定完整的 ORM 对象和/或列。

>>> from sqlalchemy import update
>>> stmt = (
...     update(User)
...     .where(User.name == "squidward")
...     .values(fullname="Squidward Tentacles")
...     .returning(User)
... )
>>> result = session.scalars(stmt)
UPDATE user_account SET fullname=? WHERE user_account.name = ? RETURNING id, name, fullname, species [...] ('Squidward Tentacles', 'squidward')
>>> print(result.all()) [User(name='squidward', fullname='Squidward Tentacles')]

RETURNING 的支持也与 fetch 同步策略兼容,该策略也使用 RETURNING。ORM 将适当地组织 RETURNING 中的列,以便同步正常进行,并且返回的 Result 将包含请求的实体和 SQL 列,并按请求的顺序排列。

版本 2.0 中的新功能: UpdateBase.returning() 可用于 ORM 启用的 UPDATE 和 DELETE,同时仍然与 fetch 同步策略完全兼容。

针对联接表继承的自定义 WHERE 条件的 UPDATE/DELETE

ORM 通过主键进行批量 UPDATE 不同,具有 WHERE 条件的 UPDATE/DELETE 功能每次调用 Session.execute() 仅发出一个 UPDATE 或 DELETE 语句。这意味着,当针对多表映射(例如联接表继承映射中的子类)运行 update()delete() 语句时,该语句必须符合后端的当前功能,这些功能可能包括后端不支持引用多个表的 UPDATE 或 DELETE 语句,或者对该功能的支持有限。这意味着,对于联接继承子类等映射,ORM 版本的具有 WHERE 条件的 UPDATE/DELETE 功能的使用范围有限或根本无法使用,具体取决于具体情况。

为联接表子类发出多行 UPDATE 语句的最直接方法是仅引用子表。这意味着 Update() 结构应仅引用子表本地的属性,如下面的示例所示

>>> stmt = (
...     update(Manager)
...     .where(Manager.id == 1)
...     .values(manager_name="Sandy Cheeks, President")
... )
>>> session.execute(stmt)
UPDATE manager SET manager_name=? WHERE manager.id = ? [...] ('Sandy Cheeks, President', 1) <...>

通过以上形式,一种在任何 SQL 后端上都起作用的引用基表以定位行的基本方法是使用子查询

>>> stmt = (
...     update(Manager)
...     .where(
...         Manager.id
...         == select(Employee.id).where(Employee.name == "sandy").scalar_subquery()
...     )
...     .values(manager_name="Sandy Cheeks, President")
... )
>>> session.execute(stmt)
UPDATE manager SET manager_name=? WHERE manager.id = (SELECT employee.id FROM employee WHERE employee.name = ?) RETURNING id [...] ('Sandy Cheeks, President', 'sandy')
<...>

对于支持 UPDATE…FROM 的后端,可以将子查询表示为其他普通 WHERE 条件,但是两个表之间的条件必须以某种方式明确说明

>>> stmt = (
...     update(Manager)
...     .where(Manager.id == Employee.id, Employee.name == "sandy")
...     .values(manager_name="Sandy Cheeks, President")
... )
>>> session.execute(stmt)
UPDATE manager SET manager_name=? FROM employee WHERE manager.id = employee.id AND employee.name = ? [...] ('Sandy Cheeks, President', 'sandy')
<...>

对于 DELETE,预期基表和子表中的行会同时被 DELETEd。要 **不** 使用级联外键 DELETE 联接继承对象的许多行,请分别为每个表发出 DELETE

>>> from sqlalchemy import delete
>>> session.execute(delete(Manager).where(Manager.id == 1))
DELETE FROM manager WHERE manager.id = ? [...] (1,)
<...> >>> session.execute(delete(Employee).where(Employee.id == 1))
DELETE FROM employee WHERE employee.id = ? [...] (1,)
<...>

总的来说,对于更新和删除联接继承和其他多表映射的行,应 **优先** 使用正常的 工作单元 流程,除非存在使用自定义 WHERE 条件的性能原因。

旧版查询方法

支持 WHERE 的 ORM 启用的 UPDATE/DELETE 功能最初是现在已弃用的 Query 对象的一部分,位于 Query.update()Query.delete() 方法中。这些方法仍然可用,并提供与 ORM 使用自定义 WHERE 条件进行 UPDATE 和 DELETE 中描述的功能相似的子集。主要区别在于旧版方法不支持显式 RETURNING。