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

SQLAlchemy 1.1文档

特殊关系持久化模式

指向自己的行/相互依赖的行

这是一个非常特殊的情况,其中relationship()必须执行INSERT和第二个UPDATE才能正确填充行(反之亦然UPDATE和DELETE为了删除而不违反外键约束)。这两个用例是:

  • 一个表包含一个自己的外键,一行将有一个外键值指向自己的主键。
  • 每个表包含一个引用另一个表的外键,每个表中的一行引用另一个表。

例如:

          user
---------------------------------
user_id    name   related_user_id
   1       'ed'          1

要么:

             widget                                                  entry
-------------------------------------------             ---------------------------------
widget_id     name        favorite_entry_id             entry_id      name      widget_id
   1       'somewidget'          5                         5       'someentry'     1

在第一种情况下,一行指向自己。从技术上讲,使用PostgreSQL或Oracle等序列的数据库可以使用先前生成的值同时插入行,但依赖于自动增量式主键标识符的数据库不能。relationship()总是在刷新过程中假设一行行数的“父/子”模型,所以除非直接填充主键/外键列,否则relationship()需要使用两个语句。

在第二种情况下,必须在任何引用“条目”行之前插入“小部件”行,但是直到生成“条目”行后才能设置该“小部件”行的“favorite_entry_id”列。在这种情况下,使用两个INSERT语句插入“widget”和“entry”行通常是不可能的。必须执行更新以保持外键约束的完成。例外情况是,如果外键被配置为“延迟直到提交”(某些数据库支持的功能),并且手动填充了标识符(本质上绕过relationship())。

为了使用补充的UPDATE语句,我们使用relationship()post_update选项。这指定两行之间的链接应该在两行都被INSERTED之后使用UPDATE语句创建;它还会导致在发送DELETE之前通过UPDATE将行彼此解除关联。The flag should be placed on just one of the relationships, preferably the many-to-one side. 下面我们举例说明一个完整的例子,包括两个ForeignKey结构:

from sqlalchemy import Integer, ForeignKey, Column
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import relationship

Base = declarative_base()

class Entry(Base):
    __tablename__ = 'entry'
    entry_id = Column(Integer, primary_key=True)
    widget_id = Column(Integer, ForeignKey('widget.widget_id'))
    name = Column(String(50))

class Widget(Base):
    __tablename__ = 'widget'

    widget_id = Column(Integer, primary_key=True)
    favorite_entry_id = Column(Integer,
                            ForeignKey('entry.entry_id',
                            name="fk_favorite_entry"))
    name = Column(String(50))

    entries = relationship(Entry, primaryjoin=
                                    widget_id==Entry.widget_id)
    favorite_entry = relationship(Entry,
                                primaryjoin=
                                    favorite_entry_id==Entry.entry_id,
                                post_update=True)

当一个针对上述配置的结构被刷新时,“widget”行将被INSERT减去“favorite_entry_id”的值,然后所有的“entry”行将被INSERTed引用父“widget”行,然后将填充一个UPDATE语句“小部件”表的“favorite_entry_id”列(暂时是一行):

>>> w1 = Widget(name='somewidget')
>>> e1 = Entry(name='someentry')
>>> w1.favorite_entry = e1
>>> w1.entries = [e1]
>>> session.add_all([w1, e1])
sql>>> session.commit()

我们可以指定的额外配置是在Widget上提供更全面的外键约束,以保证favorite_entry_id引用Entry也指这个Widget我们可以使用复合外键,如下所示:

from sqlalchemy import Integer, ForeignKey, String, \
        Column, UniqueConstraint, ForeignKeyConstraint
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import relationship

Base = declarative_base()

class Entry(Base):
    __tablename__ = 'entry'
    entry_id = Column(Integer, primary_key=True)
    widget_id = Column(Integer, ForeignKey('widget.widget_id'))
    name = Column(String(50))
    __table_args__ = (
        UniqueConstraint("entry_id", "widget_id"),
    )

class Widget(Base):
    __tablename__ = 'widget'

    widget_id = Column(Integer, autoincrement='ignore_fk', primary_key=True)
    favorite_entry_id = Column(Integer)

    name = Column(String(50))

    __table_args__ = (
        ForeignKeyConstraint(
            ["widget_id", "favorite_entry_id"],
            ["entry.widget_id", "entry.entry_id"],
            name="fk_favorite_entry"
        ),
    )

    entries = relationship(Entry, primaryjoin=
                                    widget_id==Entry.widget_id,
                                    foreign_keys=Entry.widget_id)
    favorite_entry = relationship(Entry,
                                primaryjoin=
                                    favorite_entry_id==Entry.entry_id,
                                foreign_keys=favorite_entry_id,
                                post_update=True)

