SQL 表达式映射
本页面已合并至 ORM 映射类配置索引。
映射表列
本节内容已整合到使用声明性配置表一节中。
关系配置
本节描述了relationship()
函数及其用法的深入讨论。关于关系的介绍,请从使用 ORM 相关对象开始,参阅 SQLAlchemy 统一教程。
- 基本关系模式
- 声明式 vs. 命令式形式
- 一对多
- 使用集合、列表或其他集合类型进行一对多
- 为一对多配置删除行为
- 多对一
- 可空多对一
- 一对一
- 为非注释配置设置 uselist=False
- 多对多
- 设置双向多对多关系
- 使用延迟评估形式的“次要”参数
- 使用集合、列表或其他集合类型进行多对多
- 从多对多表中删除行
- 关联对象
- 将关联对象与多对多访问模式相结合
- 延迟评估关系参数
- 在声明后为映射类添加关系
- 使用多对多的“次要”参数进行延迟评估
- 邻接列表关系
- 复合邻接列表
- 自引用查询策略
- 配置自引用急加载
- 配置关系连接方式
- 处理多个连接路径
- 指定备用连接条件
- 创建自定义外键条件
- 在连接条件中使用自定义运算符
- 基于 SQL 函数的自定义运算符
- 重叠的外键
- 非关系比较 / 材料化路径
- 自引用多对多关系
- 复合“次要”连接
- 与别名类的关系
- 将别名类映射与类型化集成并避免早期映射器配置
- 在查询中使用别名类目标
- 使用窗口函数进行行限制关系
- 构建支持查询的属性
- 关于使用 viewonly 关系参数的注意事项
- 在 Python 中进行突变,包括具有 viewonly=True 的反向引用不适用
- viewonly=True 集合 / 属性直到过期才重新查询
- 处理大型集合
- 只写关系
- 创建和持久化新的只写集合
- 向现有集合添加新项目
- 查询项目
- 删除项目
- 批量插入新项目
- 项目的批量更新和删除
- 只写集合 - API 文档
- 动态关系加载器
- 动态关系加载器 - API
- 设置 RaiseLoad
- 使用被动删除
- 集合自定义和 API 详情
- 自定义集合访问
- 字典集合
- 自定义集合实现
- 通过装饰器注释自定义集合
- 自定义基于字典的集合
- 仪器化和自定义类型
- 集合 API
attribute_keyed_dict()
column_keyed_dict()
keyfunc_mapping()
attribute_mapped_collection
column_mapped_collection
mapped_collection
KeyFuncDict
MappedCollection
- 集合内部
bulk_replace()
collection
collection_adapter
CollectionAdapter
InstrumentedDict
InstrumentedList
InstrumentedSet
prepare_instrumentation()
- 特殊关系持久化模式
- 指向自身的行/相互依赖的行
- 可变主键/更新级联
- 模拟无外键支持的有限 ON UPDATE CASCADE
- 使用传统的 ‘backref’ 关系参数
- Backref 默认参数
- 指定 Backref 参数
- 关系 API
relationship()
backref()
dynamic_loader()
foreign()
remote()
基本关系模式
本节通过基本关系模式的快速概述,使用基于Mapped
注释类型的声明性样式映射来进行说明。
每个以下章节的设置如下:
from __future__ import annotations from typing import List from sqlalchemy import ForeignKey from sqlalchemy import Integer from sqlalchemy.orm import Mapped from sqlalchemy.orm import mapped_column from sqlalchemy.orm import DeclarativeBase from sqlalchemy.orm import relationship class Base(DeclarativeBase): pass
声明式与命令式形式的对比
随着 SQLAlchemy 的发展,不同的 ORM 配置样式已经出现。在本节和其他使用带有注释的声明性映射的示例中,相应的非注释形式应该使用所需的类或字符串类名作为传递给relationship()
的第一个参数。下面的示例说明了本文档中使用的形式,这是一个完全使用 PEP 484 注释的声明性示例,其中 relationship()
构造还从 Mapped
注释中派生出目标类和集合类型,这是 SQLAlchemy 声明式映射的最现代形式:
class Parent(Base): __tablename__ = "parent_table" id: Mapped[int] = mapped_column(primary_key=True) children: Mapped[List["Child"]] = relationship(back_populates="parent") class Child(Base): __tablename__ = "child_table" id: Mapped[int] = mapped_column(primary_key=True) parent_id: Mapped[int] = mapped_column(ForeignKey("parent_table.id")) parent: Mapped["Parent"] = relationship(back_populates="children")
相比之下,使用不带注释的声明式映射是更加“经典”的映射形式,其中relationship()
要求直接传递所有参数,就像下面的示例中所示:
class Parent(Base): __tablename__ = "parent_table" id = mapped_column(Integer, primary_key=True) children = relationship("Child", back_populates="parent") class Child(Base): __tablename__ = "child_table" id = mapped_column(Integer, primary_key=True) parent_id = mapped_column(ForeignKey("parent_table.id")) parent = relationship("Parent", back_populates="children")
最后,使用命令式映射,这是 SQLAlchemy 在声明式之前的原始映射形式(尽管仍然是一小部分用户偏爱的形式),以上配置看起来如下:
registry.map_imperatively( Parent, parent_table, properties={"children": relationship("Child", back_populates="parent")}, ) registry.map_imperatively( Child, child_table, properties={"parent": relationship("Parent", back_populates="children")}, )
此外,非注释映射的默认集合样式是list
。要在没有注释的情况下使用set
或其他集合,请使用relationship.collection_class
参数进行指定:
class Parent(Base): __tablename__ = "parent_table" id = mapped_column(Integer, primary_key=True) children = relationship("Child", collection_class=set, ...)
关于relationship()
的集合配置的详细信息,请参阅自定义集合访问。
根据需要将带有注释和不带注释 / 命令式样式之间的其他差异进行说明
一对多
一对多关系在子表上放置一个引用父表的外键。然后在父表上指定relationship()
,表示引用子项的集合:
class Parent(Base): __tablename__ = "parent_table" id: Mapped[int] = mapped_column(primary_key=True) children: Mapped[List["Child"]] = relationship() class Child(Base): __tablename__ = "child_table" id: Mapped[int] = mapped_column(primary_key=True) parent_id: Mapped[int] = mapped_column(ForeignKey("parent_table.id"))
要在一对多关系中建立双向关系,其中“反向”方是多对一,请指定一个额外的relationship()
并使用relationship.back_populates
参数将两者连接起来,使用每个relationship()
的属性名称作为另一个relationship.back_populates
上的值:
class Parent(Base): __tablename__ = "parent_table" id: Mapped[int] = mapped_column(primary_key=True) children: Mapped[List["Child"]] = relationship(back_populates="parent") class Child(Base): __tablename__ = "child_table" id: Mapped[int] = mapped_column(primary_key=True) parent_id: Mapped[int] = mapped_column(ForeignKey("parent_table.id")) parent: Mapped["Parent"] = relationship(back_populates="children")
Child
将获得一个具有多对一语义的parent
属性。
使用集合、列表或其他集合类型进行一对多关系
使用带注释的声明性映射,relationship()
所使用的集合类型是从传递给Mapped
容器类型的集合类型派生出来的。前一节中的示例可以编写为使用set
而不是list
作为Parent.children
集合,使用Mapped[Set["Child"]]
:
class Parent(Base): __tablename__ = "parent_table" id: Mapped[int] = mapped_column(primary_key=True) children: Mapped[Set["Child"]] = relationship(back_populates="parent")
在使用非带注释形式的映射时,可以通过relationship.collection_class
参数传递要用作集合的 Python 类。
另请参阅
自定义集合访问 - 包含了对集合配置的进一步细节,包括一些将relationship()
映射到字典的技巧。
配置一对多的删除行为
往往情况下,当它们所属的Parent
被删除时,所有的Child
对象都应该被删除。为了配置这种行为,使用在 delete 中描述的delete
级联选项。另一个选项是,当Child
对象与其父对象解除关联时,可以将Child
对象本身删除。该行为在 delete-orphan 中描述。
另请参阅
delete
使用 ORM 关联的外键 ON DELETE cascade
delete-orphan ## 多对一
多对一(Many to one)在父表中放置一个外键,指向子表。relationship()
在父表上声明,在此将创建一个新的标量持有属性:
class Parent(Base): __tablename__ = "parent_table" id: Mapped[int] = mapped_column(primary_key=True) child_id: Mapped[int] = mapped_column(ForeignKey("child_table.id")) child: Mapped["Child"] = relationship() class Child(Base): __tablename__ = "child_table" id: Mapped[int] = mapped_column(primary_key=True)
上面的例子展示了假定非空行为的多对一关系;下一节,可空的多对一(Nullable Many-to-One),说明了可空版本。
双向行为通过添加第二个 relationship()
并在两个方向上应用 relationship.back_populates
参数来实现,在另一个 relationship()
的属性名称作为 relationship.back_populates
的值:
class Parent(Base): __tablename__ = "parent_table" id: Mapped[int] = mapped_column(primary_key=True) child_id: Mapped[int] = mapped_column(ForeignKey("child_table.id")) child: Mapped["Child"] = relationship(back_populates="parents") class Child(Base): __tablename__ = "child_table" id: Mapped[int] = mapped_column(primary_key=True) parents: Mapped[List["Parent"]] = relationship(back_populates="child")
可空的多对一(Nullable Many-to-One)
在上述例子中,Parent.child
关系未被类型化为允许 None
;这源于 Parent.child_id
列本身不可为空,因为它使用 Mapped[int]
类型。如果我们希望 Parent.child
是可空的多对一关系,我们可以将 Parent.child_id
和 Parent.child
都设置为 Optional[]
,在这种情况下,配置将如下所示:
from typing import Optional class Parent(Base): __tablename__ = "parent_table" id: Mapped[int] = mapped_column(primary_key=True) child_id: Mapped[Optional[int]] = mapped_column(ForeignKey("child_table.id")) child: Mapped[Optional["Child"]] = relationship(back_populates="parents") class Child(Base): __tablename__ = "child_table" id: Mapped[int] = mapped_column(primary_key=True) parents: Mapped[List["Parent"]] = relationship(back_populates="child")
上面,DDL 中将创建 Parent.child_id
列以允许 NULL
值。当使用 mapped_column()
与显式类型声明时,指定 child_id: Mapped[Optional[int]]
等效于在 Column
上设置 Column.nullable
为 True
,而 child_id: Mapped[int]
等效于将其设置为 False
。有关此行为的背景,请参见 mapped_column() 从 Mapped 注释中派生数据类型和可为空性。
提示
如果使用 Python 3.10 或更高版本,PEP 604 语法更方便,可以使用 | None
指示可选类型,与PEP 563延迟注释评估结合使用,这样就不需要使用带字符串引号的类型,如下所示:
from __future__ import annotations class Parent(Base): __tablename__ = "parent_table" id: Mapped[int] = mapped_column(primary_key=True) child_id: Mapped[int | None] = mapped_column(ForeignKey("child_table.id")) child: Mapped[Child | None] = relationship(back_populates="parents") class Child(Base): __tablename__ = "child_table" id: Mapped[int] = mapped_column(primary_key=True) parents: Mapped[List[Parent]] = relationship(back_populates="child") ```## 一对一(One To One) 一对一(One To One)在外键视角上本质上是一对多(One To Many)关系,但表示任何时候只会有一行引用特定父行。 当使用带有`Mapped`注释的映射时,通过在关系的两端都应用非集合类型的`Mapped`注释来实现“一对一”约定,这将使 ORM 意识到不应在任一侧使用集合,就像下面的示例一样: ```py class Parent(Base): __tablename__ = "parent_table" id: Mapped[int] = mapped_column(primary_key=True) child: Mapped["Child"] = relationship(back_populates="parent") class Child(Base): __tablename__ = "child_table" id: Mapped[int] = mapped_column(primary_key=True) parent_id: Mapped[int] = mapped_column(ForeignKey("parent_table.id")) parent: Mapped["Parent"] = relationship(back_populates="child")
在上述情况中,当我们加载一个Parent
对象时,Parent.child
属性将引用单个Child
对象而不是集合。如果我们用一个新的Child
对象替换Parent.child
的值,ORM 的工作单元过程将用新的对象替换以前的对象,将以前的child.parent_id
列默认设置为 NULL,除非设置了特定的级联行为。
提示
正如之前提到的,ORM 将“一对一”模式视为一种约定,其中它假设当它加载Parent.child
属性时,将只返回一行。如果返回多行,ORM 将发出警告。
但是,上述关系的Child.parent
一侧仍然保持为“多对一”关系。单独使用它,它将无法检测到分配超过一个Child
的情况,除非设置了relationship.single_parent
参数,这可能很有用:
class Child(Base): __tablename__ = "child_table" id: Mapped[int] = mapped_column(primary_key=True) parent_id: Mapped[int] = mapped_column(ForeignKey("parent_table.id")) parent: Mapped["Parent"] = relationship(back_populates="child", single_parent=True)
在设置此参数之外,“一对多”侧(在这里按照惯例是一对一)也无法可靠地检测到一个Parent
关联多个Child
的情况,例如,多个Child
对象处于挂起状态且不在数据库中持久存在的情况。
是否使用relationship.single_parent
,建议数据库模式包含一个唯一约束,以指示Child.parent_id
列应该是唯一的,以确保在数据库级别上,只有一个Child
行可以同时引用特定的Parent
行(有关__table_args__
元组语法的背景,请参阅声明性表配置):
from sqlalchemy import UniqueConstraint class Child(Base): __tablename__ = "child_table" id: Mapped[int] = mapped_column(primary_key=True) parent_id: Mapped[int] = mapped_column(ForeignKey("parent_table.id")) parent: Mapped["Parent"] = relationship(back_populates="child") __table_args__ = (UniqueConstraint("parent_id"),)
新版本 2.0 中:relationship()
构造可以从给定的Mapped
注释中派生出relationship.uselist
参数的有效值。
将非注释配置的 uselist 参数设置为 False
当使用没有 Mapped
注解的 relationship()
时,可以通过在通常是“多”的一侧将 relationship.uselist
参数设置为 False
来启用一对一模式,如下所示的非注解式声明配置:
class Parent(Base): __tablename__ = "parent_table" id = mapped_column(Integer, primary_key=True) child = relationship("Child", uselist=False, back_populates="parent") class Child(Base): __tablename__ = "child_table" id = mapped_column(Integer, primary_key=True) parent_id = mapped_column(ForeignKey("parent_table.id")) parent = relationship("Parent", back_populates="child") ```## 多对多 Many to Many 在两个类之间添加了一个关联表。关联表几乎总是作为一个核心 `Table` 对象或其他核心可选择的对象,比如一个 `Join` 对象来给出,并且通过 `relationship()` 函数的 `relationship.secondary` 参数来指定。通常,`Table` 使用与声明基类关联的 `MetaData` 对象,这样 `ForeignKey` 指令就可以定位远程表以进行关联: ```py from __future__ import annotations from sqlalchemy import Column from sqlalchemy import Table from sqlalchemy import ForeignKey from sqlalchemy import Integer from sqlalchemy.orm import Mapped from sqlalchemy.orm import mapped_column from sqlalchemy.orm import DeclarativeBase from sqlalchemy.orm import relationship class Base(DeclarativeBase): pass # note for a Core table, we use the sqlalchemy.Column construct, # not sqlalchemy.orm.mapped_column association_table = Table( "association_table", Base.metadata, Column("left_id", ForeignKey("left_table.id")), Column("right_id", ForeignKey("right_table.id")), ) class Parent(Base): __tablename__ = "left_table" id: Mapped[int] = mapped_column(primary_key=True) children: Mapped[List[Child]] = relationship(secondary=association_table) class Child(Base): __tablename__ = "right_table" id: Mapped[int] = mapped_column(primary_key=True)
提示
上面的“关联表”中已经建立了指向关系两侧实体表的外键约束。association.left_id
和 association.right_id
的每个数据类型通常从引用表中推断出,并且可以省略。虽然 SQLAlchemy 并不强制要求,但也建议将引用两个实体表的列建立在唯一约束或更常见的主键约束中;这样可以确保无论应用程序端出现什么问题,都不会在表中持久化重复的行:
association_table = Table( "association_table", Base.metadata, Column("left_id", ForeignKey("left_table.id"), primary_key=True), Column("right_id", ForeignKey("right_table.id"), primary_key=True), )
设置双向 Many-to-many
对于双向关系,关系的两侧都包含一个集合。使用 relationship.back_populates
进行指定,并且对于每个 relationship()
指定共同的关联表:
from __future__ import annotations from sqlalchemy import Column from sqlalchemy import Table from sqlalchemy import ForeignKey from sqlalchemy import Integer from sqlalchemy.orm import Mapped from sqlalchemy.orm import mapped_column from sqlalchemy.orm import DeclarativeBase from sqlalchemy.orm import relationship class Base(DeclarativeBase): pass association_table = Table( "association_table", Base.metadata, Column("left_id", ForeignKey("left_table.id"), primary_key=True), Column("right_id", ForeignKey("right_table.id"), primary_key=True), ) class Parent(Base): __tablename__ = "left_table" id: Mapped[int] = mapped_column(primary_key=True) children: Mapped[List[Child]] = relationship( secondary=association_table, back_populates="parents" ) class Child(Base): __tablename__ = "right_table" id: Mapped[int] = mapped_column(primary_key=True) parents: Mapped[List[Parent]] = relationship( secondary=association_table, back_populates="children" )
SqlAlchemy 2.0 中文文档(十一)(2)https://developer.aliyun.com/article/1562988