SqlAlchemy-2-0-中文文档-八-

SqlAlchemy 2.0 中文文档(八)

原文:docs.sqlalchemy.org/en/20/contents.html

编写继承映射的 SELECT 语句

原文:docs.sqlalchemy.org/en/20/orm/queryguide/inheritance.html

关于本文档

本节利用了使用 ORM 继承 功能配置的 ORM 映射,描述在 映射类继承层次结构 中。重点将放在 连接表继承,因为这是最复杂的 ORM 查询情况。

查看此页面的 ORM 设置。

从基类 vs. 特定子类进行 SELECT

构建在连接继承层次结构中的类上的 SELECT 语句将针对将类映射到的表以及任何现有的超级表进行查询,并使用 JOIN 将它们链接在一起。然后,查询将返回请求类型的对象以及请求类型的任何子类型,使用每行中的 鉴别器 值来确定正确的类型。下面的查询是针对 EmployeeManager 子类建立的,然后返回的结果将只包含 Manager 类型的对象:

>>> from sqlalchemy import select
>>> stmt = select(Manager).order_by(Manager.id)
>>> managers = session.scalars(stmt).all()
BEGIN  (implicit)
SELECT  manager.id,  employee.id  AS  id_1,  employee.name,  employee.type,  employee.company_id,  manager.manager_name
FROM  employee  JOIN  manager  ON  employee.id  =  manager.id  ORDER  BY  manager.id
[...]  ()
>>> print(managers)
[Manager('Mr. Krabs')]

当 SELECT 语句针对继承层次结构中的基类时,默认行为是仅将该类的表包括在渲染的 SQL 中,并且不使用 JOIN。与所有情况一样,鉴别器 列用于区分不同的请求子类型,然后返回任何可能的子类型的对象。返回的对象将具有对应于基表的属性填充,对应于子表的属性将以未加载状态开始,在访问时自动加载。子属性的加载可配置为以多种方式更“急切”,在本节后面讨论。

下面的示例创建了针对 Employee 超类的查询。这表示结果集中可能包含任何类型的对象,包括 ManagerEngineerEmployee

>>> from sqlalchemy import select
>>> stmt = select(Employee).order_by(Employee.id)
>>> objects = session.scalars(stmt).all()
BEGIN  (implicit)
SELECT  employee.id,  employee.name,  employee.type,  employee.company_id
FROM  employee  ORDER  BY  employee.id
[...]  ()
>>> print(objects)
[Manager('Mr. Krabs'), Engineer('SpongeBob'), Engineer('Squidward')]

在上面的例子中,未包括 ManagerEngineer 的附加表在 SELECT 中,这意味着返回的对象尚未包含来自那些表中表示的数据,在本例中是 Manager 类的 .manager_name 属性以及 Engineer 类的 .engineer_info 属性。这些属性起始状态为 过期,当首次使用 延迟加载 访问时,它们将自动填充:

>>> mr_krabs = objects[0]
>>> print(mr_krabs.manager_name)
SELECT  manager.manager_name  AS  manager_manager_name
FROM  manager
WHERE  ?  =  manager.id
[...]  (1,)
Eugene H. Krabs

如果加载了大量对象,则此惰性加载行为并不理想,因为消费应用程序将需要访问特定于子类的属性,这将是 N 加一问题的一个例子,每行都会发出额外的 SQL。这些额外的 SQL 可能会影响性能,并且也与使用 asyncio 等方法不兼容。此外,在我们对Employee对象的查询中,由于查询仅针对基本表,我们无法添加涉及特定于子类的属性(如ManagerEngineer)的 SQL 条件。接下来的两个部分详细介绍了两种不同方式解决这两个问题的构造,selectin_polymorphic()加载器选项和with_polymorphic()实体构造。

使用 selectin_polymorphic()

要解决在访问子类属性时的性能问题,可以使用selectin_polymorphic()加载策略,以便一次性急切地加载这些附加属性。此加载选项的工作方式类似于selectinload()关系加载策略,对于在层次结构中加载的对象,会对每个子表发出额外的 SELECT 语句,使用IN来根据主键查询附加行。

selectinload()接受作为其参数的基本实体,该实体正在被查询,然后是该实体的子类序列,对于这些子类,应为传入的行加载其特定属性:

>>> from sqlalchemy.orm import selectin_polymorphic
>>> loader_opt = selectin_polymorphic(Employee, [Manager, Engineer])

然后,使用selectin_polymorphic()构造作为加载器选项,将其传递给Select.options()方法的Select。该示例说明了如何使用selectin_polymorphic()来急切加载ManagerEngineer子类的本地列:

>>> from sqlalchemy.orm import selectin_polymorphic
>>> loader_opt = selectin_polymorphic(Employee, [Manager, Engineer])
>>> stmt = select(Employee).order_by(Employee.id).options(loader_opt)
>>> objects = session.scalars(stmt).all()
BEGIN  (implicit)
SELECT  employee.id,  employee.name,  employee.type,  employee.company_id
FROM  employee  ORDER  BY  employee.id
[...]  ()
SELECT  manager.id  AS  manager_id,  employee.id  AS  employee_id,
employee.type  AS  employee_type,  manager.manager_name  AS  manager_manager_name
FROM  employee  JOIN  manager  ON  employee.id  =  manager.id
WHERE  employee.id  IN  (?)  ORDER  BY  employee.id
[...]  (1,)
SELECT  engineer.id  AS  engineer_id,  employee.id  AS  employee_id,
employee.type  AS  employee_type,  engineer.engineer_info  AS  engineer_engineer_info
FROM  employee  JOIN  engineer  ON  employee.id  =  engineer.id
WHERE  employee.id  IN  (?,  ?)  ORDER  BY  employee.id
[...]  (2,  3)
>>> print(objects)
[Manager('Mr. Krabs'), Engineer('SpongeBob'), Engineer('Squidward')]

上述示例说明了发出两个额外的 SELECT 语句,以便急切地获取附加属性,例如Engineer.engineer_infoManager.manager_name。现在,我们可以在加载的对象上访问这些子属性,而无需发出任何额外的 SQL 语句:

>>> print(objects[0].manager_name)
Eugene H. Krabs

提示

selectin_polymorphic() 加载选项尚未针对基础 employee 表的情况进行优化,因此在第二个和第三个“急加载”查询中不需要包含 employee 表;因此在上面的示例中,我们看到从 employeemanagerengineer 的 JOIN,即使 employee 的列已经加载。这与 selectinload() 关系策略形成对比,在这方面更为复杂,并且在不需要时可以排除 JOIN。

对现有急加载应用 selectin_polymorphic()

除了将 selectin_polymorphic() 指定为由语句加载的顶级实体的选项外,我们还可以指示在现有加载目标上应用 selectin_polymorphic()。由于我们的设置映射包含一个父 Company 实体,其具有引用 Employee 实体的 Company.employees relationship(),我们可以说明针对 Company 实体的 SELECT,它急加载所有 Employee 对象以及其子类型的所有属性,如下所示,通过将 Load.selectin_polymorphic() 作为链式加载器选项应用;在此形式中,第一个参数是从前一个加载器选项隐式获取的(在本例中为 selectinload()),因此我们仅指示要加载的附加目标子类:

>>> from sqlalchemy.orm import selectinload
>>> stmt = select(Company).options(
...     selectinload(Company.employees).selectin_polymorphic([Manager, Engineer])
... )
>>> for company in session.scalars(stmt):
...     print(f"company: {company.name}")
...     print(f"employees: {company.employees}")
BEGIN  (implicit)
SELECT  company.id,  company.name
FROM  company
[...]  ()
SELECT  employee.company_id  AS  employee_company_id,  employee.id  AS  employee_id,
employee.name  AS  employee_name,  employee.type  AS  employee_type
FROM  employee
WHERE  employee.company_id  IN  (?)
[...]  (1,)
SELECT  manager.id  AS  manager_id,  employee.id  AS  employee_id,
employee.type  AS  employee_type,
manager.manager_name  AS  manager_manager_name
FROM  employee  JOIN  manager  ON  employee.id  =  manager.id
WHERE  employee.id  IN  (?)  ORDER  BY  employee.id
[...]  (1,)
SELECT  engineer.id  AS  engineer_id,  employee.id  AS  employee_id,
employee.type  AS  employee_type,
engineer.engineer_info  AS  engineer_engineer_info
FROM  employee  JOIN  engineer  ON  employee.id  =  engineer.id
WHERE  employee.id  IN  (?,  ?)  ORDER  BY  employee.id
[...]  (2,  3)
company: Krusty Krab
employees: [Manager('Mr. Krabs'), Engineer('SpongeBob'), Engineer('Squidward')]

另请参见

多态子类型的急加载 - 演示了使用 with_polymorphic() 而不是上述等效示例的示例 ### 将加载选项应用于由 selectin_polymorphic 加载的子类

selectin_polymorphic() 发出的 SELECT 语句本身是 ORM 语句,因此我们还可以添加其他加载选项(例如文档中记录的那些位于 关系加载技术) ,这些选项引用特定的子类。这些选项应该作为同级应用于 selectin_polymorphic() 选项,即在 select.options() 内用逗号分隔。

例如,如果我们考虑Manager映射器与名为Paperwork的实体之间有一对多关系,我们可以结合使用selectin_polymorphic()selectinload()来急加载所有Manager对象上的这个集合,其中Manager对象的子属性也会被急加载:

>>> from sqlalchemy.orm import selectin_polymorphic
>>> stmt = (
...     select(Employee)
...     .order_by(Employee.id)
...     .options(
...         selectin_polymorphic(Employee, [Manager, Engineer]),
...         selectinload(Manager.paperwork),
...     )
... )
>>> objects = session.scalars(stmt).all()
SELECT  employee.id,  employee.name,  employee.type,  employee.company_id
FROM  employee  ORDER  BY  employee.id
[...]  ()
SELECT  manager.id  AS  manager_id,  employee.id  AS  employee_id,  employee.type  AS  employee_type,  manager.manager_name  AS  manager_manager_name
FROM  employee  JOIN  manager  ON  employee.id  =  manager.id
WHERE  employee.id  IN  (?)  ORDER  BY  employee.id
[...]  (1,)
SELECT  paperwork.manager_id  AS  paperwork_manager_id,  paperwork.id  AS  paperwork_id,  paperwork.document_name  AS  paperwork_document_name
FROM  paperwork
WHERE  paperwork.manager_id  IN  (?)
[...]  (1,)
SELECT  engineer.id  AS  engineer_id,  employee.id  AS  employee_id,  employee.type  AS  employee_type,  engineer.engineer_info  AS  engineer_engineer_info
FROM  employee  JOIN  engineer  ON  employee.id  =  engineer.id
WHERE  employee.id  IN  (?,  ?)  ORDER  BY  employee.id
[...]  (2,  3)
>>> print(objects[0])
Manager('Mr. Krabs')
>>> print(objects[0].paperwork)
[Paperwork('Secret Recipes'), Paperwork('Krabby Patty Orders')]

当选择selectin_polymorphic本身作为子选项时应用加载器选项

版本 2.0.21 中新增。

前面的章节介绍了selectin_polymorphic()selectinload()作为兄弟选项使用的示例,两者都在单个调用select.options()内使用。 如果目标实体已经从父关系加载,例如在示例将selectin_polymorphic()应用于现有的急加载中,我们可以使用Load.options()方法应用此“兄弟”模式,该方法将子选项应用于父级,如在使用Load.options()指定子选项中所示。 下面我们结合这两个示例,加载Company.employees,同时加载ManagerEngineer类的属性,以及急加载Manager.paperwork属性:

>>> from sqlalchemy.orm import selectinload
>>> stmt = select(Company).options(
...     selectinload(Company.employees).options(
...         selectin_polymorphic(Employee, [Manager, Engineer]),
...         selectinload(Manager.paperwork),
...     )
... )
>>> for company in session.scalars(stmt):
...     print(f"company: {company.name}")
...     for employee in company.employees:
...         if isinstance(employee, Manager):
...             print(f"manager: {employee.name} paperwork: {employee.paperwork}")
BEGIN  (implicit)
SELECT  company.id,  company.name
FROM  company
[...]  ()
SELECT  employee.company_id  AS  employee_company_id,  employee.id  AS  employee_id,  employee.name  AS  employee_name,  employee.type  AS  employee_type
FROM  employee
WHERE  employee.company_id  IN  (?)
[...]  (1,)
SELECT  manager.id  AS  manager_id,  employee.id  AS  employee_id,  employee.type  AS  employee_type,  manager.manager_name  AS  manager_manager_name
FROM  employee  JOIN  manager  ON  employee.id  =  manager.id
WHERE  employee.id  IN  (?)  ORDER  BY  employee.id
[...]  (1,)
SELECT  paperwork.manager_id  AS  paperwork_manager_id,  paperwork.id  AS  paperwork_id,  paperwork.document_name  AS  paperwork_document_name
FROM  paperwork
WHERE  paperwork.manager_id  IN  (?)
[...]  (1,)
SELECT  engineer.id  AS  engineer_id,  employee.id  AS  employee_id,  employee.type  AS  employee_type,  engineer.engineer_info  AS  engineer_engineer_info
FROM  employee  JOIN  engineer  ON  employee.id  =  engineer.id
WHERE  employee.id  IN  (?,  ?)  ORDER  BY  employee.id
[...]  (2,  3)
company: Krusty Krab
manager: Mr. Krabs paperwork: [Paperwork('Secret Recipes'), Paperwork('Krabby Patty Orders')]

配置 mappers 上的 selectin_polymorphic()

可以在特定的 mappers 上配置selectin_polymorphic()的行为,以便默认生效,通过使用Mapper.polymorphic_load参数,在每个子类基础上使用值"selectin"。 下面的示例演示了在 EngineerManager 子类中使用此参数的情况:

class Employee(Base):
    __tablename__ = "employee"
    id = mapped_column(Integer, primary_key=True)
    name = mapped_column(String(50))
    type = mapped_column(String(50))

    __mapper_args__ = {"polymorphic_identity": "employee", "polymorphic_on": type}

class Engineer(Employee):
    __tablename__ = "engineer"
    id = mapped_column(Integer, ForeignKey("employee.id"), primary_key=True)
    engineer_info = mapped_column(String(30))

    __mapper_args__ = {
        "polymorphic_load": "selectin",
        "polymorphic_identity": "engineer",
    }

class Manager(Employee):
    __tablename__ = "manager"
    id = mapped_column(Integer, ForeignKey("employee.id"), primary_key=True)
    manager_name = mapped_column(String(30))

    __mapper_args__ = {
        "polymorphic_load": "selectin",
        "polymorphic_identity": "manager",
    }

使用上述映射,针对Employee类的 SELECT 语句在发出语句时会自动假定使用selectin_polymorphic(Employee, [Engineer, Manager])作为加载器选项。 ## 使用with_polymorphic()

与仅影响对象加载的 selectin_polymorphic() 相比,with_polymorphic() 构造影响了多态结构的 SQL 查询如何呈现,通常是作为一系列左外连接到每个包含的子表的 LEFT OUTER JOIN。这个连接结构被称为 多态可选择项。通过同时提供几个子表的视图,with_polymorphic() 提供了一种一次跨越多个继承类写 SELECT 语句的方法,并能够根据各个子表添加过滤条件的能力。

with_polymorphic() 本质上是 aliased() 构造的一种特殊形式。它接受的参数形式与 selectin_polymorphic() 类似,即被查询的基本实体,后跟一系列该实体的子类,其特定属性应该被加载到传入的行中:

>>> from sqlalchemy.orm import with_polymorphic
>>> employee_poly = with_polymorphic(Employee, [Engineer, Manager])

为了指示所有子类都应该成为实体的一部分,with_polymorphic() 还将接受字符串 "*",该字符串可以替代类序列以指示所有类(请注意,这目前还不被 selectin_polymorphic() 支持):

>>> employee_poly = with_polymorphic(Employee, "*")

下面的示例演示了与前一节中演示的相同操作,一次加载所有 ManagerEngineer 的所有列:

>>> stmt = select(employee_poly).order_by(employee_poly.id)
>>> objects = session.scalars(stmt).all()
BEGIN  (implicit)
SELECT  employee.id,  employee.name,  employee.type,  employee.company_id,
manager.id  AS  id_1,  manager.manager_name,  engineer.id  AS  id_2,  engineer.engineer_info
FROM  employee
LEFT  OUTER  JOIN  manager  ON  employee.id  =  manager.id
LEFT  OUTER  JOIN  engineer  ON  employee.id  =  engineer.id  ORDER  BY  employee.id
[...]  ()
>>> print(objects)
[Manager('Mr. Krabs'), Engineer('SpongeBob'), Engineer('Squidward')]

selectin_polymorphic() 一样,子类的属性已经被加载:

>>> print(objects[0].manager_name)
Eugene H. Krabs

由于 with_polymorphic() 默认生成的可选择项使用 LEFT OUTER JOIN,从数据库的角度来看,查询并不像 selectin_polymorphic() 所采用的方法那样优化,简单的 SELECT 语句仅使用基于每个表的 JOIN 发出。

使用 with_polymorphic() 过滤子类属性

with_polymorphic() 构造使包含的子类映射器上的属性可用,通过包含允许对子类的引用的命名空间。在前一节中创建的 employee_poly 构造包括名为 .Engineer.Manager 的属性,这些属性为 EngineerManager 提供了关于多态 SELECT 的命名空间。在下面的示例中,我们可以使用 or_() 构造同时针对两个类创建条件:

>>> from sqlalchemy import or_
>>> employee_poly = with_polymorphic(Employee, [Engineer, Manager])
>>> stmt = (
...     select(employee_poly)
...     .where(
...         or_(
...             employee_poly.Manager.manager_name == "Eugene H. Krabs",
...             employee_poly.Engineer.engineer_info
...             == "Senior Customer Engagement Engineer",
...         )
...     )
...     .order_by(employee_poly.id)
... )
>>> objects = session.scalars(stmt).all()
SELECT  employee.id,  employee.name,  employee.type,  employee.company_id,  manager.id  AS  id_1,
manager.manager_name,  engineer.id  AS  id_2,  engineer.engineer_info
FROM  employee
LEFT  OUTER  JOIN  manager  ON  employee.id  =  manager.id
LEFT  OUTER  JOIN  engineer  ON  employee.id  =  engineer.id
WHERE  manager.manager_name  =  ?  OR  engineer.engineer_info  =  ?
ORDER  BY  employee.id
[...]  ('Eugene H. Krabs',  'Senior Customer Engagement Engineer')
>>> print(objects)
[Manager('Mr. Krabs'), Engineer('Squidward')]
```  ### 使用别名化与 with_polymorphic

`with_polymorphic()` 构造,作为 `aliased()` 的特例,也提供了 `aliased()` 的基本功能,即对多态可选择本身的“别名化”。具体来说,这意味着两个或更多个引用相同类层次结构的 `with_polymorphic()` 实体可以同时在单个语句中使用。

要在连接继承映射中使用此功能,通常我们希望传递两个参数,`with_polymorphic.aliased` 以及 `with_polymorphic.flat`。`with_polymorphic.aliased` 参数表示多态可选择应该由此构造唯一的别名引用。`with_polymorphic.flat` 参数是特定于默认的 LEFT OUTER JOIN 多态可选择,并指示语句中应使用更优化的别名化形式。

为了说明这个特性,下面的示例发出了一个选择两个单独的多态实体,`Employee` 与 `Engineer` 连接,以及 `Employee` 与 `Manager` 连接的 SELECT。由于这两个多态实体都将在其多态可选择中包含基本的 `employee` 表,必须应用别名以区分这个表在其两个不同的上下文中。这两个多态实体被视为两个独立的表,因此通常需要以某种方式相互连接,如下所示,在这里实体在 `company_id` 列上与彼此连接,并附加一些额外的限制条件针对 `Employee` / `Manager` 实体:

```py
>>> manager_employee = with_polymorphic(Employee, [Manager], aliased=True, flat=True)
>>> engineer_employee = with_polymorphic(Employee, [Engineer], aliased=True, flat=True)
>>> stmt = (
...     select(manager_employee, engineer_employee)
...     .join(
...         engineer_employee,
...         engineer_employee.company_id == manager_employee.company_id,
...     )
...     .where(
...         or_(
...             manager_employee.name == "Mr. Krabs",
...             manager_employee.Manager.manager_name == "Eugene H. Krabs",
...         )
...     )
...     .order_by(engineer_employee.name, manager_employee.name)
... )
>>> for manager, engineer in session.execute(stmt):
...     print(f"{manager} {engineer}")
SELECT
employee_1.id,  employee_1.name,  employee_1.type,  employee_1.company_id,
manager_1.id  AS  id_1,  manager_1.manager_name,
employee_2.id  AS  id_2,  employee_2.name  AS  name_1,  employee_2.type  AS  type_1,
employee_2.company_id  AS  company_id_1,  engineer_1.id  AS  id_3,  engineer_1.engineer_info
FROM  employee  AS  employee_1
LEFT  OUTER  JOIN  manager  AS  manager_1  ON  employee_1.id  =  manager_1.id
JOIN
  (employee  AS  employee_2  LEFT  OUTER  JOIN  engineer  AS  engineer_1  ON  employee_2.id  =  engineer_1.id)
ON  employee_2.company_id  =  employee_1.company_id
WHERE  employee_1.name  =  ?  OR  manager_1.manager_name  =  ?
ORDER  BY  employee_2.name,  employee_1.name
[...]  ('Mr. Krabs',  'Eugene H. Krabs')
Manager('Mr. Krabs') Manager('Mr. Krabs')
Manager('Mr. Krabs') Engineer('SpongeBob')
Manager('Mr. Krabs') Engineer('Squidward')

在上面的例子中,with_polymorphic.flat 的行为是,多态可选项保持为其各自表的 LEFT OUTER JOIN,这些表本身被赋予匿名别名。还生成了一个右嵌套 JOIN。

当省略with_polymorphic.flat 参数时,通常行为是每个多态可选项都被包含在子查询中,产生更加冗长的形式:

>>> manager_employee = with_polymorphic(Employee, [Manager], aliased=True)
>>> engineer_employee = with_polymorphic(Employee, [Engineer], aliased=True)
>>> stmt = (
...     select(manager_employee, engineer_employee)
...     .join(
...         engineer_employee,
...         engineer_employee.company_id == manager_employee.company_id,
...     )
...     .where(
...         or_(
...             manager_employee.name == "Mr. Krabs",
...             manager_employee.Manager.manager_name == "Eugene H. Krabs",
...         )
...     )
...     .order_by(engineer_employee.name, manager_employee.name)
... )
>>> print(stmt)
SELECT  anon_1.employee_id,  anon_1.employee_name,  anon_1.employee_type,
anon_1.employee_company_id,  anon_1.manager_id,  anon_1.manager_manager_name,  anon_2.employee_id  AS  employee_id_1,
anon_2.employee_name  AS  employee_name_1,  anon_2.employee_type  AS  employee_type_1,
anon_2.employee_company_id  AS  employee_company_id_1,  anon_2.engineer_id,  anon_2.engineer_engineer_info
FROM
(SELECT  employee.id  AS  employee_id,  employee.name  AS  employee_name,  employee.type  AS  employee_type,
employee.company_id  AS  employee_company_id,
manager.id  AS  manager_id,  manager.manager_name  AS  manager_manager_name
FROM  employee  LEFT  OUTER  JOIN  manager  ON  employee.id  =  manager.id)  AS  anon_1
JOIN
(SELECT  employee.id  AS  employee_id,  employee.name  AS  employee_name,  employee.type  AS  employee_type,
employee.company_id  AS  employee_company_id,  engineer.id  AS  engineer_id,  engineer.engineer_info  AS  engineer_engineer_info
FROM  employee  LEFT  OUTER  JOIN  engineer  ON  employee.id  =  engineer.id)  AS  anon_2
ON  anon_2.employee_company_id  =  anon_1.employee_company_id
WHERE  anon_1.employee_name  =  :employee_name_2  OR  anon_1.manager_manager_name  =  :manager_manager_name_1
ORDER  BY  anon_2.employee_name,  anon_1.employee_name 

上述形式在历史上更容易移植到不一定支持右嵌套 JOIN 的后端,并且在使用诸如具体表继承映射以及一般情况下使用替代多态可选项时,可能也是合适的。 ### 在映射上配置 with_polymorphic()

selectin_polymorphic() 类似,with_polymorphic() 构造也支持一个经过映射配置的版本,可以通过两种不同的方式进行配置,要么在基类上使用 mapper.with_polymorphic 参数,要么以更现代的形式在每个子类上使用 Mapper.polymorphic_load 参数,传递值为 "inline"

警告

对于加入继承映射,更倾向于在查询中明确使用 with_polymorphic() ,或者对于隐式的子类急加载使用 Mapper.polymorphic_load"selectin" 为参数,而不是使用本节中描述的映射级别的 mapper.with_polymorphic 参数。该参数调用了旨在重写 SELECT 语句中的 FROM 子句的复杂启发式规则,可能会干扰更复杂语句的构建,尤其是那些涉及到引用同一映射实体的嵌套子查询的语句。

例如,我们可以使用以下方式声明我们的 Employee 映射,将 Mapper.polymorphic_load 设为 "inline"

class Employee(Base):
    __tablename__ = "employee"
    id = mapped_column(Integer, primary_key=True)
    name = mapped_column(String(50))
    type = mapped_column(String(50))

    __mapper_args__ = {"polymorphic_identity": "employee", "polymorphic_on": type}

class Engineer(Employee):
    __tablename__ = "engineer"
    id = mapped_column(Integer, ForeignKey("employee.id"), primary_key=True)
    engineer_info = mapped_column(String(30))

    __mapper_args__ = {
        "polymorphic_load": "inline",
        "polymorphic_identity": "engineer",
    }

class Manager(Employee):
    __tablename__ = "manager"
    id = mapped_column(Integer, ForeignKey("employee.id"), primary_key=True)
    manager_name = mapped_column(String(30))

    __mapper_args__ = {
        "polymorphic_load": "inline",
        "polymorphic_identity": "manager",
    }

对于上述映射,针对 Employee 类的 SELECT 语句将自动假设在发出语句时使用 with_polymorphic(Employee, [Engineer, Manager]) 作为主要实体:

print(select(Employee))
SELECT  employee.id,  employee.name,  employee.type,  engineer.id  AS  id_1,
engineer.engineer_info,  manager.id  AS  id_2,  manager.manager_name
FROM  employee
LEFT  OUTER  JOIN  engineer  ON  employee.id  =  engineer.id
LEFT  OUTER  JOIN  manager  ON  employee.id  =  manager.id 

当使用映射器级“with polymorphic”时,查询也可以直接引用子类实体,其中它们隐式地表示多态查询中的连接表。在上面的例子中,我们可以自由地直接引用 ManagerEngineer 对默认的 Employee 实体进行查询:

print(
 select(Employee).where(
 or_(Manager.manager_name == "x", Engineer.engineer_info == "y")
 )
)
SELECT  employee.id,  employee.name,  employee.type,  engineer.id  AS  id_1,
engineer.engineer_info,  manager.id  AS  id_2,  manager.manager_name
FROM  employee
LEFT  OUTER  JOIN  engineer  ON  employee.id  =  engineer.id
LEFT  OUTER  JOIN  manager  ON  employee.id  =  manager.id
WHERE  manager.manager_name  =  :manager_name_1
OR  engineer.engineer_info  =  :engineer_info_1 

然而,如果我们需要在单独的别名上下文中引用 Employee 实体或其子实体,我们将再次直接使用 with_polymorphic() 来定义这些别名实体,如 使用别名与 with_polymorphic 中所示。

