映射类继承层次结构


SQLAlchemy 支持三种形式的继承:


  • 单个表继承 – 单个表表示多种类型的类;


  • 具体表继承 – 每种类型的类都由独立的表表示;


  • Join Table inheritance (联接表继承) – 类层次结构在依赖表之间进行分解。每个类都由其自己的表表示,该表仅包含该类的本地属性。


最常见的继承形式是 single 和 joined table,而具体继承带来了更多的配置挑战。


当映射器配置为继承关系时,SQLAlchemy 能够多态加载元素,这意味着单个查询可以返回多种类型的对象。


另请参阅


为继承映射编写 SELECT 语句 - 在 ORM 查询指南


继承映射配方 - 联合继承、单一继承和具体继承的完整示例


连接表继承


在联接表继承中,类层次结构中的每个类都由一个不同的表表示。查询层次结构中的特定子类将呈现为其继承路径中所有表的 SQL JOIN。如果查询的类是基类,则改为查询基表,并可选择同时包含其他表或允许稍后加载特定于子表的属性。


在所有情况下,要实例化给定行的最终类都由在基类上定义的鉴别器列或 SQL 表达式确定,这将产生与特定子类关联的标量值。


联接的继承层次结构中的基类配置了额外的参数,这些参数将指示多态鉴别器列,以及基类本身的多态标识符(可选):

from sqlalchemy import ForeignKey
from sqlalchemy.orm import DeclarativeBase
from sqlalchemy.orm import Mapped
from sqlalchemy.orm import mapped_column


class Base(DeclarativeBase):
    pass


class Employee(Base):
    __tablename__ = "employee"
    id: Mapped[int] = mapped_column(primary_key=True)
    name: Mapped[str]
    type: Mapped[str]

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

    def __repr__(self):
        return f"{self.__class__.__name__}({self.name!r})"


在上面的示例中,鉴别器是 type 列,以使用 Mapper.polymorphic_on 参数配置的列为准。这 parameter 接受列向表达式,指定为字符串 要使用的映射属性的名称或作为列表达式对象,例如 Columnmapped_column() 构造。


鉴别器列将存储一个指示对象类型的值 表示。该列可以是任何数据类型,但 string 和 integer 是最常见的。 要应用于此的实际数据值 列中特定行的 Mapper.polymorphic_identity参数,如下所述。


虽然多态性鉴别器表达不是绝对必要的,但如果需要多态性加载,则需要多态性鉴别器表达。在基表上建立列是实现此目的的最简单方法,但是非常复杂的继承映射可能会使用 SQL 表达式(例如 CASE 表达式)作为多态鉴别器。


注意


目前,只有一个鉴别器列或 SQL 表达式可以是 为整个继承层次结构配置,通常在层次结构中最基本的类上。尚不支持 “Cascading” polymorphic discriminator 表达式。


接下来,我们定义 EmployeeEngineerManager 子类。每个都包含表示它们所代表的子类所特有的属性的列。每个 table 还必须包含一个 primary key 列(或多个列)以及对父 table 的外键引用:

class Engineer(Employee):
    __tablename__ = "engineer"
    id: Mapped[int] = mapped_column(ForeignKey("employee.id"), primary_key=True)
    engineer_name: Mapped[str]

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


class Manager(Employee):
    __tablename__ = "manager"
    id: Mapped[int] = mapped_column(ForeignKey("employee.id"), primary_key=True)
    manager_name: Mapped[str]

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


在上面的示例中,每个映射都指定了 Mapper.polymorphic_identity 参数。 此值填充由 Mapper.polymorphic_on Base Mapper 上建立的参数。Mapper.polymorphic_identity 参数对于整个层次结构中的每个映射类应该是唯一的,并且每个映射的类应该只有一个 “identity”;如上所述,不支持某些子类引入第二个身份的“级联”身份。


ORM 使用由 Mapper.polymorphic_identity 中设置的值 order 来确定加载行时行属于哪个类 多态性。 在上面的示例中,表示 Employeetype 列中将具有值 'employee';同样,每个 Engineer 都会得到 'engineer' 值,每个 Manager 都会得到 'manager' 值。无论继承映射是否使用 子类的不同联接表,如 Join Table Inheritance 或 All 一个表作为单个表继承,此值应为 persisted 并在查询时对 ORM 可用。这 Mapper.polymorphic_identity参数也适用于混凝土 table 继承,但实际上并未持久化;请参阅后面的部分,网址为 具体表继承了解详细信息。


在多态设置中,最常见的是外键约束建立在与主键本身相同的列或同一列上,但这不是必需的;与主键不同的列也可以通过外键引用父级。从基表构造 JOIN 到子类的方式也是可直接自定义的,但是这很少是必需的。


