版本:1.1.0b2 |发布日期:2016年7月1日

SQLAlchemy 1.1文档

ORM配置

我如何映射一个没有主键的表?

SQLAlchemy ORM为了映射到一个特定的表,需要至少有一列表示为主键列;多列即复合主键当然也是完全可行的。这些列不是需要实际上被数据库知道为主键列,尽管它们是一个好主意。只有列行为作为主键才有必要。作为一个唯一的,不能为空的标识符。

大多数ORM要求对象具有某种主键,因为内存中的对象必须对应于数据库表中唯一可识别的行;至少,这允许对象可以作为UPDATE和DELETE语句的对象,这将只影响该对象的行,而不会影响其他行。但是,主键的重要性远不止于此。在SQLAlchemy中,所有ORM映射的对象总是在Session中唯一地使用称为identity map的模式链接到它们的特定数据库行, SQLAlchemy使用的工作单位系统,也是ORM使用最常见(也不常见)模式的关键。

注意

需要注意的是,我们只讨论SQLAlchemy ORM;一个建立在Core上的应用程序,仅处理Table对象,select()构造等,不需要任何主键以任何方式呈现在表格上或与表格相关联(尽管在SQL中,所有表格都应该确实有某种主键,以免需要实际更新或删除特定的行)。

在几乎所有情况下,表都有一个所谓的candidate key,它是唯一标识一行的一列或一系列列。如果一个表真的没有这个,并且实际上有完全重复的行,那么这个表就不对应于第一范式,并且不能被映射。否则,无论包含最佳候选键的列是否可以直接应用于映射器:

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保留字或类似的列?

在映射中,可以给出基于列的属性的任何名字。请参阅Naming Columns Distinctly from Attribute Names

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

这些信息全部来自Mapper对象。

要获取特定映射类的Mapper,请在其上调用inspect()函数:

from sqlalchemy import inspect

mapper = inspect(MyClass)

从那里,关于这个类的所有信息都可以通过如下属性来访问:

我得到一个警告或错误“隐式地组合列X属性Y”

这个条件是指当一个映射包含两个由于名称而被映射到相同属性名称的列时,但没有任何迹象表明这是有意的。A mapped class needs to have explicit names for every attribute that is to store an independent value; when two columns have the same name and aren’t disambiguated, they fall under the same attribute and the effect is that the value from one column is copied into the other, based on which column was assigned to the attribute first.

这种行为通常是可取的,在两个列通过继承映射中的外键关系链接在一起的情况下,可以不加警告地进行。当发生警告或异常时,可以通过将列分配给不同名称的属性,或者如果需要将它们组合在一起来解决该问题,可以使用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版本开始,检测到上述条件,并且会警告ABid相同名称的属性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'))

Suppose we did want A.id and B.id to be mirrors of each other, despite the fact that B.a_id is where A.id is related. 我们可以使用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'))

我正在使用Declarative,并使用and_()or_()设置primaryjoin / secondaryjoin,并且收到有关外键的错误消息。/ T4>

你在做这个吗?:

class MyClass(Base):
    # ....

    foo = relationship("Dest", primaryjoin=and_("MyClass.id==Dest.foo_id", "MyClass.foo==Dest.bar"))

这是两个字符串表达式的and_(),SQLAlchemy不能应用任何映射。Declarative允许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])

为什么LIMIT(特别是subqueryload())需要ORDER BY¶ T7>

当没有设置明确的排序时,关系数据库可以以任意顺序返回行。虽然此排序通常对应于表中行的自然顺序,但并不是所有数据库和所有查询都是这种情况。The consequence of this is that any query that limits rows using LIMIT or OFFSET should always specify an ORDER BY. 否则,哪些行将被实际返回是不确定的。

当我们使用一个像Query.first()这样的SQLAlchemy方法时,实际上我们在查询中应用了一个LIMIT,所以没有明确的排序,我们实际上回来了。虽然我们可能没有注意到这一点,但是对于通常以自然顺序返回行的数据库的简单查询,如果我们还使用orm.subqueryload()加载相关集合,则会变得更加棘手,而我们可能不会按照预期加载收藏。

SQLAlchemy通过发出一个单独的查询来实现orm.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|
+------------+-----------------+---------------+

以上,我们收到2个user.idaddresses行,1个都没有。我们浪费了两行,实际上并没有加载集合。这是一个阴险的错误,因为没有查看SQL和结果,ORM不会显示有任何问题;如果我们访问Useraddresses,它会为集合发出一个惰性负载,我们不会看到任何实际上出错的地方。

这个问题的解决方案是始终指定一个确定性的排序顺序,以便主查询总是返回相同的一组行。This generally means that you should Query.order_by() on a unique column on the table. 主键是一个很好的选择:

session.query(User).options(subqueryload(User.addresses)).order_by(User.id).first()

请注意,joinedload()不会遇到同样的问题,因为只发出一个查询,因此加载查询不能与主查询不同。