为了映射到一个特定的表,sqlAlchemy ORM需要至少有一列被表示为主键列;多列,即复合主键当然也是完全可行的。这些列有 not 数据库实际上需要知道它们是主键列,不过最好知道它们是主键列。只需要列 表现 就像主键一样,例如作为行的唯一且不可为空的标识符。
大多数ORM都要求对象定义某种主键,因为内存中的对象必须对应于数据库表中唯一可标识的行;至少,这允许对象以更新和删除语句为目标,这些语句只影响对象的行,而不影响其他行。然而,主键的重要性远远超出了这一点。在SQLAlchemy中,所有ORM映射对象始终在 Session 使用名为 identity map ,是sqlAlchemy使用的工作单元系统的中心模式,也是ORM使用最常见(而不是最常见)模式的关键。
注解
需要注意的是,我们只讨论sqlAlchemy ORM;一个建立在核心之上并且只处理 Table 物体, select() 结构等, 不 需要任何主键以任何方式出现在一个表上或与之关联(不过,在SQL中,所有表实际上都应该有某种主键,以免实际需要更新或删除特定的行)。
在几乎所有情况下,表都有一个所谓的 candidate key ,它是唯一标识一行的一列或一系列列。如果某个表确实没有此功能,并且具有实际完全重复的行,则该表不对应于 first normal form 并且无法映射。否则,组成最佳候选键的任何列都可以直接应用于映射器::
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)
)基于列的属性可以被赋予映射中所需的任何名称。见 从属性名称清楚地命名列 .
此信息可从 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 对于使用继承映射到组合可选项的映射器。
这个条件指的是当一个映射包含两个列,由于它们的名称而被映射到同一个属性名下,但是没有迹象表明这是有意的。映射类需要为每个要存储独立值的属性都有显式名称;当两个列具有相同的名称并且没有消除歧义时,它们属于同一属性,其效果是一列中的值是 已复制 另一个,根据先分配给属性的列。
这种行为通常是可取的,如果两列通过继承映射中的外键关系链接在一起,则允许这种行为而不发出警告。当出现警告或异常时,可以通过将列分配给不同的命名属性来解决问题,或者如果需要将它们组合在一起,则可以使用 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开始,检测到上述情况,并警告 id 列 A 和 B 正在同一命名属性下组合 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_() ,我收到一条有关外键的错误消息。¶你在这样做吗?::
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() )?¶当没有设置显式排序时,关系数据库可以以任意顺序返回行。虽然这种排序通常与表中的行的自然顺序相对应,但并非所有数据库和查询都是如此。其结果是任何限制行使用的查询 LIMIT 或 OFFSET 应该 总是 指定一个 ORDER BY . 否则,将实际返回哪些行是不确定的。
当我们使用像 Query.first() 我们实际上正在申请 LIMIT 对于查询,如果没有显式的排序,我们实际返回的行是不确定的。虽然对于通常按自然顺序返回行的数据库上的简单查询,我们可能不会注意到这一点,但如果我们也使用 subqueryload() 加载相关集合,我们可能无法按预期加载集合。
SqlAlchemy实现 subqueryload() 通过发出单独的查询,其结果与第一个查询的结果匹配。我们看到两个这样发出的查询:
>>> session.query(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.query(User).options(subqueryload(User.addresses)).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|
+------------+-----------------+---------------+上面,我们收到两个 addresses 行为 user.id 2个,1个没有。我们浪费了两行,未能实际加载集合。这是一个潜在的错误,因为如果不查看SQL和结果,ORM将不会显示出任何问题;如果我们访问 addresses 对于 User 我们有,它将为集合发出一个懒惰的负载,我们不会看到任何实际出错。
此问题的解决方案是始终指定确定性排序顺序,以便主查询始终返回相同的行集。这通常意味着你应该 Query.order_by() 在表的唯一列上。主键是一个很好的选择:
session.query(User).options(subqueryload(User.addresses)).order_by(User.id).first()请注意 joinedload() 由于只发出了一个查询,所以装载器策略不会遇到相同的问题,因此装载查询不能与主查询不同。同样, selectinload() 热切的加载程序策略也没有这个问题,因为它将其集合加载直接链接到刚加载的主键值。
参见
flambé! the dragon and The Alchemist image designs created and generously donated by Rotem Yaari.
Created using Sphinx 4.2.0.