SQLAlchemy 2.0 文档
非传统映射¶
将类映射到多个表¶
除了普通表之外,映射器还可以针对任意关系单元 (称为可选择项) 进行构建。例如,join()
函数创建了一个由多个表组成的可选择单元,它拥有自己的组合主键,可以像Table
一样进行映射。
from sqlalchemy import Table, Column, Integer, String, MetaData, join, ForeignKey
from sqlalchemy.orm import DeclarativeBase
from sqlalchemy.orm import column_property
metadata_obj = MetaData()
# define two Table objects
user_table = Table(
"user",
metadata_obj,
Column("id", Integer, primary_key=True),
Column("name", String),
)
address_table = Table(
"address",
metadata_obj,
Column("id", Integer, primary_key=True),
Column("user_id", Integer, ForeignKey("user.id")),
Column("email_address", String),
)
# define a join between them. This
# takes place across the user.id and address.user_id
# columns.
user_address_join = join(user_table, address_table)
class Base(DeclarativeBase):
metadata = metadata_obj
# map to it
class AddressUser(Base):
__table__ = user_address_join
id = column_property(user_table.c.id, address_table.c.user_id)
address_id = address_table.c.id
在上面的示例中,联接表达了 user
和 address
表的列。user.id
和 address.user_id
列通过外键关联,因此在映射中它们被定义为一个属性 AddressUser.id
,使用 column_property()
表示特殊的列映射。基于配置的这一部分,映射将在刷新发生时将新的主键值从 user.id
复制到 address.user_id
列中。
此外,address.id
列被显式地映射到名为 address_id
的属性。这样做是为了消除歧义 address.id
列的映射与同名的 AddressUser.id
属性之间的关系,该属性被分配为引用 user
表与 address.user_id
外键的组合。
上述映射的自然主键是 (user.id, address.id)
的组合,因为它们是 user
和 address
表组合在一起的主键列。AddressUser
对象的身份将以这两个值表示,并从 AddressUser
对象表示为 (AddressUser.id, AddressUser.address_id)
。
在引用 AddressUser.id
列时,大多数 SQL 表达式只会使用列列表中的第一个列,因为这两个列是同义词。但是,对于像 GROUP BY 表达式这样的特殊用例,其中需要同时引用两个列,并且使用正确的上下文,即适应别名等,可以使用访问器 Comparator.expressions
stmt = select(AddressUser).group_by(*AddressUser.id.expressions)
版本 1.3.17 中的新增内容: 添加了 Comparator.expressions
访问器。
注意
如上所示,针对多个表的映射支持持久性,即目标表中行的 INSERT、UPDATE 和 DELETE 操作。但是,它不支持同时对一个记录执行 UPDATE 一个表,并对其他表执行 INSERT 或 DELETE 操作。也就是说,如果记录 PtoQ 映射到表“p”和“q”,它基于“p”和“q”的 LEFT OUTER JOIN 有一行,如果 UPDATE 过程要更改现有记录中“q”表的数据,那么“q”中的行必须存在;如果主键标识已经存在,它不会发出 INSERT 操作。如果行不存在,对于大多数支持报告 UPDATE 影响的行数的 DBAPI 驱动程序,ORM 将无法检测到更新的行并引发错误;否则,数据将被静默忽略。
允许对相关行进行即时“插入”的方案可能会使用 .MapperEvents.before_update 事件,并且类似于
from sqlalchemy import event
@event.listens_for(PtoQ, "before_update")
def receive_before_update(mapper, connection, target):
if target.some_required_attr_on_q is None:
connection.execute(q_table.insert(), {"id": target.id})
在上面的示例中,通过使用 Table.insert()
创建 INSERT 结构,然后使用给定的 Connection
(它与用于发出其他 SQL 的同一个连接) 执行它,将行插入到 q_table
表中。用户提供的逻辑必须检测到从“p”到“q”的 LEFT OUTER JOIN 对于“q”侧没有条目。
将类映射到任意子查询¶
类似于针对联接进行映射,也可以将普通的 select()
对象与映射器一起使用。下面的示例片段说明了将名为 Customer
的类映射到一个 select()
,该 select()
包括与子查询的联接。
from sqlalchemy import select, func
subq = (
select(
func.count(orders.c.id).label("order_count"),
func.max(orders.c.price).label("highest_order"),
orders.c.customer_id,
)
.group_by(orders.c.customer_id)
.subquery()
)
customer_select = (
select(customers, subq)
.join_from(customers, subq, customers.c.id == subq.c.customer_id)
.subquery()
)
class Customer(Base):
__table__ = customer_select
在上面,由 customer_select
表示的整行将是 customers
表的所有列,以及 subq
子查询公开的列,即 order_count
、highest_order
和 customer_id
。将 Customer
类映射到此可选择项,然后创建一个包含这些属性的类。
当 ORM 持久化 Customer
的新实例时,实际上只有 customers
表会收到 INSERT 操作。这是因为 orders
表的主键没有在映射中表示;ORM 仅会对映射了主键的表发出 INSERT 操作。
一个类使用多个映射器¶
在现代 SQLAlchemy 中,某个特定类一次只由一个所谓的主要映射器映射。该映射器参与三个主要的功能领域:查询、持久化和映射类的检测。主要映射器的基本原理与 Mapper
修改类本身有关,它不仅将类持久化为特定 Table
,而且检测类的属性,这些属性的结构专门根据表元数据进行组织。不可能将多个映射器以同等程度地关联到一个类,因为只有一个映射器可以实际检测该类。
“非主要”映射器的概念在 SQLAlchemy 的多个版本中都存在,但在版本 1.3 中,此功能已被弃用。非主要映射器有用的唯一情况是,当针对备用可选择项构建指向类的关系时。此用例现在适合使用 aliased
构造,并在 指向别名类的关系 中描述。
就能够完全持久化到不同场景下不同表格的类用例而言,早期的 SQLAlchemy 版本提供了一个从 Hibernate 移植过来的功能,称为“实体名称”功能。但是,一旦映射类本身成为 SQL 表达式构建的来源,这种用例在 SQLAlchemy 中变得不可行;也就是说,类的属性本身直接链接到映射的表格列。该功能被移除并替换为一个简单的面向食谱的方法来完成此任务,而无需任何工具的歧义性——创建新的子类,每个子类都单独映射。这种模式现在作为食谱提供,请访问 实体名称。
flambé! 龙和 炼金术士 图像设计由 Rotem Yaari 创建并慷慨捐赠。
使用 Sphinx 7.2.6 创建。文档最后生成时间:2024 年 11 月 8 日星期五,美国东部时间上午 8:41:19