对于对多态可选的更集中的控制,可以使用更传统的映射器级多态控制形式,即 Mapper.with_polymorphic 参数,配置在基类上。此参数接受与 with_polymorphic() 构造相当的参数,然而,在连接继承映射中的常见用法是使用普通的星号,表示所有子表都应该进行 LEFT OUTER JOIN,如下所示:

class Employee(Base):
    __tablename__ = "employee"
    id = mapped_column(Integer, primary_key=True)
    name = mapped_column(String(50))
    type = mapped_column(String(50))

    __mapper_args__ = {
        "polymorphic_identity": "employee",
        "with_polymorphic": "*",
        "polymorphic_on": type,
    }

class Engineer(Employee):
    __tablename__ = "engineer"
    id = mapped_column(Integer, ForeignKey("employee.id"), primary_key=True)
    engineer_info = mapped_column(String(30))

    __mapper_args__ = {
        "polymorphic_identity": "engineer",
    }

class Manager(Employee):
    __tablename__ = "manager"
    id = mapped_column(Integer, ForeignKey("employee.id"), primary_key=True)
    manager_name = mapped_column(String(30))

    __mapper_args__ = {
        "polymorphic_identity": "manager",
    }

总的来说,with_polymorphic() 和诸如 Mapper.with_polymorphic 这样的选项使用的 LEFT OUTER JOIN 格式可能从 SQL 和数据库优化器的角度来看会比较繁琐;对于连接继承映射中子类属性的一般加载,应该更倾向于使用 selectin_polymorphic() 方法,或者将 Mapper.polymorphic_load 设置为 "selectin" 的映射器级等效,只在需要时才在每个查询基础上使用 with_polymorphic()。 ## 加入到特定子类型或 with_polymorphic() 实体

由于with_polymorphic()实体是aliased()的一种特殊情况,在将多态实体视为连接的目标时,特别是在使用relationship()构造作为 ON 子句时,我们使用与常规别名相同的技术,如在 Using Relationship to join between aliased targets 中详细说明的,最简洁的方式是使用PropComparator.of_type()。在下面的示例中,我们演示了从父实体Company沿着一对多关系Company.employees进行连接,该关系在 setup 中配置为链接到Employee对象,使用一个with_polymorphic()实体作为目标:

>>> employee_plus_engineer = with_polymorphic(Employee, [Engineer])
>>> stmt = (
...     select(Company.name, employee_plus_engineer.name)
...     .join(Company.employees.of_type(employee_plus_engineer))
...     .where(
...         or_(
...             employee_plus_engineer.name == "SpongeBob",
...             employee_plus_engineer.Engineer.engineer_info
...             == "Senior Customer Engagement Engineer",
...         )
...     )
... )
>>> for company_name, emp_name in session.execute(stmt):
...     print(f"{company_name} {emp_name}")
SELECT  company.name,  employee.name  AS  name_1
FROM  company  JOIN  (employee  LEFT  OUTER  JOIN  engineer  ON  employee.id  =  engineer.id)  ON  company.id  =  employee.company_id
WHERE  employee.name  =  ?  OR  engineer.engineer_info  =  ?
[...]  ('SpongeBob',  'Senior Customer Engagement Engineer')
Krusty Krab SpongeBob
Krusty Krab Squidward

更直接地说,PropComparator.of_type()也与任何类型的继承映射一起使用,以将一个relationship()的连接限制为特定的子类型。上面的查询可以严格按照Engineer目标来编写,如下所示:

>>> stmt = (
...     select(Company.name, Engineer.name)
...     .join(Company.employees.of_type(Engineer))
...     .where(
...         or_(
...             Engineer.name == "SpongeBob",
...             Engineer.engineer_info == "Senior Customer Engagement Engineer",
...         )
...     )
... )
>>> for company_name, emp_name in session.execute(stmt):
...     print(f"{company_name} {emp_name}")
SELECT  company.name,  employee.name  AS  name_1
FROM  company  JOIN  (employee  JOIN  engineer  ON  employee.id  =  engineer.id)  ON  company.id  =  employee.company_id
WHERE  employee.name  =  ?  OR  engineer.engineer_info  =  ?
[...]  ('SpongeBob',  'Senior Customer Engagement Engineer')
Krusty Krab SpongeBob
Krusty Krab Squidward

从上面可以观察到,直接加入到Engineer目标,而不是使用with_polymorphic(Employee, [Engineer])的“多态可选择”具有一个有用的特性,即使用内连接而不是左外连接,从 SQL 优化器的角度来看,这通常更具性能。

多态子类型的急切加载

在前一节中用Select.join()方法演示的PropComparator.of_type()也可以等效地应用于 relationship loader options,如selectinload()joinedload()

作为一个基本示例,如果我们希望加载Company对象,并且使用with_polymorphic()构造来对整个层次结构的Company.employees的所有元素进行急切加载,我们可以编写如下代码:

>>> all_employees = with_polymorphic(Employee, "*")
>>> stmt = select(Company).options(selectinload(Company.employees.of_type(all_employees)))
>>> for company in session.scalars(stmt):
...     print(f"company: {company.name}")
...     print(f"employees: {company.employees}")
SELECT  company.id,  company.name
FROM  company
[...]  ()
SELECT  employee.company_id  AS  employee_company_id,  employee.id  AS  employee_id,
employee.name  AS  employee_name,  employee.type  AS  employee_type,  manager.id  AS  manager_id,
manager.manager_name  AS  manager_manager_name,  engineer.id  AS  engineer_id,
engineer.engineer_info  AS  engineer_engineer_info
FROM  employee
LEFT  OUTER  JOIN  manager  ON  employee.id  =  manager.id
LEFT  OUTER  JOIN  engineer  ON  employee.id  =  engineer.id
WHERE  employee.company_id  IN  (?)
[...]  (1,)
company:  Krusty  Krab
employees:  [Manager('Mr. Krabs'),  Engineer('SpongeBob'),  Engineer('Squidward')] 

上述查询可以直接与前一节中将selectin_polymorphic()应用于现有的急加载中的selectin_polymorphic()版本进行比较。

另请参阅

selectin_polymorphic()应用于现有的急加载 - 演示了使用selectin_polymorphic()相同等效的示例,而不是上面的例子。 ## 单一继承映射的 SELECT 语句

单一表继承设置

本节讨论单表继承,描述在单表继承中使用单个表表示层次结构中的多个类。

查看本节的 ORM 设置。

与联接继承映射相比,对于单一继承映射,构造 SELECT 语句往往更简单,因为对于全部单一继承层次结构,只有一个表。

无论继承层次结构是否全是单一继承或具有联接和单一继承的混合,单一继承的 SELECT 语句都通过添加额外的 WHERE 条件来区分针对基类和子类的查询。

例如,对于Employee的单一继承示例映射的查询将使用简单的表 SELECT 加载ManagerEngineerEmployee类型的对象:

>>> stmt = select(Employee).order_by(Employee.id)
>>> for obj in session.scalars(stmt):
...     print(f"{obj}")
BEGIN  (implicit)
SELECT  employee.id,  employee.name,  employee.type
FROM  employee  ORDER  BY  employee.id
[...]  ()
Manager('Mr. Krabs')
Engineer('SpongeBob')
Engineer('Squidward')

当针对特定子类发出加载时,会向 SELECT 添加限制行的其他条件,例如下面对Engineer实体执行的 SELECT:

>>> stmt = select(Engineer).order_by(Engineer.id)
>>> objects = session.scalars(stmt).all()
SELECT  employee.id,  employee.name,  employee.type,  employee.engineer_info
FROM  employee
WHERE  employee.type  IN  (?)  ORDER  BY  employee.id
[...]  ('engineer',)
>>> for obj in objects:
...     print(f"{obj}")
Engineer('SpongeBob')
Engineer('Squidward')

优化单一继承的属性加载

单一继承映射关于如何选择子类上的属性的默认行为与联接继承的行为类似,即子类特定的属性仍然默认发出第二个 SELECT。在下面的示例中,加载一个Manager类型的单个Employee,但由于请求的类是Employee,所以默认情况下不会出现Manager.manager_name属性,并且当访问时会发出额外的 SELECT:

>>> mr_krabs = session.scalars(select(Employee).where(Employee.name == "Mr. Krabs")).one()
BEGIN  (implicit)
SELECT  employee.id,  employee.name,  employee.type
FROM  employee
WHERE  employee.name  =  ?
[...]  ('Mr. Krabs',)
>>> mr_krabs.manager_name
SELECT  employee.manager_name  AS  employee_manager_name
FROM  employee
WHERE  employee.id  =  ?  AND  employee.type  IN  (?)
[...]  (1,  'manager')
'Eugene H. Krabs'

要更改此行为,对于单一继承以及联接继承加载中使用的额外属性的急加载,同样的一般概念也适用于单一继承,包括使用selectin_polymorphic()选项以及with_polymorphic()选项,后者简单地包含了额外的列,并且从 SQL 的角度来看,对于单一继承映射更有效:

>>> employees = with_polymorphic(Employee, "*")
>>> stmt = select(employees).order_by(employees.id)
>>> objects = session.scalars(stmt).all()
BEGIN  (implicit)
SELECT  employee.id,  employee.name,  employee.type,
employee.manager_name,  employee.engineer_info
FROM  employee  ORDER  BY  employee.id
[...]  ()
>>> for obj in objects:
...     print(f"{obj}")
Manager('Mr. Krabs')
Engineer('SpongeBob')
Engineer('Squidward')
>>> objects[0].manager_name
'Eugene H. Krabs'

由于加载单继承子类映射的开销通常很小,因此建议对于那些预计加载其特定子类属性是常见的子类,包括 Mapper.polymorphic_load 参数,并将其设置为 "inline"。下面是一个示例,说明了包含此选项的 设置:

>>> class Base(DeclarativeBase):
...     pass
>>> class Employee(Base):
...     __tablename__ = "employee"
...     id: Mapped[int] = mapped_column(primary_key=True)
...     name: Mapped[str]
...     type: Mapped[str]
...
...     def __repr__(self):
...         return f"{self.__class__.__name__}({self.name!r})"
...
...     __mapper_args__ = {
...         "polymorphic_identity": "employee",
...         "polymorphic_on": "type",
...     }
>>> class Manager(Employee):
...     manager_name: Mapped[str] = mapped_column(nullable=True)
...     __mapper_args__ = {
...         "polymorphic_identity": "manager",
...         "polymorphic_load": "inline",
...     }
>>> class Engineer(Employee):
...     engineer_info: Mapped[str] = mapped_column(nullable=True)
...     __mapper_args__ = {
...         "polymorphic_identity": "engineer",
...         "polymorphic_load": "inline",
...     }

根据上述映射,ManagerEngineer 类将自动在针对 Employee 实体的 SELECT 语句中包含它们的列:

>>> print(select(Employee))
SELECT  employee.id,  employee.name,  employee.type,
employee.manager_name,  employee.engineer_info
FROM  employee 

继承加载 API

对象名称 描述
selectin_polymorphic(base_cls, classes) 指示应对子类的所有属性进行急切加载。
with_polymorphic(base, classes[, selectable, flat, ...]) 生成一个 AliasedClass 构造,指定给定基类的后代映射器的列。
function sqlalchemy.orm.with_polymorphic(base: Type[_O] | Mapper[_O], classes: Literal['*'] | Iterable[Type[Any]], selectable: Literal[False, None] | FromClause = False, flat: bool = False, polymorphic_on: ColumnElement[Any] | None = None, aliased: bool = False, innerjoin: bool = False, adapt_on_names: bool = False, _use_mapper_path: bool = False) → AliasedClass[_O]

生成一个 AliasedClass 构造,指定给定基类的后代映射器的列。

使用此方法将确保每个后代映射器的表都包含在 FROM 子句中,并允许对这些表使用 filter() 条件。结果实例还将已加载这些列,因此不需要对这些列进行“后获取”。

另请参阅

使用 with_polymorphic() - 对 with_polymorphic() 的全面讨论。

参数:

  • base – 要别名化的基类。

  • classes – 一个类或映射器,或类/映射器列表,它们都继承自基类。或者,它也可以是字符串 '*',在这种情况下,所有下降映射的类都将添加到 FROM 子句中。

  • aliased – 当为 True 时,可选择的将被别名化。对于 JOIN,这意味着 JOIN 将从子查询中进行 SELECT,除非设置了 with_polymorphic.flat 标志为 True,这对于简单的用例是推荐的。

  • flat – 布尔值,将传递给FromClause.alias()调用,以便联接对象的别名别名联接内部的各个表,而不是创建子查询。这通常由所有现代数据库支持,关于右嵌套联接通常会产生更有效的查询。建议设置此标志,只要生成的 SQL 是功能性的。

  • selectable

    将用于替代生成的 FROM 子句的表或子查询。如果任何所需类使用具体表继承,则此参数是必需的,因为 SQLAlchemy 当前无法自动生成表之间的 UNION。如果使用,selectable参数必须表示每个映射类映射的所有表和列的完整集。否则,未考虑的映射列将直接附加到 FROM 子句,这通常会导致不正确的结果。

    当保持其默认值False时,将为选择行使用分配给基本映射器的多态可选择对象。但是,也可以将其传递为None,这将绕过配置的多态可选择对象,并代替构造给定目标类的临时可选择对象;对于联接表继承,这将是一个包含所有目标映射器及其子类的联接。

  • polymorphic_on – 用作给定可选择对象的“判别器”列。如果未提供,则将使用基类映射器的polymorphic_on属性(如果有)。这对于默认没有多态加载行为的映射非常有用。

  • innerjoin – 如果为 True,则使用 INNER JOIN。只有在仅查询一个特定的子类型时才应指定此选项

  • adapt_on_names

    通过aliased.adapt_on_names参数传递到别名对象。在给定可选择对象与现有映射的可选择对象没有直接关联的情况下,这可能会有所帮助。

    自版本 1.4.33 起新增。

function sqlalchemy.orm.selectin_polymorphic(base_cls: _EntityType[Any], classes: Iterable[Type[Any]]) → _AbstractLoad

指示应针对特定子类的所有属性进行急加载。

这使用额外的 SELECT 与所有匹配的主键值进行 IN 比较,并且是与mapper.polymorphic_load参数上的"selectin"设置对应的每个查询的类似物。

自版本 1.2 起新增。

另请参阅

使用selectin_polymorphic()

从基类 vs. 特定子类进行 SELECT

对于联合继承层次结构中的类构建的 SELECT 语句将查询该类映射到的表,以及任何存在的超级表,使用 JOIN 将它们链接在一起。然后,该查询将返回请求类型的对象以及请求类型的任何子类型,使用每行中的鉴别器值来确定正确的类型。下面的查询是针对EmployeeManager子类建立的,然后返回的结果将仅包含Manager类型的对象:

>>> from sqlalchemy import select
>>> stmt = select(Manager).order_by(Manager.id)
>>> managers = session.scalars(stmt).all()
BEGIN  (implicit)
SELECT  manager.id,  employee.id  AS  id_1,  employee.name,  employee.type,  employee.company_id,  manager.manager_name
FROM  employee  JOIN  manager  ON  employee.id  =  manager.id  ORDER  BY  manager.id
[...]  ()
>>> print(managers)
[Manager('Mr. Krabs')]

当 SELECT 语句针对层次结构中的基类时,默认行为是仅包括该类的表在渲染的 SQL 中,并且不会使用 JOIN。与所有情况一样,鉴别器列用于区分不同的请求子类型,然后结果是返回任何可能的子类型的对象。返回的对象将具有与基本表对应的属性填充,而与子表对应的属性将以未加载状态开始,在访问时自动加载。子属性的加载可配置为以各种方式更加“急切”,这将在本节后面讨论。

下面的示例创建了针对Employee超类的查询。这表示结果集中可能包含任何类型的对象,包括ManagerEngineerEmployee

>>> from sqlalchemy import select
>>> stmt = select(Employee).order_by(Employee.id)
>>> objects = session.scalars(stmt).all()
BEGIN  (implicit)
SELECT  employee.id,  employee.name,  employee.type,  employee.company_id
FROM  employee  ORDER  BY  employee.id
[...]  ()
>>> print(objects)
[Manager('Mr. Krabs'), Engineer('SpongeBob'), Engineer('Squidward')]

上面,并未包括ManagerEngineer的附加表在 SELECT 中,这意味着返回的对象尚不包含来自这些表的数据,例如本示例中Manager类的.manager_name属性以及Engineer类的.engineer_info属性。这些属性起始处于过期状态,并且在首次访问时将自动填充自己,使用延迟加载:

>>> mr_krabs = objects[0]
>>> print(mr_krabs.manager_name)
SELECT  manager.manager_name  AS  manager_manager_name
FROM  manager
WHERE  ?  =  manager.id
[...]  (1,)
Eugene H. Krabs

如果已加载大量对象,则此惰性加载行为是不可取的,因为消费应用程序将需要访问特定于子类的属性,这将是一个 N 加一问题的示例,每行发出额外的 SQL。这些额外的 SQL 可能会影响性能,并且还可能与诸如使用 asyncio 等方法不兼容。此外,在我们对Employee对象的查询中,由于查询仅针对基本表,因此我们无法以ManagerEngineer的术语添加涉及特定于子类的属性的 SQL 条件。接下来的两个部分详细介绍了两种以不同方式解决这两个问题的构造,selectin_polymorphic()加载器选项和with_polymorphic()实体构造。

使用 selectin_polymorphic()

为了解决在访问子类属性时的性能问题,可以使用selectin_polymorphic()加载器策略,一次性预加载这些额外的属性到许多对象中。此加载器选项的工作方式类似于selectinload()关系加载器策略,针对加载在层次结构中的对象发出额外的 SELECT 语句,使用IN查询基于主键的额外行。

selectinload()接受作为参数被查询的基本实体,然后是该实体的一系列子类,这些子类的特定属性应加载到传入的行中:

>>> from sqlalchemy.orm import selectin_polymorphic
>>> loader_opt = selectin_polymorphic(Employee, [Manager, Engineer])

然后,selectin_polymorphic()构造被用作加载器选项,将其传递给Select.options()方法Select。该示例说明了使用selectin_polymorphic()急切加载ManagerEngineer子类本地列的用法:

>>> from sqlalchemy.orm import selectin_polymorphic
>>> loader_opt = selectin_polymorphic(Employee, [Manager, Engineer])
>>> stmt = select(Employee).order_by(Employee.id).options(loader_opt)
>>> objects = session.scalars(stmt).all()
BEGIN  (implicit)
SELECT  employee.id,  employee.name,  employee.type,  employee.company_id
FROM  employee  ORDER  BY  employee.id
[...]  ()
SELECT  manager.id  AS  manager_id,  employee.id  AS  employee_id,
employee.type  AS  employee_type,  manager.manager_name  AS  manager_manager_name
FROM  employee  JOIN  manager  ON  employee.id  =  manager.id
WHERE  employee.id  IN  (?)  ORDER  BY  employee.id
[...]  (1,)
SELECT  engineer.id  AS  engineer_id,  employee.id  AS  employee_id,
employee.type  AS  employee_type,  engineer.engineer_info  AS  engineer_engineer_info
FROM  employee  JOIN  engineer  ON  employee.id  =  engineer.id
WHERE  employee.id  IN  (?,  ?)  ORDER  BY  employee.id
[...]  (2,  3)
>>> print(objects)
[Manager('Mr. Krabs'), Engineer('SpongeBob'), Engineer('Squidward')]

上面的示例说明了额外发出的两个额外的 SELECT 语句,以便急切地获取额外的属性,如Engineer.engineer_infoManager.manager_name。我们现在可以访问这些被加载的对象上的子属性,而不需要发出任何额外的 SQL 语句:

>>> print(objects[0].manager_name)
Eugene H. Krabs

提示

selectin_polymorphic() 加载选项尚未针对基本的 employee 表进行优化,因此在第二和第三个“急加载”查询中不需要包含 employee 表;因此,在上面的示例中,我们看到了从 employeemanagerengineer 的 JOIN,即使 employee 的列已经加载。这与selectinload() 关系策略形成对比,在这方面更加复杂,并且在不需要时可以消除 JOIN。

将 selectin_polymorphic() 应用于现有的急加载

除了将 selectin_polymorphic() 指定为由语句加载的顶级实体的选项之外,我们还可以在现有加载的目标上指示 selectin_polymorphic()。由于我们的设置映射包括一个带有引用 Employee 实体的 Company.employees relationship() 的父 Company 实体,我们可以说明针对 Company 实体进行的 SELECT,该 SELECT 急切加载所有 Employee 对象以及其子类型上的所有属性,方法是将 Load.selectin_polymorphic() 应用为链接的加载选项;在此形式中,第一个参数从前一个加载选项隐式获得(在本例中为 selectinload()),因此我们只指示我们希望加载的附加目标子类:

>>> from sqlalchemy.orm import selectinload
>>> stmt = select(Company).options(
...     selectinload(Company.employees).selectin_polymorphic([Manager, Engineer])
... )
>>> for company in session.scalars(stmt):
...     print(f"company: {company.name}")
...     print(f"employees: {company.employees}")
BEGIN  (implicit)
SELECT  company.id,  company.name
FROM  company
[...]  ()
SELECT  employee.company_id  AS  employee_company_id,  employee.id  AS  employee_id,
employee.name  AS  employee_name,  employee.type  AS  employee_type
FROM  employee
WHERE  employee.company_id  IN  (?)
[...]  (1,)
SELECT  manager.id  AS  manager_id,  employee.id  AS  employee_id,
employee.type  AS  employee_type,
manager.manager_name  AS  manager_manager_name
FROM  employee  JOIN  manager  ON  employee.id  =  manager.id
WHERE  employee.id  IN  (?)  ORDER  BY  employee.id
[...]  (1,)
SELECT  engineer.id  AS  engineer_id,  employee.id  AS  employee_id,
employee.type  AS  employee_type,
engineer.engineer_info  AS  engineer_engineer_info
FROM  employee  JOIN  engineer  ON  employee.id  =  engineer.id
WHERE  employee.id  IN  (?,  ?)  ORDER  BY  employee.id
[...]  (2,  3)
company: Krusty Krab
employees: [Manager('Mr. Krabs'), Engineer('SpongeBob'), Engineer('Squidward')]

另请参见

多态子类型的急加载 - 展示了使用 with_polymorphic() 的相同示例 ### 将加载选项应用于由 selectin_polymorphic 加载的子类

selectin_polymorphic() 发出的 SELECT 语句本身是 ORM 语句,因此我们还可以添加其他加载选项(例如文档中记录的关系加载技术) ,这些选项引用特定的子类。这些选项应作为兄弟应用于 selectin_polymorphic() 选项,即在 select.options() 中用逗号分隔。

例如,如果我们考虑 Manager 映射器与名为 Paperwork 的实体之间有一对多关系,我们可以结合使用 selectin_polymorphic()selectinload() 在所有 Manager 对象上急加载此集合,其中 Manager 对象的子属性也被急加载:

>>> from sqlalchemy.orm import selectin_polymorphic
>>> stmt = (
...     select(Employee)
...     .order_by(Employee.id)
...     .options(
...         selectin_polymorphic(Employee, [Manager, Engineer]),
...         selectinload(Manager.paperwork),
...     )
... )
>>> objects = session.scalars(stmt).all()
SELECT  employee.id,  employee.name,  employee.type,  employee.company_id
FROM  employee  ORDER  BY  employee.id
[...]  ()
SELECT  manager.id  AS  manager_id,  employee.id  AS  employee_id,  employee.type  AS  employee_type,  manager.manager_name  AS  manager_manager_name
FROM  employee  JOIN  manager  ON  employee.id  =  manager.id
WHERE  employee.id  IN  (?)  ORDER  BY  employee.id
[...]  (1,)
SELECT  paperwork.manager_id  AS  paperwork_manager_id,  paperwork.id  AS  paperwork_id,  paperwork.document_name  AS  paperwork_document_name
FROM  paperwork
WHERE  paperwork.manager_id  IN  (?)
[...]  (1,)
SELECT  engineer.id  AS  engineer_id,  employee.id  AS  employee_id,  employee.type  AS  employee_type,  engineer.engineer_info  AS  engineer_engineer_info
FROM  employee  JOIN  engineer  ON  employee.id  =  engineer.id
WHERE  employee.id  IN  (?,  ?)  ORDER  BY  employee.id
[...]  (2,  3)
>>> print(objects[0])
Manager('Mr. Krabs')
>>> print(objects[0].paperwork)
[Paperwork('Secret Recipes'), Paperwork('Krabby Patty Orders')]

当 selectin_polymorphic 本身是子选项时应用加载器选项

2.0.21 版中的新功能。

前一节说明了 selectin_polymorphic()selectinload() 作为兄弟选项的用法,都在单个对 select.options() 的调用中使用。 如果目标实体已经从父关系加载,例如在将 selectin_polymorphic() 应用于现有的急加载示例中,我们可以使用 Load.options() 方法应用此“兄弟”模式,该方法将子选项应用于父选项,如在使用 Load.options() 指定子选项示例中说明的。 下面我们结合两个示例,加载 Company.employees,还加载 ManagerEngineer 类的属性,以及急加载 Manager.paperwork 属性:

>>> from sqlalchemy.orm import selectinload
>>> stmt = select(Company).options(
...     selectinload(Company.employees).options(
...         selectin_polymorphic(Employee, [Manager, Engineer]),
...         selectinload(Manager.paperwork),
...     )
... )
>>> for company in session.scalars(stmt):
...     print(f"company: {company.name}")
...     for employee in company.employees:
...         if isinstance(employee, Manager):
...             print(f"manager: {employee.name} paperwork: {employee.paperwork}")
BEGIN  (implicit)
SELECT  company.id,  company.name
FROM  company
[...]  ()
SELECT  employee.company_id  AS  employee_company_id,  employee.id  AS  employee_id,  employee.name  AS  employee_name,  employee.type  AS  employee_type
FROM  employee
WHERE  employee.company_id  IN  (?)
[...]  (1,)
SELECT  manager.id  AS  manager_id,  employee.id  AS  employee_id,  employee.type  AS  employee_type,  manager.manager_name  AS  manager_manager_name
FROM  employee  JOIN  manager  ON  employee.id  =  manager.id
WHERE  employee.id  IN  (?)  ORDER  BY  employee.id
[...]  (1,)
SELECT  paperwork.manager_id  AS  paperwork_manager_id,  paperwork.id  AS  paperwork_id,  paperwork.document_name  AS  paperwork_document_name
FROM  paperwork
WHERE  paperwork.manager_id  IN  (?)
[...]  (1,)
SELECT  engineer.id  AS  engineer_id,  employee.id  AS  employee_id,  employee.type  AS  employee_type,  engineer.engineer_info  AS  engineer_engineer_info
FROM  employee  JOIN  engineer  ON  employee.id  =  engineer.id
WHERE  employee.id  IN  (?,  ?)  ORDER  BY  employee.id
[...]  (2,  3)
company: Krusty Krab
manager: Mr. Krabs paperwork: [Paperwork('Secret Recipes'), Paperwork('Krabby Patty Orders')]

配置 mappers 上的 selectin_polymorphic()

可以在特定的 mappers 上配置 selectin_polymorphic() 的行为,以便默认情况下执行,通过使用 Mapper.polymorphic_load 参数,在每个子类基础上使用值 "selectin"。 以下示例说明了在 EngineerManager 子类中使用此参数的用法:

class Employee(Base):
    __tablename__ = "employee"
    id = mapped_column(Integer, primary_key=True)
    name = mapped_column(String(50))
    type = mapped_column(String(50))

    __mapper_args__ = {"polymorphic_identity": "employee", "polymorphic_on": type}

class Engineer(Employee):
    __tablename__ = "engineer"
    id = mapped_column(Integer, ForeignKey("employee.id"), primary_key=True)
    engineer_info = mapped_column(String(30))

    __mapper_args__ = {
        "polymorphic_load": "selectin",
        "polymorphic_identity": "engineer",
    }