上面的映射具有一个组合ForeignKeyConstraint桥接widget_idfavorite_entry_id列。To ensure that Widget.widget_id remains an “autoincrementing” column we specify autoincrement to the value "ignore_fk" on Column, and additionally on each relationship() we must limit those columns considered as part of the foreign key for the purposes of joining and cross-population.

可变主键/更新级联

当实体主键发生变化时,引用主键的相关项目也必须更新。对于强制执行参照完整性的数据库,最好的策略是使用数据库的ON UPDATE CASCADE功能,以便将主键更改传播到引用的外键 - 除非约束被标记为“可推迟”,否则值不能在任何时候不同步。也就是说,直到交易完成才执行。

It is highly recommended that an application which seeks to employ natural primary keys with mutable values to use the ON UPDATE CASCADE capabilities of the database. 说明这一点的示例映射是:

class User(Base):
    __tablename__ = 'user'
    __table_args__ = {'mysql_engine': 'InnoDB'}

    username = Column(String(50), primary_key=True)
    fullname = Column(String(100))

    addresses = relationship("Address")


class Address(Base):
    __tablename__ = 'address'
    __table_args__ = {'mysql_engine': 'InnoDB'}

    email = Column(String(50), primary_key=True)
    username = Column(String(50),
                ForeignKey('user.username', onupdate="cascade")
            )

Above, we illustrate onupdate="cascade" on the ForeignKey object, and we also illustrate the mysql_engine='InnoDB' setting which, on a MySQL backend, ensures that the InnoDB engine supporting referential integrity is used. 在使用SQLite时,应该使用Foreign Key Support中描述的配置启用参照完整性。

也可以看看

Using Passive Deletes - 支持ON DELETE CASCADE与关系

orm.mapper.passive_updates - similar feature on mapper()

在没有外键支持的情况下模拟有限的ON UPDATE CASCADE

在那些使用不支持参照完整性的数据库的情况下,以及具有可变值的自然主键时,SQLAlchemy提供了一个功能,以便允许主键值传递到已经引用的外键到通过针对立即引用其值已更改的主键列的外键列发出UPDATE语句来限制范围。当使用MyISAM存储引擎时,没有引用完整性功能的主要平台是MySQL,而PRAGMA foreign_keys = ON / t2>编译指示不使用。Oracle数据库也不支持ON UPDATE CASCADE,但是由于它仍然强制引用完整性,被标记为可延迟,以便SQLAlchemy可以发出UPDATE语句。

The feature is enabled by setting the passive_updates flag to False, most preferably on a one-to-many or many-to-many relationship(). 当“更新”不再是“被动”时,这表明SQLAlchemy将针对父对象引用的集合中引用的对象使用变化的主键值单独发出UPDATE语句。这也意味着集合将被完全加载到内存中(如果尚未本地存在的话)。

我们之前使用passive_updates=False的映射如下所示:

class User(Base):
    __tablename__ = 'user'

    username = Column(String(50), primary_key=True)
    fullname = Column(String(100))

    # passive_updates=False *only* needed if the database
    # does not implement ON UPDATE CASCADE
    addresses = relationship("Address", passive_updates=False)

class Address(Base):
    __tablename__ = 'address'

    email = Column(String(50), primary_key=True)
    username = Column(String(50), ForeignKey('user.username'))

passive_updates=False的主要局限性包括:

  • 它比直接数据库ON UPDATE CASCADE执行得更差,因为它需要使用SELECT完全预加载受影响的集合,并且还必须针对这些值发出UPDATE语句,它将尝试在“批处理”中运行,但仍然运行在每个在DBAPI级别的基础上。
  • 该功能不能“级联”多个级别。That is, if mapping X has a foreign key which refers to the primary key of mapping Y, but then mapping Y’s primary key is itself a foreign key to mapping Z, passive_updates=False cannot cascade a change in primary key value from Z to X.
  • 仅在关系的多对一侧配置passive_updates=False将不会产生完全效果,因为工作单元仅通过当前标识映射搜索可能引用该对象的对象有一个变异的主键,而不是整个数据库。

As virtually all databases other than Oracle now support ON UPDATE CASCADE, it is highly recommended that traditional ON UPDATE CASCADE support be used in the case that natural and mutable primary key values are in use.