完成联接的继承映射后,对 Employee 进行查询 将返回 EmployeeEngineerManager 的组合 对象。新保存的 EngineerManagerEmployee 对象将自动使用正确的 “discriminator” 值填充 employee.type 列,在本例中为 “engineer”“manager”“employee”,视情况而定。


具有 Joined 继承的关系


联接表继承完全支持关系。涉及联接继承类的关系应以层次结构中也对应于外键约束的类为目标;在下面,由于 employee 表具有返回到 Company 表的外键约束,因此在 Company 之间设置了关系 和员工

from __future__ import annotations

from sqlalchemy.orm import relationship


class Company(Base):
    __tablename__ = "company"
    id: Mapped[int] = mapped_column(primary_key=True)
    name: Mapped[str]
    employees: Mapped[List[Employee]] = relationship(back_populates="company")


class Employee(Base):
    __tablename__ = "employee"
    id: Mapped[int] = mapped_column(primary_key=True)
    name: Mapped[str]
    type: Mapped[str]
    company_id: Mapped[int] = mapped_column(ForeignKey("company.id"))
    company: Mapped[Company] = relationship(back_populates="employees")

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


class Manager(Employee): ...


class Engineer(Employee): ...


如果外键约束位于与子类对应的表上,则关系应以该子类为目标。在下面的示例中,有一个从 managercompany 的外键约束,因此在 ManagerCompany 类之间建立了关系:

class Company(Base):
    __tablename__ = "company"
    id: Mapped[int] = mapped_column(primary_key=True)
    name: Mapped[str]
    managers: Mapped[List[Manager]] = relationship(back_populates="company")


class Employee(Base):
    __tablename__ = "employee"
    id: Mapped[int] = mapped_column(primary_key=True)
    name: Mapped[str]
    type: Mapped[str]

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


class Manager(Employee):
    __tablename__ = "manager"
    id: Mapped[int] = mapped_column(ForeignKey("employee.id"), primary_key=True)
    manager_name: Mapped[str]

    company_id: Mapped[int] = mapped_column(ForeignKey("company.id"))
    company: Mapped[Company] = relationship(back_populates="managers")

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


class Engineer(Employee): ...


在上面,Manager 类将具有 Manager.company 属性; Company 将具有一个 Company.managers 属性,该属性始终针对 employeemanager 表的联接进行加载。


加载 Join 继承映射


有关继承加载技术的背景信息,包括要在 mapper 配置时和查询时查询的 table 的配置,请参阅为继承映射编写 SELECT 语句部分。


单表继承


单个 table 继承表示单个 table 中所有子类的所有属性。具有该类唯一属性的特定子类将将它们保留在表中的列中,如果该行引用不同类型的对象,则这些列为 NULL。


查询层次结构中的特定子类将呈现为针对基表的 SELECT,其中将包含一个 WHERE 子句,该子句将行限制为具有特定值或鉴别器列或表达式中存在的值的行。


与联接表继承相比,单个表继承具有简单性的优势;查询的效率要高得多,因为只需要涉及一个表即可加载每个表示类的对象。


单表继承配置看起来与联接表继承非常相似,只是只有基类指定了__tablename__。基表上还需要一个鉴别器列,以便可以区分类。


即使子类共享其所有属性的基表,但在使用 Declare 时,仍可以在子类上指定mapped_column对象,这表明该列将仅映射到该子类;mapped_column将应用于同一基础 Table 对象:

class Employee(Base):
    __tablename__ = "employee"
    id: Mapped[int] = mapped_column(primary_key=True)
    name: Mapped[str]
    type: Mapped[str]

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


class Manager(Employee):
    manager_data: Mapped[str] = mapped_column(nullable=True)

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


class Engineer(Employee):
    engineer_info: Mapped[str] = mapped_column(nullable=True)

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


请注意,派生类 Manager 和 Engineer 的映射器省略了 __tablename__,表示他们没有自己的映射表。此外mapped_column,带有 nullable=True 包含在内;由于为这些类声明的 Python 类型不包含 Optional[],因此该列通常会映射为 NOT NULL,这并不合适,因为此列只期望为对应于该特定子类的那些行填充。


使用 use_existing_column 解决列冲突


请注意,在上一节中,manager_nameengineer_info 列被“向上移动”以应用于Employee.__table__,因为它们在没有自己的 table 的子类上声明。当两个子类想要指定同一列时,会出现一个棘手的情况,如下所示:

from datetime import datetime


class Employee(Base):
    __tablename__ = "employee"
    id: Mapped[int] = mapped_column(primary_key=True)
    name: Mapped[str]
    type: Mapped[str]

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