class Manager(Employee):
    __tablename__ = "manager"
    id = mapped_column(Integer, ForeignKey("employee.id"), primary_key=True)
    manager_name = mapped_column(String(30))

    __mapper_args__ = {
        "polymorphic_load": "selectin",
        "polymorphic_identity": "manager",
    }

以上映射,针对 Employee 类的 SELECT 语句在发出语句时将自动假定使用 selectin_polymorphic(Employee, [Engineer, Manager]) 作为加载器选项。

将 selectin_polymorphic() 应用于现有的急加载

在指定为语句加载的顶级实体选项时,除了 selectin_polymorphic() 之外,我们还可以在现有加载的目标上指示 selectin_polymorphic()。由于我们的 设置 映射包括一个父 Company 实体,其具有引用到 Employee 实体的 Company.employees relationship(),我们可以如下所示对 Company 实体进行选择,该选择会急切地加载所有 Employee 对象以及其子类型的所有属性,方法是将 Load.selectin_polymorphic() 应用为链接的加载器选项;在这种形式中,第一个参数是从上一个加载器选项中隐含的(在本例中是 selectinload()),因此我们只指示我们希望加载的附加目标子类:

>>> from sqlalchemy.orm import selectinload
>>> stmt = select(Company).options(
...     selectinload(Company.employees).selectin_polymorphic([Manager, Engineer])
... )
>>> for company in session.scalars(stmt):
...     print(f"company: {company.name}")
...     print(f"employees: {company.employees}")
BEGIN  (implicit)
SELECT  company.id,  company.name
FROM  company
[...]  ()
SELECT  employee.company_id  AS  employee_company_id,  employee.id  AS  employee_id,
employee.name  AS  employee_name,  employee.type  AS  employee_type
FROM  employee
WHERE  employee.company_id  IN  (?)
[...]  (1,)
SELECT  manager.id  AS  manager_id,  employee.id  AS  employee_id,
employee.type  AS  employee_type,
manager.manager_name  AS  manager_manager_name
FROM  employee  JOIN  manager  ON  employee.id  =  manager.id
WHERE  employee.id  IN  (?)  ORDER  BY  employee.id
[...]  (1,)
SELECT  engineer.id  AS  engineer_id,  employee.id  AS  employee_id,
employee.type  AS  employee_type,
engineer.engineer_info  AS  engineer_engineer_info
FROM  employee  JOIN  engineer  ON  employee.id  =  engineer.id
WHERE  employee.id  IN  (?,  ?)  ORDER  BY  employee.id
[...]  (2,  3)
company: Krusty Krab
employees: [Manager('Mr. Krabs'), Engineer('SpongeBob'), Engineer('Squidward')]

另请参见

多态子类型的急切加载 - 使用 with_polymorphic() 演示了与上述相同的等效示例

将加载器选项应用于由 selectin_polymorphic 加载的子类

selectin_polymorphic() 发出的 SELECT 语句本身是 ORM 语句,因此我们也可以添加其他加载器选项(例如文档中记录的那些位于 关系加载技术),这些选项是指特定的子类。这些选项应作为 selectin_polymorphic() 选项的 siblings 应用,即在 select.options() 中用逗号分隔。

例如,如果我们考虑到 Manager 映射器有一个 一对多 关系,指向一个名为 Paperwork 的实体,我们可以结合使用 selectin_polymorphic()selectinload() 来急切地加载所有 Manager 对象上的此集合,其中 Manager 对象的子属性也被急切地加载:

>>> from sqlalchemy.orm import selectin_polymorphic
>>> stmt = (
...     select(Employee)
...     .order_by(Employee.id)
...     .options(
...         selectin_polymorphic(Employee, [Manager, Engineer]),
...         selectinload(Manager.paperwork),
...     )
... )
>>> objects = session.scalars(stmt).all()
SELECT  employee.id,  employee.name,  employee.type,  employee.company_id
FROM  employee  ORDER  BY  employee.id
[...]  ()
SELECT  manager.id  AS  manager_id,  employee.id  AS  employee_id,  employee.type  AS  employee_type,  manager.manager_name  AS  manager_manager_name
FROM  employee  JOIN  manager  ON  employee.id  =  manager.id
WHERE  employee.id  IN  (?)  ORDER  BY  employee.id
[...]  (1,)
SELECT  paperwork.manager_id  AS  paperwork_manager_id,  paperwork.id  AS  paperwork_id,  paperwork.document_name  AS  paperwork_document_name
FROM  paperwork
WHERE  paperwork.manager_id  IN  (?)
[...]  (1,)
SELECT  engineer.id  AS  engineer_id,  employee.id  AS  employee_id,  employee.type  AS  employee_type,  engineer.engineer_info  AS  engineer_engineer_info
FROM  employee  JOIN  engineer  ON  employee.id  =  engineer.id
WHERE  employee.id  IN  (?,  ?)  ORDER  BY  employee.id
[...]  (2,  3)
>>> print(objects[0])
Manager('Mr. Krabs')
>>> print(objects[0].paperwork)
[Paperwork('Secret Recipes'), Paperwork('Krabby Patty Orders')]

当 selectin_polymorphic 本身是子选项时应用加载器选项

新版本 2.0.21 中新增。

前一节说明了selectin_polymorphic()selectinload()作为兄弟选项使用的示例,两者都在单个调用select.options()中使用。如果目标实体已经从父关系中加载,就像在将 selectin_polymorphic()应用于现有的急加载的示例中一样,我们可以使用Load.options()方法应用这种“兄弟”模式,将子选项应用于父选项,如在使用 Load.options()指定子选项中所示。下面我们结合这两个示例来加载Company.employees,同时加载ManagerEngineer类的属性,以及急加载Manager.paperwork属性:

>>> from sqlalchemy.orm import selectinload
>>> stmt = select(Company).options(
...     selectinload(Company.employees).options(
...         selectin_polymorphic(Employee, [Manager, Engineer]),
...         selectinload(Manager.paperwork),
...     )
... )
>>> for company in session.scalars(stmt):
...     print(f"company: {company.name}")
...     for employee in company.employees:
...         if isinstance(employee, Manager):
...             print(f"manager: {employee.name} paperwork: {employee.paperwork}")
BEGIN  (implicit)
SELECT  company.id,  company.name
FROM  company
[...]  ()
SELECT  employee.company_id  AS  employee_company_id,  employee.id  AS  employee_id,  employee.name  AS  employee_name,  employee.type  AS  employee_type
FROM  employee
WHERE  employee.company_id  IN  (?)
[...]  (1,)
SELECT  manager.id  AS  manager_id,  employee.id  AS  employee_id,  employee.type  AS  employee_type,  manager.manager_name  AS  manager_manager_name
FROM  employee  JOIN  manager  ON  employee.id  =  manager.id
WHERE  employee.id  IN  (?)  ORDER  BY  employee.id
[...]  (1,)
SELECT  paperwork.manager_id  AS  paperwork_manager_id,  paperwork.id  AS  paperwork_id,  paperwork.document_name  AS  paperwork_document_name
FROM  paperwork
WHERE  paperwork.manager_id  IN  (?)
[...]  (1,)
SELECT  engineer.id  AS  engineer_id,  employee.id  AS  employee_id,  employee.type  AS  employee_type,  engineer.engineer_info  AS  engineer_engineer_info
FROM  employee  JOIN  engineer  ON  employee.id  =  engineer.id
WHERE  employee.id  IN  (?,  ?)  ORDER  BY  employee.id
[...]  (2,  3)
company: Krusty Krab
manager: Mr. Krabs paperwork: [Paperwork('Secret Recipes'), Paperwork('Krabby Patty Orders')]
```  #### 当 selectin_polymorphic 本身是子选项时应用加载器选项

2.0.21 版本中的新功能。

前一节说明了`selectin_polymorphic()`和`selectinload()`作为兄弟选项使用的示例,两者都在单个调用`select.options()`中使用。如果目标实体已经从父关系中加载,就像在将 selectin_polymorphic()应用于现有的急加载的示例中一样,我们可以使用`Load.options()`方法应用这种“兄弟”模式,将子选项应用于父选项,如在使用 Load.options()指定子选项中所示。下面我们结合这两个示例来加载`Company.employees`,同时加载`Manager`和`Engineer`类的属性,以及急加载`Manager.paperwork`属性:

```py
>>> from sqlalchemy.orm import selectinload
>>> stmt = select(Company).options(
...     selectinload(Company.employees).options(
...         selectin_polymorphic(Employee, [Manager, Engineer]),
...         selectinload(Manager.paperwork),
...     )
... )
>>> for company in session.scalars(stmt):
...     print(f"company: {company.name}")
...     for employee in company.employees:
...         if isinstance(employee, Manager):
...             print(f"manager: {employee.name} paperwork: {employee.paperwork}")
BEGIN  (implicit)
SELECT  company.id,  company.name
FROM  company
[...]  ()
SELECT  employee.company_id  AS  employee_company_id,  employee.id  AS  employee_id,  employee.name  AS  employee_name,  employee.type  AS  employee_type
FROM  employee
WHERE  employee.company_id  IN  (?)
[...]  (1,)
SELECT  manager.id  AS  manager_id,  employee.id  AS  employee_id,  employee.type  AS  employee_type,  manager.manager_name  AS  manager_manager_name
FROM  employee  JOIN  manager  ON  employee.id  =  manager.id
WHERE  employee.id  IN  (?)  ORDER  BY  employee.id
[...]  (1,)
SELECT  paperwork.manager_id  AS  paperwork_manager_id,  paperwork.id  AS  paperwork_id,  paperwork.document_name  AS  paperwork_document_name
FROM  paperwork
WHERE  paperwork.manager_id  IN  (?)
[...]  (1,)
SELECT  engineer.id  AS  engineer_id,  employee.id  AS  employee_id,  employee.type  AS  employee_type,  engineer.engineer_info  AS  engineer_engineer_info
FROM  employee  JOIN  engineer  ON  employee.id  =  engineer.id
WHERE  employee.id  IN  (?,  ?)  ORDER  BY  employee.id
[...]  (2,  3)
company: Krusty Krab
manager: Mr. Krabs paperwork: [Paperwork('Secret Recipes'), Paperwork('Krabby Patty Orders')]

配置 selectin_polymorphic()在映射器上

selectin_polymorphic()的行为可以在特定的映射器上进行配置,以便默认情况下发生,通过使用Mapper.polymorphic_load参数,在每个子类上使用值"selectin"。下面的示例说明了在EngineerManager子类中使用此参数的方法:

class Employee(Base):
    __tablename__ = "employee"
    id = mapped_column(Integer, primary_key=True)
    name = mapped_column(String(50))
    type = mapped_column(String(50))

    __mapper_args__ = {"polymorphic_identity": "employee", "polymorphic_on": type}

class Engineer(Employee):
    __tablename__ = "engineer"
    id = mapped_column(Integer, ForeignKey("employee.id"), primary_key=True)
    engineer_info = mapped_column(String(30))

    __mapper_args__ = {
        "polymorphic_load": "selectin",
        "polymorphic_identity": "engineer",
    }

class Manager(Employee):
    __tablename__ = "manager"
    id = mapped_column(Integer, ForeignKey("employee.id"), primary_key=True)
    manager_name = mapped_column(String(30))

    __mapper_args__ = {
        "polymorphic_load": "selectin",
        "polymorphic_identity": "manager",
    }

使用上述映射,针对Employee类的 SELECT 语句在发出语句时将自动假定使用selectin_polymorphic(Employee, [Engineer, Manager])作为加载选项。

使用 with_polymorphic()

与仅影响对象加载的 selectin_polymorphic() 相比,with_polymorphic() 构造影响多态结构的 SQL 查询的呈现方式,通常作为一系列 LEFT OUTER JOIN 到每个包含的子表。这个连接结构称为 多态可选择。通过提供一次查看多个子表的视图,with_polymorphic() 提供了一种一次跨多个继承类编写 SELECT 语句的方法,并能够根据各个子表添加过滤条件。

with_polymorphic() 本质上是 aliased() 构造的一种特殊形式。它接受的参数形式与 selectin_polymorphic() 相似,即被查询的基本实体,后跟一系列这个实体的子类,用于加载其特定属性的传入行:

>>> from sqlalchemy.orm import with_polymorphic
>>> employee_poly = with_polymorphic(Employee, [Engineer, Manager])

为了指示所有子类都应该成为实体的一部分,with_polymorphic() 还将接受字符串 "*",它可以在类序列的位置传递,以表示所有类(请注意,selectin_polymorphic() 尚不支持这一点):

>>> employee_poly = with_polymorphic(Employee, "*")

下面的示例演示了与前一节中相同操作的示例,一次加载 ManagerEngineer 的所有列:

>>> stmt = select(employee_poly).order_by(employee_poly.id)
>>> objects = session.scalars(stmt).all()
BEGIN  (implicit)
SELECT  employee.id,  employee.name,  employee.type,  employee.company_id,
manager.id  AS  id_1,  manager.manager_name,  engineer.id  AS  id_2,  engineer.engineer_info
FROM  employee
LEFT  OUTER  JOIN  manager  ON  employee.id  =  manager.id
LEFT  OUTER  JOIN  engineer  ON  employee.id  =  engineer.id  ORDER  BY  employee.id
[...]  ()
>>> print(objects)
[Manager('Mr. Krabs'), Engineer('SpongeBob'), Engineer('Squidward')]

selectin_polymorphic() 类似,子类上的属性已经加载:

>>> print(objects[0].manager_name)
Eugene H. Krabs

由于 with_polymorphic() 生成的默认可选择使用 LEFT OUTER JOIN,从数据库的角度来看,查询不像 selectin_polymorphic() 采用的方法那样优化,后者仅使用 JOINs 在每个表上发出简单的 SELECT 语句。

使用 with_polymorphic() 过滤子类属性

with_polymorphic() 构造使包含的子类映射器上的属性可用,通过包含允许引用子类的命名空间。 上一节中创建的 employee_poly 构造包含名为 .Engineer.Manager 的属性,这些属性在多态 SELECT 的术语中为 EngineerManager 提供命名空间。 在下面的示例中,我们可以使用 or_() 构造同时针对两个类创建条件:

>>> from sqlalchemy import or_
>>> employee_poly = with_polymorphic(Employee, [Engineer, Manager])
>>> stmt = (
...     select(employee_poly)
...     .where(
...         or_(
...             employee_poly.Manager.manager_name == "Eugene H. Krabs",
...             employee_poly.Engineer.engineer_info
...             == "Senior Customer Engagement Engineer",
...         )
...     )
...     .order_by(employee_poly.id)
... )
>>> objects = session.scalars(stmt).all()
SELECT  employee.id,  employee.name,  employee.type,  employee.company_id,  manager.id  AS  id_1,
manager.manager_name,  engineer.id  AS  id_2,  engineer.engineer_info
FROM  employee
LEFT  OUTER  JOIN  manager  ON  employee.id  =  manager.id
LEFT  OUTER  JOIN  engineer  ON  employee.id  =  engineer.id
WHERE  manager.manager_name  =  ?  OR  engineer.engineer_info  =  ?
ORDER  BY  employee.id
[...]  ('Eugene H. Krabs',  'Senior Customer Engagement Engineer')
>>> print(objects)
[Manager('Mr. Krabs'), Engineer('Squidward')]
```  ### 使用 `with_polymorphic` 进行别名处理

`with_polymorphic()` 构造作为 `aliased()` 的一种特殊情况,还提供了 `aliased()` 的基本功能,即对多态可选择本身进行“别名”处理。 具体来说,这意味着两个或多个引用相同类层次结构的 `with_polymorphic()` 实体可以同时在单个语句中使用。

要在联合继承映射中使用此功能,通常需要传递两个参数,`with_polymorphic.aliased` 和 `with_polymorphic.flat`。 `with_polymorphic.aliased` 参数指示多态可选择应该使用本结构唯一的别名来引用。 `with_polymorphic.flat` 参数特定于默认的 LEFT OUTER JOIN 多态可选择,并指示在语句中应使用更优化的别名形式。

为了说明此功能,下面的示例发出了对两个单独的多态实体 `Employee` 与 `Engineer`,以及 `Employee` 与 `Manager` 的 SELECT。由于这两个多态实体都将在其多态可选择中包括基本的 `employee` 表,因此必须应用别名以区分此表在其两个不同的上下文中。 这两个多态实体被视为两个单独的表,因此通常需要以某种方式彼此连接,如下例所示,其中实体在 `company_id` 列上与彼此连接,并附加一些针对 `Employee` / `Manager` 实体的额外限制条件:

```py
>>> manager_employee = with_polymorphic(Employee, [Manager], aliased=True, flat=True)
>>> engineer_employee = with_polymorphic(Employee, [Engineer], aliased=True, flat=True)
>>> stmt = (
...     select(manager_employee, engineer_employee)
...     .join(
...         engineer_employee,
...         engineer_employee.company_id == manager_employee.company_id,
...     )
...     .where(
...         or_(
...             manager_employee.name == "Mr. Krabs",
...             manager_employee.Manager.manager_name == "Eugene H. Krabs",
...         )
...     )
...     .order_by(engineer_employee.name, manager_employee.name)
... )
>>> for manager, engineer in session.execute(stmt):
...     print(f"{manager} {engineer}")
SELECT
employee_1.id,  employee_1.name,  employee_1.type,  employee_1.company_id,
manager_1.id  AS  id_1,  manager_1.manager_name,
employee_2.id  AS  id_2,  employee_2.name  AS  name_1,  employee_2.type  AS  type_1,
employee_2.company_id  AS  company_id_1,  engineer_1.id  AS  id_3,  engineer_1.engineer_info
FROM  employee  AS  employee_1
LEFT  OUTER  JOIN  manager  AS  manager_1  ON  employee_1.id  =  manager_1.id
JOIN
  (employee  AS  employee_2  LEFT  OUTER  JOIN  engineer  AS  engineer_1  ON  employee_2.id  =  engineer_1.id)
ON  employee_2.company_id  =  employee_1.company_id
WHERE  employee_1.name  =  ?  OR  manager_1.manager_name  =  ?
ORDER  BY  employee_2.name,  employee_1.name
[...]  ('Mr. Krabs',  'Eugene H. Krabs')
Manager('Mr. Krabs') Manager('Mr. Krabs')
Manager('Mr. Krabs') Engineer('SpongeBob')
Manager('Mr. Krabs') Engineer('Squidward')

在上面的示例中,with_polymorphic.flat的行为是多态选择保持为它们各自表的 LEFT OUTER JOIN,这些表本身被赋予匿名别名。还生成了右嵌套 JOIN。

当省略with_polymorphic.flat参数时,通常的行为是将每个多态可选择包含在子查询中,产生更冗长的形式:

>>> manager_employee = with_polymorphic(Employee, [Manager], aliased=True)
>>> engineer_employee = with_polymorphic(Employee, [Engineer], aliased=True)
>>> stmt = (
...     select(manager_employee, engineer_employee)
...     .join(
...         engineer_employee,
...         engineer_employee.company_id == manager_employee.company_id,
...     )
...     .where(
...         or_(
...             manager_employee.name == "Mr. Krabs",
...             manager_employee.Manager.manager_name == "Eugene H. Krabs",
...         )
...     )
...     .order_by(engineer_employee.name, manager_employee.name)
... )
>>> print(stmt)
SELECT  anon_1.employee_id,  anon_1.employee_name,  anon_1.employee_type,
anon_1.employee_company_id,  anon_1.manager_id,  anon_1.manager_manager_name,  anon_2.employee_id  AS  employee_id_1,
anon_2.employee_name  AS  employee_name_1,  anon_2.employee_type  AS  employee_type_1,
anon_2.employee_company_id  AS  employee_company_id_1,  anon_2.engineer_id,  anon_2.engineer_engineer_info
FROM
(SELECT  employee.id  AS  employee_id,  employee.name  AS  employee_name,  employee.type  AS  employee_type,
employee.company_id  AS  employee_company_id,
manager.id  AS  manager_id,  manager.manager_name  AS  manager_manager_name
FROM  employee  LEFT  OUTER  JOIN  manager  ON  employee.id  =  manager.id)  AS  anon_1
JOIN
(SELECT  employee.id  AS  employee_id,  employee.name  AS  employee_name,  employee.type  AS  employee_type,
employee.company_id  AS  employee_company_id,  engineer.id  AS  engineer_id,  engineer.engineer_info  AS  engineer_engineer_info
FROM  employee  LEFT  OUTER  JOIN  engineer  ON  employee.id  =  engineer.id)  AS  anon_2
ON  anon_2.employee_company_id  =  anon_1.employee_company_id
WHERE  anon_1.employee_name  =  :employee_name_2  OR  anon_1.manager_manager_name  =  :manager_manager_name_1
ORDER  BY  anon_2.employee_name,  anon_1.employee_name 

上述形式在历史上更容易移植到不一定支持右嵌套 JOIN 的后端,并且在使用with_polymorphic()时,“多态可选择”不是表的简单 LEFT OUTER JOIN 时,如使用具体表继承映射以及一般情况下使用替代多态可选择时,此形式也可能是合适的。### 在映射器上配置 with_polymorphic()

selectin_polymorphic()相似,with_polymorphic()构造也支持由映射器配置的版本,可以通过两种不同的方式进行配置,要么在基类上使用mapper.with_polymorphic参数,要么以更现代的形式在每个子类上使用Mapper.polymorphic_load参数,传递值"inline"

警告

对于加入继承映射,请优先在查询中显式使用with_polymorphic(),或者对于隐式急切子类加载使用Mapper.polymorphic_load"selectin",而不是使用本节中描述的映射器级mapper.with_polymorphic参数。此参数调用旨在重写 SELECT 语句中的 FROM 子句的复杂启发式方法,可能会干扰构造更复杂语句的构造,特别是那些引用相同映射实体的嵌套子查询。

例如,我们可以使用Mapper.polymorphic_load将我们的Employee映射声明为"inline",如下所示:

class Employee(Base):
    __tablename__ = "employee"
    id = mapped_column(Integer, primary_key=True)
    name = mapped_column(String(50))
    type = mapped_column(String(50))

    __mapper_args__ = {"polymorphic_identity": "employee", "polymorphic_on": type}

class Engineer(Employee):
    __tablename__ = "engineer"
    id = mapped_column(Integer, ForeignKey("employee.id"), primary_key=True)
    engineer_info = mapped_column(String(30))

    __mapper_args__ = {
        "polymorphic_load": "inline",
        "polymorphic_identity": "engineer",
    }

class Manager(Employee):
    __tablename__ = "manager"
    id = mapped_column(Integer, ForeignKey("employee.id"), primary_key=True)
    manager_name = mapped_column(String(30))

    __mapper_args__ = {
        "polymorphic_load": "inline",
        "polymorphic_identity": "manager",
    }

使用上述映射,针对Employee类的 SELECT 语句将自动假定在发出语句时将使用with_polymorphic(Employee, [Engineer, Manager])作为主要实体:

print(select(Employee))
SELECT  employee.id,  employee.name,  employee.type,  engineer.id  AS  id_1,
engineer.engineer_info,  manager.id  AS  id_2,  manager.manager_name
FROM  employee
LEFT  OUTER  JOIN  engineer  ON  employee.id  =  engineer.id
LEFT  OUTER  JOIN  manager  ON  employee.id  =  manager.id 

当使用映射器级“with polymorphic”时,查询也可以直接引用子类实体,其中它们隐式地表示多态查询中的连接表。在上面的例子中,我们可以自由地针对默认的Employee实体直接引用ManagerEngineer

print(
 select(Employee).where(
 or_(Manager.manager_name == "x", Engineer.engineer_info == "y")
 )
)
SELECT  employee.id,  employee.name,  employee.type,  engineer.id  AS  id_1,
engineer.engineer_info,  manager.id  AS  id_2,  manager.manager_name
FROM  employee
LEFT  OUTER  JOIN  engineer  ON  employee.id  =  engineer.id
LEFT  OUTER  JOIN  manager  ON  employee.id  =  manager.id
WHERE  manager.manager_name  =  :manager_name_1
OR  engineer.engineer_info  =  :engineer_info_1 

但是,如果我们需要在单独的别名上下文中引用Employee实体或其子实体,我们将再次直接使用with_polymorphic() 来定义这些别名实体,如使用 with_polymorphic 进行别名处理中所示。

为了更集中地控制多态可选项,可以使用更传统的映射器级多态控制形式,即Mapper.with_polymorphic 参数,配置在基类上。此参数接受的参数与with_polymorphic() 构造类似,但在联合继承映射中的常见用法是纯星号,表示所有子表应该 LEFT OUTER JOIN,如下所示:

class Employee(Base):
    __tablename__ = "employee"
    id = mapped_column(Integer, primary_key=True)
    name = mapped_column(String(50))
    type = mapped_column(String(50))

    __mapper_args__ = {
        "polymorphic_identity": "employee",
        "with_polymorphic": "*",
        "polymorphic_on": type,
    }

class Engineer(Employee):
    __tablename__ = "engineer"
    id = mapped_column(Integer, ForeignKey("employee.id"), primary_key=True)
    engineer_info = mapped_column(String(30))

    __mapper_args__ = {
        "polymorphic_identity": "engineer",
    }

class Manager(Employee):
    __tablename__ = "manager"
    id = mapped_column(Integer, ForeignKey("employee.id"), primary_key=True)
    manager_name = mapped_column(String(30))

    __mapper_args__ = {
        "polymorphic_identity": "manager",
    }

总的来说,with_polymorphic()Mapper.with_polymorphic 等选项所使用的 LEFT OUTER JOIN 格式从 SQL 和数据库优化器的角度来看可能比较繁琐;对于联合继承映射中子类属性的一般加载,可能更倾向于使用 selectin_polymorphic() 方法,或者将其映射级别等效设置为 Mapper.polymorphic_load"selectin",仅在需要时基于每个查询使用 with_polymorphic()。 ### 使用 with_polymorphic 过滤子类属性

with_polymorphic() 构造使得包含的子类映射器上的属性可用,通过包含允许引用子类的命名空间。在上一节中创建的employee_poly构造包括名为.Engineer.Manager的属性,它们为多态 SELECT 提供了EngineerManager的命名空间。在下面的示例中,我们可以使用or_() 构造同时对两个类创建条件:

>>> from sqlalchemy import or_
>>> employee_poly = with_polymorphic(Employee, [Engineer, Manager])
>>> stmt = (
...     select(employee_poly)
...     .where(
...         or_(
...             employee_poly.Manager.manager_name == "Eugene H. Krabs",
...             employee_poly.Engineer.engineer_info
...             == "Senior Customer Engagement Engineer",
...         )
...     )
...     .order_by(employee_poly.id)
... )
>>> objects = session.scalars(stmt).all()
SELECT  employee.id,  employee.name,  employee.type,  employee.company_id,  manager.id  AS  id_1,
manager.manager_name,  engineer.id  AS  id_2,  engineer.engineer_info
FROM  employee
LEFT  OUTER  JOIN  manager  ON  employee.id  =  manager.id
LEFT  OUTER  JOIN  engineer  ON  employee.id  =  engineer.id
WHERE  manager.manager_name  =  ?  OR  engineer.engineer_info  =  ?
ORDER  BY  employee.id
[...]  ('Eugene H. Krabs',  'Senior Customer Engagement Engineer')
>>> print(objects)
[Manager('Mr. Krabs'), Engineer('Squidward')]

使用 with_polymorphic 进行别名处理

with_polymorphic()构造,作为aliased()的一个特例,还提供了aliased()的基本特性,即多态可选项本身的“别名”。具体来说,这意味着两个或多个指向相同类层次结构的with_polymorphic()实体可以同时在单个语句中使用。

