SQLAlchemy 2.0 文档
SQLAlchemy ORM
- ORM 快速入门
- ORM 映射类配置
- 关系配置
- ORM 查询指南
- 为 ORM 映射类编写 SELECT 语句
- 为继承映射编写 SELECT 语句
- 启用 ORM 的 INSERT、UPDATE 和 DELETE 语句¶
- 列加载选项
- 关系加载技术
- 用于查询的 ORM API 功能
- 旧版查询 API
- 使用 Session
- 事件和内部机制
- ORM 扩展
- ORM 示例
项目版本
启用 ORM 的 INSERT、UPDATE 和 DELETE 语句¶
Session.execute()
方法,除了处理启用 ORM 的 Select
对象外,还可以容纳启用 ORM 的 Insert
、Update
和 Delete
对象,通过各种方式,每种方式都用于一次性 INSERT、UPDATE 或 DELETE 许多数据库行。还有特定于方言的对启用 ORM 的 “upsert” 的支持,这些 “upsert” 是 INSERT 语句,它们自动利用 UPDATE 来处理已存在的行。
下表总结了本文档中讨论的调用形式
ORM 用例 |
使用的 DML 构造 |
数据通过 ... 传递 |
支持 RETURNING 吗? |
支持多表映射吗? |
---|---|---|---|---|
字典列表到 |
||||
字典列表到 |
否 |
|||
字典列表到 |
否 |
|||
字典列表到 |
否 |
|||
关键字到 |
ORM 批量 INSERT 语句¶
insert()
构造可以根据 ORM 类构建,并传递给 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 映射属性,这些属性与映射的 Column
或 mapped_column()
声明以及 composite 声明对齐。如果 ORM 映射属性名称和实际数据库列名称碰巧不同,则键应与 ORM 映射属性名称 而 不是 实际数据库列名称匹配。
在版本 2.0 中更改: 将 Insert
构造传递给 Session.execute()
方法现在会调用 “批量插入”,它使用与旧版 Session.bulk_insert_mappings()
方法相同的功能。与 1.x 系列相比,这是一个行为更改,在 1.x 系列中,Insert
将以核心为中心的方式解释,使用列名称作为值键;现在接受 ORM 属性键。核心样式功能可通过将执行选项 {"dml_strategy": "raw"}
传递给 Session.execution_options
参数的 Session.execute()
来实现。
通过 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
现在在 ORM 上下文中解释来自 Insert
、Update
甚至 Delete
构造的 RETURNING 子句,这意味着可以将列表达式和 ORM 映射实体的混合传递给 Insert.returning()
方法,然后将以 ORM 结果从 Select
等构造中传递的方式交付,包括映射实体将在结果中作为 ORM 映射对象交付。对 ORM 加载器选项(如 load_only()
和 selectinload()
)的有限支持也存在。
将 RETURNING 记录与输入数据顺序关联¶
当将批量 INSERT 与 RETURNING 一起使用时,重要的是要注意,大多数数据库后端不提供对 RETURNING 返回的记录顺序的正式保证,包括不保证它们的顺序将与输入记录的顺序相对应。对于需要确保 RETURNING 记录可以与输入数据关联的应用程序,可以指定附加参数 Insert.returning.sort_by_parameter_order
,根据后端,它可以使用特殊的 INSERT 形式,这些形式维护一个令牌,用于适当地重新排序返回的行,或者在某些情况下,例如在下面使用 SQLite 后端的示例中,操作将一次 INSERT 一行
>>> 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;这里的基本原理是,以便包含服务器端 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 行的主键值关联到用于 INSERT 到 “子” 表中的参数集中,这就是为什么上面说明的 SQLite 后端透明地降级为使用非批量语句。有关此功能的背景信息,请参阅 将 RETURNING 行与参数集关联。
带 SQL 表达式的 ORM 批量插入¶
ORM 批量插入功能支持添加一组固定的参数,这些参数可能包括要应用于每个目标行的 SQL 表达式。为了实现这一点,请结合使用 Insert.values()
方法,传递将应用于所有行的参数字典,以及通常的批量调用形式,方法是在调用 Session.execute()
时包含包含单个行值的参数字典列表。
例如,给定一个包含 “timestamp” 列的 ORM 映射
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]
如果我们想要 INSERT 一系列 LogRecord
元素,每个元素都有一个唯一的 message
字段,但是我们想要将 SQL 函数 now()
应用于所有行,我们可以在 Insert.values()
中传递 timestamp
,然后使用 “批量” 模式传递其他记录
>>> 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(...))]
带每行 SQL 表达式的 ORM 批量插入¶
Insert.values()
方法本身直接容纳参数字典列表。当以这种方式使用 Insert
构造时,而不向 Session.execute.params
参数传递任何参数字典列表时,不使用批量 ORM 插入模式,而是完全按照给定的方式渲染 INSERT 语句并精确调用一次。这种操作模式对于在逐行基础上传递 SQL 表达式的情况可能很有用,并且也用于将 “upsert” 语句与 ORM 一起使用,这将在本章后面的 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": "sandy@company.com",
... },
... {
... "user_id": select(User.id).where(User.name == "spongebob"),
... "email_address": "spongebob@company.com",
... },
... {
... "user_id": select(User.id).where(User.name == "patrick"),
... "email_address": "patrick@company.com",
... },
... ]
... )
... .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', 'sandy@company.com', 'spongebob', 'spongebob@company.com',
'patrick', 'patrick@company.com')
>>> print(address_result.all())
[Address(email_address='sandy@company.com'),
Address(email_address='spongebob@company.com'),
Address(email_address='patrick@company.com')]
由于上面未使用批量 ORM 插入模式,因此以下功能不存在
连接表继承 或其他多表映射不受支持,因为这将需要多个 INSERT 语句。
异构参数集 不受支持 - VALUES 集中的每个元素都必须具有相同的列。
核心级别的扩展优化(例如 insertmanyvalues 提供的批处理)不可用;语句将需要确保参数总数不超过后端数据库施加的限制。
由于上述原因,除非有明确的基本原理(即正在使用 “upsert” 或需要在每个参数集中嵌入每行 SQL 表达式),否则通常不建议将多个参数集与带有 ORM INSERT 语句的 Insert.values()
一起使用。
另请参阅
旧版 Session 批量 INSERT 方法¶
Session
包括用于执行 “批量” INSERT 和 UPDATE 语句的旧版方法。这些方法与 SQLAlchemy 2.0 版本的这些功能共享实现,这些功能在 ORM 批量 INSERT 语句 和 按主键批量 UPDATE ORM 中描述,但是缺少许多功能,即 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 功能,包括
SQLite - 使用
Insert
,文档位于 INSERT…ON CONFLICT (Upsert)PostgreSQL - 使用
Insert
,文档位于 INSERT…ON CONFLICT (Upsert)MySQL/MariaDB - 使用
Insert
,文档位于 INSERT…ON DUPLICATE KEY UPDATE (Upsert)
用户应查看以上部分,了解有关正确构造这些对象的背景信息;特别是,“upsert” 方法通常需要引用原始语句,因此语句通常分两个单独的步骤构造。
在 外部方言 中提到的第三方后端也可能具有类似的构造。
虽然 SQLAlchemy 尚未提供与后端无关的 upsert 构造,但上述 Insert
变体仍然是 ORM 兼容的,因为它们可以像 Insert
构造本身一样使用,如 使用每行 SQL 表达式进行 ORM 批量插入 中所述,即通过将要 INSERT 的所需行嵌入到 Insert.values()
方法中。在下面的示例中,SQLite insert()
函数用于生成一个包含 “ON CONFLICT DO UPDATE” 支持的 Insert
构造。然后将该语句传递给 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')
<...>
将 RETURNING 与 upsert 语句一起使用¶
从 SQLAlchemy ORM 的角度来看,upsert 语句看起来像常规的 Insert
构造,其中包括 Insert.returning()
与 upsert 语句的工作方式与 使用每行 SQL 表达式进行 ORM 批量插入 中演示的方式相同,因此可以传递任何列表达式或相关的 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 返回语句插入或 upsert 的每一行的 ORM 对象。该示例还添加了 填充现有对象 执行选项的使用。此选项指示对于 User
对象,如果它们已存在于 Session
中,则对于已存在的行,应使用新行的数据刷新这些对象。对于纯 Insert
语句,此选项并不重要,因为生成的每一行都是全新的主键标识。但是,当 Insert
也包含 “upsert” 选项时,它也可能从已存在的行中产生结果,因此可能已在 Session
对象的 标识映射 中表示主键标识。
另请参阅
ORM 批量主键更新¶
可以像使用 ORM 批量 INSERT 语句 中描述的 Insert
语句一样,将 Update
构造与 Session.execute()
一起使用,传递许多参数字典的列表,每个字典代表与单个主键值对应的单个行。这种用法不应与更常见的 ORM Update
语句用法混淆,后者使用显式的 WHERE 子句,这在 使用自定义 WHERE 条件进行 ORM UPDATE 和 DELETE 操作 中有详细说明。
对于 UPDATE 的 “批量” 版本,update()
构造是根据 ORM 类创建的,并传递给 Session.execute()
方法;生成的 Update
对象应没有 values 并且通常没有 WHERE 条件,也就是说,不使用 Update.values()
方法,并且通常不使用 Update.where()
,但在需要添加其他过滤条件的不常见情况下可以使用。
将 Update
构造以及参数字典列表(每个字典都包含完整的主键值)传递给 Session.execute()
将调用语句的 批量主键更新模式,生成适当的 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 批量主键更新 结合使用。但是,此条件始终是已存在的 WHERE 条件的补充,后者包括主键值。
当使用 “批量主键更新” 功能时,RETURNING 功能不可用;多个参数字典列表必然使用 DBAPI executemany,其通常形式通常不支持结果行。
在版本 2.0 中更改: 将 Update
构造与参数字典列表一起传递给 Session.execute()
方法现在会调用 “批量更新”,这使用了与旧版 Session.bulk_update_mappings()
方法相同的功能。与 1.x 系列相比,这是一个行为更改,在 1.x 系列中,Update
仅在显式 WHERE 条件和内联 VALUES 的情况下受支持。
为具有多个参数集的 UPDATE 语句禁用 ORM 批量主键更新¶
当满足以下条件时,将自动使用 ORM 批量主键更新功能,该功能为每个记录运行一个 UPDATE 语句,其中包含每个主键值的 WHERE 条件:
给定的 UPDATE 语句是针对 ORM 实体的
使用
Session
执行语句,而不是 CoreConnection
传递的参数是字典列表。
为了在不使用 “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')]
<...>
连接表继承的批量主键更新¶
当使用具有连接表继承的映射时,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 条件(即使用 Update.where()
和 Delete.where()
方法)构造 Update
和 Delete
构造时,可以通过将它们传递给 Session.execute()
在 ORM 上下文中调用它们,而无需使用 Session.execute.params
参数。对于 Update
,要更新的值应使用 Update.values()
传递。
这种使用模式与先前在 ORM 批量主键更新 中描述的功能不同,因为 ORM 按原样使用给定的 WHERE 子句,而不是将 WHERE 子句固定为主键。这意味着单个 UPDATE 或 DELETE 语句可以一次影响多行。
例如,以下是一个 UPDATE 示例,它影响多行的 “fullname” 字段
>>> 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 级联与 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 对象将移动到 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 中更改: 当使用 ORM 启用的 UPDATE 和 DELETE 以及 WHERE 条件时,可以将显式的
UpdateBase.returning()
与'fetch'
同步策略结合使用。实际语句将包含'fetch'
策略所需列与请求列之间的并集。'evaluate'
- 这表示在 Python 中评估 UPDATE 或 DELETE 语句中给出的 WHERE 条件,以查找Session
中的匹配对象。此方法不会向操作添加任何 SQL 往返,并且在缺少 RETURNING 支持的情况下,可能会更有效。对于具有复杂条件的 UPDATE 或 DELETE 语句,'evaluate'
策略可能无法在 Python 中评估表达式,并将引发错误。如果发生这种情况,请改为对操作使用'fetch'
策略。提示
如果 SQL 表达式使用
Operators.op()
或custom_op
功能使用自定义运算符,则可以使用Operators.op.params.python_impl
参数来指示"evaluate"
同步策略将使用的 Python 函数。2.0 版本中的新增功能。
警告
如果 UPDATE 操作要在具有许多已过期对象的
Session
上运行,则应避免使用"evaluate"
策略,因为它必然需要刷新对象才能根据给定的 WHERE 条件测试它们,这将为每个对象发出一个 SELECT。在这种情况下,特别是如果后端支持 RETURNING,则应首选"fetch"
策略。False
- 不同步会话。对于不支持 RETURNING 且无法使用"evaluate"
策略的后端,此选项可能很有用。在这种情况下,Session
中对象的状态保持不变,并且不会自动对应于发出的 UPDATE 或 DELETE 语句(如果通常对应于匹配行的此类对象存在)。
将 RETURNING 与 UPDATE/DELETE 和自定义 WHERE 条件一起使用¶
UpdateBase.returning()
方法与 ORM 启用的 UPDATE 和 DELETE 以及 WHERE 条件完全兼容。可以指示 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 批量主键更新 不同,使用 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 条件,否则对于更新和删除连接继承和其他多表映射的行,应首选正常的 工作单元 流程。
旧版查询方法¶
ORM 启用的带 WHERE 子句的 UPDATE/DELETE 功能最初是现在已弃用的 Query
对象的一部分,位于 Query.update()
和 Query.delete()
方法中。这些方法仍然可用,并提供与 ORM UPDATE 和 DELETE 与自定义 WHERE 条件 中描述的相同功能的子集。主要区别在于旧方法不提供显式的 RETURNING 支持。
flambé! 龙和 The Alchemist 图像设计由 Rotem Yaari 创建并慷慨捐赠。
使用 Sphinx 7.2.6 创建。文档最后生成时间:Tue 11 Mar 2025 02:40:17 PM EDT