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

SQLAlchemy 1.1文档

邻接列表关系

邻接列表模式是一种常见的关系模式,表中包含对其自身的外键引用。这是在平坦表格中表示分层数据的最常见方式。其他方法包括嵌套集合,有时称为“修改前序”,以及物化路径尽管修改前序在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. 此功能执行与上述相似的“别名”步骤,而不需要明确的实体。调用Query.filter()以及在别名连接之后的类似操作将Node实体修改为别名的实体:

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()也会从过滤调用中移除“别名”:

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()