要在连接继承映射中使用此功能,通常我们希望传递两个参数,with_polymorphic.aliased以及with_polymorphic.flatwith_polymorphic.aliased参数指示多态可选项应该被引用为此结构唯一的别名。with_polymorphic.flat参数是特定于默认的 LEFT OUTER JOIN 多态可选项,并指示在语句中应该使用更优化的别名形式。

为了说明这个特性,下面的示例发出了一个选择两个单独的多态实体,EmployeeEngineer连接,以及EmployeeManager连接。由于这两个多态实体都将包含基本的employee表在其多态可选项中,必须应用别名以便在它们的两个不同的上下文中区分此表。这两个多态实体被视为两个单独的表,并且通常需要以某种方式相互连接,如下所示,在company_id列上与Employee / Manager实体一起加入了一些额外的限制条件:

>>> manager_employee = with_polymorphic(Employee, [Manager], aliased=True, flat=True)
>>> engineer_employee = with_polymorphic(Employee, [Engineer], aliased=True, flat=True)
>>> stmt = (
...     select(manager_employee, engineer_employee)
...     .join(
...         engineer_employee,
...         engineer_employee.company_id == manager_employee.company_id,
...     )
...     .where(
...         or_(
...             manager_employee.name == "Mr. Krabs",
...             manager_employee.Manager.manager_name == "Eugene H. Krabs",
...         )
...     )
...     .order_by(engineer_employee.name, manager_employee.name)
... )
>>> for manager, engineer in session.execute(stmt):
...     print(f"{manager} {engineer}")
SELECT
employee_1.id,  employee_1.name,  employee_1.type,  employee_1.company_id,
manager_1.id  AS  id_1,  manager_1.manager_name,
employee_2.id  AS  id_2,  employee_2.name  AS  name_1,  employee_2.type  AS  type_1,
employee_2.company_id  AS  company_id_1,  engineer_1.id  AS  id_3,  engineer_1.engineer_info
FROM  employee  AS  employee_1
LEFT  OUTER  JOIN  manager  AS  manager_1  ON  employee_1.id  =  manager_1.id
JOIN
  (employee  AS  employee_2  LEFT  OUTER  JOIN  engineer  AS  engineer_1  ON  employee_2.id  =  engineer_1.id)
ON  employee_2.company_id  =  employee_1.company_id
WHERE  employee_1.name  =  ?  OR  manager_1.manager_name  =  ?
ORDER  BY  employee_2.name,  employee_1.name
[...]  ('Mr. Krabs',  'Eugene H. Krabs')
Manager('Mr. Krabs') Manager('Mr. Krabs')
Manager('Mr. Krabs') Engineer('SpongeBob')
Manager('Mr. Krabs') Engineer('Squidward')

在上面的示例中,with_polymorphic.flat的行为是,多态的可选项仍然保持为它们各自表的 LEFT OUTER JOIN,并且它们本身被赋予匿名别名。还会产生右嵌套的 JOIN。

当省略with_polymorphic.flat参数时,通常行为是每个多态可选项被封装在一个子查询中,生成更详细的形式:

>>> manager_employee = with_polymorphic(Employee, [Manager], aliased=True)
>>> engineer_employee = with_polymorphic(Employee, [Engineer], aliased=True)
>>> stmt = (
...     select(manager_employee, engineer_employee)
...     .join(
...         engineer_employee,
...         engineer_employee.company_id == manager_employee.company_id,
...     )
...     .where(
...         or_(
...             manager_employee.name == "Mr. Krabs",
...             manager_employee.Manager.manager_name == "Eugene H. Krabs",
...         )
...     )
...     .order_by(engineer_employee.name, manager_employee.name)
... )
>>> print(stmt)
SELECT  anon_1.employee_id,  anon_1.employee_name,  anon_1.employee_type,
anon_1.employee_company_id,  anon_1.manager_id,  anon_1.manager_manager_name,  anon_2.employee_id  AS  employee_id_1,
anon_2.employee_name  AS  employee_name_1,  anon_2.employee_type  AS  employee_type_1,
anon_2.employee_company_id  AS  employee_company_id_1,  anon_2.engineer_id,  anon_2.engineer_engineer_info
FROM
(SELECT  employee.id  AS  employee_id,  employee.name  AS  employee_name,  employee.type  AS  employee_type,
employee.company_id  AS  employee_company_id,
manager.id  AS  manager_id,  manager.manager_name  AS  manager_manager_name
FROM  employee  LEFT  OUTER  JOIN  manager  ON  employee.id  =  manager.id)  AS  anon_1
JOIN
(SELECT  employee.id  AS  employee_id,  employee.name  AS  employee_name,  employee.type  AS  employee_type,
employee.company_id  AS  employee_company_id,  engineer.id  AS  engineer_id,  engineer.engineer_info  AS  engineer_engineer_info
FROM  employee  LEFT  OUTER  JOIN  engineer  ON  employee.id  =  engineer.id)  AS  anon_2
ON  anon_2.employee_company_id  =  anon_1.employee_company_id
WHERE  anon_1.employee_name  =  :employee_name_2  OR  anon_1.manager_manager_name  =  :manager_manager_name_1
ORDER  BY  anon_2.employee_name,  anon_1.employee_name 

上述形式在历史上更容易移植到不一定支持右嵌套 JOIN 的后端,并且在使用with_polymorphic()时,“多态选择”不是简单的左外连接表时,例如使用具体表继承映射以及一般情况下使用替代多态选择时,可能更合适。

在映射器上配置 with_polymorphic()

selectin_polymorphic()一样,with_polymorphic()构造也支持一个通过映射器配置的版本,可以通过两种不同的方式配置,一种是在基类上使用mapper.with_polymorphic参数,另一种是在每个子类上使用更现代的形式,在Mapper.polymorphic_load参数上传递值"inline"

警告

对于加入继承映射,请优先在查询中显式使用with_polymorphic(),或者对于隐式急切子类加载,请使用Mapper.polymorphic_load"selectin",而不是使用本节中描述的映射器级别的mapper.with_polymorphic参数。此参数调用复杂的启发式算法,旨在重写 SELECT 语句中的 FROM 子句,可能会干扰更复杂语句的构建,特别是那些引用相同映射实体的嵌套子查询。

例如,我们可以使用Mapper.polymorphic_load将我们的Employee映射状态声明为"inline",如下所示:

class Employee(Base):
    __tablename__ = "employee"
    id = mapped_column(Integer, primary_key=True)
    name = mapped_column(String(50))
    type = mapped_column(String(50))

    __mapper_args__ = {"polymorphic_identity": "employee", "polymorphic_on": type}

class Engineer(Employee):
    __tablename__ = "engineer"
    id = mapped_column(Integer, ForeignKey("employee.id"), primary_key=True)
    engineer_info = mapped_column(String(30))

    __mapper_args__ = {
        "polymorphic_load": "inline",
        "polymorphic_identity": "engineer",
    }

class Manager(Employee):
    __tablename__ = "manager"
    id = mapped_column(Integer, ForeignKey("employee.id"), primary_key=True)
    manager_name = mapped_column(String(30))

    __mapper_args__ = {
        "polymorphic_load": "inline",
        "polymorphic_identity": "manager",
    }

有了上述映射,针对Employee类的 SELECT 语句在发出语句时将自动假定使用with_polymorphic(Employee, [Engineer, Manager])作为主要实体:

print(select(Employee))
SELECT  employee.id,  employee.name,  employee.type,  engineer.id  AS  id_1,
engineer.engineer_info,  manager.id  AS  id_2,  manager.manager_name
FROM  employee
LEFT  OUTER  JOIN  engineer  ON  employee.id  =  engineer.id
LEFT  OUTER  JOIN  manager  ON  employee.id  =  manager.id 

当使用映射器级别的“多态”时,查询还可以直接引用子类实体,在这里它们隐式地代表了多态查询中的联接表。在上面的示例中,我们可以自由地直接引用ManagerEngineer对默认的Employee实体进行查询:

print(
 select(Employee).where(
 or_(Manager.manager_name == "x", Engineer.engineer_info == "y")
 )
)
SELECT  employee.id,  employee.name,  employee.type,  engineer.id  AS  id_1,
engineer.engineer_info,  manager.id  AS  id_2,  manager.manager_name
FROM  employee
LEFT  OUTER  JOIN  engineer  ON  employee.id  =  engineer.id
LEFT  OUTER  JOIN  manager  ON  employee.id  =  manager.id
WHERE  manager.manager_name  =  :manager_name_1
OR  engineer.engineer_info  =  :engineer_info_1 

但是,如果我们需要在单独的别名上下文中引用Employee实体或其子实体,我们将再次直接使用with_polymorphic()来定义这些别名实体,如使用别名与 with_polymorphic 中所示。

为了更加集中地控制多态可选择的内容,可以使用更为传统的映射器级别多态控制形式,即Mapper.with_polymorphic参数,配置在基类上。该参数接受与with_polymorphic()构造类似的参数,但在连接继承映射中的常见用法是使用纯*号,表示应该 LEFT OUTER JOIN 所有子表,例如:

class Employee(Base):
    __tablename__ = "employee"
    id = mapped_column(Integer, primary_key=True)
    name = mapped_column(String(50))
    type = mapped_column(String(50))

    __mapper_args__ = {
        "polymorphic_identity": "employee",
        "with_polymorphic": "*",
        "polymorphic_on": type,
    }

class Engineer(Employee):
    __tablename__ = "engineer"
    id = mapped_column(Integer, ForeignKey("employee.id"), primary_key=True)
    engineer_info = mapped_column(String(30))

    __mapper_args__ = {
        "polymorphic_identity": "engineer",
    }

class Manager(Employee):
    __tablename__ = "manager"
    id = mapped_column(Integer, ForeignKey("employee.id"), primary_key=True)
    manager_name = mapped_column(String(30))

    __mapper_args__ = {
        "polymorphic_identity": "manager",
    }

总的来说,with_polymorphic()所使用的 LEFT OUTER JOIN 格式以及诸如Mapper.with_polymorphic等选项,从 SQL 和数据库优化器的角度来看可能有些繁琐;对于连接继承映射中子类属性的一般加载,应该优先考虑selectin_polymorphic()方法,或者在映射器级别设置Mapper.polymorphic_load"selectin",只在需要时在每个查询中使用with_polymorphic()

连接到特定子类型或 with_polymorphic()实体

由于with_polymorphic()实体是aliased()的一个特殊情况,为了将多态实体视为连接的目标,特别是在使用relationship()构造作为 ON 子句时,我们使用与常规别名相同的技术,如 Using Relationship to join between aliased targets 中详细描述的,最简洁的方法是使用PropComparator.of_type()。在下面的示例中,我们说明了从父Company实体沿着一对多关系Company.employees进行连接,该关系在 setup 中配置为链接到Employee对象,使用with_polymorphic()实体作为目标:

>>> employee_plus_engineer = with_polymorphic(Employee, [Engineer])
>>> stmt = (
...     select(Company.name, employee_plus_engineer.name)
...     .join(Company.employees.of_type(employee_plus_engineer))
...     .where(
...         or_(
...             employee_plus_engineer.name == "SpongeBob",
...             employee_plus_engineer.Engineer.engineer_info
...             == "Senior Customer Engagement Engineer",
...         )
...     )
... )
>>> for company_name, emp_name in session.execute(stmt):
...     print(f"{company_name} {emp_name}")
SELECT  company.name,  employee.name  AS  name_1
FROM  company  JOIN  (employee  LEFT  OUTER  JOIN  engineer  ON  employee.id  =  engineer.id)  ON  company.id  =  employee.company_id
WHERE  employee.name  =  ?  OR  engineer.engineer_info  =  ?
[...]  ('SpongeBob',  'Senior Customer Engagement Engineer')
Krusty Krab SpongeBob
Krusty Krab Squidward

更直接地,PropComparator.of_type() 也用于任何类型的继承映射,以限制 relationship() 中的连接到 relationship() 的特定子类型。上述查询可以严格按照 Engineer 目标编写如下:

>>> stmt = (
...     select(Company.name, Engineer.name)
...     .join(Company.employees.of_type(Engineer))
...     .where(
...         or_(
...             Engineer.name == "SpongeBob",
...             Engineer.engineer_info == "Senior Customer Engagement Engineer",
...         )
...     )
... )
>>> for company_name, emp_name in session.execute(stmt):
...     print(f"{company_name} {emp_name}")
SELECT  company.name,  employee.name  AS  name_1
FROM  company  JOIN  (employee  JOIN  engineer  ON  employee.id  =  engineer.id)  ON  company.id  =  employee.company_id
WHERE  employee.name  =  ?  OR  engineer.engineer_info  =  ?
[...]  ('SpongeBob',  'Senior Customer Engagement Engineer')
Krusty Krab SpongeBob
Krusty Krab Squidward

如上所示,直接加入到 Engineer 目标,而不是 with_polymorphic(Employee, [Engineer]) 的 “多态可选择项”,具有使用内连接而不是左外连接的有用特性,从 SQL 优化器的角度来看,通常更具性能。

多态子类型的急加载

使用 PropComparator.of_type() 方法的示例见前一节中的 Select.join() 方法,同样可以等效地应用于关系加载器选项,例如 selectinload()joinedload()

作为基本示例,如果我们希望加载 Company 对象,并且另外急加载 Company.employees 的所有元素,使用 with_polymorphic() 构造针对完整层次结构,我们可以编写如下:

>>> all_employees = with_polymorphic(Employee, "*")
>>> stmt = select(Company).options(selectinload(Company.employees.of_type(all_employees)))
>>> for company in session.scalars(stmt):
...     print(f"company: {company.name}")
...     print(f"employees: {company.employees}")
SELECT  company.id,  company.name
FROM  company
[...]  ()
SELECT  employee.company_id  AS  employee_company_id,  employee.id  AS  employee_id,
employee.name  AS  employee_name,  employee.type  AS  employee_type,  manager.id  AS  manager_id,
manager.manager_name  AS  manager_manager_name,  engineer.id  AS  engineer_id,
engineer.engineer_info  AS  engineer_engineer_info
FROM  employee
LEFT  OUTER  JOIN  manager  ON  employee.id  =  manager.id
LEFT  OUTER  JOIN  engineer  ON  employee.id  =  engineer.id
WHERE  employee.company_id  IN  (?)
[...]  (1,)
company:  Krusty  Krab
employees:  [Manager('Mr. Krabs'),  Engineer('SpongeBob'),  Engineer('Squidward')] 

上述查询可直接与前一节中演示的 selectin_polymorphic() 版本进行比较 将 selectin_polymorphic() 应用于现有急加载。

另见

将 selectin_polymorphic() 应用于现有急加载 - 演示了与上述相同的等效示例,使用 selectin_polymorphic() 替代 ### 多态子类型的急加载

使用PropComparator.of_type()方法,如前一节中的Select.join()方法所示,也可以等效地应用于关系加载器选项,例如selectinload()joinedload()

作为基本示例,如果我们希望加载Company对象,并使用with_polymorphic()构造针对完整层次结构的,同时急切地加载Company.employees的所有元素,我们可以写成:

>>> all_employees = with_polymorphic(Employee, "*")
>>> stmt = select(Company).options(selectinload(Company.employees.of_type(all_employees)))
>>> for company in session.scalars(stmt):
...     print(f"company: {company.name}")
...     print(f"employees: {company.employees}")
SELECT  company.id,  company.name
FROM  company
[...]  ()
SELECT  employee.company_id  AS  employee_company_id,  employee.id  AS  employee_id,
employee.name  AS  employee_name,  employee.type  AS  employee_type,  manager.id  AS  manager_id,
manager.manager_name  AS  manager_manager_name,  engineer.id  AS  engineer_id,
engineer.engineer_info  AS  engineer_engineer_info
FROM  employee
LEFT  OUTER  JOIN  manager  ON  employee.id  =  manager.id
LEFT  OUTER  JOIN  engineer  ON  employee.id  =  engineer.id
WHERE  employee.company_id  IN  (?)
[...]  (1,)
company:  Krusty  Krab
employees:  [Manager('Mr. Krabs'),  Engineer('SpongeBob'),  Engineer('Squidward')] 

上述查询可以直接与前一节中将 selectin_polymorphic()应用于现有急切加载中所示的selectin_polymorphic()版本进行比较。

另请参阅

将 selectin_polymorphic()应用于现有急切加载 - 演示了与上述相同的例子,但使用了selectin_polymorphic()代替

单一继承映射的 SELECT 语句

单一表继承设置

本节讨论单一表继承,描述在单一表继承中使用单个表来表示层次结构中的多个类。

查看此部分的 ORM 设置。

与连接继承映射相比,为单一继承映射构造 SELECT 语句通常更简单,因为对于全单一继承层次结构,只有一个表。

无论继承层次结构是全单一继承还是具有混合连接和单一继承,单一继承的 SELECT 语句通过使用附加的 WHERE 条件限制 SELECT 语句来区分对基类和子类的查询。

例如,针对Employee的单一继承示例映射的查询将使用表的简单 SELECT 来加载ManagerEngineerEmployee类型的对象:

>>> stmt = select(Employee).order_by(Employee.id)
>>> for obj in session.scalars(stmt):
...     print(f"{obj}")
BEGIN  (implicit)
SELECT  employee.id,  employee.name,  employee.type
FROM  employee  ORDER  BY  employee.id
[...]  ()
Manager('Mr. Krabs')
Engineer('SpongeBob')
Engineer('Squidward')

当为特定子类发出加载时,将向 SELECT 中添加附加条件以限制行,例如在下面执行针对Engineer实体的 SELECT:

>>> stmt = select(Engineer).order_by(Engineer.id)
>>> objects = session.scalars(stmt).all()
SELECT  employee.id,  employee.name,  employee.type,  employee.engineer_info
FROM  employee
WHERE  employee.type  IN  (?)  ORDER  BY  employee.id
[...]  ('engineer',)
>>> for obj in objects:
...     print(f"{obj}")
Engineer('SpongeBob')
Engineer('Squidward')

优化单一继承的属性加载

单继承映射关于如何 SELECT 子类上的属性的默认行为类似于连接继承的行为,即子类特定的属性默认情况下仍然会发出第二个 SELECT。在下面的示例中,加载了类型为 Manager 的单个 Employee,但是由于请求的类是 Employee,所以 Manager.manager_name 属性默认情况下不会存在,并且在访问时会发出额外的 SELECT:

>>> mr_krabs = session.scalars(select(Employee).where(Employee.name == "Mr. Krabs")).one()
BEGIN  (implicit)
SELECT  employee.id,  employee.name,  employee.type
FROM  employee
WHERE  employee.name  =  ?
[...]  ('Mr. Krabs',)
>>> mr_krabs.manager_name
SELECT  employee.manager_name  AS  employee_manager_name
FROM  employee
WHERE  employee.id  =  ?  AND  employee.type  IN  (?)
[...]  (1,  'manager')
'Eugene H. Krabs'

要改变这种行为,对于单继承,与连接继承加载中使用的相同的一般概念也适用于急切地加载这些额外属性,包括使用 selectin_polymorphic() 选项以及 with_polymorphic() 选项,后者只是简单地包含了额外的列,并且从 SQL 视角来看对于单继承映射更为高效:

>>> employees = with_polymorphic(Employee, "*")
>>> stmt = select(employees).order_by(employees.id)
>>> objects = session.scalars(stmt).all()
BEGIN  (implicit)
SELECT  employee.id,  employee.name,  employee.type,
employee.manager_name,  employee.engineer_info
FROM  employee  ORDER  BY  employee.id
[...]  ()
>>> for obj in objects:
...     print(f"{obj}")
Manager('Mr. Krabs')
Engineer('SpongeBob')
Engineer('Squidward')
>>> objects[0].manager_name
'Eugene H. Krabs'

由于加载单继承子类映射的开销通常很小,因此建议单继承映射在那些预计其特定子类属性加载是常见的子类中包含 Mapper.polymorphic_load 参数,并将其设置为 "inline"。下面是一个修改后的示例,说明了这个设置的一个示例:

>>> class Base(DeclarativeBase):
...     pass
>>> class Employee(Base):
...     __tablename__ = "employee"
...     id: Mapped[int] = mapped_column(primary_key=True)
...     name: Mapped[str]
...     type: Mapped[str]
...
...     def __repr__(self):
...         return f"{self.__class__.__name__}({self.name!r})"
...
...     __mapper_args__ = {
...         "polymorphic_identity": "employee",
...         "polymorphic_on": "type",
...     }
>>> class Manager(Employee):
...     manager_name: Mapped[str] = mapped_column(nullable=True)
...     __mapper_args__ = {
...         "polymorphic_identity": "manager",
...         "polymorphic_load": "inline",
...     }
>>> class Engineer(Employee):
...     engineer_info: Mapped[str] = mapped_column(nullable=True)
...     __mapper_args__ = {
...         "polymorphic_identity": "engineer",
...         "polymorphic_load": "inline",
...     }

有了上面的映射,ManagerEngineer 类将自动在针对 Employee 实体的 SELECT 语句中包含它们的列:

>>> print(select(Employee))
SELECT  employee.id,  employee.name,  employee.type,
employee.manager_name,  employee.engineer_info
FROM  employee 

优化单继承属性加载

单继承映射关于如何 SELECT 子类上的属性的默认行为类似于连接继承的行为,即子类特定的属性默认情况下仍然会发出第二个 SELECT。在下面的示例中,加载了类型为 Manager 的单个 Employee,但是由于请求的类是 Employee,所以 Manager.manager_name 属性默认情况下不会存在,并且在访问时会发出额外的 SELECT:

>>> mr_krabs = session.scalars(select(Employee).where(Employee.name == "Mr. Krabs")).one()
BEGIN  (implicit)
SELECT  employee.id,  employee.name,  employee.type
FROM  employee
WHERE  employee.name  =  ?
[...]  ('Mr. Krabs',)
>>> mr_krabs.manager_name
SELECT  employee.manager_name  AS  employee_manager_name
FROM  employee
WHERE  employee.id  =  ?  AND  employee.type  IN  (?)
[...]  (1,  'manager')
'Eugene H. Krabs'

要改变这种行为,对于单继承,与连接继承加载中使用的相同的一般概念也适用于急切地加载这些额外属性,包括使用 selectin_polymorphic() 选项以及 with_polymorphic() 选项,后者只是简单地包含了额外的列,并且从 SQL 视角来看对于单继承映射更为高效:

>>> employees = with_polymorphic(Employee, "*")
>>> stmt = select(employees).order_by(employees.id)
>>> objects = session.scalars(stmt).all()
BEGIN  (implicit)
SELECT  employee.id,  employee.name,  employee.type,
employee.manager_name,  employee.engineer_info
FROM  employee  ORDER  BY  employee.id
[...]  ()
>>> for obj in objects:
...     print(f"{obj}")
Manager('Mr. Krabs')
Engineer('SpongeBob')
Engineer('Squidward')
>>> objects[0].manager_name
'Eugene H. Krabs'

由于加载单继承子类映射的开销通常很小,因此建议在那些预计其特定子类属性的加载是常见的单继承映射中,将Mapper.polymorphic_load参数设置为"inline"。下面是一个示例,演示了 setup 如何修改以包含此选项:

>>> class Base(DeclarativeBase):
...     pass
>>> class Employee(Base):
...     __tablename__ = "employee"
...     id: Mapped[int] = mapped_column(primary_key=True)
...     name: Mapped[str]
...     type: Mapped[str]
...
...     def __repr__(self):
...         return f"{self.__class__.__name__}({self.name!r})"
...
...     __mapper_args__ = {
...         "polymorphic_identity": "employee",
...         "polymorphic_on": "type",
...     }
>>> class Manager(Employee):
...     manager_name: Mapped[str] = mapped_column(nullable=True)
...     __mapper_args__ = {
...         "polymorphic_identity": "manager",
...         "polymorphic_load": "inline",
...     }
>>> class Engineer(Employee):
...     engineer_info: Mapped[str] = mapped_column(nullable=True)
...     __mapper_args__ = {
...         "polymorphic_identity": "engineer",
...         "polymorphic_load": "inline",
...     }

有了上面的映射,ManagerEngineer类的列将自动包含在针对Employee实体的 SELECT 语句中:

>>> print(select(Employee))
SELECT  employee.id,  employee.name,  employee.type,
employee.manager_name,  employee.engineer_info
FROM  employee 

继承加载 API

对象名称 描述
selectin_polymorphic(base_cls, classes) 指示应针对特定子类的所有属性进行急切加载。
with_polymorphic(base, classes[, selectable, flat, ...]) 产生一个AliasedClass构造,该构造指定了给定基类的后代映射器的列。
function sqlalchemy.orm.with_polymorphic(base: Type[_O] | Mapper[_O], classes: Literal['*'] | Iterable[Type[Any]], selectable: Literal[False, None] | FromClause = False, flat: bool = False, polymorphic_on: ColumnElement[Any] | None = None, aliased: bool = False, innerjoin: bool = False, adapt_on_names: bool = False, _use_mapper_path: bool = False) → AliasedClass[_O]

产生一个AliasedClass构造,该构造指定了给定基类的后代映射器的列。

使用这种方法将确保每个子类映射器的表都包含在 FROM 子句中,并允许对这些表使用 filter()条件。结果实例也将已经加载了那些列,因此不需要对这些列进行“后获取”。

请参阅

使用 with_polymorphic() - with_polymorphic()的全面讨论。

参数:

  • base – 要别名化的基类。

  • classes – 单个类或映射器,或者继承自基类的类/映射器列表。或者,它也可以是字符串'*',在这种情况下,所有下降的映射类将被添加到 FROM 子句中。

  • aliased – 当为 True 时,可选择的将被别名。对于 JOIN,这意味着 JOIN 将从子查询中 SELECT,除非设置了with_polymorphic.flat标志为 True,这对于更简单的用例是推荐的。

  • flat – 布尔值,将被传递到 FromClause.alias() 调用,以便 Join 对象的别名将别名为加入内的各个表,而不是创建子查询。这通常受到所有现代数据库的支持,关于右嵌套连接,通常会生成更有效的查询。只要生成的 SQL 有效,建议设置此标志。

  • selectable

    将用于替代生成的 FROM 子句的表或子查询。如果所需的任何类使用具体表继承,这个参数是必需的,因为 SQLAlchemy 目前无法自动在表之间生成 UNION。如果使用,selectable 参数必须表示每个映射类映射的所有表和列的完整集。否则,未解释的映射列将直接附加到 FROM 子句,这通常会导致结果不正确。

    当将其保留在默认值 False 时,分配给基本 mapper 的多态可选择将用于选择行。但是,它也可以传递为 None,这将绕过配置的多态可选择,而是为给定的目标类构造一个临时选择; 对于联接表继承,这将是包含所有目标映射器及其子类的联接。

  • polymorphic_on – 作为给定可选择对象的“鉴别器”列使用的列。如果未给出,则将使用基类的 mapper 的 polymorphic_on 属性(如果有)。这对于默认不具有多态加载行为的映射非常有用。

  • innerjoin – 如果为 True,将使用 INNER JOIN。只有在仅查询一个特定子类型时才应指定此选项

  • adapt_on_names

    aliased.adapt_on_names 参数传递给别名对象。在给定可选择对象与现有映射可选择对象不直接相关的情况下,这可能会很有用。

    新版本 1.4.33 中新增。

function sqlalchemy.orm.selectin_polymorphic(base_cls: _EntityType[Any], classes: Iterable[Type[Any]]) → _AbstractLoad

表示应该对子类特定的所有属性进行急切加载。

这使用了一个额外的 SELECT 与所有匹配的主键值进行 IN 操作,它是对 mapper.polymorphic_load 参数上的 "selectin" 设置的每个查询的类似。

新版本 1.2 中新增。

另请参见

使用 selectin_polymorphic()

