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

SQLAlchemy 1.1文档

邻接列表关系

The adjacency list pattern is a common relational pattern whereby a table contains a foreign key reference to itself. 这是在平坦表格中表示分层数据的最常见的方式。其他方法包括嵌套集,有时也称为“修改前序”,以及物化路径尽管在SQL查询中对流行性进行评估时,修改前序具有吸引力,但由于并发性,复杂性的降低以及修改后的前序几乎没有优势,所以邻接列表模型可能是大多数分层存储需求中最适合的模式通过可以将子树完全加载到应用程序空间的应用程序。

在这个例子中,我们将使用一个名为Node的映射类来表示一个树结构:

class Node(Base):
    __tablename__ = 'node'
    id = Column(Integer, primary_key=True)
    parent_id = Column(Integer, ForeignKey('node.id'))
    data = Column(String(50))
    children = relationship("Node")

有了这个结构,一个图形如下:

root --+---> child1
       +---> child2 --+--> subchild1
       |              +--> subchild2
       +---> child3

将用以下数据表示:

id       parent_id     data
---      -------       ----
1        NULL          root
2        1             child1
3        1             child2
4        3             subchild1
5        3             subchild2
6        1             child3

这里的relationship()配置与“正常”一对多关系的工作方式相同,除了“方向”,即关系是一对多还是一对多多对一,默认情况下是一对多的。为了将关系建立为多对一,额外的指令被称为remote_side,它是ColumnColumn对象的集合这表明那些应该被认为是“遥远”的:

class Node(Base):
    __tablename__ = 'node'
    id = Column(Integer, primary_key=True)
    parent_id = Column(Integer, ForeignKey('node.id'))
    data = Column(String(50))
    parent = relationship("Node", remote_side=[id])

其中id列作为parent relationship()remote_side,从而建立parent_id作为“本地”方面,然后该关系表现为多对一。

一如既往,可以使用backref()函数将两个方向组合为双向关系:

class Node(Base):
    __tablename__ = 'node'
    id = Column(Integer, primary_key=True)
    parent_id = Column(Integer, ForeignKey('node.id'))
    data = Column(String(50))
    children = relationship("Node",
                backref=backref('parent', remote_side=[id])
            )

在SQLAlchemy中有几个例子可以说明自我指涉策略;这些包括Adjacency ListXML Persistence

复合邻接表

邻接列表关系的子类别是在连接条件的“本地”和“远程”侧都存在特定列的罕见情况。一个例子是下面的Folder类;使用复合主键,account_id列引用自身,以指示与父文件位于同一帐户内的子文件夹;而folder_id指的是该帐户中的特定文件夹:

class Folder(Base):
    __tablename__ = 'folder'
    __table_args__ = (
      ForeignKeyConstraint(
          ['account_id', 'parent_id'],
          ['folder.account_id', 'folder.folder_id']),
    )

    account_id = Column(Integer, primary_key=True)
    folder_id = Column(Integer, primary_key=True)
    parent_id = Column(Integer)
    name = Column(String)

    parent_folder = relationship("Folder",
                        backref="child_folders",
                        remote_side=[account_id, folder_id]
                  )

在上面,我们将account_id传递到remote_side列表中。relationship()可以识别两边的account_id列,并将“remote”列与folder_id列对齐,承认在“偏远”一边是唯一存在的。

0.8版新增功能:支持relationship()中的自引用组合键,其中列指向自身。

自引用查询策略

查询自引用结构的工作方式与其他查询类似:

# get all nodes named 'child2'
session.query(Node).filter(Node.data=='child2')

但是,当试图从树的一级到另一级加入外键时需要格外小心。在SQL中,从表到它自身的连接要求表达式的至少一边是“别名”,以便可以毫不含糊地引用它。

从ORM教程中的Using Aliasesorm.aliased()构造通常用于提供ORM实体的“别名”。使用这种技术从Node加入自己的过程如下所示:

from sqlalchemy.orm import aliased

nodealias = aliased(Node)
sqlsession.query(Node).filter(Node.data=='subchild1').\
                join(nodealias, Node.parent).\
                filter(nodealias.data=="child2").\
                all()

Query.join() also includes a feature known as Query.join.aliased that can shorten the verbosity self- referential joins, at the expense of query flexibility. 此功能执行与上述类似的“别名”步骤,而不需要明确的实体。Calls to Query.filter() and similar subsequent to the aliased join will adapt the Node entity to be that of the alias:

sqlsession.query(Node).filter(Node.data=='subchild1').\
        join(Node.parent, aliased=True).\
        filter(Node.data=='child2').\
        all()

要将标准添加到较长连接的多个点,请将Query.join.from_joinpoint添加到其他join()调用中:

# get all nodes named 'subchild1' with a
# parent named 'child2' and a grandparent 'root'
sqlsession.query(Node).\
        filter(Node.data=='subchild1').\
        join(Node.parent, aliased=True).\
        filter(Node.data=='child2').\
        join(Node.parent, aliased=True, from_joinpoint=True).\
        filter(Node.data=='root').\
        all()

Query.reset_joinpoint() will also remove the “aliasing” from filtering calls:

session.query(Node).\
        join(Node.children, aliased=True).\
        filter(Node.data == 'foo').\
        reset_joinpoint().\
        filter(Node.data == 'bar')

有关使用Query.join.aliased随意链接自我引用节点链的示例,请参阅XML Persistence

配置自引用快速加载

在正常的查询操作期间,使用从父表到子表的连接或外连接,使得父对象及其直接的子集合或引用可以从单个SQL语句填充,或者对于所有直接子集合填充第二个语句。在加入相关项目时,SQLAlchemy的联接和子查询预加载在所有情况下使用别名表,因此与自引用加入兼容。但是,为了使用自引用关系进行加载,需要告诉SQLAlchemy应该加入和/或查询多少级别的深度;否则急切的负载将不会发生。此深度设置通过join_depth进行配置:

class Node(Base):
    __tablename__ = 'node'
    id = Column(Integer, primary_key=True)
    parent_id = Column(Integer, ForeignKey('node.id'))
    data = Column(String(50))
    children = relationship("Node",
                    lazy="joined",
                    join_depth=2)

sqlsession.query(Node).all()