class Engineer(Employee):
    __mapper_args__ = {
        "polymorphic_identity": "engineer",
    }
    start_date: Mapped[datetime] = mapped_column(nullable=True)


class Manager(Employee):
    __mapper_args__ = {
        "polymorphic_identity": "manager",
    }
    start_date: Mapped[datetime] = mapped_column(nullable=True)


在上面,在 EngineerManager 上声明的 start_date 列 将导致错误:

sqlalchemy.exc.ArgumentError: Column 'start_date' on class Manager conflicts
with existing column 'employee.start_date'.  If using Declarative,
consider using the use_existing_column parameter of mapped_column() to
resolve conflicts.


上述场景对 Declarative 映射系统提出了歧义,可以使用 mapped_column.use_existing_column parameter 的 mapped_column() 上,该参数指示 mapped_column() 要查看存在的继承超类,并使用已经 mapped(如果已经存在),则 else 来映射新列:

from sqlalchemy import DateTime


class Employee(Base):
    __tablename__ = "employee"
    id: Mapped[int] = mapped_column(primary_key=True)
    name: Mapped[str]
    type: Mapped[str]

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


class Engineer(Employee):
    __mapper_args__ = {
        "polymorphic_identity": "engineer",
    }

    start_date: Mapped[datetime] = mapped_column(
        nullable=True, use_existing_column=True
    )


class Manager(Employee):
    __mapper_args__ = {
        "polymorphic_identity": "manager",
    }

    start_date: Mapped[datetime] = mapped_column(
        nullable=True, use_existing_column=True
    )


在上面,当映射 Manager 时,start_date列已经存在于 Employee 类中,由 工程师已经映射了。这 mapped_column.use_existing_column 参数向 mapped_column() 指示它应该在映射的 Table 上查找请求的 Column 首先是员工,如果存在,请维护该现有映射。如果不存在,mapped_column() 将正常映射该列,将其添加为 Table 中由 员工超类。


2.0.0b4 版中的新增功能:- 添加了 mapped_column.use_existing_column , 它提供了一种与 2.0 兼容的方法,用于在继承的 子类。 前面的方法结合了 declared_attr.__table__ 也可以继续运行,但缺少 PEP 484 键入支持。


类似的概念可以与 mixin 类一起使用(请参阅 使用 Mixin 组合映射层次结构),以定义来自可重用 mixin 类的特定列序列和/或其他映射属性:

class Employee(Base):
    __tablename__ = "employee"
    id: Mapped[int] = mapped_column(primary_key=True)
    name: Mapped[str]
    type: Mapped[str]

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


class HasStartDate:
    start_date: Mapped[datetime] = mapped_column(
        nullable=True, use_existing_column=True
    )


class Engineer(HasStartDate, Employee):
    __mapper_args__ = {
        "polymorphic_identity": "engineer",
    }


class Manager(HasStartDate, Employee):
    __mapper_args__ = {
        "polymorphic_identity": "manager",
    }


单表继承的关系


单个表继承完全支持关系。配置的方式与 join 继承相同;外键属性应位于关系的 “foreign” 端的同一类上:

class Company(Base):
    __tablename__ = "company"
    id: Mapped[int] = mapped_column(primary_key=True)
    name: Mapped[str]
    employees: Mapped[List[Employee]] = relationship(back_populates="company")


class Employee(Base):
    __tablename__ = "employee"
    id: Mapped[int] = mapped_column(primary_key=True)
    name: Mapped[str]
    type: Mapped[str]
    company_id: Mapped[int] = mapped_column(ForeignKey("company.id"))
    company: Mapped[Company] = relationship(back_populates="employees")

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


class Manager(Employee):
    manager_data: Mapped[str] = mapped_column(nullable=True)

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


class Engineer(Employee):
    engineer_info: Mapped[str] = mapped_column(nullable=True)

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


此外,与 join 继承的情况一样,我们可以创建涉及特定子类的关系。查询时, SELECT 语句将包含一个 WHERE 子句,该子句将类选择限制为该子类或子类:

class Company(Base):
    __tablename__ = "company"
    id: Mapped[int] = mapped_column(primary_key=True)
    name: Mapped[str]
    managers: Mapped[List[Manager]] = relationship(back_populates="company")


class Employee(Base):
    __tablename__ = "employee"
    id: Mapped[int] = mapped_column(primary_key=True)
    name: Mapped[str]
    type: Mapped[str]

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


class Manager(Employee):
    manager_name: Mapped[str] = mapped_column(nullable=True)

    company_id: Mapped[int] = mapped_column(ForeignKey("company.id"))
    company: Mapped[Company] = relationship(back_populates="managers")

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


class Engineer(Employee):
    engineer_info: Mapped[str] = mapped_column(nullable=True)

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