ORM-启用的 INSERT、UPDATE 和 DELETE 语句

原文:docs.sqlalchemy.org/en/20/orm/queryguide/dml.html

关于本文档

本节利用了首次在 SQLAlchemy 统一教程中展示的 ORM 映射,如声明映射类一节所示,以及映射类继承层次结构一节中展示的继承映射。

查看此页面的 ORM 设置。

除了处理 ORM 启用的Select对象外,Session.execute()方法还可以容纳 ORM 启用的InsertUpdateDelete对象,它们分别以各种方式用于一次性插入、更新或删除多个数据库行。此外,还有特定于方言的支持 ORM 启用的“upserts”,这是一种自动使用 UPDATE 来处理已经存在的行的 INSERT 语句。

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

ORM 用例 使用的 DML 构造 使用以下方式传递数据 是否支持 RETURNING? 是否支持多表映射?
ORM 批量插入语句 insert() 字典列表到Session.execute.params
使用 SQL 表达式的 ORM 批量插入 insert() 使用Insert.values()Session.execute.params
使用每行 SQL 表达式进行 ORM 批量插入 insert() 字典列表Insert.values()
ORM “upsert” 语句 insert() 字典列表Insert.values()
通过主键进行 ORM 批量更新 update() 字典列表Session.execute.params
使用自定义 WHERE 条件的 ORM UPDATE 和 DELETE update(), delete() 关键字Update.values() 部分,需要手动步骤

ORM 批量插入语句

一个insert()构造可以根据 ORM 类构建,并传递给Session.execute()方法。发送到Session.execute.params参数的参数字典列表,与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 系列相比的行为变更,在那里 Insert 将以 Core 为中心的方式解释,使用列名作为值键;现在接受 ORM 属性键。通过将执行选项 {"dml_strategy": "raw"}传递给 Session.execute()Session.execution_options 参数,可以使用 Core 风格的功能。

使用 RETURNING 获取新对象

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

举个例子,我们可以运行与之前相同的语句,同时使用 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 采用了由 SQLite 后端请求的插入多个值功能所使用的形式,在这里,单个参数字典被嵌入到一个单个的 INSERT 语句中,以便可以使用 RETURNING。

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

将返回的记录与输入数据顺序相关联

在使用带 RETURNING 的批量 INSERT 时,重要的是要注意,大多数数据库后端不提供返回的 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 架构中实现。

参见

将返回的行与参数集相关联 - 介绍了确保输入数据和结果行之间对应关系的方法背景,而不会显著降低性能 ### 使用异构参数字典

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 语句,正确地将服务器生成的主键值传递给依赖于它们的表行。此处还支持 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') 

提示

加入继承映射的批量插入要求 ORM 在内部使用Insert.returning.sort_by_parameter_order参数,以便它可以将来自基表的 RETURNING 行的主键值与用于插入“子”表的参数集相关联,这就是为什么上面示例中的 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]

如果我们想要插入一系列具有唯一message字段的LogRecord元素,但是我们希望对所有行应用 SQL 函数now(),我们可以在Insert.values()中传递timestamp,然后使用“bulk”模式传递额外的记录:

>>> 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 表达式的情况有用,也适用于使用 ORM 的“upsert”语句时,本章后面的文档中有介绍,位于 ORM “upsert” Statements。

下面是一个构造性的示例,其中嵌入了每行 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 提供的批处理; 语句需要确保参数的总数不超过后端数据库施加的限制。

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

另请参阅

ORM “upsert” Statements ### Legacy Session Bulk INSERT Methods

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 构造,这些构造还具有执行“upserts”或将参数集中的现有行转换为近似 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 兼容,因为它们可以像在 ORM Bulk Insert with Per Row SQL Expressions 中所记录的那样使用与 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 语句的使用方式与在 ORM Bulk Insert with Per Row SQL Expressions 中演示的方式相同,因此可以传递任何列表达式或相关的 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 构造可以与 Session.execute() 以类似的方式使用,就像 ORM 批量插入语句中描述的使用 Insert 语句一样,传递一个参数字典列表,每个字典表示对应单个主键值的单个行。这种用法不应与在 ORM 中更常见的使用 Update 语句的方式混淆,该方式使用显式的 WHERE 子句,在 ORM UPDATE and DELETE with Custom WHERE Criteria 中有文档记录。

对于“批量”版本的 UPDATE,通过 ORM 类构造一个update()语句,并传递给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 批量更新

当:

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

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

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

为了调用不使用“按主键的 ORM 批量更新”的 UPDATE 语句,直接使用 Session.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 批量更新的行为与使用映射进行批量插入时类似;如 联合表继承的批量插入 中所述,批量更新操作将为映射中表示的每个表发出一条 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)]
<...>
```  ### 旧版会话批量更新方法

如传统会话批量 INSERT 方法所讨论的,`Session.bulk_update_mappings()`方法是批量更新的传统形式,当解释具有给定主键参数的`update()`语句时,ORM 在内部使用它;但是,当使用传统版本时,诸如会话同步支持之类的功能是不包括的。

下面的示例:

```py
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"},
 ],
)

请参见

传统会话批量 INSERT 方法 ## 使用自定义 WHERE 条件的 ORM UPDATE 和 DELETE

当使用自定义 WHERE 条件构造UpdateDelete构造时(即使用Update.where()Delete.where()方法),可以通过将它们传递给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-启用的 Update 和 Delete 的重要说明和注意事项

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 条件会自动包含。这仅适用于没有自己表的子类映射器。

  • ORM 更新和删除操作支持 with_loader_criteria() 选项;此处的条件将被添加到正在发出的 UPDATE 或 DELETE 语句的条件中,并在“同步”过程中考虑。

  • 要拦截启用 ORM 的 UPDATE 和 DELETE 操作以使用事件处理程序,请使用SessionEvents.do_orm_execute()事件。 ### 选择同步策略

在使用update()delete()与启用 ORM 执行一起使用Session.execute()时,将存在额外的 ORM 特定功能,该功能将同步语句更改的状态与当前存在于Session的 identity map 中的对象的状态。通过“同步”,我们指的是更新的属性将使用新值刷新,或者至少过期,以便在下次访问时重新填充其新值,并且删除的对象将移至 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.python_impl 参数指示将由"evaluate"同步策略使用的 Python 函数。

    2.0 版中的新功能。

    警告

    如果要在具有许多已过期对象的Session上运行 UPDATE 操作,则应避免使用"evaluate"策略,因为它将必须刷新对象以便根据给定的 WHERE 条件测试它们,这将为每个对象发出一个 SELECT。在这种情况下,特别是如果后端支持 RETURNING,则应优先选择"fetch"策略。

  • False - 不同步会话。该选项对于不支持 RETURNING 的后端可能很有用,其中无法使用"evaluate"策略。在这种情况下,Session 中对象的状态不变,不会自动对应于发出的 UPDATE 或 DELETE 语句,如果存在通常与匹配行对应的对象。### 使用 UPDATE/DELETE 和自定义 WHERE 条件的 RETURNING

UpdateBase.returning() 方法与启用 ORM 的 UPDATE 和 DELETE 以 WHERE 条件完全兼容。完整的 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 同步策略兼容,fetch 同样使用 RETURNING。ORM 将适当地组织 RETURNING 中的列,以便同步进程顺利进行,并且返回的 Result 将以请求的实体和 SQL 列的请求顺序包含。

2.0 版中的新功能:UpdateBase.returning() 可用于启用 ORM 的 UPDATE 和 DELETE,并仍保留与 fetch 同步策略的完全兼容性。### 使用自定义 WHERE 条件进行连接表继承的 UPDATE/DELETE

带有 WHERE 条件的 UPDATE/DELETE 功能,不像 基于主键的 ORM 大规模 UPDATE,每次调用 Session.execute() 时只发出单个 UPDATE 或 DELETE 语句。这意味着当针对多表映射运行 update()delete() 语句时,如连接表继承映射中的子类,该语句必须符合后端当前的功能,这可能包括后端不支持引用多个表的 UPDATE 或 DELETE 语句,或者仅对此有限支持。这意味着对于诸如连接继承子类之类的映射,UPDATE/DELETE 功能的 ORM 版本只能在有限程度上使用或根本无法使用,具体取决于具体情况。

对于连接表子类发出多行 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 多行连接继承对象,应分别发出针对每个表的 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 and DELETE with Custom WHERE Criteria 描述的部分相同的功能。主要区别在于旧版方法不提供显式的 RETURNING 支持。

请参阅

Query.update()

Query.delete() ## ORM 批量插入语句

可以基于 ORM 类构建 insert() 构造,并将其传递给 Session.execute() 方法。发送到 Session.execute.params 参数的参数字典列表,与 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')]
<...>

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

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

使用 RETURNING 获取新对象

批量 ORM 插入功能支持为选定的后端进行 INSERT..RETURNING,该功能可以返回一个 Result 对象,该对象可以返回单个列以及对应于新生成记录的完全构造的 ORM 对象。INSERT..RETURNING 需要使用支持 SQL RETURNING 语法以及支持带有 RETURNING 的 executemany 的后端;除了 MySQL(包括 MariaDB)之外,所有 SQLAlchemy 包含的 后端都支持此功能。

例如,我们可以运行与之前相同的语句,添加使用 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 采用了由 SQLite 后端请求的 insertmanyvalues 功能使用的形式,其中个别参数字典被内联到单个 INSERT 语句中,以便使用 RETURNING。

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

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

使用带有 RETURNING 的批量 INSERT 时,重要的是要注意,大多数数据库后端不保证从 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 值的行将使用不引用这些列的语句进行插入;这样做的理由是,包含服务器端插入默认值的后端和模式可能对 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')
... 

在上面的示例中,四行的批量插入被分成三个单独的语句,第二个语句重新格式化以不引用包含 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 批量插入建立在传统的 unit of work 系统使用的内部系统之上,以发出 INSERT 语句。这意味着对于一个被映射到多个表的 ORM 实体,通常是使用 joined table inheritance 映射的实体,批量插入操作将为每个由映射表示的表发出一个 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') 

提示

插入连接继承映射的批量操作要求 ORM 内部使用 Insert.returning.sort_by_parameter_order 参数,以便它可以将来自 RETURNING 行的主键值从基表相关联到用于插入到“子”表中的参数集,这就是为什么上面示例中的 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]

如果我们想要插入一系列具有唯一 message 字段的 LogRecord 元素,但是我们希望将 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 表达式的情况下可能有用,并且在使用 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": "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 语句。

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

  • 不可用于核心级别的规模优化,例如 insertmanyvalues 提供的批处理;语句需要确保参数的总数不超过由支持数据库施加的限制。

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

另请参见

ORM “upsert” 语句 ### 传统会话批量插入方法

Session 包括执行“批量”插入和更新语句的传统方法。 这些方法与 SQLAlchemy 2.0 版本的这些功能共享实现,描述在 ORM 批量插入语句 和 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构造,这些构造还具有执行“upserts”或将参数集中的现有行转换为近似 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构造本身一样使用,方法是将要插入的期望行嵌入到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()与在 ORM Bulk Insert with Per Row SQL Expressions 中演示的方式一样与 upsert 语句一起工作,以便传递任何列表达式或相关的 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 对象的 标识映射 中表示了主键标识。

另见

已有对象填充 ### 使用 RETURNING 获取新对象

批量 ORM 插入功能支持选定后端的 INSERT..RETURNING,它可以返回一个 Result 对象,该对象可以返回单独的列以及与新生成记录相对应的完全构造的 ORM 对象。INSERT..RETURNING 需要使用支持 SQL RETURNING 语法以及支持带 RETURNING 的 executemany 的后端;除了 MySQL(MariaDB 已包含在内)之外,此功能在所有 SQLAlchemy 包含的 后端中都可用。

例如,我们可以运行与之前相同的语句,添加对 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 采用了由 SQLite 后端请求的 insertmanyvalues 功能使用的形式,其中个别参数字典被内联到单个 INSERT 语句中,以便使用 RETURNING。

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

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

在使用带有 RETURNING 的批量 INSERT 时,重要的是要注意,大多数数据库后端没有明确保证返回的 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 行与参数集相关联 - 关于采取的方法背景,以确保输入数据与结果行之间的对应关系,而不会显著降低性能 #### 将 RETURNING 记录与输入数据顺序相关联

当使用带有 RETURNING 的批量 INSERT 时,重要的是要注意,大多数数据库后端不保证返回 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;其理由是后端和包含服务器端 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')
... 

上面,四行的批量插入被分解为三个单独的语句,第二个语句重新格式化以不引用包含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')]
... 

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

从版本 2.0.23 开始:添加了render_nulls执行选项,它镜像了传统Session.bulk_insert_mappings.render_nulls参数的行为。

批量插入联合表继承

ORM 批量插入建立在传统工作单元系统中使用的内部系统之上,以发出 INSERT 语句。这意味着对于映射到多个表的 ORM 实体,通常是使用联合表继承映射的实体,批量插入操作将为映射的每个表发出一个 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') 

提示

批量插入联合继承映射要求 ORM 在内部使用Insert.returning.sort_by_parameter_order参数,以便它可以将 RETURNING 表中的主键值与用于插入“子”表的参数集相关联,这就是为什么上面的 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]

如果我们想要插入一系列具有唯一 message 字段的 LogRecord 元素,但是我们希望将 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 表达式的情况下非常有用,并且在使用 ORM 进行“upsert”语句时也会使用,后文会在本章节中的 ORM “upsert” Statements 进行详细记录。

以下是嵌入每行 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 所提供的批处理等核心级别的规模优化;语句将需要确保参数总数不超过由后端数据库施加的限制。

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

另请参阅

ORM“upsert”语句 #### 使用每行 SQL 表达式进行 ORM 批量插入

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": "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 提供的批处理;语句需要确保参数的总数不超过由支持数据库施加的限制。

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

另请参阅

ORM “upsert”语句

旧版会话批量插入方法

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"}])

另请参阅

旧版会话批量 UPDATE 方法

ORM “upsert” 语句

SQLAlchemy 的部分后端可能包含特定于方言的Insert构造,此外还具有执行“upserts”或将参数集中的现有行转换为近似 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构造本身一样使用,即通过在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 语句的工作方式相同,就像在 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 对象的身份映射中表示了主键标识。

另请参阅

使用 RETURNING 的 upsert 语句 #### 使用 RETURNING 语句的合并插入

从 SQLAlchemy ORM 的角度来看,upsert 语句看起来像是常规的 Insert 构造,这包括 Insert.returning() 在与示例 每行 SQL 表达式的 ORM 批量插入 中展示的方式上与 upsert 语句一样工作,因此可以传递任何列表达式或相关的 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 构造可以与 Session.execute() 一起使用,类似于描述的 Insert 语句在 ORM 批量插入语句 中的使用方式,传递许多参数字典的列表,每个字典代表一个对应于单个主键值的单独行。这种用法不应与更常见的使用 Update 语句与 ORM 一起使用的方式混淆,使用显式的 WHERE 子句,该方式在 ORM 更新和删除自定义 WHERE 条件 中有记录。

对于 UPDATE 的“批量”版本,将根据 ORM 类制作一个 update() 构造,并传递给 Session.execute() 方法;生成的 Update 对象应该没有值,通常也没有 WHERE 条件,也就是说,不使用 Update.values() 方法,通常也不使用 Update.where(),但在需要添加额外过滤条件的不寻常情况下可能会使用。

传递包含完整主键值的参数字典列表以及 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)]
<...>

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

像批量插入功能一样,这里也支持异构参数列表,其中参数将被分组为更新运行的子批次。

在 2.0.11 版本中更改:可以使用Update.where()方法添加额外的 WHERE 条件与 ORM 按主键批量更新相结合。但是,此条件始终是额外添加的,这包括主键值。

在使用“按主键批量更新”功能时,不支持 RETURNING 功能;多个参数字典列表必须使用 DBAPI 的 executemany,通常情况下不支持结果行。

在 2.0 版本中更改:将Update构造传递给Session.execute()方法,以及参数字典列表,现在会调用“批量更新”,这与传统的Session.bulk_update_mappings()方法使用相同的功能。这是与 1.x 系列不同的行为更改,1.x 系列中的Update只支持显式的 WHERE 条件和内联 VALUES。

禁用多参数集 UPDATE 语句的按主键批量 ORM 更新

当满足以下条件时,自动使用 ORM 按主键批量更新功能:

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

  2. 使用Session执行语句,而不是核心Connection

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

为了在不使用“ORM 按主键批量更新”功能的情况下调用 UPDATE 语句,请直接针对Connection使用Session.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 批量更新与 ORM 批量插入具有类似的行为;如在 Bulk INSERT for Joined Table Inheritance 中所述,批量 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 批量更新方法

如在旧版 Session 批量插入方法中讨论的那样,`Session.bulk_update_mappings()`方法是批量更新的旧版形式,ORM 在解释给定带有主键参数的`update()`语句时内部使用;但是,当使用旧版时,不包括诸如会话同步支持等功能。

下面的示例:

```py
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"},
 ],
)

另请参阅

旧版 Session 批量插入方法 ### 禁用 UPDATE 语句的多参数集的基于主键的批量 ORM 更新

当满足以下条件时,会自动使用基于主键的 ORM 批量更新功能,该功能对每个包含主键值的记录运行 UPDATE 语句,并包括每个主键值的 WHERE 条件:

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

  2. 使用Session执行该语句,而不是使用核心Connection

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

为了调用 UPDATE 语句而不使用“基于主键的 ORM 批量更新”,直接使用Session.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 批量更新在使用具有联合表继承的映射时与 ORM 批量插入具有相似的行为;正如联合表继承的批量插入中所描述的,批量更新操作将为映射中表示的每个表发出一个更新语句,其中给定的参数包括要更新的值(未受影响的表将被跳过)。

示例:

>>> 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.bulk_update_mappings() 方法是批量更新的旧式形式,当给定主键参数时,ORM 在解释 update() 语句时内部使用它;然而,当使用旧版时,诸如会话同步支持之类的功能将不包括在内。

下面的示例:

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 更新和删除

当使用自定义 WHERE 条件构建 UpdateDelete 构造时(即使用 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()

列加载选项

原文:docs.sqlalchemy.org/en/20/orm/queryguide/columns.html

关于本文档

本节介绍了有关加载列的其他选项。使用的映射包括将存储大字符串值的列,我们可能希望限制它们何时加载。

查看此页面的 ORM 设置。以下示例中的一些将重新定义 Book 映射器以修改某些列定义。

使用列推迟限制加载的列

列推迟 指的是在查询该类型的对象时,从 SELECT 语句中省略的 ORM 映射列。这里的一般原理是性能,在表中具有很少使用的列,并且具有潜在的大数据值,因为在每次查询时完全加载这些列可能会耗费时间和/或内存。当实体加载时,SQLAlchemy ORM 提供了各种控制列加载的方式。

本节大多数示例演示了ORM 加载器选项。这些是传递给 Select.options() 方法的小构造,该方法是 Select 对象的一部分,当对象编译为 SQL 字符串时,ORM 将使用它们。

使用 load_only() 减少加载的列

load_only()加载器选项是在加载对象时最为便捷的选项,当已知只有少量列将被访问时,可以使用该选项。该选项接受一个可变数量的类绑定属性对象,指示应该加载的列映射属性,除了主键之外的所有其他列映射属性将不包括在检索的列中。在下面的示例中,Book 类包含列 .title.summary.cover_photo。使用 load_only() 我们可以指示 ORM 仅预先加载 .title.summary 列:

>>> from sqlalchemy import select
>>> from sqlalchemy.orm import load_only
>>> stmt = select(Book).options(load_only(Book.title, Book.summary))
>>> books = session.scalars(stmt).all()
SELECT  book.id,  book.title,  book.summary
FROM  book
[...]  ()
>>> for book in books:
...     print(f"{book.title}  {book.summary}")
100 Years of Krabby Patties  some long summary
Sea Catch 22  another long summary
The Sea Grapes of Wrath  yet another summary
A Nut Like No Other  some long summary
Geodesic Domes: A Retrospective  another long summary
Rocketry for Squirrels  yet another summary

在上面的示例中,SELECT 语句省略了 .cover_photo 列,并仅包括 .title.summary,以及主键列 .id;ORM 通常会始终获取主键列,因为这些列是必需的,以建立行的标识。

加载后,对象通常将对其余未加载属性应用延迟加载行为,这意味着当首次访问时,将在当前事务中发出一个 SQL 语句以加载值。在下面的示例中,访问 .cover_photo 会发出一个 SELECT 语句来加载其值:

>>> img_data = books[0].cover_photo
SELECT  book.cover_photo  AS  book_cover_photo
FROM  book
WHERE  book.id  =  ?
[...]  (1,) 

惰性加载始终使用对象处于 持久 状态的 Session 进行。如果对象从任何 Session 中 分离,操作将失败,引发异常。

作为在访问时进行惰性加载的替代方法,延迟列还可以配置为在访问时引发信息异常,而不考虑它们的附加状态。当使用 load_only() 构造时,可以使用 load_only.raiseload 参数来指示此行为。有关背景和示例,请参阅 使用 raiseload 防止延迟列加载 部分。

提示

正如其他地方所指出的,当使用异步 I/O(asyncio) 时,惰性加载不可用。

使用 load_only() 处理多个实体

load_only() 限制自己仅适用于其属性列表中引用的单个实体(目前不允许传递跨越多个实体的属性列表)。在下面的示例中,给定的 load_only() 选项仅适用于 Book 实体。选择的 User 实体不受影响;在生成的 SELECT 语句中,所有 user_account 列均存在,而 book 表仅存在 book.idbook.title

>>> stmt = select(User, Book).join_from(User, Book).options(load_only(Book.title))
>>> print(stmt)
SELECT  user_account.id,  user_account.name,  user_account.fullname,
book.id  AS  id_1,  book.title
FROM  user_account  JOIN  book  ON  user_account.id  =  book.owner_id 

如果我们想要将 load_only() 选项应用于 UserBook,我们将使用两个单独的选项:

>>> stmt = (
...     select(User, Book)
...     .join_from(User, Book)
...     .options(load_only(User.name), load_only(Book.title))
... )
>>> print(stmt)
SELECT  user_account.id,  user_account.name,  book.id  AS  id_1,  book.title
FROM  user_account  JOIN  book  ON  user_account.id  =  book.owner_id 

在相关对象和集合上使用 load_only()

当使用关系加载器来控制相关对象的加载时,任何关系加载器的 Load.load_only() 方法都可以用于将 load_only() 规则应用于子实体上的列。在下面的示例中,selectinload() 用于在每个 User 对象上加载相关的 books 集合。通过将 Load.load_only() 应用于结果选项对象,当为关系加载对象时,生成的 SELECT 将仅引用 title 列以及主键列:

>>> from sqlalchemy.orm import selectinload
>>> stmt = select(User).options(selectinload(User.books).load_only(Book.title))
>>> for user in session.scalars(stmt):
...     print(f"{user.fullname}   {[b.title for b in user.books]}")
SELECT  user_account.id,  user_account.name,  user_account.fullname
FROM  user_account
[...]  ()
SELECT  book.owner_id  AS  book_owner_id,  book.id  AS  book_id,  book.title  AS  book_title
FROM  book
WHERE  book.owner_id  IN  (?,  ?)
[...]  (1,  2)
Spongebob Squarepants   ['100 Years of Krabby Patties', 'Sea Catch 22', 'The Sea Grapes of Wrath']
Sandy Cheeks   ['A Nut Like No Other', 'Geodesic Domes: A Retrospective', 'Rocketry for Squirrels']

load_only() 也可以应用于子实体,而无需声明要为关系本身使用的加载样式。如果我们不想更改 User.books 的默认加载方式,但仍然要应用于 Book 的 load only 规则,我们将使用 defaultload() 选项进行链接,在这种情况下,它将保留默认关系加载样式 "lazy",并将我们的自定义 load_only() 规则应用于为每个 User.books 集合发出的 SELECT 语句:

>>> from sqlalchemy.orm import defaultload
>>> stmt = select(User).options(defaultload(User.books).load_only(Book.title))
>>> for user in session.scalars(stmt):
...     print(f"{user.fullname}   {[b.title for b in user.books]}")
SELECT  user_account.id,  user_account.name,  user_account.fullname
FROM  user_account
[...]  ()
SELECT  book.id  AS  book_id,  book.title  AS  book_title
FROM  book
WHERE  ?  =  book.owner_id
[...]  (1,)
Spongebob Squarepants   ['100 Years of Krabby Patties', 'Sea Catch 22', 'The Sea Grapes of Wrath']
SELECT  book.id  AS  book_id,  book.title  AS  book_title
FROM  book
WHERE  ?  =  book.owner_id
[...]  (2,)
Sandy Cheeks   ['A Nut Like No Other', 'Geodesic Domes: A Retrospective', 'Rocketry for Squirrels']
```  ### 使用 `defer()` 省略特定列

`defer()` 加载器选项是 `load_only()` 的一种更细粒度的替代方案,它允许将单个特定列标记为“不加载”。在下面的示例中,`defer()` 直接应用于 `.cover_photo` 列,而所有其他列的行为保持不变:

