映射类继承层次结构
SQLAlchemy 支持三种继承形式:
- 单表继承 – 几种类别的类别由单个表表示;
- 具体表继承 – 每种类别的类别都由独立的表表示;
- 联接表继承 – 类层次结构在依赖表之间分解。每个类由其自己的表表示,该表仅包含该类本地的属性。
最常见的继承形式是单一和联接表,而具体继承则提出了更多的配置挑战。
当映射器配置在继承关系中时,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
参数进行配置。该参数接受一个面向列的表达式,可以指定为要使用的映射属性的字符串名称,也可以指定为列表达式对象,如 Column
或 mapped_column()
构造。
鉴别器列将存储指示行内表示的对象类型的值。该列可以是任何数据类型,但字符串和整数是最常见的。要为数据库中的特定行应用到该列的实际数据值是使用下面描述的 Mapper.polymorphic_identity
参数指定的。
尽管多态鉴别器表达式不是严格必需的,但如果需要多态加载,则需要它。在基础表上建立列是实现这一点的最简单方法,然而非常复杂的继承映射可能会使用 SQL 表达式,例如 CASE 表达式,作为多态鉴别器。
注意
目前,整个继承层次结构只能配置一个鉴别器列或 SQL 表达式,通常在层次结构中最基本的类上。暂时不支持“级联”多态鉴别器表达式。
我们接下来定义 Engineer
和 Manager
的 Employee
子类。每个类包含代表其所代表的子类的唯一属性的列。每个表还必须包含一个主键列(或列),以及对父表的外键引用:
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
参数指定的列。每个映射类的Mapper.polymorphic_identity
参数应在整个层次结构中是唯一的,并且每个映射类应只有一个“标识”;如上所述,“级联”标识不支持一些子类引入第二个标识的情况。
ORM 使用由Mapper.polymorphic_identity
设置的值来确定加载行的多态时行属于哪个类。在上面的示例中,每个代表Employee
的行将在其type
列中具有值'employee'
;类似地,每个Engineer
将获得值'engineer'
,每个Manager
将获得值'manager'
。无论继承映射是否为子类使用不同的连接表(如连接表继承)或所有一个表(如单表继承),这个值都应该被持久化并在查询时对 ORM 可用。Mapper.polymorphic_identity
参数也适用于具体表继承,但实际上并没有持久化;有关详细信息,请参阅后面的具体表继承部分。
在多态设置中,最常见的是外键约束建立在与主键本身相同的列或列上,但这并非必需;也可以使与主键不同的列引用到父级的外键。从基表到子类的 JOIN 的构建方式也是可直接自定义的,但这很少是必要的。
使用连接继承映射完成后,针对Employee
的查询将返回Employee
、Engineer
和Manager
对象的组合。新保存的Engineer
、Manager
和Employee
对象在这种情况下将自动填充employee.type
列中的正确“识别器”值,如"engineer"
、"manager"
或"employee"
。
使用连接继承的关系
使用连接表继承完全支持关系。涉及连接继承类的关系应该针对在层次结构中也对应于外键约束的类;在下面的示例中,由于employee
表有一个回到company
表的外键约束,因此关系被设置在Company
和Employee
之间:
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): ...
如果外键约束在对应于子类的表上,关系应该指向该子类。在下面的示例中,有一个从manager
到company
的外键约束,因此关系建立在Manager
和Company
类之间:
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
属性,总是对employee
和manager
表一起加载。
加载连接继承映射
请参阅编写继承映射的 SELECT 语句部分,了解关于继承加载技术的背景,包括在映射器配置时间和查询时间配置要查询的表。## 单表继承
单表继承在单个表中表示所有子类的所有属性。具有唯一于该类的属性的特定子类将在表中的列中保留它们,如果行引用了不同类型的对象,则这些列将为空。
在层次结构中查询特定子类将呈现为针对基表的 SELECT 查询,其中将包括一个 WHERE 子句,该子句限制行为具有鉴别器列或表达式中存在的特定值或值的行。
单表继承相对于联接表继承具有简单性的优势;查询要高效得多,因为只需要涉及一个表来加载每个表示类的对象。
单表继承配置看起来很像联接表继承,除了只有基类指定了__tablename__
。还需要在基表上有一个鉴别器列,以便类可以彼此区分。
即使子类共享所有属性的基表,在使用声明性时,仍然可以在子类上指定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__
,这表明它们没有自己的映射表。另外,包含了一个带有nullable=True
的mapped_column()
指令;由于为这些类声明的 Python 类型不包括Optional[]
,因此该列通常被映射为NOT NULL
,这对于该列只期望被填充为那些对应于该特定子类的行并不合适。
使用use_existing_column
解决列冲突
请注意,在上一节中,manager_name
和engineer_info
列被“上移”以应用于Employee.__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)
上面,在Engineer
和Manager
上声明的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.
上述场景对声明式映射系统存在一种模糊性,可以通过在mapped_column()
上使用mapped_column.use_existing_column
参数来解决,该参数指示mapped_column()
在存在继承的超类时查找并使用已经映射的列,如果已经存在,则映射一个新列:
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
类中,已经由 Engineer
映射提供。mapped_column.use_existing_column
参数指示 mapped_column()
应首先在 Employee
的映射 Table
中查找请求的 Column
,如果存在,则保持该现有映射。如果不存在,mapped_column()
将正常映射列,将其添加为由 Employee
超类引用的 Table
中的列之一。
版本 2.0.0b4 中新增:- 添加了mapped_column.use_existing_column
,提供了一种在继承子类上有条件地映射列的 2.0 兼容方法。之前的方法结合了 declared_attr
和对父类 .__table__
的查找,仍然可以正常工作,但缺乏PEP 484的类型支持。
可以使用类似的概念来定义特定系列的列和/或其他可重复使用的混合类中的映射属性(请参阅使用混合类组合映射层次结构):
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", }
单表继承关系
关系完全支持单表继承。配置方式与连接继承的方式相同;外键属性应该在关系的“外键”一侧的同一类上:
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", }
同样,类似于连接继承的情况,我们可以创建涉及特定子类的关系。在查询时,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
属性,该属性始终针对employee
加载,并附加一个 WHERE 子句,限制行为具有type = 'manager'
的行。
使用polymorphic_abstract
构建更深层次的层次结构
在 2.0 版本中新增。
在构建任何类型的继承层次结构时,映射类可以包括设置为True
的Mapper.polymorphic_abstract
参数,这表明该类应该被正常映射,但不希望直接实例化,并且不包括Mapper.polymorphic_identity
。然后可以声明这个映射类的子类,这些子类本身可以包括一个Mapper.polymorphic_identity
,因此可以正常使用。这允许一系列子类被一个通用的基类引用,该基类在层次结构中被视为“抽象”,在查询和relationship()
声明中都是如此。这种用法与在 Declarative 中使用 abstract 属性不同,后者将目标类完全取消映射,因此无法单独作为映射类使用。Mapper.polymorphic_abstract
可以应用于层次结构中的任何类或类,包括同时应用于多个级别的类。
例如,假设Manager
和Principal
都被分类到一个超类Executive
,而Engineer
和Sysadmin
被分类到一个超类Technologist
。Executive
和Technologist
都不会被实例化,因此没有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"}
在上面的示例中,新的类 Technologist
和 Executive
都是普通的映射类,并且还指示要添加到超类的新列 executive_background
和 competencies
。然而,它们都缺少对 Mapper.polymorphic_identity
的设置;这是因为不希望直接实例化 Technologist
或 Executive
;我们总是会有 Manager
、Principal
、Engineer
或 SysAdmin
中的一个。然而,我们可以查询 Principal
和 Technologist
角色,并且让它们成为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')
Technologist
和 Executive
的抽象映射类也可以作为relationship()
映射的目标,就像任何其他映射类一样。我们可以扩展上面的示例,包括 Company
,其中包含单独的集合 Company.technologists
和 Company.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.technologists
和 Company.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 - 声明参数,允许在继承层次结构中完全取消映射声明的类,同时仍然继承自映射的超类。
加载单表继承映射
单表继承的加载技术与连接表继承的加载技术大部分相同,并且提供了这两种映射类型之间的高度抽象,因此很容易在它们之间切换,以及在单个继承层次结构中混合使用它们(只需从要单继承的子类中省略 __tablename__
)。请参阅编写用于继承映射的 SELECT 语句和单表继承映射的 SELECT 语句章节,了解有关继承加载技术的文档,包括在映射器配置时间和查询时间配置要查询的类。## 具体表继承
具体继承将每个子类映射到其自己的独立表,每个表包含产生该类实例所需的所有列。具体继承配置默认以非多态方式查询;对于特定类的查询将仅查询该类的表,并且仅返回该类的实例。通过在映射器内配置特殊的 SELECT,通常会将所有表的 UNION 作为结果来启用具体类的多态加载。
警告
具体表继承比连接或单表继承复杂得多,在使用关系、急加载和多态加载方面功能受限,尤其是与其一起使用时。当以多态方式使用时,会生成非常大的查询,其中包含不会像简单连接那样执行得好的 UNION。强烈建议如果需要关系加载和多态加载的灵活性,尽量使用连接或单表继承。如果不需要多态加载,则每个类完全引用自己的表时可以使用普通的非继承映射。
相比于连接和单表继承在“多态”加载方面更为流畅,具体继承在这方面更为麻烦。因此,当不需要多态加载时,具体继承更为合适。建立涉及具体继承类的关系也更为麻烦。
要将类标记为使用具体继承,需要在__mapper_args__
中添加Mapper.concrete
参数。这表示对于声明式和映射来说,超类表不应被视为映射的一部分:
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
列不会被复制到Manager
或Engineer
映射的表中。 - 虽然
Engineer
和Manager
类与Employee
之间有映射关系,但它们不包括多态加载。这意味着,如果我们查询Employee
对象,manager
和engineer
表根本不会被查询。
SqlAlchemy 2.0 中文文档(九)(2)https://developer.aliyun.com/article/1560664