在上面,Manager 类将具有 Manager.company 属性; Company 将具有一个 Company.managers 属性,该属性始终针对员工加载,并带有一个额外的 WHERE 子句,该子句将行限制为 type = 'manager' 的行。


使用 polymorphic_abstract 构建更深的层次结构


2.0 版的新Function。


在构建任何类型的继承层次结构时,映射的类可以包含 Mapper.polymorphic_abstract 参数设置为 True,即 表示类应该正常映射,但不希望 直接实例化,并且不会包含 Mapper.polymorphic_identity。然后可以声明子类 作为此映射类的子类,它们本身可以包含一个 Mapper.polymorphic_identity,因此可以正常使用。这允许一系列子类一次被一个公共基类引用,该基类在层次结构中被认为是“抽象的”,无论是在查询还是在 relationship() 声明中。这种用法不同于将 __abstract__ 属性与 Declare 一起使用,后者使目标类完全未映射,因此本身不能用作映射类。Mapper.polymorphic_abstract 可以应用于层次结构中任何级别的任何类或类,包括同时应用于多个级别。


例如,假设 ManagerPrincipal 都根据超类 Executive 进行分类,而 EngineerSysadmin 根据超类 Technologist 进行分类。既不是 Executive 也不是 Technologist 是 ever 实例化的,因此没有 Mapper.polymorphic_identity。可以使用 Mapper.polymorphic_abstract 配置这些类,如下所示:

class Employee(Base):
    __tablename__ = "employee"
    id: Mapped[int] = mapped_column(primary_key=True)
    name: Mapped[str]
    type: Mapped[str]

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


class Executive(Employee):
    """An executive of the company"""

    executive_background: Mapped[str] = mapped_column(nullable=True)

    __mapper_args__ = {"polymorphic_abstract": True}


class Technologist(Employee):
    """An employee who works with technology"""

    competencies: Mapped[str] = mapped_column(nullable=True)

    __mapper_args__ = {"polymorphic_abstract": True}


class Manager(Executive):
    """a manager"""

    __mapper_args__ = {"polymorphic_identity": "manager"}


class Principal(Executive):
    """a principal of the company"""

    __mapper_args__ = {"polymorphic_identity": "principal"}


class Engineer(Technologist):
    """an engineer"""

    __mapper_args__ = {"polymorphic_identity": "engineer"}


class SysAdmin(Technologist):
    """a systems administrator"""

    __mapper_args__ = {"polymorphic_identity": "sysadmin"}


在上面的示例中,新类 TechnologistExecutive 是普通的映射类,并且还指示要添加到 称为 executive_backgroundCompetencies 的超类。但是,它们都缺乏Mapper.polymorphic_identity设置;这是因为预计 TechnologistExecutive 永远不会直接实例化;我们总是有 ManagerPrincipal工程师系统管理员。 但是,我们可以查询 PrincipalTechnologist 角色,以及让它们成为 relationship() 的目标。下面的示例演示了 Technologist 对象的 SELECT 语句:

session.scalars(select(Technologist)).all()
SELECT employee.id, employee.name, employee.type, employee.competencies FROM employee WHERE employee.type IN (?, ?) [...] ('engineer', 'sysadmin')


TechnologistExecutive abstract 映射类也可以成为 relationship() 映射的目标,就像任何其他映射类一样。我们可以将上面的例子扩展到包括 Company,以及单独的集合 Company.technologistsCompany.principals

class Company(Base):
    __tablename__ = "company"
    id = Column(Integer, primary_key=True)

    executives: Mapped[List[Executive]] = relationship()
    technologists: Mapped[List[Technologist]] = relationship()


class Employee(Base):
    __tablename__ = "employee"
    id: Mapped[int] = mapped_column(primary_key=True)

    # foreign key to "company.id" is added
    company_id: Mapped[int] = mapped_column(ForeignKey("company.id"))

    # rest of mapping is the same
    name: Mapped[str]
    type: Mapped[str]

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


# Executive, Technologist, Manager, Principal, Engineer, SysAdmin
# classes from previous example would follow here unchanged


使用上面的映射,我们可以在 Company.technologistsCompany.executives 之间单独使用连接和关系加载技术:

session.scalars(
    select(Company)
    .join(Company.technologists)
    .where(Technologist.competency.ilike("%java%"))
    .options(selectinload(Company.executives))
).all()
SELECT company.id FROM company JOIN employee ON company.id = employee.company_id AND employee.type IN (?, ?) WHERE lower(employee.competencies) LIKE lower(?) [...] ('engineer', 'sysadmin', '%java%') SELECT employee.company_id AS employee_company_id, employee.id AS employee_id, employee.name AS employee_name, employee.type AS employee_type, employee.executive_background AS employee_executive_background FROM employee WHERE employee.company_id IN (?) AND employee.type IN (?, ?) [...] (1, 'manager', 'principal')