```py
>>> from sqlalchemy.orm import defer
>>> stmt = select(Book).where(Book.owner_id == 2).options(defer(Book.cover_photo))
>>> books = session.scalars(stmt).all()
SELECT  book.id,  book.owner_id,  book.title,  book.summary
FROM  book
WHERE  book.owner_id  =  ?
[...]  (2,)
>>> for book in books:
...     print(f"{book.title}: {book.summary}")
A Nut Like No Other: some long summary
Geodesic Domes: A Retrospective: another long summary
Rocketry for Squirrels: yet another summary

load_only() 一样,未加载的列默认情况下会在使用 惰性加载 访问时加载自身:

>>> img_data = books[0].cover_photo
SELECT  book.cover_photo  AS  book_cover_photo
FROM  book
WHERE  book.id  =  ?
[...]  (4,) 

可以在一条语句中使用多个 defer() 选项来标记多个列为延迟加载。

load_only() 一样,defer() 选项也包括使延迟属性在访问时引发异常而不是惰性加载的能力。这在部分 使用 raiseload 防止延迟列加载 中有所说明。 ### 使用 raiseload 防止延迟列加载

在使用 load_only()defer() 加载器选项时,对于对象上标记为延迟加载的属性,默认行为是在首次访问时,在当前事务中发出 SELECT 语句以加载它们的值。通常需要防止此加载发生,并在访问属性时引发异常,指示没有预期需要为该列查询数据库。典型的场景是使用已知对操作进行操作所需的所有列加载对象,然后将它们传递到视图层。应该捕获在视图层内部发出的任何进一步的 SQL 操作,以便可以调整预先加载的操作以适应该额外的数据,而不是产生额外的惰性加载。

对于此用例,defer()load_only()选项包括一个布尔参数defer.raiseload,当设置为True时,将导致受影响的属性在访问时引发异常。在下面的示例中,延迟加载的列.cover_photo将禁止属性访问:

>>> book = session.scalar(
...     select(Book).options(defer(Book.cover_photo, raiseload=True)).where(Book.id == 4)
... )
SELECT  book.id,  book.owner_id,  book.title,  book.summary
FROM  book
WHERE  book.id  =  ?
[...]  (4,)
>>> book.cover_photo
Traceback (most recent call last):
...
sqlalchemy.exc.InvalidRequestError: 'Book.cover_photo' is not available due to raiseload=True

当使用load_only()指定一组非延迟加载列时,可以使用load_only.raiseload参数来应用raiseload行为到其余列,该参数将应用于所有延迟属性:

>>> session.expunge_all()
>>> book = session.scalar(
...     select(Book).options(load_only(Book.title, raiseload=True)).where(Book.id == 5)
... )
SELECT  book.id,  book.title
FROM  book
WHERE  book.id  =  ?
[...]  (5,)
>>> book.summary
Traceback (most recent call last):
...
sqlalchemy.exc.InvalidRequestError: 'Book.summary' is not available due to raiseload=True

注意

目前尚不可能在一条语句中混合使用指向同一实体的load_only()defer()选项,以改变某些属性的raiseload行为;目前,这样做将产生未定义的属性加载行为。

另请参阅

defer.raiseload功能是与关系可用的相同“raiseload”功能的列级别版本。对于关系的“raiseload”,请参阅本指南的关系加载技术部分中的使用 raiseload 防止不必要的延迟加载。 ## 在映射上配置列延迟加载

对于映射列,默认情况下,defer()的功能可作为映射列的默认行为,这对于不应在每次查询时无条件加载的列可能是合适的。要配置,请使用mapped_column.deferred参数的mapped_column()。下面的示例说明了对Book的映射,该示例将默认列延迟应用于summarycover_photo列:

>>> class Book(Base):
...     __tablename__ = "book"
...     id: Mapped[int] = mapped_column(primary_key=True)
...     owner_id: Mapped[int] = mapped_column(ForeignKey("user_account.id"))
...     title: Mapped[str]
...     summary: Mapped[str] = mapped_column(Text, deferred=True)
...     cover_photo: Mapped[bytes] = mapped_column(LargeBinary, deferred=True)
...
...     def __repr__(self) -> str:
...         return f"Book(id={self.id!r}, title={self.title!r})"

使用上述映射,针对Book的查询将自动不包括summarycover_photo列:

>>> book = session.scalar(select(Book).where(Book.id == 2))
SELECT  book.id,  book.owner_id,  book.title
FROM  book
WHERE  book.id  =  ?
[...]  (2,) 

与所有延迟加载一样,当首次访问已加载对象上的延迟属性时,默认行为是它们将延迟加载它们的值:

>>> img_data = book.cover_photo
SELECT  book.cover_photo  AS  book_cover_photo
FROM  book
WHERE  book.id  =  ?
[...]  (2,) 

defer()load_only()加载器选项一样,映射器级别的延迟加载还包括一个选项,当语句中没有其他选项时,可以发生raiseload行为,而不是惰性加载。这允许映射其中某些列默认情况下不加载,并且在语句中不使用明确指令时也永远不会懒加载。有关如何配置和使用此行为的背景信息,请参阅配置映射器级别的raiseload行为一节。

对于命令式映射器、映射 SQL 表达式使用deferred()

deferred()函数是早期的、更通用的“延迟列”映射指令,在引入mapped_column()构造之前就存在于 SQLAlchemy 中。

在配置 ORM 映射器时使用deferred(),并接受任意 SQL 表达式或Column对象。因此,它适用于非声明式命令式映射,将其传递给map_imperatively.properties字典:

from sqlalchemy import Blob
from sqlalchemy import Column
from sqlalchemy import ForeignKey
from sqlalchemy import Integer
from sqlalchemy import String
from sqlalchemy import Table
from sqlalchemy import Text
from sqlalchemy.orm import registry

mapper_registry = registry()

book_table = Table(
    "book",
    mapper_registry.metadata,
    Column("id", Integer, primary_key=True),
    Column("title", String(50)),
    Column("summary", Text),
    Column("cover_image", Blob),
)

class Book:
    pass

mapper_registry.map_imperatively(
    Book,
    book_table,
    properties={
        "summary": deferred(book_table.c.summary),
        "cover_image": deferred(book_table.c.cover_image),
    },
)

当映射的 SQL 表达式应该延迟加载时,deferred()也可以用于替代column_property()

from sqlalchemy.orm import deferred

class User(Base):
    __tablename__ = "user"

    id: Mapped[int] = mapped_column(primary_key=True)
    firstname: Mapped[str] = mapped_column()
    lastname: Mapped[str] = mapped_column()
    fullname: Mapped[str] = deferred(firstname + " " + lastname)

另请参阅

使用 column_property - 在 SQL 表达式作为映射属性一节中

应用负载、持久性和映射选项到命令式表列 - 在使用声明式配置表一节中

使用undefer()来“急切地”加载延迟列

对于默认配置为延迟加载的映射上的列,undefer()选项将导致任何通常延迟加载的列变为未延迟加载,即与映射的所有其他列一起提前加载。例如,我们可以将undefer()应用于在前述映射中标记为延迟加载的Book.summary列:

>>> from sqlalchemy.orm import undefer
>>> book = session.scalar(select(Book).where(Book.id == 2).options(undefer(Book.summary)))
SELECT  book.id,  book.owner_id,  book.title,  book.summary
FROM  book
WHERE  book.id  =  ?
[...]  (2,) 

现在,Book.summary列已经被急切地加载,并且可以在不发出额外 SQL 的情况下访问:

>>> print(book.summary)
another long summary

将延迟列分组加载

通常,当列使用 mapped_column(deferred=True) 进行映射时,当在对象上访问延迟属性时,将发出 SQL 仅加载该特定列而不加载其他列,即使映射还有其他标记为延迟的列。在延迟属性是应一次性加载的一组属性的常见情况下,而不是为每个属性单独发出 SQL,可以使用 mapped_column.deferred_group 参数,该参数接受一个任意字符串,该字符串将定义要取消延迟的一组常见列:

>>> class Book(Base):
...     __tablename__ = "book"
...     id: Mapped[int] = mapped_column(primary_key=True)
...     owner_id: Mapped[int] = mapped_column(ForeignKey("user_account.id"))
...     title: Mapped[str]
...     summary: Mapped[str] = mapped_column(
...         Text, deferred=True, deferred_group="book_attrs"
...     )
...     cover_photo: Mapped[bytes] = mapped_column(
...         LargeBinary, deferred=True, deferred_group="book_attrs"
...     )
...
...     def __repr__(self) -> str:
...         return f"Book(id={self.id!r}, title={self.title!r})"

使用上述映射,访问 summarycover_photo 将同时加载两个列,只需使用一个 SELECT 语句:

>>> book = session.scalar(select(Book).where(Book.id == 2))
SELECT  book.id,  book.owner_id,  book.title
FROM  book
WHERE  book.id  =  ?
[...]  (2,)
>>> img_data, summary = book.cover_photo, book.summary
SELECT  book.summary  AS  book_summary,  book.cover_photo  AS  book_cover_photo
FROM  book
WHERE  book.id  =  ?
[...]  (2,) 

使用 undefer_group() 按组取消延迟加载

如果在前一节中引入了 mapped_column.deferred_group 配置了延迟列,则可以指示整个组使用 undefer_group() 选项进行急切加载,传递要急切加载的组的字符串名称:

>>> from sqlalchemy.orm import undefer_group
>>> book = session.scalar(
...     select(Book).where(Book.id == 2).options(undefer_group("book_attrs"))
... )
SELECT  book.id,  book.owner_id,  book.title,  book.summary,  book.cover_photo
FROM  book
WHERE  book.id  =  ?
[...]  (2,) 

summarycover_photo 都可以在不加载其他内容的情况下使用:

>>> img_data, summary = book.cover_photo, book.summary

使用通配符取消延迟加载

大多数 ORM 加载器选项接受通配符表达式,由 "*" 表示,表示该选项应用于所有相关属性。如果映射具有一系列延迟列,则可以一次性取消所有这些列的延迟,而无需使用组名,只需指定通配符:

>>> book = session.scalar(select(Book).where(Book.id == 3).options(undefer("*")))
SELECT  book.id,  book.owner_id,  book.title,  book.summary,  book.cover_photo
FROM  book
WHERE  book.id  =  ?
[...]  (3,) 

配置映射器级别的“提前加载”行为

在 使用 raiseload 防止延迟列加载 中首次引入的 “raiseload” 行为也可以作为默认的映射器级别行为应用,使用 mapped_column.deferred_raiseload 参数的 mapped_column()。当使用此参数时,受影响的列将在所有情况下在访问时引发,除非在查询时显式地使用 undefer()load_only() 进行“取消延迟”:

>>> class Book(Base):
...     __tablename__ = "book"
...     id: Mapped[int] = mapped_column(primary_key=True)
...     owner_id: Mapped[int] = mapped_column(ForeignKey("user_account.id"))
...     title: Mapped[str]
...     summary: Mapped[str] = mapped_column(Text, deferred=True, deferred_raiseload=True)
...     cover_photo: Mapped[bytes] = mapped_column(
...         LargeBinary, deferred=True, deferred_raiseload=True
...     )
...
...     def __repr__(self) -> str:
...         return f"Book(id={self.id!r}, title={self.title!r})"

使用上述映射,.summary.cover_photo 列默认情况下不可加载:

>>> book = session.scalar(select(Book).where(Book.id == 2))
SELECT  book.id,  book.owner_id,  book.title
FROM  book
WHERE  book.id  =  ?
[...]  (2,)
>>> book.summary
Traceback (most recent call last):
...
sqlalchemy.exc.InvalidRequestError: 'Book.summary' is not available due to raiseload=True

只有在查询时重写它们的行为,通常使用 undefer()undefer_group(),或者更少见的 defer(),属性才能被加载。下面的示例将 undefer('*') 应用于未延迟加载所有属性,并且还利用了填充现有对象来刷新已加载对象的加载器选项:

>>> book = session.scalar(
...     select(Book)
...     .where(Book.id == 2)
...     .options(undefer("*"))
...     .execution_options(populate_existing=True)
... )
SELECT  book.id,  book.owner_id,  book.title,  book.summary,  book.cover_photo
FROM  book
WHERE  book.id  =  ?
[...]  (2,)
>>> book.summary
'another long summary'
```  ## 将任意 SQL 表达式加载到对象上

如选择 ORM 实体和属性及其他地方所讨论的,可以使用 `select()` 结构在结果集中加载任意 SQL 表达式。比如,如果我们想要发出一个查询,加载 `User` 对象,但也包括每个 `User` 拥有多少书籍的计数,我们可以使用 `func.count(Book.id)` 将“计数”列添加到一个查询中,该查询包括与 `Book` 的 JOIN 以及按所有者 id 进行的 GROUP BY。这将产生 `Row` 对象,每个对象包含两个条目,一个是 `User`,一个是 `func.count(Book.id)`:

```py
>>> from sqlalchemy import func
>>> stmt = select(User, func.count(Book.id)).join_from(User, Book).group_by(Book.owner_id)
>>> for user, book_count in session.execute(stmt):
...     print(f"Username: {user.name}  Number of books: {book_count}")
SELECT  user_account.id,  user_account.name,  user_account.fullname,
count(book.id)  AS  count_1
FROM  user_account  JOIN  book  ON  user_account.id  =  book.owner_id
GROUP  BY  book.owner_id
[...]  ()
Username: spongebob  Number of books: 3
Username: sandy  Number of books: 3

在上面的例子中,User 实体和“书籍数量”SQL 表达式分别返回。然而,一个常见的用例是生成一个查询,仅产生 User 对象,可以通过Session.scalars()来迭代,其中 func.count(Book.id) SQL 表达式的结果被动态地应用到每个 User 实体上。最终结果类似于在类上使用 column_property() 将任意 SQL 表达式映射到类的情况,只是 SQL 表达式可以在查询时进行修改。对于这种用例,SQLAlchemy 提供了 with_expression() 加载器选项,当与映射器级别的 query_expression() 指令结合使用时,可以产生这种结果。

要将 with_expression() 应用于查询,映射类必须预先使用 query_expression() 指令配置了一个 ORM 映射属性;这个指令将在映射类上生成一个适合接收查询时 SQL 表达式的属性。下面我们将一个新属性 User.book_count 添加到 User 中。这个 ORM 映射属性是只读的,没有默认值;在加载的实例上访问它通常会产生 None

>>> from sqlalchemy.orm import query_expression
>>> class User(Base):
...     __tablename__ = "user_account"
...     id: Mapped[int] = mapped_column(primary_key=True)
...     name: Mapped[str]
...     fullname: Mapped[Optional[str]]
...     book_count: Mapped[int] = query_expression()
...
...     def __repr__(self) -> str:
...         return f"User(id={self.id!r}, name={self.name!r}, fullname={self.fullname!r})"

使用我们映射中配置的 User.book_count 属性,我们可以使用 with_expression() 加载器选项,将数据从 SQL 表达式应用到每个 User 对象中加载的自定义 SQL 表达式中:

>>> from sqlalchemy.orm import with_expression
>>> stmt = (
...     select(User)
...     .join_from(User, Book)
...     .group_by(Book.owner_id)
...     .options(with_expression(User.book_count, func.count(Book.id)))
... )
>>> for user in session.scalars(stmt):
...     print(f"Username: {user.name}  Number of books: {user.book_count}")
SELECT  count(book.id)  AS  count_1,  user_account.id,  user_account.name,
user_account.fullname
FROM  user_account  JOIN  book  ON  user_account.id  =  book.owner_id
GROUP  BY  book.owner_id
[...]  ()
Username: spongebob  Number of books: 3
Username: sandy  Number of books: 3

在上述示例中,我们将我们的 func.count(Book.id) 表达式从 select() 构造的 columns 参数中移出,并将其放入 with_expression() 加载器选项中。ORM 然后将其视为一个特殊的列加载选项,动态应用于语句。

query_expression() 映射有以下注意事项:

  • 在未使用 with_expression() 来填充属性的对象上,对象实例上的属性将具有值 None,除非在映射上将 query_expression.default_expr 参数设置为默认的 SQL 表达式。

  • with_expression()不会在已加载的对象上填充,除非使用了 Populate Existing。如下示例不起作用,因为 A 对象已经加载:

    # load the first A
    obj = session.scalars(select(A).order_by(A.id)).first()
    
    # load the same A with an option; expression will **not** be applied
    # to the already-loaded object
    obj = session.scalars(select(A).options(with_expression(A.expr, some_expr))).first()
    

    要确保在现有对象上重新加载属性,请使用 Populate Existing 执行选项以确保重新填充所有列:

    obj = session.scalars(
        select(A)
        .options(with_expression(A.expr, some_expr))
        .execution_options(populate_existing=True)
    ).first()
    
  • 当对象过期时,with_expression() SQL 表达式会丢失。一旦对象过期,无论是通过 Session.expire() 还是通过 Session.commit() 的 expire_on_commit 行为,SQL 表达式及其值将不再与属性关联,并且在后续访问时将返回 None

  • with_expression() 作为对象加载选项,仅对查询的最外层部分以及对完整实体的查询起作用,而不适用于任意列选择、子查询或复合语句的元素,比如 UNION。请参阅下一节 使用 with_expression() 与 UNIONs、其他子查询 查看示例。

  • 映射的属性不能应用于查询的其他部分,比如 WHERE 子句、ORDER BY 子句,并且使用临时表达式;也就是说,以下示例不起作用:

    # can't refer to A.expr elsewhere in the query
    stmt = (
        select(A)
        .options(with_expression(A.expr, A.x + A.y))
        .filter(A.expr > 5)
        .order_by(A.expr)
    )
    

    在上述 WHERE 子句和 ORDER BY 子句中,A.expr 表达式将解析为 NULL。要在整个查询中使用该表达式,请赋值给一个变量然后使用它:

    # assign desired expression up front, then refer to that in
    # the query
    a_expr = A.x + A.y
    stmt = (
        select(A)
        .options(with_expression(A.expr, a_expr))
        .filter(a_expr > 5)
        .order_by(a_expr)
    )
    

另请参阅

with_expression() 选项是一种特殊选项,用于在查询时动态应用 SQL 表达式到映射类。对于在映射器上配置的普通固定 SQL 表达式,请参阅 SQL 表达式作为映射属性 部分。

使用 with_expression() 与 UNIONs、其他子查询

with_expression() 构造是一种 ORM 加载器选项,因此只能应用于要加载特定 ORM 实体的 SELECT 语句的最外层级。如果在 select() 中使用,而后将其用作子查询或作为复合语句中的元素,如 UNION,它将不起作用。

要在子查询中使用任意 SQL 表达式,应使用常规的 Core 风格添加表达式的方法。要将子查询派生的表达式组装到 ORM 实体的 query_expression() 属性上,应在 ORM 对象加载的顶层使用 with_expression(),引用子查询中的 SQL 表达式。

在下面的示例中,使用两个 select() 构造针对带有额外 SQL 表达式标记为 expr 的 ORM 实体 A,并使用 union_all() 组合。然后,在最顶层,从此 UNION 中 SELECT A 实体,使用在 从 UNION 和其他集合操作中选择实体 中描述的查询技术,添加一个选项,使用 with_expression() 提取此 SQL 表达式到新加载的 A 实例上:

>>> from sqlalchemy import union_all
>>> s1 = (
...     select(User, func.count(Book.id).label("book_count"))
...     .join_from(User, Book)
...     .where(User.name == "spongebob")
... )
>>> s2 = (
...     select(User, func.count(Book.id).label("book_count"))
...     .join_from(User, Book)
...     .where(User.name == "sandy")
... )
>>> union_stmt = union_all(s1, s2)
>>> orm_stmt = (
...     select(User)
...     .from_statement(union_stmt)
...     .options(with_expression(User.book_count, union_stmt.selected_columns.book_count))
... )
>>> for user in session.scalars(orm_stmt):
...     print(f"Username: {user.name}  Number of books: {user.book_count}")
SELECT  user_account.id,  user_account.name,  user_account.fullname,  count(book.id)  AS  book_count
FROM  user_account  JOIN  book  ON  user_account.id  =  book.owner_id
WHERE  user_account.name  =  ?
UNION  ALL
SELECT  user_account.id,  user_account.name,  user_account.fullname,  count(book.id)  AS  book_count
FROM  user_account  JOIN  book  ON  user_account.id  =  book.owner_id
WHERE  user_account.name  =  ?
[...]  ('spongebob',  'sandy')
Username: spongebob  Number of books: 3
Username: sandy  Number of books: 3

列加载 API

对象名称 描述
defer(key, *addl_attrs, [raiseload]) 指示给定的面向列的属性应该被延迟加载,例如,直到访问时才加载。
deferred(column, *additional_columns, [group, raiseload, comparator_factory, init, repr, default, default_factory, compare, kw_only, active_history, expire_on_flush, info, doc]) 指示默认情况下不加载的基于列的映射属性。
load_only(*attrs, [raiseload]) 表示对于特定实体,仅加载给定的列属性名列表;所有其他列将被延迟加载。
query_expression([default_expr], *, [repr, compare, expire_on_flush, info, doc]) 指示从查询时 SQL 表达式填充的属性。
undefer(key, *addl_attrs) 指示给定的基于列的属性应取消延迟加载,例如,可以在实体的 SELECT 语句中指定。
undefer_group(name) 指示给定延迟组名中的列应取消延迟加载。
with_expression(key, expression) 将临时 SQL 表达式应用于“延迟表达式”属性。
function sqlalchemy.orm.defer(key: Literal['*'] | QueryableAttribute[Any], *addl_attrs: Literal['*'] | QueryableAttribute[Any], raiseload: bool = False) → _AbstractLoad

指示给定的基于列的属性应延迟加载,例如,直到访问时才加载。

此函数是 Load 接口的一部分,并支持方法链接和独立操作。

例如:

from sqlalchemy.orm import defer

session.query(MyClass).options(
 defer(MyClass.attribute_one),
 defer(MyClass.attribute_two)
)

要指定对相关类的属性进行延迟加载,可以逐个令牌指定路径,并指定沿链的每个链接的加载样式。要保留链接的加载样式不变,请使用 defaultload()

session.query(MyClass).options(
 defaultload(MyClass.someattr).defer(RelatedClass.some_column)
)

可以使用 Load.options() 一次捆绑与关系相关的多个延迟选项:

select(MyClass).options(
 defaultload(MyClass.someattr).options(
 defer(RelatedClass.some_column),
 defer(RelatedClass.some_other_column),
 defer(RelatedClass.another_column)
 )
)

参数:

  • key – 要延迟加载的属性。

  • raiseload – 在访问延迟属性时,引发 InvalidRequestError 而不是懒加载值。用于防止生成不需要的 SQL。

版本 1.4 中的新功能。

另请参阅

限制哪些列随列延迟加载 - 在 ORM 查询指南 中

load_only()

undefer()

function sqlalchemy.orm.deferred(column: _ORMColumnExprArgument[_T], *additional_columns: _ORMColumnExprArgument[Any], group: str | None = None, raiseload: bool = False, comparator_factory: Type[PropComparator[_T]] | None = None, init: _NoArg | bool = _NoArg.NO_ARG, repr: _NoArg | bool = _NoArg.NO_ARG, default: Any | None = _NoArg.NO_ARG, default_factory: _NoArg | Callable[[], _T] = _NoArg.NO_ARG, compare: _NoArg | bool = _NoArg.NO_ARG, kw_only: _NoArg | bool = _NoArg.NO_ARG, active_history: bool = False, expire_on_flush: bool = True, info: _InfoType | None = None, doc: str | None = None) → MappedSQLExpression[_T]

表示默认情况下不会加载的基于列的映射属性,除非访问。

在使用 mapped_column() 时,通过使用 mapped_column.deferred 参数提供了与 deferred() 构造相同的功能。

参数:

  • *columns – 要映射的列。通常这是一个单独的 Column 对象,但是为了支持在同一属性下映射多个列,也支持集合。

  • raiseload

    布尔值,如果为 True,则表示如果执行加载操作,则应引发异常。

    1.4 版中的新内容。

额外的参数与 column_property() 相同。

另请参阅

对命令式映射器、映射的 SQL 表达式使用 deferred()

function sqlalchemy.orm.query_expression(default_expr: _ORMColumnExprArgument[_T] = <sqlalchemy.sql.elements.Null object>, *, repr: Union[_NoArg, bool] = _NoArg.NO_ARG, compare: Union[_NoArg, bool] = _NoArg.NO_ARG, expire_on_flush: bool = True, info: Optional[_InfoType] = None, doc: Optional[str] = None) → MappedSQLExpression[_T]

指示从查询时间 SQL 表达式填充的属性。

参数:

default_expr – 可选的 SQL 表达式对象,如果没有后续使用 with_expression() 分配,则将在所有情况下使用。

1.2 版中的新内容。

另请参阅

将任意 SQL 表达式加载到对象 - 背景和用法示例

function sqlalchemy.orm.load_only(*attrs: Literal['*'] | QueryableAttribute[Any], raiseload: bool = False) → _AbstractLoad

指示对于特定实体,只加载给定的列名列表;所有其他属性将被延迟。

此函数是 Load 接口的一部分,支持方法链接和独立操作。

示例 - 给定一个类 User,只加载 namefullname 属性:

session.query(User).options(load_only(User.name, User.fullname))

示例 - 给定一个关系 User.addresses -> Address,为 User.addresses 集合指定子查询加载,但在每个 Address 对象上仅加载 email_address 属性:

session.query(User).options(
 subqueryload(User.addresses).load_only(Address.email_address)
)

对于具有多个实体的语句,可以使用 Load 构造函数来明确指定引导实体:

stmt = (
 select(User, Address)
 .join(User.addresses)
 .options(
 Load(User).load_only(User.name, User.fullname),
 Load(Address).load_only(Address.email_address),
 )
)

与 populate_existing 执行选项一起使用时,只会刷新列出的属性。

参数:

  • *attrs – 要加载的属性,所有其他属性都将延迟。

  • raiseload

    当访问延迟属性时,引发 InvalidRequestError 而不是惰性加载值。用于防止不必要的 SQL 发出。

    2.0 版中的新内容。

另请参阅

限制加载的列与列延迟 - 在 ORM 查询指南 中

参数:

  • *attrs – 要加载的属性,所有其他属性都将延迟。

  • raiseload

    当访问延迟属性时,引发 InvalidRequestError 而不是惰性加载值。用于防止不必要的 SQL 发出。

    2.0 版中的新内容。

function sqlalchemy.orm.undefer(key: Literal['*'] | QueryableAttribute[Any], *addl_attrs: Literal['*'] | QueryableAttribute[Any]) → _AbstractLoad

指示给定的基于列的属性应该取消延迟,例如,在整个实体的 SELECT 语句中指定。

通常在映射上设置未延迟的列作为deferred() 属性。

此函数是 Load 接口的一部分,支持方法链接和独立操作。

示例:

# undefer two columns
session.query(MyClass).options(
 undefer(MyClass.col1), undefer(MyClass.col2)
)

# undefer all columns specific to a single class using Load + *
session.query(MyClass, MyOtherClass).options(
 Load(MyClass).undefer("*")
)

# undefer a column on a related object
select(MyClass).options(
 defaultload(MyClass.items).undefer(MyClass.text)
)

参数:

key – 要取消延迟的属性。

另请参阅

使用列推迟限制加载的列 - 在 ORM 查询指南 中

defer()

undefer_group()

function sqlalchemy.orm.undefer_group(name: str) → _AbstractLoad

指示给定延迟组名内的列应取消延迟。

正在取消延迟的列在映射上设置为 deferred() 属性,并包括一个“组”名称。

例如:

session.query(MyClass).options(undefer_group("large_attrs"))

要取消相关实体上的一组属性的延迟加载,可以使用关系加载器选项(如defaultload())拼写路径:

select(MyClass).options(
 defaultload("someattr").undefer_group("large_attrs")
)

另请参阅

使用列推迟限制加载的列 - 在 ORM 查询指南 中

defer()

undefer()

function sqlalchemy.orm.with_expression(key: _AttrType, expression: _ColumnExpressionArgument[Any]) → _AbstractLoad

将临时 SQL 表达式应用于“延迟表达式”属性。

此选项与query_expression() mapper-level 构造一起使用,指示应该是临时 SQL 表达式目标的属性。

例如:

stmt = select(SomeClass).options(
 with_expression(SomeClass.x_y_expr, SomeClass.x + SomeClass.y)
)

版本 1.2 中的新增内容。

参数:

  • key – 要填充的属性

  • expr – 要应用于属性的 SQL 表达式。

另请参阅

将任意 SQL 表达式加载到对象上 - 背景和使用示例

使用列推迟限制加载的列

列推迟是指在查询该类型的对象时,ORM 映射的列在 SELECT 语句中被省略的列。 这里的一般原因是性能,在表具有很少使用的列且具有潜在的大数据值的情况下,完全在每次查询时加载这些列可能会耗费时间和/或内存。 SQLAlchemy ORM 提供了多种控制加载列的方式。

本节中的大多数示例都是ORM 加载器选项的示例。 这些是小型构造,传递给 Select.options() 方法的 Select 对象,然后在对象编译为 SQL 字符串时由 ORM 消耗。

使用 load_only() 来减少加载的列

load_only() 加载器选项是在已知只会访问少量列的对象时使用的最快捷的选项。此选项接受一个可变数量的类绑定属性对象,指示应加载的那些列映射属性,其中除主键外的所有其他列映射属性将不会成为被获取的列的一部分。在下面的示例中,Book 类包含列 .title.summary.cover_photo。使用 load_only(),我们可以指示 ORM 仅预先加载 .title.summary 列:

>>> from sqlalchemy import select
>>> from sqlalchemy.orm import load_only
>>> stmt = select(Book).options(load_only(Book.title, Book.summary))
>>> books = session.scalars(stmt).all()
SELECT  book.id,  book.title,  book.summary
FROM  book
[...]  ()
>>> for book in books:
...     print(f"{book.title}  {book.summary}")
100 Years of Krabby Patties  some long summary
Sea Catch 22  another long summary
The Sea Grapes of Wrath  yet another summary
A Nut Like No Other  some long summary
Geodesic Domes: A Retrospective  another long summary
Rocketry for Squirrels  yet another summary

在上面的例子中,SELECT 语句省略了 .cover_photo 列,并且仅包含了 .title.summary 列,以及主键列 .id;ORM 通常会始终获取主键列,因为这些列是必需的,用于建立行的标识。

一旦加载,对象通常会对其余未加载的属性应用惰性加载行为,这意味着当首次访问任何属性时,将在当前事务中发出 SQL 语句以加载该值。下面,访问 .cover_photo 会发出一个 SELECT 语句来加载其值:

>>> img_data = books[0].cover_photo
SELECT  book.cover_photo  AS  book_cover_photo
FROM  book
WHERE  book.id  =  ?
[...]  (1,) 

惰性加载始终使用对象处于持久状态的Session发出。如果对象已分离于任何Session,操作将失败,引发异常。

作为在访问时惰性加载的替代方案,还可以配置延迟列在访问时引发一个信息性异常,而不考虑它们的附加状态。当使用 load_only() 构造时,可以使用 load_only.raiseload 参数来指示这一点。有关背景和示例,请参见使用 raiseload 防止延迟列加载部分。

提示

如其他地方所述,在使用异步 I/O (asyncio)时,不可用惰性加载。

使用 load_only() 与多个实体

load_only() 限制自身仅针对其属性列表中引用的单个实体(目前不允许传递跨越多个实体的属性列表)。在下面的示例中,给定的 load_only() 选项仅适用于 Book 实体。也选择的 User 实体不受影响;在生成的 SELECT 语句中,user_account 的所有列都存在,而 book 表只有 book.idbook.title

>>> stmt = select(User, Book).join_from(User, Book).options(load_only(Book.title))
>>> print(stmt)
SELECT  user_account.id,  user_account.name,  user_account.fullname,
book.id  AS  id_1,  book.title
FROM  user_account  JOIN  book  ON  user_account.id  =  book.owner_id 

如果我们想要将 load_only() 选项应用于 UserBook,我们将使用两个单独的选项:

>>> stmt = (
...     select(User, Book)
...     .join_from(User, Book)
...     .options(load_only(User.name), load_only(Book.title))
... )
>>> print(stmt)
SELECT  user_account.id,  user_account.name,  book.id  AS  id_1,  book.title
FROM  user_account  JOIN  book  ON  user_account.id  =  book.owner_id 

在相关对象和集合上使用 load_only()

当使用关系加载器来控制相关对象的加载时,任何关系加载器的 Load.load_only() 方法都可以用于将 load_only() 规则应用于子实体的列。在下面的示例中,使用 selectinload() 来加载每个 User 对象上的相关 books 集合。通过将 Load.load_only() 应用于生成的选项对象,当加载关系的对象时,生成的 SELECT 语句将仅引用 title 列以及主键列:

>>> from sqlalchemy.orm import selectinload
>>> stmt = select(User).options(selectinload(User.books).load_only(Book.title))
>>> for user in session.scalars(stmt):
...     print(f"{user.fullname}   {[b.title for b in user.books]}")
SELECT  user_account.id,  user_account.name,  user_account.fullname
FROM  user_account
[...]  ()
SELECT  book.owner_id  AS  book_owner_id,  book.id  AS  book_id,  book.title  AS  book_title
FROM  book
WHERE  book.owner_id  IN  (?,  ?)
[...]  (1,  2)
Spongebob Squarepants   ['100 Years of Krabby Patties', 'Sea Catch 22', 'The Sea Grapes of Wrath']
Sandy Cheeks   ['A Nut Like No Other', 'Geodesic Domes: A Retrospective', 'Rocketry for Squirrels']

load_only() 也可以应用于子实体,而无需声明要用于关系本身的加载样式。如果我们不想更改 User.books 的默认加载样式,但仍要对 Book 应用仅加载规则,我们将使用 defaultload() 选项进行链接,在这种情况下,将保留 "lazy" 的默认关系加载样式,并将我们的自定义 load_only() 规则应用于为每个 User.books 集合发出的 SELECT 语句:

>>> from sqlalchemy.orm import defaultload
>>> stmt = select(User).options(defaultload(User.books).load_only(Book.title))
>>> for user in session.scalars(stmt):
...     print(f"{user.fullname}   {[b.title for b in user.books]}")
SELECT  user_account.id,  user_account.name,  user_account.fullname
FROM  user_account
[...]  ()
SELECT  book.id  AS  book_id,  book.title  AS  book_title
FROM  book
WHERE  ?  =  book.owner_id
[...]  (1,)
Spongebob Squarepants   ['100 Years of Krabby Patties', 'Sea Catch 22', 'The Sea Grapes of Wrath']
SELECT  book.id  AS  book_id,  book.title  AS  book_title
FROM  book
WHERE  ?  =  book.owner_id
[...]  (2,)
Sandy Cheeks   ['A Nut Like No Other', 'Geodesic Domes: A Retrospective', 'Rocketry for Squirrels']
```  ### 使用 `defer()` 省略特定列

