SQLAlchemy 2.0 文档
常见问题解答
- 安装
- 连接 / 引擎
- MetaData / 模式
- SQL 表达式
- ORM 配置¶
- 性能
- 会话 / 查询
- 第三方集成问题
项目版本
- 上一个: SQL 表达式
- 下一个: 性能
- 上级: 首页
- 在此页
ORM 配置¶
如何映射一个没有主键的表?¶
SQLAlchemy ORM 为了映射到特定的表,需要至少有一列被指定为主键列;当然,多列,即复合主键也是完全可行的。这些列 不需要 实际上被数据库识别为主键列,尽管这样做是个好主意。 只需要这些列的行为像主键一样,例如,作为行的唯一且不可为空的标识符。
大多数 ORM 要求对象定义某种主键,因为内存中的对象必须对应于数据库表中的唯一可标识行;至少,这允许对象可以被 UPDATE 和 DELETE 语句定位,这些语句将仅影响该对象的行,而不影响其他行。 然而,主键的重要性远远不止于此。 在 SQLAlchemy 中,所有 ORM 映射的对象始终在其 Session
中唯一链接到其特定的数据库行,使用称为 身份映射 的模式,该模式是 SQLAlchemy 采用的工作单元系统的核心,也是 ORM 最常见(和不太常见)的用法模式的关键。
注意
重要的是要注意,我们仅在讨论 SQLAlchemy ORM;构建在 Core 之上并仅处理 Table
对象,select()
构造等的应用程序,不需要 在表上或与表关联的任何主键(尽管同样,在 SQL 中,所有表都应该真正具有某种主键,除非您实际上需要更新或删除特定行)。
在几乎所有情况下,表都具有所谓的 候选键,它是唯一标识行的列或一系列列。 如果表确实没有这个,并且具有实际的完全重复的行,则该表不符合 第一范式,并且无法映射。 否则,构成最佳候选键的任何列都可以直接应用于映射器
class SomeClass(Base):
__table__ = some_table_with_no_pk
__mapper_args__ = {
"primary_key": [some_table_with_no_pk.c.uid, some_table_with_no_pk.c.bar]
}
更好的是,当使用完全声明的表元数据时,在这些列上使用 primary_key=True
标志
class SomeClass(Base):
__tablename__ = "some_table_with_no_pk"
uid = Column(Integer, primary_key=True)
bar = Column(String, primary_key=True)
关系数据库中的所有表都应具有主键。 即使是多对多关联表 - 主键也将是两个关联列的组合
CREATE TABLE my_association (
user_id INTEGER REFERENCES user(id),
account_id INTEGER REFERENCES account(id),
PRIMARY KEY (user_id, account_id)
)
如何配置一个作为 Python 保留字或类似的列?¶
基于列的属性可以在映射中给出任何所需的名称。 请参阅 显式命名声明式映射列。
给定一个映射类,如何获取所有列、关系、映射属性等的列表?¶
此信息全部可从 Mapper
对象中获得。
要获取特定映射类的 Mapper
,请在其上调用 inspect()
函数
from sqlalchemy import inspect
mapper = inspect(MyClass)
从那里,可以通过诸如以下的属性访问有关该类的所有信息
Mapper.attrs
- 所有映射属性的命名空间。 属性本身是MapperProperty
的实例,其中包含可以指向映射的 SQL 表达式或列(如果适用)的附加属性。Mapper.column_attrs
- 映射的属性命名空间,仅限于列和 SQL 表达式属性。 您可能想使用Mapper.columns
直接获取Column
对象。Mapper.relationships
- 所有RelationshipProperty
属性的命名空间。Mapper.all_orm_descriptors
- 所有映射属性的命名空间,加上使用诸如hybrid_property
、AssociationProxy
等系统定义的用户定义的属性。Mapper.columns
- 与映射关联的Column
对象和其他命名 SQL 表达式的命名空间。Mapper.mapped_table
- 此映射器映射到的Table
或其他可选择对象。Mapper.local_table
- “本地”到此映射器的Table
;这与Mapper.mapped_table
不同,在映射器使用继承映射到组合的可选择对象的情况下。
我收到关于“隐式组合属性 Y 下的列 X”的警告或错误¶
这种情况是指当映射包含两个列时,由于它们的名称,它们被映射在相同的属性名称下,但没有迹象表明这是有意的。 映射的类需要为每个要存储独立值的属性使用显式名称;当两个列具有相同的名称且未消除歧义时,它们会归于同一属性之下,其效果是,根据哪个列首先分配给该属性,复制 一个列的值到另一个列中。
在通过继承映射中的外键关系将两列链接在一起的情况下,此行为通常是理想的,并且在没有警告的情况下被允许。 当出现警告或异常时,可以通过将列分配给不同命名的属性来解决该问题,或者如果希望将它们组合在一起,则可以使用 column_property()
使其显式化。
给定以下示例
from sqlalchemy import Integer, Column, ForeignKey
from sqlalchemy.ext.declarative import declarative_base
Base = declarative_base()
class A(Base):
__tablename__ = "a"
id = Column(Integer, primary_key=True)
class B(A):
__tablename__ = "b"
id = Column(Integer, primary_key=True)
a_id = Column(Integer, ForeignKey("a.id"))
从 SQLAlchemy 0.9.5 版本开始,检测到上述情况,并将警告 A
和 B
的 id
列正在相同的命名属性 id
下组合,这在上面是一个严重的问题,因为它意味着 B
对象的主键将始终镜像其 A
的主键。
解决此问题的映射如下
class A(Base):
__tablename__ = "a"
id = Column(Integer, primary_key=True)
class B(A):
__tablename__ = "b"
b_id = Column("id", Integer, primary_key=True)
a_id = Column(Integer, ForeignKey("a.id"))
假设我们确实希望 A.id
和 B.id
彼此镜像,尽管 B.a_id
是 A.id
相关的位置。 我们可以使用 column_property()
将它们组合在一起
class A(Base):
__tablename__ = "a"
id = Column(Integer, primary_key=True)
class B(A):
__tablename__ = "b"
# probably not what you want, but this is a demonstration
id = column_property(Column(Integer, primary_key=True), A.id)
a_id = Column(Integer, ForeignKey("a.id"))
我正在使用声明式,并使用 and_()
或 or_()
设置 primaryjoin/secondaryjoin,并且我收到关于外键的错误消息。¶
你这样做吗?
class MyClass(Base):
# ....
foo = relationship(
"Dest", primaryjoin=and_("MyClass.id==Dest.foo_id", "MyClass.foo==Dest.bar")
)
那是两个字符串表达式的 and_()
,SQLAlchemy 无法对其应用任何映射。 声明式允许将 relationship()
参数指定为字符串,这些字符串使用 eval()
转换为表达式对象。 但这不会在 and_()
表达式内部发生 - 这是一个声明式仅应用于作为字符串传递给 primaryjoin 或其他参数的整体的特殊操作
class MyClass(Base):
# ....
foo = relationship(
"Dest", primaryjoin="and_(MyClass.id==Dest.foo_id, MyClass.foo==Dest.bar)"
)
或者,如果您需要的对象已经可用,请跳过字符串
class MyClass(Base):
# ....
foo = relationship(
Dest, primaryjoin=and_(MyClass.id == Dest.foo_id, MyClass.foo == Dest.bar)
)
相同的想法适用于所有其他参数,例如 foreign_keys
# wrong !
foo = relationship(Dest, foreign_keys=["Dest.foo_id", "Dest.bar_id"])
# correct !
foo = relationship(Dest, foreign_keys="[Dest.foo_id, Dest.bar_id]")
# also correct !
foo = relationship(Dest, foreign_keys=[Dest.foo_id, Dest.bar_id])
# if you're using columns from the class that you're inside of, just use the column objects !
class MyClass(Base):
foo_id = Column(...)
bar_id = Column(...)
# ...
foo = relationship(Dest, foreign_keys=[foo_id, bar_id])
为什么建议将 ORDER BY
与 LIMIT
一起使用(特别是与 subqueryload()
一起使用)?¶
当 ORDER BY 未用于返回行的 SELECT 语句时,关系数据库可以自由地以任何任意顺序返回匹配的行。 虽然这种排序通常对应于表内行的自然顺序,但并非所有数据库和所有查询都是如此。 这带来的后果是,任何使用 LIMIT
或 OFFSET
限制行数的查询,或者仅仅选择结果的第一行并丢弃其余行的查询,在返回哪个结果行方面将是不确定的,假设有多个行匹配查询条件。
虽然我们可能不会注意到通常以自然顺序返回行的数据库上的简单查询,但如果我们还使用 subqueryload()
来加载相关集合,则问题会变得更加严重,并且我们可能无法按预期加载集合。
SQLAlchemy 通过发出单独的查询来实现 subqueryload()
,其结果与第一个查询的结果匹配。 我们看到像这样发出的两个查询
>>> session.scalars(select(User).options(subqueryload(User.addresses))).all()
-- the "main" query
SELECT users.id AS users_id
FROM users
-- the "load" query issued by subqueryload
SELECT addresses.id AS addresses_id,
addresses.user_id AS addresses_user_id,
anon_1.users_id AS anon_1_users_id
FROM (SELECT users.id AS users_id FROM users) AS anon_1
JOIN addresses ON anon_1.users_id = addresses.user_id
ORDER BY anon_1.users_id
第二个查询将第一个查询嵌入为行源。 当内部查询在没有排序的情况下使用 OFFSET
和/或 LIMIT
时,两个查询可能看不到相同的结果
>>> user = session.scalars(
... select(User).options(subqueryload(User.addresses)).limit(1)
... ).first()
-- the "main" query
SELECT users.id AS users_id
FROM users
LIMIT 1
-- the "load" query issued by subqueryload
SELECT addresses.id AS addresses_id,
addresses.user_id AS addresses_user_id,
anon_1.users_id AS anon_1_users_id
FROM (SELECT users.id AS users_id FROM users LIMIT 1) AS anon_1
JOIN addresses ON anon_1.users_id = addresses.user_id
ORDER BY anon_1.users_id
根据数据库的特定情况,我们可能会得到如下两个查询的结果
-- query #1
+--------+
|users_id|
+--------+
| 1|
+--------+
-- query #2
+------------+-----------------+---------------+
|addresses_id|addresses_user_id|anon_1_users_id|
+------------+-----------------+---------------+
| 3| 2| 2|
+------------+-----------------+---------------+
| 4| 2| 2|
+------------+-----------------+---------------+
上面,我们收到用户 ID 为 2 的 addresses
行,而用户 ID 为 1 的没有。 我们浪费了两行,并且实际上未能加载集合。 这是一个隐蔽的错误,因为如果不查看 SQL 和结果,ORM 不会显示任何问题;如果我们访问我们拥有的 User
的 addresses
,它将发出集合的延迟加载,我们不会看到实际上发生了任何错误。
解决此问题的方法是始终指定确定性的排序顺序,以便主查询始终返回相同的行集。 这通常意味着您应该在表上的唯一列上 Select.order_by()
。 主键是此目的的理想选择
session.scalars(
select(User).options(subqueryload(User.addresses)).order_by(User.id).limit(1)
).first()
请注意,joinedload()
预先加载策略不会遇到相同的问题,因为只发出一个查询,因此加载查询不可能与主查询不同。 同样,selectinload()
预先加载策略也没有此问题,因为它将其集合加载直接链接到刚刚加载的主键值。
另请参阅
default
、default_factory
和 insert_default
是什么,我应该使用哪个?¶
由于添加了 PEP-681 数据类转换,这在 SQLAlchemy 的 API 中有点冲突,它对其命名约定很严格。 如果您使用 MappedAsDataclass
,如 声明式数据类映射 中所示,则 PEP-681 会发挥作用。 如果您不使用 MappedAsDataclass,则它不适用。
第一部分 - 不使用数据类的经典 SQLAlchemy¶
当 不 使用 MappedAsDataclass
时,就像 SQLAlchemy 多年来一样,mapped_column()
(和 Column
)构造支持参数 mapped_column.default
。 这表示 Python 端默认值(与作为数据库模式定义一部分的服务器端默认值相反),该默认值将在发出 INSERT
语句时生效。 此默认值可以是静态 Python 值(如字符串)、Python 可调用函数或 SQLAlchemy SQL 构造中的 任何一个。 有关 mapped_column.default
的完整文档,请参阅 客户端调用的 SQL 表达式。
当将 mapped_column.default
与 不 使用 MappedAsDataclass
的 ORM 映射一起使用时,此默认值/可调用对象 在您首次构造对象时不会显示在您的对象上。 它仅在 SQLAlchemy 为您的对象准备 INSERT
语句时才会生效。
需要注意的一个非常重要的事情是,当使用 mapped_column()
(和 Column
)时,经典 mapped_column.default
参数也可以使用一个新名称 mapped_column.insert_default
。 如果您构建一个 mapped_column()
并且您 不 使用 MappedAsDataclass
,则 mapped_column.default
和 mapped_column.insert_default
参数是 同义的。
第二部分 - 将数据类支持与 MappedAsDataclass 结合使用¶
当您 正在 使用 MappedAsDataclass
时,即在 声明式数据类映射 中使用的特定形式的映射,mapped_column.default
关键字的含义会发生变化。 我们认识到此名称更改其行为并非理想,但是别无选择,因为 PEP-681 要求 mapped_column.default
具有此含义。
当使用数据类时,mapped_column.default
参数必须按照 Python 数据类 中描述的方式使用 - 它指的是常量值(如字符串或数字),并且 在构造对象时立即应用于您的对象。 它目前也自动应用于 Column
的 mapped_column.default
参数,即使对象上不存在,它也会在 INSERT
语句中自动使用。 如果您想为您的数据类使用可调用对象,该对象将在构造对象时应用,您可以使用 mapped_column.default_factory
。
要访问上面第一部分中描述的 mapped_column.default
的仅 INSERT
行为,您应该改用 mapped_column.insert_default
参数。 当使用数据类时,mapped_column.insert_default
继续是 Core 级“default”过程的直接途径,其中该参数可以是静态值或可调用对象。
构造 |
与数据类一起使用? |
不使用数据类时工作? |
接受标量? |
接受可调用对象? |
立即填充对象? |
---|---|---|---|---|---|
✔ |
✔ |
✔ |
仅当没有数据类时 |
仅当有数据类时 |
|
✔ |
✔ |
✔ |
✔ |
✖ |
|
✔ |
✖ |
✖ |
✔ |
仅当有数据类时 |
flambé! 龙和 The Alchemist 图像设计由 Rotem Yaari 创作并慷慨捐赠。
使用 Sphinx 7.2.6 创建。 文档最后生成时间:2025 年 3 月 11 日星期二下午 02:40:17 EDT