另请参阅


__abstract__ - 声明性参数,它允许声明性类在层次结构中完全取消映射,同时仍从映射的超类扩展。


加载单继承映射


单表继承的加载技术与 用于联接表继承的 API 和高度抽象的 在这两种映射类型之间提供,以便于在 它们以及将它们混合在一个层次结构中(只是省略了 __tablename__ 来自任何要成为单继承的子类)。请参阅编写 SELECT 语句以获取继承映射用于单一继承映射的 SELECT 语句,用于有关继承加载技术的文档,包括要在映射器配置时和查询时查询的类的配置。


具体表继承


具体继承将每个子类映射到其自己的不同表,每个表都包含生成该类的实例所需的所有列。默认情况下,具体继承配置以非多态方式查询;对特定类的查询将仅查询该类的 table,并且仅返回该类的实例。通过在 mapper 中配置一个特殊的 SELECT 来启用具体类的多态加载,该 SELECT 通常作为所有表的 UNION 生成。


警告


具体表继承比联接或单个表继承复杂得多,并且在功能上也受到更多限制 特别是与关系、预先加载、 和多态性加载。 当多态使用时,它会产生 使用 UNIONS 的非常大的查询,其执行效果不如简单联接。强烈建议,如果需要关系加载和多态加载的灵活性,请尽可能使用联接或单表继承。如果不需要多态加载,则如果每个类都完全引用自己的表,则可以使用普通的非继承映射。


虽然 join 和 single table 继承在 “polymorphic” 加载中很流畅,但在具体继承中则更为尴尬。因此,当多态加载时,具体继承更合适 不是必需的。建立涉及具体继承类的关系也更加尴尬。


要将类建立为使用具体继承,请添加 Mapper.concrete __mapper_args__参数。这向 Declarative 和 mapping 表明不应将超类表视为 Map 的一部分:

class Employee(Base):
    __tablename__ = "employee"

    id = mapped_column(Integer, primary_key=True)
    name = mapped_column(String(50))


class Manager(Employee):
    __tablename__ = "manager"

    id = mapped_column(Integer, primary_key=True)
    name = mapped_column(String(50))
    manager_data = mapped_column(String(50))

    __mapper_args__ = {
        "concrete": True,
    }


class Engineer(Employee):
    __tablename__ = "engineer"

    id = mapped_column(Integer, primary_key=True)
    name = mapped_column(String(50))
    engineer_info = mapped_column(String(50))

    __mapper_args__ = {
        "concrete": True,
    }