`defer()` 加载选项是对 `load_only()` 的更精细的替代方案,允许将单个特定列标记为“不加载”。在下面的示例中,`defer()` 直接应用于 `.cover_photo` 列,保持所有其他列的行为不变:

```py
>>> from sqlalchemy.orm import defer
>>> stmt = select(Book).where(Book.owner_id == 2).options(defer(Book.cover_photo))
>>> books = session.scalars(stmt).all()
SELECT  book.id,  book.owner_id,  book.title,  book.summary
FROM  book
WHERE  book.owner_id  =  ?
[...]  (2,)
>>> for book in books:
...     print(f"{book.title}: {book.summary}")
A Nut Like No Other: some long summary
Geodesic Domes: A Retrospective: another long summary
Rocketry for Squirrels: yet another summary

load_only() 相同,未加载的列默认情况下将在使用惰性加载时自行加载:

>>> img_data = books[0].cover_photo
SELECT  book.cover_photo  AS  book_cover_photo
FROM  book
WHERE  book.id  =  ?
[...]  (4,) 

可以在一个语句中使用多个 defer() 选项来标记多个列为延迟加载。

load_only() 相同,defer() 选项也包括使延迟属性在访问时引发异常而不是惰性加载的功能。这在使用 raiseload 防止延迟列加载一节中进行了说明。### 使用 raiseload 防止延迟列加载

当使用 load_only()defer() 加载器选项时,对象上标记为延迟的属性具有默认行为,即在首次访问时,将在当前事务中发出 SELECT 语句以加载其值。通常需要阻止此加载操作,并在访问属性时引发异常,指示不期望为此列查询数据库的需要。典型的情况是加载具有操作所需的所有已知列的对象,然后将它们传递到视图层。视图层中发出的任何进一步的 SQL 操作都应该被捕获,以便调整前期加载操作以适应那些额外的数据,而不是额外的惰性加载。

对于这种用例,defer()load_only() 选项包括一个布尔参数 defer.raiseload,当设置为 True 时,将导致受影响的属性在访问时引发异常。在下面的示例中,延迟加载的列 .cover_photo 将禁止属性访问:

>>> book = session.scalar(
...     select(Book).options(defer(Book.cover_photo, raiseload=True)).where(Book.id == 4)
... )
SELECT  book.id,  book.owner_id,  book.title,  book.summary
FROM  book
WHERE  book.id  =  ?
[...]  (4,)
>>> book.cover_photo
Traceback (most recent call last):
...
sqlalchemy.exc.InvalidRequestError: 'Book.cover_photo' is not available due to raiseload=True

当使用 load_only() 命名一组特定的非延迟加载列时,可以使用 load_only.raiseload 参数将 raiseload 行为应用于其余列,该参数将应用于所有延迟加载属性:

>>> session.expunge_all()
>>> book = session.scalar(
...     select(Book).options(load_only(Book.title, raiseload=True)).where(Book.id == 5)
... )
SELECT  book.id,  book.title
FROM  book
WHERE  book.id  =  ?
[...]  (5,)
>>> book.summary
Traceback (most recent call last):
...
sqlalchemy.exc.InvalidRequestError: 'Book.summary' is not available due to raiseload=True

注意

目前尚不能在一个语句中混合使用指向同一实体的 load_only()defer() 选项,以改变某些属性的 raiseload 行为;目前,这样做会产生未定义的属性加载行为。

另请参见

defer.raiseload 功能是关系的同一“raiseload”功能的列级版本。有关关系的“raiseload”,请参见本指南的关系加载技术部分中的使用 load_only() 减少加载的列。

当已知只有少数几列将被访问时,load_only()加载器选项是最方便的选项。该选项接受一个变量数量的类绑定属性对象,指示应该加载的列映射属性,除了主键之外的所有其他列映射属性都不会成为获取的列的一部分。在下面的示例中,Book 类包含列 .title.summary.cover_photo。使用load_only(),我们可以指示 ORM 仅预先加载 .title.summary 列:

>>> from sqlalchemy import select
>>> from sqlalchemy.orm import load_only
>>> stmt = select(Book).options(load_only(Book.title, Book.summary))
>>> books = session.scalars(stmt).all()
SELECT  book.id,  book.title,  book.summary
FROM  book
[...]  ()
>>> for book in books:
...     print(f"{book.title}  {book.summary}")
100 Years of Krabby Patties  some long summary
Sea Catch 22  another long summary
The Sea Grapes of Wrath  yet another summary
A Nut Like No Other  some long summary
Geodesic Domes: A Retrospective  another long summary
Rocketry for Squirrels  yet another summary

在上面的示例中,SELECT 语句省略了 .cover_photo 列,仅包含了 .title.summary,以及主键列 .id;ORM 通常会获取主键列,因为这些列是必需的,以建立行的标识。

加载后,对象通常将对其余未加载的属性应用惰性加载行为,这意味着首次访问任何属性时,将在当前事务中发出 SQL 语句以加载值。下面,访问 .cover_photo 会发出一个 SELECT 语句来加载它的值:

>>> img_data = books[0].cover_photo
SELECT  book.cover_photo  AS  book_cover_photo
FROM  book
WHERE  book.id  =  ?
[...]  (1,) 

惰性加载始终使用对象所处的处于持久状态的 Session 发出。如果对象从任何Session中分离,操作将失败,引发异常。

作为访问时惰性加载的替代方案,还可以配置延迟列以在访问时引发信息性异常,而不考虑它们的附加状态。在使用load_only()构造时,可以使用load_only.raiseload参数来指示此情况。有关背景和示例,请参阅使用 raiseload 防止延迟列加载部分。

提示

正如在其他地方所指出的,使用异步 I/O(asyncio)时不可用惰性加载。

使用 load_only() 处理多个实体

load_only() 限制了其属性列表中所引用的单个实体(当前不允许传递跨越多个实体的属性列表)。在下面的示例中,给定的 load_only() 选项仅适用于 Book 实体。被选中的 User 实体不受影响;在生成的 SELECT 语句中,user_account 的所有列都存在,而 book 表只有 book.idbook.title

>>> stmt = select(User, Book).join_from(User, Book).options(load_only(Book.title))
>>> print(stmt)
SELECT  user_account.id,  user_account.name,  user_account.fullname,
book.id  AS  id_1,  book.title
FROM  user_account  JOIN  book  ON  user_account.id  =  book.owner_id 

如果我们想要同时将 load_only() 选项应用于 UserBook,我们将使用两个单独的选项:

>>> stmt = (
...     select(User, Book)
...     .join_from(User, Book)
...     .options(load_only(User.name), load_only(Book.title))
... )
>>> print(stmt)
SELECT  user_account.id,  user_account.name,  book.id  AS  id_1,  book.title
FROM  user_account  JOIN  book  ON  user_account.id  =  book.owner_id 

对相关对象和集合使用 load_only()

在使用 关系加载器 控制相关对象加载时,任何关系加载器的 Load.load_only() 方法都可以用于将 load_only() 规则应用于子实体上的列。在下面的示例中,selectinload() 用于加载每个 User 对象上的相关 books 集合。通过将 Load.load_only() 应用于结果选项对象,当为关系加载对象时,生成的 SELECT 仅引用 title 列以及主键列:

>>> from sqlalchemy.orm import selectinload
>>> stmt = select(User).options(selectinload(User.books).load_only(Book.title))
>>> for user in session.scalars(stmt):
...     print(f"{user.fullname}   {[b.title for b in user.books]}")
SELECT  user_account.id,  user_account.name,  user_account.fullname
FROM  user_account
[...]  ()
SELECT  book.owner_id  AS  book_owner_id,  book.id  AS  book_id,  book.title  AS  book_title
FROM  book
WHERE  book.owner_id  IN  (?,  ?)
[...]  (1,  2)
Spongebob Squarepants   ['100 Years of Krabby Patties', 'Sea Catch 22', 'The Sea Grapes of Wrath']
Sandy Cheeks   ['A Nut Like No Other', 'Geodesic Domes: A Retrospective', 'Rocketry for Squirrels']

load_only() 还可以应用于子实体,而无需声明要在关系本身使用的加载样式。如果我们不想改变 User.books 的默认加载样式,但仍要对 Book 应用加载规则,我们将使用 defaultload() 选项进行关联,在这种情况下,将保留默认关系加载样式 "lazy",并将我们的自定义 load_only() 规则应用于为每个 User.books 集合发出的 SELECT 语句:

>>> from sqlalchemy.orm import defaultload
>>> stmt = select(User).options(defaultload(User.books).load_only(Book.title))
>>> for user in session.scalars(stmt):
...     print(f"{user.fullname}   {[b.title for b in user.books]}")
SELECT  user_account.id,  user_account.name,  user_account.fullname
FROM  user_account
[...]  ()
SELECT  book.id  AS  book_id,  book.title  AS  book_title
FROM  book
WHERE  ?  =  book.owner_id
[...]  (1,)
Spongebob Squarepants   ['100 Years of Krabby Patties', 'Sea Catch 22', 'The Sea Grapes of Wrath']
SELECT  book.id  AS  book_id,  book.title  AS  book_title
FROM  book
WHERE  ?  =  book.owner_id
[...]  (2,)
Sandy Cheeks   ['A Nut Like No Other', 'Geodesic Domes: A Retrospective', 'Rocketry for Squirrels']

使用 load_only() 处理多个实体

load_only() 限制了其属性列表中所引用的单个实体(当前不允许传递跨越多个实体的属性列表)。在下面的示例中,给定的 load_only() 选项仅适用于 Book 实体。被选中的 User 实体不受影响;在生成的 SELECT 语句中,user_account 的所有列都存在,而 book 表只有 book.idbook.title

>>> stmt = select(User, Book).join_from(User, Book).options(load_only(Book.title))
>>> print(stmt)
SELECT  user_account.id,  user_account.name,  user_account.fullname,
book.id  AS  id_1,  book.title
FROM  user_account  JOIN  book  ON  user_account.id  =  book.owner_id 

如果我们想要将load_only()选项应用于UserBook,我们将使用两个单独的选项:

>>> stmt = (
...     select(User, Book)
...     .join_from(User, Book)
...     .options(load_only(User.name), load_only(Book.title))
... )
>>> print(stmt)
SELECT  user_account.id,  user_account.name,  book.id  AS  id_1,  book.title
FROM  user_account  JOIN  book  ON  user_account.id  =  book.owner_id 

在相关对象和集合上使用 load_only()

当使用关系加载器来控制相关对象的加载时,可以使用任何关系加载器的Load.load_only()方法将load_only()规则应用于子实体上的列。在下面的示例中,使用selectinload()加载每个User对象上的相关books集合。通过将Load.load_only()应用于结果选项对象,当为关系加载对象时,生成的 SELECT 将仅引用title列以及主键列:

>>> from sqlalchemy.orm import selectinload
>>> stmt = select(User).options(selectinload(User.books).load_only(Book.title))
>>> for user in session.scalars(stmt):
...     print(f"{user.fullname}   {[b.title for b in user.books]}")
SELECT  user_account.id,  user_account.name,  user_account.fullname
FROM  user_account
[...]  ()
SELECT  book.owner_id  AS  book_owner_id,  book.id  AS  book_id,  book.title  AS  book_title
FROM  book
WHERE  book.owner_id  IN  (?,  ?)
[...]  (1,  2)
Spongebob Squarepants   ['100 Years of Krabby Patties', 'Sea Catch 22', 'The Sea Grapes of Wrath']
Sandy Cheeks   ['A Nut Like No Other', 'Geodesic Domes: A Retrospective', 'Rocketry for Squirrels']

load_only()也可以应用于子实体,而无需说明用于关系本身的加载样式。如果我们不想更改User.books的默认加载样式,但仍要将加载仅规则应用于Book,我们将使用defaultload()选项进行链接,在这种情况下,将保留默认关系加载样式"lazy",并将我们的自定义load_only()规则应用于为每个User.books集合发出的 SELECT 语句:

>>> from sqlalchemy.orm import defaultload
>>> stmt = select(User).options(defaultload(User.books).load_only(Book.title))
>>> for user in session.scalars(stmt):
...     print(f"{user.fullname}   {[b.title for b in user.books]}")
SELECT  user_account.id,  user_account.name,  user_account.fullname
FROM  user_account
[...]  ()
SELECT  book.id  AS  book_id,  book.title  AS  book_title
FROM  book
WHERE  ?  =  book.owner_id
[...]  (1,)
Spongebob Squarepants   ['100 Years of Krabby Patties', 'Sea Catch 22', 'The Sea Grapes of Wrath']
SELECT  book.id  AS  book_id,  book.title  AS  book_title
FROM  book
WHERE  ?  =  book.owner_id
[...]  (2,)
Sandy Cheeks   ['A Nut Like No Other', 'Geodesic Domes: A Retrospective', 'Rocketry for Squirrels']

使用 defer() 来省略特定列

defer()加载器选项是对load_only()的更精细的替代,它允许将单个特定列标记为“不加载”。在下面的示例中,直接应用defer().cover_photo列,保持所有其他列的行为不变:

>>> from sqlalchemy.orm import defer
>>> stmt = select(Book).where(Book.owner_id == 2).options(defer(Book.cover_photo))
>>> books = session.scalars(stmt).all()
SELECT  book.id,  book.owner_id,  book.title,  book.summary
FROM  book
WHERE  book.owner_id  =  ?
[...]  (2,)
>>> for book in books:
...     print(f"{book.title}: {book.summary}")
A Nut Like No Other: some long summary
Geodesic Domes: A Retrospective: another long summary
Rocketry for Squirrels: yet another summary

load_only()一样,默认情况下未加载的列在使用惰性加载时会自行加载:

>>> img_data = books[0].cover_photo
SELECT  book.cover_photo  AS  book_cover_photo
FROM  book
WHERE  book.id  =  ?
[...]  (4,) 

可以在一条语句中使用多个defer()选项,以将多个列标记为延迟加载。

load_only()一样,defer()选项也包括将延迟属性在访问时引发异常而不是惰性加载的能力。这在 使用 raiseload 防止延迟列加载 部分中有所说明。

使用 raiseload 防止延迟加载列

当使用 load_only()defer() 加载器选项时,标记为延迟加载的对象属性在首次访问时具有默认行为,即在当前事务中发出 SELECT 语句以加载其值。通常需要阻止此加载的发生,并在访问属性时引发异常,表示不期望需要查询数据库以获取此列的需求。典型场景是使用已知需要用于操作进行的所有列加载对象,然后将其传递到视图层。应捕获视图层内发出的任何进一步的 SQL 操作,以便可以调整预先加载的操作以适应该额外的数据,而不是产生额外的惰性加载。

对于此用例,defer()load_only() 选项包括一个布尔参数 defer.raiseload,当设置为 True 时,将导致受影响的属性在访问时引发异常。在下面的示例中,延迟列 .cover_photo 将禁止属性访问:

>>> book = session.scalar(
...     select(Book).options(defer(Book.cover_photo, raiseload=True)).where(Book.id == 4)
... )
SELECT  book.id,  book.owner_id,  book.title,  book.summary
FROM  book
WHERE  book.id  =  ?
[...]  (4,)
>>> book.cover_photo
Traceback (most recent call last):
...
sqlalchemy.exc.InvalidRequestError: 'Book.cover_photo' is not available due to raiseload=True

当使用 load_only() 命名一组特定的非延迟加载列时,可以使用 load_only.raiseload 参数将 raiseload 行为应用于其余列,该参数将应用于所有延迟加载的属性:

>>> session.expunge_all()
>>> book = session.scalar(
...     select(Book).options(load_only(Book.title, raiseload=True)).where(Book.id == 5)
... )
SELECT  book.id,  book.title
FROM  book
WHERE  book.id  =  ?
[...]  (5,)
>>> book.summary
Traceback (most recent call last):
...
sqlalchemy.exc.InvalidRequestError: 'Book.summary' is not available due to raiseload=True

注意

目前还不能混合使用 load_only()defer() 选项,这两个选项指向同一个实体,在一个语句中改变某些属性的 raiseload 行为;目前这样做会产生未定义的属性加载行为。

另请参阅

defer.raiseload 特性是与关系对应的相同“raiseload”特性的列级版本。有关关系的“raiseload”,请参见防止不必要的惰性加载使用 raiseload 在本指南的关系加载技术部分。

配置映射上的列延迟

defer() 的功能作为映射列的默认行为可用,适用于不应在每次查询时无条件加载的列。要配置,请使用 mapped_column.deferred 参数。下面的示例说明了对 Book 应用默认列延迟加载的映射:

>>> class Book(Base):
...     __tablename__ = "book"
...     id: Mapped[int] = mapped_column(primary_key=True)
...     owner_id: Mapped[int] = mapped_column(ForeignKey("user_account.id"))
...     title: Mapped[str]
...     summary: Mapped[str] = mapped_column(Text, deferred=True)
...     cover_photo: Mapped[bytes] = mapped_column(LargeBinary, deferred=True)
...
...     def __repr__(self) -> str:
...         return f"Book(id={self.id!r}, title={self.title!r})"

使用上述映射,对 Book 的查询将自动不包括 summarycover_photo 列:

>>> book = session.scalar(select(Book).where(Book.id == 2))
SELECT  book.id,  book.owner_id,  book.title
FROM  book
WHERE  book.id  =  ?
[...]  (2,) 

与所有延迟加载属性一样,当首次访问加载的对象上的延迟加载属性时,默认行为是它们将 延迟加载 它们的值:

>>> img_data = book.cover_photo
SELECT  book.cover_photo  AS  book_cover_photo
FROM  book
WHERE  book.id  =  ?
[...]  (2,) 

defer()load_only() 加载器选项一样,映射器级别的延迟还包括一个选项,即当语句中没有其他选项时,可以发生 raiseload 行为,而不是延迟加载。这允许某些列不会默认加载,并且也永远不会在语句中使用显式指令时延迟加载。请参阅 配置映射器级别的raiseload行为 部分,了解如何配置和使用此行为的背景信息。

使用 deferred() 来命令式映射,映射 SQL 表达式

deferred() 函数是早期的、更通用的“延迟列”映射指令,在引入 SQLAlchemy 的 mapped_column() 构造之前就存在。

deferred() 在配置 ORM 映射器时使用,接受任意的 SQL 表达式或 Column 对象。因此,它适用于非声明性 命令式映射,将其传递给 map_imperatively.properties 字典:

from sqlalchemy import Blob
from sqlalchemy import Column
from sqlalchemy import ForeignKey
from sqlalchemy import Integer
from sqlalchemy import String
from sqlalchemy import Table
from sqlalchemy import Text
from sqlalchemy.orm import registry

mapper_registry = registry()

book_table = Table(
    "book",
    mapper_registry.metadata,
    Column("id", Integer, primary_key=True),
    Column("title", String(50)),
    Column("summary", Text),
    Column("cover_image", Blob),
)

class Book:
    pass

mapper_registry.map_imperatively(
    Book,
    book_table,
    properties={
        "summary": deferred(book_table.c.summary),
        "cover_image": deferred(book_table.c.cover_image),
    },
)

当映射的 SQL 表达式应该在延迟加载时,可以使用 deferred() 代替 column_property()

from sqlalchemy.orm import deferred

class User(Base):
    __tablename__ = "user"

    id: Mapped[int] = mapped_column(primary_key=True)
    firstname: Mapped[str] = mapped_column()
    lastname: Mapped[str] = mapped_column()
    fullname: Mapped[str] = deferred(firstname + " " + lastname)

另请参阅

使用 column_property - 在 SQL 表达式作为映射属性 部分

对声明性表列应用加载、持久性和映射选项 - 在使用声明性进行表配置章节中

使用undefer()来“急切地”加载延迟列

对于默认配置为延迟的映射上的列,undefer()选项将导致任何通常延迟的列都会在前端加载,也就是说,与映射的所有其他列一起加载。例如,我们可以对前面映射中标记为延迟的Book.summary列应用undefer()

>>> from sqlalchemy.orm import undefer
>>> book = session.scalar(select(Book).where(Book.id == 2).options(undefer(Book.summary)))
SELECT  book.id,  book.owner_id,  book.title,  book.summary
FROM  book
WHERE  book.id  =  ?
[...]  (2,) 

Book.summary列现在已经被急切加载,并且可以在不发出额外 SQL 的情况下访问:

>>> print(book.summary)
another long summary

按组加载延迟列

通常,当一个列使用mapped_column(deferred=True)进行映射时,当在对象上访问延迟属性时,SQL 将被发出以仅加载该特定列,而不加载其他列,即使映射还有其他被标记为延迟的列也是如此。在延迟属性是应该一次性加载一组属性的情况下,而不是针对每个属性单独发出 SQL 时,可以使用mapped_column.deferred_group参数,它接受一个任意字符串,用于定义要取消延迟的列的通用组:

>>> class Book(Base):
...     __tablename__ = "book"
...     id: Mapped[int] = mapped_column(primary_key=True)
...     owner_id: Mapped[int] = mapped_column(ForeignKey("user_account.id"))
...     title: Mapped[str]
...     summary: Mapped[str] = mapped_column(
...         Text, deferred=True, deferred_group="book_attrs"
...     )
...     cover_photo: Mapped[bytes] = mapped_column(
...         LargeBinary, deferred=True, deferred_group="book_attrs"
...     )
...
...     def __repr__(self) -> str:
...         return f"Book(id={self.id!r}, title={self.title!r})"

使用上述映射,访问summarycover_photo将同时使用一个 SELECT 语句加载两个列:

>>> book = session.scalar(select(Book).where(Book.id == 2))
SELECT  book.id,  book.owner_id,  book.title
FROM  book
WHERE  book.id  =  ?
[...]  (2,)
>>> img_data, summary = book.cover_photo, book.summary
SELECT  book.summary  AS  book_summary,  book.cover_photo  AS  book_cover_photo
FROM  book
WHERE  book.id  =  ?
[...]  (2,) 

使用undefer_group()按组取消延迟

如果延迟列配置为使用前一节中引入的mapped_column.deferred_group,则可以使用undefer_group()选项来急切加载整个组,传递要急切加载的组的字符串名称:

>>> from sqlalchemy.orm import undefer_group
>>> book = session.scalar(
...     select(Book).where(Book.id == 2).options(undefer_group("book_attrs"))
... )
SELECT  book.id,  book.owner_id,  book.title,  book.summary,  book.cover_photo
FROM  book
WHERE  book.id  =  ?
[...]  (2,) 

summarycover_photo都可以在不进行额外加载的情况下使用:

>>> img_data, summary = book.cover_photo, book.summary

通配符上的取消延迟

大多数 ORM 加载器选项都接受通配符表达式,用"*"表示,表示该选项应用于所有相关属性。如果一个映射具有一系列延迟列,那么所有这些列都可以一次性进行取消延迟,而不需要使用组名,只需指定通配符即可:

>>> book = session.scalar(select(Book).where(Book.id == 3).options(undefer("*")))
SELECT  book.id,  book.owner_id,  book.title,  book.summary,  book.cover_photo
FROM  book
WHERE  book.id  =  ?
[...]  (3,) 

配置映射级别的“raiseload”行为

首次引入的“raiseload”行为可用于 使用 raiseload 防止延迟列加载,还可以作为默认的映射器级行为应用,使用 mapped_column.deferred_raiseload 参数传递给 mapped_column()。使用此参数时,受影响的列将在所有情况下访问时引发异常,除非在查询时显式“未延迟”使用 undefer()load_only()

>>> class Book(Base):
...     __tablename__ = "book"
...     id: Mapped[int] = mapped_column(primary_key=True)
...     owner_id: Mapped[int] = mapped_column(ForeignKey("user_account.id"))
...     title: Mapped[str]
...     summary: Mapped[str] = mapped_column(Text, deferred=True, deferred_raiseload=True)
...     cover_photo: Mapped[bytes] = mapped_column(
...         LargeBinary, deferred=True, deferred_raiseload=True
...     )
...
...     def __repr__(self) -> str:
...         return f"Book(id={self.id!r}, title={self.title!r})"

使用以上映射,.summary.cover_photo 列默认情况下不可加载:

>>> book = session.scalar(select(Book).where(Book.id == 2))
SELECT  book.id,  book.owner_id,  book.title
FROM  book
WHERE  book.id  =  ?
[...]  (2,)
>>> book.summary
Traceback (most recent call last):
...
sqlalchemy.exc.InvalidRequestError: 'Book.summary' is not available due to raiseload=True

只有在查询时覆盖它们的行为,通常使用 undefer()undefer_group(),或者较少使用 defer(),属性才能被加载。下面的示例将 undefer('*') 应用于取消延迟所有属性,还使用了填充现有以刷新已加载对象的加载器选项:

>>> book = session.scalar(
...     select(Book)
...     .where(Book.id == 2)
...     .options(undefer("*"))
...     .execution_options(populate_existing=True)
... )
SELECT  book.id,  book.owner_id,  book.title,  book.summary,  book.cover_photo
FROM  book
WHERE  book.id  =  ?
[...]  (2,)
>>> book.summary
'another long summary'
```  ### 使用 `deferred()` 进行命令式映射,映射的 SQL 表达式

