ORM 配置

优质
小牛编辑
184浏览
2023-12-01

如何映射没有主键的表?

为了映射到一个特定的表,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)
)

如何配置属于python保留字或类似的列?

基于列的属性可以被赋予映射中所需的任何名称。见 从属性名称清楚地命名列 .

如何获得给定映射类的所有列、关系、映射属性等的列表?

此信息可从 Mapper 对象。

到达 Mapper 对于特定的映射类,请调用 inspect() 功能:

from sqlalchemy import inspect

mapper = inspect(MyClass)

从那里,可以通过以下属性访问有关类的所有信息:

“在属性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开始,检测到上述情况,并警告 idAB 正在同一命名属性下组合 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.idB.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() )?

当没有设置显式排序时,关系数据库可以以任意顺序返回行。虽然这种排序通常与表中的行的自然顺序相对应,但并非所有数据库和查询都是如此。其结果是任何限制行使用的查询 LIMITOFFSET 应该 总是 指定一个 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() 热切的加载程序策略也没有这个问题,因为它将其集合加载直接链接到刚加载的主键值。

参见

订购的重要性