应注意两个关键点:


  • 我们必须在每个子类上显式定义所有列,即使是 相同的名称。 列(如 此处 Employee.name 不会复制到 ManagerEngineer 为我们映射的表中。


  • 虽然 EngineerManager 类在与 Employee 的继承关系中映射,但它们仍然没有 包括多态加载。这意味着,如果我们查询 Employee 对象,则根本不查询 ManagerEngineer 表。


具体多态加载配置


具有具体继承的多态加载要求针对应具有多态加载的每个基类配置专用的 SELECT。此 SELECT 需要能够单独访问所有映射的 table,并且通常是使用 SQLAlchemy 帮助程序 polymorphic_union() 构造的 UNION 语句。


为继承映射编写 SELECT 语句中所述,任何类型的 mapper 继承配置都可以配置为默认使用 Mapper.with_polymorphic 参数从特殊的可选择对象加载。当前的公共 API 要求在首次构造 Mapper 时在 Mapper 上设置此参数。


但是,在 Declare 的情况下,mapper 和 Table 即 Mapped 在定义 Mapped 类的那一刻立即创建。 这意味着尚不能提供 Mapper.with_polymorphic 参数,因为与子类对应的 Table 对象尚未定义。


有一些策略可用于解决此循环,但是 Declarative 提供了辅助类 ConcreteBaseAbstractConcreteBase 在幕后处理这个问题。


使用 ConcreteBase,我们可以像设置其他形式的继承映射一样设置我们的具体映射:

from sqlalchemy.ext.declarative import ConcreteBase
from sqlalchemy.orm import DeclarativeBase


class Base(DeclarativeBase):
    pass


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

    __mapper_args__ = {
        "polymorphic_identity": "employee",
        "concrete": True,
    }


class Manager(Employee):
    __tablename__ = "manager"
    id = mapped_column(Integer, primary_key=True)
    name = mapped_column(String(50))
    manager_data = mapped_column(String(40))

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


class Engineer(Employee):
    __tablename__ = "engineer"
    id = mapped_column(Integer, primary_key=True)
    name = mapped_column(String(50))
    engineer_info = mapped_column(String(40))

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


在上面,Declarative 为 Mapper “初始化” 时的 employee 类;这是 Mapper 的后期配置步骤,用于解析其他依赖 Mapper。混凝土基地 helper 使用 polymorphic_union() 函数创建所有具体映射表的 UNION,然后在设置所有其他类之后创建所有具体映射表的 UNION,然后使用已经存在的基类映射器配置此语句。


选择后,多态联合会生成如下查询:

session.scalars(select(Employee)).all()
SELECT pjoin.id, pjoin.name, pjoin.type, pjoin.manager_data, pjoin.engineer_info FROM ( SELECT employee.id AS id, employee.name AS name, CAST(NULL AS VARCHAR(40)) AS manager_data, CAST(NULL AS VARCHAR(40)) AS engineer_info, 'employee' AS type FROM employee UNION ALL SELECT manager.id AS id, manager.name AS name, manager.manager_data AS manager_data, CAST(NULL AS VARCHAR(40)) AS engineer_info, 'manager' AS type FROM manager UNION ALL SELECT engineer.id AS id, engineer.name AS name, CAST(NULL AS VARCHAR(40)) AS manager_data, engineer.engineer_info AS engineer_info, 'engineer' AS type FROM engineer ) AS pjoin


上面的 UNION 查询需要为每个子表生成 “NULL” 列,以便容纳那些不属于该特定子类的列。


另请参阅


混凝土基地


抽象 Concrete 类


到目前为止,所示的具体映射显示了映射到各个表的子类和基类。在具体继承用例中,通常数据库中不表示基类,只表示子类。换句话说,基类是 “abstract” 的。


通常,当想要将两个不同的子类映射到单个表,并且不映射基类时,这可以很容易地实现。使用 Declare 时,只需使用 __abstract__ 指示符声明基类:

from sqlalchemy.orm import DeclarativeBase


class Base(DeclarativeBase):
    pass


class Employee(Base):
    __abstract__ = True


class Manager(Employee):
    __tablename__ = "manager"
    id = mapped_column(Integer, primary_key=True)
    name = mapped_column(String(50))
    manager_data = mapped_column(String(40))


class Engineer(Employee):
    __tablename__ = "engineer"
    id = mapped_column(Integer, primary_key=True)
    name = mapped_column(String(50))
    engineer_info = mapped_column(String(40))


上面,我们实际上并没有使用 SQLAlchemy 的继承映射工具;我们可以加载和持久化 ManagerEngineer 的实例 通常。 但是,当我们需要多态查询时,情况发生了变化,也就是说,我们想发出 select(Employee) 并返回 ManagerEngineer 实例的集合。 这让我们回到了 domain 的 m,我们必须构建一个特殊的 Mapper 来针对 员工才能实现这一目标。


为了修改我们的具体继承示例以说明能够进行多态加载的 “抽象” 基,我们将只有一个 engineer 和一个 manager 表,没有 employee 表中,但是 Employee 映射器将直接映射到 “polymorphic union”,而不是在本地将其指定给 Mapper.with_polymorphic 参数。


为了帮助解决这个问题,Declarative 提供了 ConcreteBase 的变体 类会自动实现这一点:

from sqlalchemy.ext.declarative import AbstractConcreteBase
from sqlalchemy.orm import DeclarativeBase


class Base(DeclarativeBase):
    pass


class Employee(AbstractConcreteBase, Base):
    strict_attrs = True

    name = mapped_column(String(50))


class Manager(Employee):
    __tablename__ = "manager"
    id = mapped_column(Integer, primary_key=True)
    name = mapped_column(String(50))
    manager_data = mapped_column(String(40))

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


class Engineer(Employee):
    __tablename__ = "engineer"
    id = mapped_column(Integer, primary_key=True)
    name = mapped_column(String(50))
    engineer_info = mapped_column(String(40))

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


Base.registry.configure()


在上面,调用 registry.configure() 方法,这将触发 Employee 类的实际映射;配置之前 步骤,该类没有映射作为它将从中查询的子表 尚未定义。 这个过程比 ConcreteBase,因为必须延迟基类的整个映射,直到声明所有子类为止。使用上述映射时,只有 ManagerEngineer 的实例 可能会持续存在;对 Employee 类进行查询将始终生成 ManagerEngineer 对象。


使用上述映射,可以根据 Employee 生成查询 class 和本地声明的任何属性,例如 Employee.name

>>> stmt = select(Employee).where(Employee.name == "n1")
>>> print(stmt)
SELECT pjoin.id, pjoin.name, pjoin.type, pjoin.manager_data, pjoin.engineer_info FROM ( SELECT engineer.id AS id, engineer.name AS name, engineer.engineer_info AS engineer_info, CAST(NULL AS VARCHAR(40)) AS manager_data, 'engineer' AS type FROM engineer UNION ALL SELECT manager.id AS id, manager.name AS name, CAST(NULL AS VARCHAR(40)) AS engineer_info, manager.manager_data AS manager_data, 'manager' AS type FROM manager ) AS pjoin WHERE pjoin.name = :name_1


AbstractConcreteBase.strict_attrs 参数表示 Employee 类应该只直接映射那些 Employee 类的本地属性,在本例中为 Employee.name 属性。其他属性(例如 Manager.manager_dataEngineer.engineer_info)仅存在于其相应的子类中。什么时候 AbstractConcreteBase.strict_attrs 未设置,则所有子类属性(例如 Manager.manager_dataEngineer.engineer_info映射到基类 Employee 上。这是一种传统的使用模式,它可能更便于查询,但其效果是所有子类共享整个层次结构的完整属性集;在上面的示例中,不使用 AbstractConcreteBase.strict_attrs 将产生无用的 Engineer.manager_nameManager.engineer_info 属性。


2.0 版本中的新功能: 添加 AbstractConcreteBase.strict_attrs 参数传递给 AbstractConcreteBase,从而产生更清晰的映射;默认值为 False,以允许旧版映射继续像在 1.x 版本中一样工作。


另请参阅


抽象混凝土基础


经典和半经典混凝土多态构型


使用 ConcreteBase 演示的声明式配置 和 AbstractConcreteBase 等效于显式使用 polymorphic_union() 的另外两种配置形式。这些配置形式显式地使用了 Table 对象,以便可以先创建“多态联合”,然后应用于 Map。这里对这些进行了说明,以阐明 polymorphic_union() 函数在 Map 方面的作用。


例如,半经典映射使用 Declarative,但单独建立 Table 对象:

metadata_obj = Base.metadata

employees_table = Table(
    "employee",
    metadata_obj,
    Column("id", Integer, primary_key=True),
    Column("name", String(50)),
)

managers_table = Table(
    "manager",
    metadata_obj,
    Column("id", Integer, primary_key=True),
    Column("name", String(50)),
    Column("manager_data", String(50)),
)

engineers_table = Table(
    "engineer",
    metadata_obj,
    Column("id", Integer, primary_key=True),
    Column("name", String(50)),
    Column("engineer_info", String(50)),
)


接下来,使用 polymorphic_union() 生成 UNION:

from sqlalchemy.orm import polymorphic_union

pjoin = polymorphic_union(
    {
        "employee": employees_table,
        "manager": managers_table,
        "engineer": engineers_table,
    },
    "type",
    "pjoin",
)


对于上述 Table 对象,可以使用 “semi-classical” 样式生成映射,其中我们将 Declarative 与 __table__ 参数结合使用;上面的多态联合通过 __mapper_args__ 传递给 Mapper.with_polymorphic 参数:

class Employee(Base):
    __table__ = employee_table
    __mapper_args__ = {
        "polymorphic_on": pjoin.c.type,
        "with_polymorphic": ("*", pjoin),
        "polymorphic_identity": "employee",
    }


class Engineer(Employee):
    __table__ = engineer_table
    __mapper_args__ = {
        "polymorphic_identity": "engineer",
        "concrete": True,
    }


class Manager(Employee):
    __table__ = manager_table
    __mapper_args__ = {
        "polymorphic_identity": "manager",
        "concrete": True,
    }


或者,相同的 Table 对象可以完全以 “经典” 样式使用,根本不使用 Declarative。演示了类似于 Declarative 提供的构造函数:

class Employee:
    def __init__(self, **kw):
        for k in kw:
            setattr(self, k, kw[k])


class Manager(Employee):
    pass


class Engineer(Employee):
    pass


employee_mapper = mapper_registry.map_imperatively(
    Employee,
    pjoin,
    with_polymorphic=("*", pjoin),
    polymorphic_on=pjoin.c.type,
)
manager_mapper = mapper_registry.map_imperatively(
    Manager,
    managers_table,
    inherits=employee_mapper,
    concrete=True,
    polymorphic_identity="manager",
)
engineer_mapper = mapper_registry.map_imperatively(
    Engineer,
    engineers_table,
    inherits=employee_mapper,
    concrete=True,
    polymorphic_identity="engineer",
)


“抽象”示例也可以使用“半古典”或“古典”样式进行映射。区别在于,我们不是将 “多态联合” 应用于 Mapper.with_polymorphic 参数,而是将其直接作为最基本映射器上的映射可选项应用。半经典映射如下所示:

from sqlalchemy.orm import polymorphic_union

pjoin = polymorphic_union(
    {
        "manager": managers_table,
        "engineer": engineers_table,
    },
    "type",
    "pjoin",
)


class Employee(Base):
    __table__ = pjoin
    __mapper_args__ = {
        "polymorphic_on": pjoin.c.type,
        "with_polymorphic": "*",
        "polymorphic_identity": "employee",
    }


class Engineer(Employee):
    __table__ = engineer_table
    __mapper_args__ = {
        "polymorphic_identity": "engineer",
        "concrete": True,
    }


class Manager(Employee):
    __table__ = manager_table
    __mapper_args__ = {
        "polymorphic_identity": "manager",
        "concrete": True,
    }


在上面,我们以与以前相同的方式使用 polymorphic_union(),只是我们省略了 employee 表。


另请参阅


Imperative Mapping - 有关命令式或 “classical” 映射的背景信息


与 Concrete 继承的关系


在具体的继承方案中,映射关系具有挑战性,因为不同的类不共享一个表。如果关系仅涉及特定类,例如前面示例中的 CompanyManager 之间的关系,则不需要特殊步骤,因为这些只是两个相关的表。


但是,如果 CompanyEmployee 具有一对多关系,则表示集合可以同时包含两者 EngineerManager 对象,这意味着 Employee 必须具有多态加载功能,并且要关联的每个表都必须有一个返回 company 表的外键。此类配置的示例如下:

from sqlalchemy.ext.declarative import ConcreteBase


class Company(Base):
    __tablename__ = "company"
    id = mapped_column(Integer, primary_key=True)
    name = mapped_column(String(50))
    employees = relationship("Employee")


class Employee(ConcreteBase, Base):
    __tablename__ = "employee"
    id = mapped_column(Integer, primary_key=True)
    name = mapped_column(String(50))
    company_id = mapped_column(ForeignKey("company.id"))

    __mapper_args__ = {
        "polymorphic_identity": "employee",
        "concrete": True,
    }


class Manager(Employee):
    __tablename__ = "manager"
    id = mapped_column(Integer, primary_key=True)
    name = mapped_column(String(50))
    manager_data = mapped_column(String(40))
    company_id = mapped_column(ForeignKey("company.id"))

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


class Engineer(Employee):
    __tablename__ = "engineer"
    id = mapped_column(Integer, primary_key=True)
    name = mapped_column(String(50))
    engineer_info = mapped_column(String(40))
    company_id = mapped_column(ForeignKey("company.id"))

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


具体继承和关系的下一个复杂性涉及我们希望 EmployeeManagerEngineer 中的一个或全部都指向 Company。对于这种情况,SQLAlchemy 具有特殊行为,因为 relationship() 放在 Employee 上 链接到 Company不起作用 对抗 ManagerEngineer 类,在 实例级别。 相反,一个独特的 relationship() 必须应用于每个类。为了实现与 Company.employees 相反的三个独立关系的双向行为, relationship.back_populates 参数在每个关系之间使用:

from sqlalchemy.ext.declarative import ConcreteBase


class Company(Base):
    __tablename__ = "company"
    id = mapped_column(Integer, primary_key=True)
    name = mapped_column(String(50))
    employees = relationship("Employee", back_populates="company")


class Employee(ConcreteBase, Base):
    __tablename__ = "employee"
    id = mapped_column(Integer, primary_key=True)
    name = mapped_column(String(50))
    company_id = mapped_column(ForeignKey("company.id"))
    company = relationship("Company", back_populates="employees")

    __mapper_args__ = {
        "polymorphic_identity": "employee",
        "concrete": True,
    }


class Manager(Employee):
    __tablename__ = "manager"
    id = mapped_column(Integer, primary_key=True)
    name = mapped_column(String(50))
    manager_data = mapped_column(String(40))
    company_id = mapped_column(ForeignKey("company.id"))
    company = relationship("Company", back_populates="employees")

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


class Engineer(Employee):
    __tablename__ = "engineer"
    id = mapped_column(Integer, primary_key=True)
    name = mapped_column(String(50))
    engineer_info = mapped_column(String(40))
    company_id = mapped_column(ForeignKey("company.id"))
    company = relationship("Company", back_populates="employees")

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


上述限制与当前实现有关,包括具体继承类不共享超类的任何属性,因此需要设置不同的关系。


加载 Concrete 继承映射


使用具体继承进行加载的选项是有限的;通常,如果使用声明性具体 mixin 之一在 Mapper 上配置了多态加载,则在当前 SQLAlchemy 版本中无法在查询时对其进行修改。通常,with_polymorphic() 函数将能够覆盖 concrete 使用的加载样式, 但是,由于当前限制,尚不支持此功能。