`deferred()` 函数是早期的、更通用的“延迟列”映射指令,它在引入 `mapped_column()` 构造之前就存在于 SQLAlchemy 中。

在配置 ORM 映射器时使用 `deferred()`,它接受任意的 SQL 表达式或 `Column` 对象。因此,它适用于非声明式的命令式映射,可以将其传递给 `map_imperatively.properties` 字典:

```py
from sqlalchemy import Blob
from sqlalchemy import Column
from sqlalchemy import ForeignKey
from sqlalchemy import Integer
from sqlalchemy import String
from sqlalchemy import Table
from sqlalchemy import Text
from sqlalchemy.orm import registry

mapper_registry = registry()

book_table = Table(
    "book",
    mapper_registry.metadata,
    Column("id", Integer, primary_key=True),
    Column("title", String(50)),
    Column("summary", Text),
    Column("cover_image", Blob),
)

class Book:
    pass

mapper_registry.map_imperatively(
    Book,
    book_table,
    properties={
        "summary": deferred(book_table.c.summary),
        "cover_image": deferred(book_table.c.cover_image),
    },
)

当映射的 SQL 表达式应该以延迟方式加载时,也可以使用 deferred() 替代 column_property()

from sqlalchemy.orm import deferred

class User(Base):
    __tablename__ = "user"

    id: Mapped[int] = mapped_column(primary_key=True)
    firstname: Mapped[str] = mapped_column()
    lastname: Mapped[str] = mapped_column()
    fullname: Mapped[str] = deferred(firstname + " " + lastname)

请参阅

使用 column_property - 在 SQL 表达式作为映射属性 部分中

应用 Imperative 表列的加载、持久化和映射选项 - 在 声明式表配置 部分中

使用undefer()“急切”加载延迟列

使用默认延迟列配置的映射上的列,undefer()选项将导致通常延迟的任何列被解除延迟,即,与映射的所有其他列一起前端加载。例如,我们可以将undefer()应用于前一映射中指定为延迟的Book.summary列:

>>> from sqlalchemy.orm import undefer
>>> book = session.scalar(select(Book).where(Book.id == 2).options(undefer(Book.summary)))
SELECT  book.id,  book.owner_id,  book.title,  book.summary
FROM  book
WHERE  book.id  =  ?
[...]  (2,) 

Book.summary列现在已经被急切加载,可以在不发出额外 SQL 的情况下访问:

>>> print(book.summary)
another long summary

按组加载延迟列

通常,当列被映射为mapped_column(deferred=True)时,当在对象上访问延迟属性时,将发出 SQL 仅加载该特定列,而不加载其他列,即使映射还有其他列也被标记为延迟。在常见情况下,延迟属性是一组应该同时加载的属性的一部分时,而不是为每个属性单独发出 SQL,可以使用mapped_column.deferred_group参数,该参数接受一个任意字符串,该字符串将定义一个通用列组以解除延迟:

>>> class Book(Base):
...     __tablename__ = "book"
...     id: Mapped[int] = mapped_column(primary_key=True)
...     owner_id: Mapped[int] = mapped_column(ForeignKey("user_account.id"))
...     title: Mapped[str]
...     summary: Mapped[str] = mapped_column(
...         Text, deferred=True, deferred_group="book_attrs"
...     )
...     cover_photo: Mapped[bytes] = mapped_column(
...         LargeBinary, deferred=True, deferred_group="book_attrs"
...     )
...
...     def __repr__(self) -> str:
...         return f"Book(id={self.id!r}, title={self.title!r})"

使用上述映射,访问summarycover_photo将一次性使用一个 SELECT 语句加载两个列:

>>> book = session.scalar(select(Book).where(Book.id == 2))
SELECT  book.id,  book.owner_id,  book.title
FROM  book
WHERE  book.id  =  ?
[...]  (2,)
>>> img_data, summary = book.cover_photo, book.summary
SELECT  book.summary  AS  book_summary,  book.cover_photo  AS  book_cover_photo
FROM  book
WHERE  book.id  =  ?
[...]  (2,) 

使用undefer_group()按组解除延迟

如果延迟列配置为mapped_column.deferred_group,如前一节介绍的,可以通过指定要急切加载的组的字符串名称来指示整个组的加载:

>>> from sqlalchemy.orm import undefer_group
>>> book = session.scalar(
...     select(Book).where(Book.id == 2).options(undefer_group("book_attrs"))
... )
SELECT  book.id,  book.owner_id,  book.title,  book.summary,  book.cover_photo
FROM  book
WHERE  book.id  =  ?
[...]  (2,) 

summarycover_photo都可用,无需额外加载:

>>> img_data, summary = book.cover_photo, book.summary

使用通配符解除延迟加载

大多数 ORM 加载器选项都接受通配符表达式,由 "*" 表示,表示该选项应用于所有相关属性。如果映射具有一系列延迟列,则可以通过指定通配符一次性解除所有这些列的延迟,而无需使用组名:

>>> book = session.scalar(select(Book).where(Book.id == 3).options(undefer("*")))
SELECT  book.id,  book.owner_id,  book.title,  book.summary,  book.cover_photo
FROM  book
WHERE  book.id  =  ?
[...]  (3,) 

配置映射器级别的“raiseload”行为

“raiseload” 行为最初是在使用 raiseload 防止延迟列加载中介绍的,也可以作为默认的映射器级行为应用,使用 mapped_column.deferred_raiseload 参数的 mapped_column()。当使用此参数时,受影响的列将在所有情况下在访问时引发异常,除非在查询时显式地使用 undefer()load_only() 进行“取消延迟”:

>>> class Book(Base):
...     __tablename__ = "book"
...     id: Mapped[int] = mapped_column(primary_key=True)
...     owner_id: Mapped[int] = mapped_column(ForeignKey("user_account.id"))
...     title: Mapped[str]
...     summary: Mapped[str] = mapped_column(Text, deferred=True, deferred_raiseload=True)
...     cover_photo: Mapped[bytes] = mapped_column(
...         LargeBinary, deferred=True, deferred_raiseload=True
...     )
...
...     def __repr__(self) -> str:
...         return f"Book(id={self.id!r}, title={self.title!r})"

使用上述映射,.summary.cover_photo 列默认情况下不可加载:

>>> book = session.scalar(select(Book).where(Book.id == 2))
SELECT  book.id,  book.owner_id,  book.title
FROM  book
WHERE  book.id  =  ?
[...]  (2,)
>>> book.summary
Traceback (most recent call last):
...
sqlalchemy.exc.InvalidRequestError: 'Book.summary' is not available due to raiseload=True

只有通过在查询时覆盖它们的行为,通常使用 undefer()undefer_group(),或者较少使用 defer(),属性才能被加载。下面的示例将 undefer('*') 应用于取消延迟加载所有属性,同时还利用填充现有对象来刷新已加载对象的加载器选项:

>>> book = session.scalar(
...     select(Book)
...     .where(Book.id == 2)
...     .options(undefer("*"))
...     .execution_options(populate_existing=True)
... )
SELECT  book.id,  book.owner_id,  book.title,  book.summary,  book.cover_photo
FROM  book
WHERE  book.id  =  ?
[...]  (2,)
>>> book.summary
'another long summary'

加载任意 SQL 表达式到对象上

如在选择 ORM 实体和属性和其他地方讨论的,select() 构造可以用于在结果集中加载任意 SQL 表达式。例如,如果我们想要发出一个查询,加载 User 对象,但还包括每个 User 拥有多少书籍的计数,我们可以使用 func.count(Book.id) 来向查询中添加一个“计数”列,该查询包括与 Book 的 JOIN 以及按所有者 id 分组。这将产生包含两个条目的 Row 对象,一个是 User,另一个是 func.count(Book.id)

>>> from sqlalchemy import func
>>> stmt = select(User, func.count(Book.id)).join_from(User, Book).group_by(Book.owner_id)
>>> for user, book_count in session.execute(stmt):
...     print(f"Username: {user.name}  Number of books: {book_count}")
SELECT  user_account.id,  user_account.name,  user_account.fullname,
count(book.id)  AS  count_1
FROM  user_account  JOIN  book  ON  user_account.id  =  book.owner_id
GROUP  BY  book.owner_id
[...]  ()
Username: spongebob  Number of books: 3
Username: sandy  Number of books: 3

在上面的例子中,User 实体和 “书籍数量” SQL 表达式是分开返回的。然而,一个常见的用例是生成一个查询,该查询仅返回 User 对象,例如可以使用 Session.scalars() 进行迭代,其中 func.count(Book.id) SQL 表达式的结果被 动态 应用于每个 User 实体。最终结果将类似于使用 column_property() 将任意 SQL 表达式映射到类的情况,只不过 SQL 表达式可以在查询时修改。对于这种用例,SQLAlchemy 提供了 with_expression() 加载器选项,当与映射器级别的 query_expression() 指令结合使用时,可能会产生此结果。

要将 with_expression() 应用于查询,映射类必须预先使用 query_expression() 指令配置好一个 ORM 映射属性;此指令将在映射类上生成一个适合接收查询时 SQL 表达式的属性。下面我们向 User 添加一个新属性 User.book_count。这个 ORM 映射属性是只读的,没有默认值;在加载的实例上访问它通常会返回 None

>>> from sqlalchemy.orm import query_expression
>>> class User(Base):
...     __tablename__ = "user_account"
...     id: Mapped[int] = mapped_column(primary_key=True)
...     name: Mapped[str]
...     fullname: Mapped[Optional[str]]
...     book_count: Mapped[int] = query_expression()
...
...     def __repr__(self) -> str:
...         return f"User(id={self.id!r}, name={self.name!r}, fullname={self.fullname!r})"

在我们的映射中配置了 User.book_count 属性后,我们可以使用 with_expression() 加载器选项从 SQL 表达式中填充数据,以便在加载每个 User 对象时应用自定义 SQL 表达式:

>>> from sqlalchemy.orm import with_expression
>>> stmt = (
...     select(User)
...     .join_from(User, Book)
...     .group_by(Book.owner_id)
...     .options(with_expression(User.book_count, func.count(Book.id)))
... )
>>> for user in session.scalars(stmt):
...     print(f"Username: {user.name}  Number of books: {user.book_count}")
SELECT  count(book.id)  AS  count_1,  user_account.id,  user_account.name,
user_account.fullname
FROM  user_account  JOIN  book  ON  user_account.id  =  book.owner_id
GROUP  BY  book.owner_id
[...]  ()
Username: spongebob  Number of books: 3
Username: sandy  Number of books: 3

在上面的例子中,我们将我们的 func.count(Book.id) 表达式从 select() 构造函数的 columns 参数中移出,并将其放入 with_expression() 加载器选项中。ORM 然后将其视为一个特殊的列加载选项,该选项动态应用于语句。

query_expression() 映射有以下注意事项:

  • 在一个对象上,如果没有使用 with_expression() 来填充属性,对象实例上的属性将具有值 None,除非在映射上设置了 query_expression.default_expr 参数为默认 SQL 表达式。

  • with_expression()不会填充已加载的对象,除非使用 Populate Existing。如下示例不会起作用,因为 A 对象已经加载:

    # load the first A
    obj = session.scalars(select(A).order_by(A.id)).first()
    
    # load the same A with an option; expression will **not** be applied
    # to the already-loaded object
    obj = session.scalars(select(A).options(with_expression(A.expr, some_expr))).first()
    

    要确保属性在现有对象上重新加载,请使用 Populate Existing 执行选项以确保重新填充所有列:

    obj = session.scalars(
        select(A)
        .options(with_expression(A.expr, some_expr))
        .execution_options(populate_existing=True)
    ).first()
    
  • 当对象过期时,with_expression() SQL 表达式会丢失。一旦对象过期,无论是通过 Session.expire() 还是通过 Session.commit() 的 expire_on_commit 行为,SQL 表达式及其值将不再与属性关联,并且在后续访问时将返回 None

  • with_expression() 作为对象加载选项,只对查询的最外层部分生效,并且仅适用于对完整实体进行的查询,而不适用于子查询中的任意列选择或复合语句(如 UNION)的元素。请参阅下一节使用 with_expression() 与 UNIONs、其他子查询中的示例。

  • 映射属性无法应用于查询的其他部分,例如 WHERE 子句、ORDER BY 子句,并利用临时表达式;也就是说,以下方式行不通:

    # can't refer to A.expr elsewhere in the query
    stmt = (
        select(A)
        .options(with_expression(A.expr, A.x + A.y))
        .filter(A.expr > 5)
        .order_by(A.expr)
    )
    

    在上述的 WHERE 子句和 ORDER BY 子句中,A.expr 表达式将会解析为 NULL。要在整个查询中使用该表达式,请将其赋值给一个变量并使用它:

    # assign desired expression up front, then refer to that in
    # the query
    a_expr = A.x + A.y
    stmt = (
        select(A)
        .options(with_expression(A.expr, a_expr))
        .filter(a_expr > 5)
        .order_by(a_expr)
    )
    

另请参见

with_expression() 选项是一种特殊选项,用于在查询时动态地将 SQL 表达式应用于映射类。对于在映射器上配置的普通固定 SQL 表达式,请参阅作为映射属性的 SQL 表达式部分。

使用 with_expression() 与 UNIONs、其他子查询

with_expression() 构造是一个 ORM 加载器选项,因此只能应用于用于加载特定 ORM 实体的 SELECT 语句的最外层级别。如果在后续用作子查询或复合语句(如 UNION)中使用,它将不起作用。

为了在子查询中使用任意的 SQL 表达式,应该使用正常的 Core 风格添加表达式的方法。要将子查询派生的表达式组装到 ORM 实体的query_expression()属性上,需要在 ORM 对象加载的顶层使用with_expression(),引用子查询中的 SQL 表达式。

在下面的示例中,针对 ORM 实体 A 使用了两个select() 构造,其中包含一个标记为 expr 的额外 SQL 表达式,并使用union_all() 进行组合。然后,在最顶层,从这个 UNION 中选择了 A 实体,使用了在从 UNIONs 和其他集合操作中选择实体中描述的查询技术,添加了一个使用with_expression()的选项,以将这个 SQL 表达式提取到新加载的 A 实例中:

>>> from sqlalchemy import union_all
>>> s1 = (
...     select(User, func.count(Book.id).label("book_count"))
...     .join_from(User, Book)
...     .where(User.name == "spongebob")
... )
>>> s2 = (
...     select(User, func.count(Book.id).label("book_count"))
...     .join_from(User, Book)
...     .where(User.name == "sandy")
... )
>>> union_stmt = union_all(s1, s2)
>>> orm_stmt = (
...     select(User)
...     .from_statement(union_stmt)
...     .options(with_expression(User.book_count, union_stmt.selected_columns.book_count))
... )
>>> for user in session.scalars(orm_stmt):
...     print(f"Username: {user.name}  Number of books: {user.book_count}")
SELECT  user_account.id,  user_account.name,  user_account.fullname,  count(book.id)  AS  book_count
FROM  user_account  JOIN  book  ON  user_account.id  =  book.owner_id
WHERE  user_account.name  =  ?
UNION  ALL
SELECT  user_account.id,  user_account.name,  user_account.fullname,  count(book.id)  AS  book_count
FROM  user_account  JOIN  book  ON  user_account.id  =  book.owner_id
WHERE  user_account.name  =  ?
[...]  ('spongebob',  'sandy')
Username: spongebob  Number of books: 3
Username: sandy  Number of books: 3
```  ### 使用 `with_expression()` 与 UNIONs,其他子查询

`with_expression()` 构造是一个 ORM 加载器选项,因此只能应用于要加载特定 ORM 实体的 SELECT 语句的最外层级别。如果在将用作子查询或作为联合等复合语句中的元素的`select()`内部使用,则不会产生任何效果。

为了在子查询中使用任意的 SQL 表达式,应该使用正常的 Core 风格添加表达式的方法。要将子查询派生的表达式组装到 ORM 实体的`query_expression()`属性上,需要在 ORM 对象加载的顶层使用`with_expression()`,引用子查询中的 SQL 表达式。

在下面的示例中,使用两个`select()`构造针对 ORM 实体 `A`,并在`expr`中标记了一个额外的 SQL 表达式,并使用`union_all()`将它们组合起来。然后,在最顶层,使用查询技术描述的 `with_expression()`从这个 UNION 中选择 `A` 实体,以将此 SQL 表达式提取到新加载的 `A` 实例上:

```py
>>> from sqlalchemy import union_all
>>> s1 = (
...     select(User, func.count(Book.id).label("book_count"))
...     .join_from(User, Book)
...     .where(User.name == "spongebob")
... )
>>> s2 = (
...     select(User, func.count(Book.id).label("book_count"))
...     .join_from(User, Book)
...     .where(User.name == "sandy")
... )
>>> union_stmt = union_all(s1, s2)
>>> orm_stmt = (
...     select(User)
...     .from_statement(union_stmt)
...     .options(with_expression(User.book_count, union_stmt.selected_columns.book_count))
... )
>>> for user in session.scalars(orm_stmt):
...     print(f"Username: {user.name}  Number of books: {user.book_count}")
SELECT  user_account.id,  user_account.name,  user_account.fullname,  count(book.id)  AS  book_count
FROM  user_account  JOIN  book  ON  user_account.id  =  book.owner_id
WHERE  user_account.name  =  ?
UNION  ALL
SELECT  user_account.id,  user_account.name,  user_account.fullname,  count(book.id)  AS  book_count
FROM  user_account  JOIN  book  ON  user_account.id  =  book.owner_id
WHERE  user_account.name  =  ?
[...]  ('spongebob',  'sandy')
Username: spongebob  Number of books: 3
Username: sandy  Number of books: 3

列加载 API

对象名称 描述
defer(key, *addl_attrs, [raiseload]) 指示给定的面向列的属性应该延迟加载,例如在访问之前不加载。
deferred(column, *additional_columns, [group, raiseload, comparator_factory, init, repr, default, default_factory, compare, kw_only, active_history, expire_on_flush, info, doc]) 表示默认情况下不加载的基于列的映射属性。
load_only(*attrs, [raiseload]) 表示对于特定实体,只应加载给定列表的基于列的属性名称;所有其他属性都将被延迟加载。
query_expression([default_expr], *, [repr, compare, expire_on_flush, info, doc]) 表示从查询时 SQL 表达式填充的属性。
undefer(key, *addl_attrs) 指示给定的面向列的属性应该取消延迟加载,例如在整个实体的 SELECT 语句中指定。
undefer_group(name) 表示给定延迟组名内的列应该取消延迟加载。
with_expression(key, expression) 将特定的 SQL 表达式应用于“延迟表达式”属性。
function sqlalchemy.orm.defer(key: Literal['*'] | QueryableAttribute[Any], *addl_attrs: Literal['*'] | QueryableAttribute[Any], raiseload: bool = False) → _AbstractLoad

指示给定的面向列的属性应该延迟加载,例如在访问之前不加载。

此函数是Load接口的一部分,支持方法链和独立操作。

例如:

from sqlalchemy.orm import defer

session.query(MyClass).options(
 defer(MyClass.attribute_one),
 defer(MyClass.attribute_two)
)

要指定相关类上属性的延迟加载,可以逐个指定路径,沿着链指定每个链接的加载样式。要保持链接的加载样式不变,请使用defaultload()

session.query(MyClass).options(
 defaultload(MyClass.someattr).defer(RelatedClass.some_column)
)

可以使用Load.options()一次捆绑多个与关系相关的延迟选项。

select(MyClass).options(
 defaultload(MyClass.someattr).options(
 defer(RelatedClass.some_column),
 defer(RelatedClass.some_other_column),
 defer(RelatedClass.another_column)
 )
)

参数:

  • key – 要延迟加载的属性。

  • raiseload – 当访问延迟属性时引发InvalidRequestError而不是惰性加载值。用于防止发出不必要的 SQL。

版本 1.4 中的新功能。

另请参阅

限制使用列延迟加载 - 在 ORM 查询指南中

load_only()

undefer()

function sqlalchemy.orm.deferred(column: _ORMColumnExprArgument[_T], *additional_columns: _ORMColumnExprArgument[Any], group: str | None = None, raiseload: bool = False, comparator_factory: Type[PropComparator[_T]] | None = None, init: _NoArg | bool = _NoArg.NO_ARG, repr: _NoArg | bool = _NoArg.NO_ARG, default: Any | None = _NoArg.NO_ARG, default_factory: _NoArg | Callable[[], _T] = _NoArg.NO_ARG, compare: _NoArg | bool = _NoArg.NO_ARG, kw_only: _NoArg | bool = _NoArg.NO_ARG, active_history: bool = False, expire_on_flush: bool = True, info: _InfoType | None = None, doc: str | None = None) → MappedSQLExpression[_T]

指示一个基于列的映射属性,默认情况下不会加载,除非访问。

在使用mapped_column()时,通过使用mapped_column.deferred参数,提供了与deferred()构造相同的功能。

参数:

  • *columns – 要映射的列。通常是单个Column对象,但为了支持在同一属性下映射多个列,也支持集合。

  • raiseload

    boolean,如果为 True,则表示在执行加载操作时应引发异常。

    版本 1.4 中的新功能。

其他参数与column_property()相同。

另请参阅

使用 deferred()为命令式映射器、映射的 SQL 表达式

function sqlalchemy.orm.query_expression(default_expr: _ORMColumnExprArgument[_T] = <sqlalchemy.sql.elements.Null object>, *, repr: Union[_NoArg, bool] = _NoArg.NO_ARG, compare: Union[_NoArg, bool] = _NoArg.NO_ARG, expire_on_flush: bool = True, info: Optional[_InfoType] = None, doc: Optional[str] = None) → MappedSQLExpression[_T]

指示从查询时 SQL 表达式填充的属性。

参数:

default_expr – 可选的 SQL 表达式对象,如果未使用with_expression()分配,则将在所有情况下使用。

版本 1.2 中的新功能。

另请参阅

将任意 SQL 表达式加载到对象上 - 背景和使用示例

function sqlalchemy.orm.load_only(*attrs: Literal['*'] | QueryableAttribute[Any], raiseload: bool = False) → _AbstractLoad

指示对于特定实体,只应加载给定的列名列表的基于列的属性;所有其他属性将被延迟加载。

此函数是Load接口的一部分,支持方法链接和独立操作。

示例 - 给定一个类User,仅加载namefullname属性:

session.query(User).options(load_only(User.name, User.fullname))

示例 - 给定一个关系User.addresses -> Address,为User.addresses集合指定子查询加载,但在每个Address对象上仅加载email_address属性:

session.query(User).options(
 subqueryload(User.addresses).load_only(Address.email_address)
)

对于具有多个实体的语句,可以使用Load构造函数来特定引用主实体:

stmt = (
 select(User, Address)
 .join(User.addresses)
 .options(
 Load(User).load_only(User.name, User.fullname),
 Load(Address).load_only(Address.email_address),
 )
)

与 populate_existing 执行选项一起使用时,只会刷新列出的属性。

参数:

  • *attrs – 需要加载的属性,其他所有属性都将被延迟加载。

  • raiseload

    当访问延迟属性时引发 InvalidRequestError 而不是懒加载值。用于防止不必要的 SQL 发出。

    2.0 版本中新增。

参见

限制加载哪些列与列延迟 - 在 ORM 查询指南 中

参数:

  • *attrs – 需要加载的属性,其他所有属性都将被延迟加载。

  • raiseload

    当访问延迟属性时引发 InvalidRequestError 而不是懒加载值。用于防止不必要的 SQL 发出。

    2.0 版本中新增。

function sqlalchemy.orm.undefer(key: Literal['*'] | QueryableAttribute[Any], *addl_attrs: Literal['*'] | QueryableAttribute[Any]) → _AbstractLoad

表示给定的面向列的属性应该取消延迟加载,例如在整个实体的 SELECT 语句中指定。

通常在映射上设置的列作为 deferred() 属性。

此函数是 Load 接口的一部分,支持方法链接和独立操作。

示例:

# undefer two columns
session.query(MyClass).options(
 undefer(MyClass.col1), undefer(MyClass.col2)
)

# undefer all columns specific to a single class using Load + *
session.query(MyClass, MyOtherClass).options(
 Load(MyClass).undefer("*")
)

# undefer a column on a related object
select(MyClass).options(
 defaultload(MyClass.items).undefer(MyClass.text)
)

参数:

key – 需要取消延迟加载的属性。

参见

限制加载哪些列与列延迟 - 在 ORM 查询指南 中

defer()

undefer_group()

function sqlalchemy.orm.undefer_group(name: str) → _AbstractLoad

表示给定延迟组名称内的列应取消延迟加载。

正在取消延迟加载的列设置在映射上作为deferred()属性,并包括一个“组”名称。

例如:

session.query(MyClass).options(undefer_group("large_attrs"))

要在相关实体上取消一组属性的延迟加载,可以使用关系加载器选项拼写出路径,例如 defaultload():

select(MyClass).options(
 defaultload("someattr").undefer_group("large_attrs")
)

参见

限制加载哪些列与列延迟 - 在 ORM 查询指南 中

defer()

undefer()

function sqlalchemy.orm.with_expression(key: _AttrType, expression: _ColumnExpressionArgument[Any]) → _AbstractLoad

对“延迟表达式”属性应用临时 SQL 表达式。

此选项与指示应该成为临时 SQL 表达式目标的属性的 query_expression() 映射器级构造一起使用。

例如:

stmt = select(SomeClass).options(
 with_expression(SomeClass.x_y_expr, SomeClass.x + SomeClass.y)
)

1.2 版本中新增。

参数:

  • key – 需要填充的属性

  • expr – 应用于属性的 SQL 表达式。

参见

将任意的 SQL 表达式加载到对象上 - 背景和使用示例

posted @ 2024-06-22 11:33  绝不原创的飞龙  阅读(4)  评论(0编辑  收藏  举报