SqlAlchemy 2.0 中文文档(十七)(4)https://developer.aliyun.com/article/1562955
使用自定义 WHERE 条件的 ORM 更新和删除
当使用自定义 WHERE 条件构建 Update
和 Delete
构造时(即使用 Update.where()
和 Delete.where()
方法),可以通过将它们传递给 Session.execute()
在 ORM 上下文中调用它们,而不使用 Session.execute.params
参数。对于 Update
,应该使用 Update.values()
传递要更新的值。
这种使用方式与之前描述的 ORM 按主键批量更新中的功能不同,ORM 使用给定的 WHERE 子句如所示,而不是将 WHERE 子句修复为按主键。这意味着单个 UPDATE 或 DELETE 语句可以一次性影响许多行。
举个例子,下面发出了一个更新,影响了多行的“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') <...>
警告
请阅读以下部分 Important Notes and Caveats for ORM-Enabled Update and Delete,了解关于 ORM 启用的 UPDATE 和 DELETE 功能与 ORM 工作单元 功能的功能差异的重要说明,例如使用 Session.delete()
方法删除单个对象。
关于 ORM 启用的更新和删除的重要说明和注意事项
ORM 启用的 UPDATE 和 DELETE 功能绕过 ORM 工作单元 自动化,以便能够发出一条匹配多行的单个 UPDATE 或 DELETE 语句,而不会增加复杂性。
- 操作不提供 Python 中的关系级联 - 假定对于需要它的任何外键引用已配置了 ON UPDATE CASCADE 和/或 ON DELETE CASCADE,否则如果正在执行外键引用,则数据库可能会发出完整性违规。请参阅 Using foreign key ON DELETE cascade with ORM relationships 中的说明,了解一些示例。
- 在 UPDATE 或 DELETE 之后,受到与相关表上的 ON UPDATE CASCADE 或 ON DELETE CASCADE 相关的影响的
Session
中的依赖对象,特别是引用现在已被删除的行的对象,可能仍然引用这些对象。一旦Session
过期,通常发生在Session.commit()
或可以通过使用Session.expire_all()
强制进行。 - 启用 ORM 的 UPDATE 和 DELETE 操作不会自动处理连接表继承。请参阅 UPDATE/DELETE with Custom WHERE Criteria for Joined Table Inheritance 部分,了解如何处理连接继承映射。
- 为了将多态标识限制为单表继承映射的特定子类,自动包含了 WHERE 条件。这仅适用于没有自己的表的子类映射。
with_loader_criteria()
选项 受支持 ,可用于 ORM 更新和删除操作;此处的条件将添加到正在发出的 UPDATE 或 DELETE 语句的条件中,并在“同步”过程中考虑到。- 为了使用事件处理程序拦截启用 ORM 的 UPDATE 和 DELETE 操作,请使用
SessionEvents.do_orm_execute()
事件。### 选择同步策略
当结合使用update()
或delete()
与启用 ORM 的执行时,使用Session.execute()
,还会出现额外的 ORM 特定功能,该功能将同步语句更改的状态与当前存在于Session
的身份映射中的对象的状态。我们所说的“同步”是指,更新的属性将使用新值刷新,或者至少会过期,以便它们在下一次访问时重新填充其新值,并且删除的对象将移动到已删除状态。
这种同步是可控的,作为“同步策略”,传递为字符串 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.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()
方法与启用了 ORM 的带有 WHERE 条件的 UPDATE 和 DELETE 完全兼容。可以指定完整的 ORM 对象和/或列用于 RETURNING:
>>> 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 语句,或者对此仅有限支持。这意味着对于诸如联接继承子类之类的映射,带有 WHERE 条件的 ORM 版本 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,预期基表和子表中的行将同时被 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,) <...>
总的来说,应该优先选择常规的 unit of work 流程来更新和删除联合继承和其他多表映射的行,除非有使用自定义 WHERE 条件的性能原因。
旧版查询方法
启用 ORM 的 UPDATE/DELETE 与 WHERE 功能最初是作为现在已过时的Query
对象的一部分,出现在Query.update()
和 Query.delete()
方法中。这些方法仍然可用,并且提供与 ORM UPDATE 和 DELETE 与自定义 WHERE 条件中描述的相同功能的子集。主要区别在于旧版方法不提供显式的 RETURNING 支持。
另请参阅
Query.update()
Query.delete()
ORM-启用的更新和删除的重要注意事项和警告
启用 ORM 的 UPDATE 和 DELETE 功能绕过 ORM 的 unit of work 自动化,而是支持发出一条单独的 UPDATE 或 DELETE 语句,一次匹配多行,而不复杂。
- 这些操作不提供 Python 中关系的级联 - 假设对于需要的任何外键引用配置了 ON UPDATE CASCADE 和/或 ON DELETE CASCADE,否则如果正在强制执行外键引用,则数据库可能会发出完整性违规。请参阅使用 ORM 关系的外键 ON DELETE 级联中的说明以获取一些示例。
- 在 UPDATE 或 DELETE 之后,
Session
中的依赖对象,受到相关表上的 ON UPDATE CASCADE 或 ON DELETE CASCADE 的影响,特别是那些引用现已被删除的行的对象,可能仍然引用这些对象。一旦Session
过期,这个问题就会得到解决,通常发生在Session.commit()
时,或者可以通过使用Session.expire_all()
来强制实现。 - 启用 ORM 的 UPDATE 和 DELETE 操作不会自动处理连接表继承。请参阅 UPDATE/DELETE with Custom WHERE Criteria for Joined Table Inheritance 部分,了解如何处理连接继承映射。
- 为了限制多态标识仅适用于单表继承映射中的特定子类,WHERE 条件会自动包含。这仅适用于没有自己的表的子类映射器。
with_loader_criteria()
选项支持ORM 更新和删除操作;这里的条件将被添加到正在发出的 UPDATE 或 DELETE 语句的条件中,并在“同步”过程中考虑到这些条件。- 要拦截 ORM 启用的 UPDATE 和 DELETE 操作以使用事件处理程序,请使用
SessionEvents.do_orm_execute()
事件。
选择同步策略
当使用update()
或delete()
与 ORM 启用的执行结合使用时,还存在额外的 ORM 特定功能,将会同步语句所更改的状态与当前存在于Session
的标识映射中的对象状态。通过“同步”,我们指的是 UPDATE 的属性将使用新值刷新,或者至少过期,以便在下次访问时重新填充为新值,而 DELETE 的对象将移至删除状态。
此同步可通过“同步策略”来控制,该策略作为字符串 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.python_impl
参数指示一个 Python 函数,该函数将由"evaluate"
同步策略使用。
2.0 版本中新增。
警告
如果要在具有许多已过期对象的Session
上运行 UPDATE 操作,则应避免使用"evaluate"
策略,因为它必然需要刷新对象以便根据给定的 WHERE 条件测试它们,这将为每个对象发出一个 SELECT。在这种情况下,特别是如果后端支持 RETURNING,则应首选"fetch"
策略。False
- 不同步会话。该选项对于不支持 RETURNING 的后端可能很有用,其中无法使用"evaluate"
策略。在这种情况下,Session
中的对象状态保持不变,并且不会自动与发出的 UPDATE 或 DELETE 语句对应,如果存在通常会与匹配行对应的对象。
使用 RETURNING 进行 UPDATE/DELETE 和自定义 WHERE 条件
UpdateBase.returning()
方法与启用 ORM 的带有 WHERE 条件的 UPDATE 和 DELETE 完全兼容。可以指定完整的 ORM 对象和/或列来进行 RETURNING:
>>> 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
同步策略的完全兼容性。
用于联接表继承的 UPDATE/DELETE 自定义 WHERE 条件
与 ORM Bulk UPDATE by Primary Key 不同,具有 WHERE 条件的 UPDATE/DELETE 功能在每次调用Session.execute()
时仅发出单个 UPDATE 或 DELETE 语句。这意味着当针对多表映射(如联接表继承映射中的子类)运行update()
或delete()
语句时,语句必须符合后端的当前能力,这可能包括后端不支持引用多个表的 UPDATE 或 DELETE 语句,或者仅对此提供有限的支持。这意味着对于诸如联接继承子类之类的映射,ORM 版本的具有 WHERE 条件的 UPDATE/DELETE 功能仅能在有限程度上或根据具体情况根本无法使用。
最直接的方法是为联接表子类发出多行更新语句,只需引用子表即可。这意味着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 操作,预期基表和子表中的行将同时被删除。要删除多行联接继承对象而不使用级联外键,需分别为每个表发出 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 条件有性能上的理由。
旧式查询方法
原始Query
对象的 ORM 启用的 UPDATE/DELETE with WHERE 功能最初是Query.update()
和Query.delete()
方法的一部分。这些方法仍然可用,并且提供与 ORM UPDATE and DELETE with Custom WHERE Criteria 描述的相同功能的子集。主要区别在于旧式方法不支持显式的 RETURNING 支持。
另请参见
Query.update()
Query.delete()
时仅发出单个 UPDATE 或 DELETE 语句。这意味着当针对多表映射(如联接表继承映射中的子类)运行update()
或delete()
语句时,语句必须符合后端的当前能力,这可能包括后端不支持引用多个表的 UPDATE 或 DELETE 语句,或者仅对此提供有限的支持。这意味着对于诸如联接继承子类之类的映射,ORM 版本的具有 WHERE 条件的 UPDATE/DELETE 功能仅能在有限程度上或根据具体情况根本无法使用。
最直接的方法是为联接表子类发出多行更新语句,只需引用子表即可。这意味着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 操作,预期基表和子表中的行将同时被删除。要删除多行联接继承对象而不使用级联外键,需分别为每个表发出 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 条件有性能上的理由。
旧式查询方法
原始Query
对象的 ORM 启用的 UPDATE/DELETE with WHERE 功能最初是Query.update()
和Query.delete()
方法的一部分。这些方法仍然可用,并且提供与 ORM UPDATE and DELETE with Custom WHERE Criteria 描述的相同功能的子集。主要区别在于旧式方法不支持显式的 RETURNING 支持。
另请参见
Query.update()
Query.delete()