个人博客章节出现的邻接列表里面本地侧和远程侧问题


#1

class Comment(db.Model):
id = db.Column(db.Integer, primary_key=True)
author = db.Column(db.String(30))
email = db.Column(db.String(254))
site = db.Column(db.String(255))
body = db.Column(db.Text)
from_admin = db.Column(db.Boolean, default=False)
reviewed = db.Column(db.Boolean, default=False)
timestamp = db.Column(db.DateTime, default=datetime.utcnow, index=True)

replied_id = db.Column(db.Integer, db.ForeignKey('comment.id'))
post_id = db.Column(db.Integer, db.ForeignKey('post.id'))

post = db.relationship('Post', back_populates='comments')
replies = db.relationship('Comment', back_populates='replied', cascade='all, delete-orphan')
replied = db.relationship('Comment', back_populates='replies', remote_side=[id])

原书236页中说replied_id是本地侧,但是我觉得replied_id和replied都是远程侧(子评论模型)的字段,replies是本地侧(父评论的模型)的字段, 是不是我哪里理解错了?


#2

replied_id定义外键,是一对多关系中的“一”方,是实际存在的字段,是本地侧,相应地需定义集合关系属性replied;


#3

本来我直觉地认为这个很好理解,但是翻书仔细一看又感觉没理解透。原书描述如下:

但是在邻接列表关系中,关系的两侧都在同一个模型中,这时SQLAlchemy就无法分辨关系的两侧。在这个关系函数中,通过将remote_side参数设为id字段,我们就把id字段定义为关系的远程侧(Remote Side),而replied_id就相应地变为本地侧(Local Side),这样反向关系就被定义为多对一,即多个回复对应一个父评论。

楼主说「replied_idreplied都属于子评论模型的字段(准确说应该是一个字段一个关系)」,我赞同,但是不是都是「远程侧」这个我就说不清了。replies是父评论模型的字段(关系),也没问题,replies是拿取该评论下的所有子评论,是根据子评论的replied_id等于父评论id拿取的。

我也只能分析到这里了。


#4

花了点时间研究了一下,基本理清了。下面我先使用一对多关系说明,然后使用自关联关系(即邻接列表)说明。

先说明一点:本地侧和远程侧应该是属于relationship这个关系函数的专用术语!意思就是说,如果不把本地侧和远程侧这些术语和relationship关系函数结合分析,可能有点摸不着头脑!本地侧和远程侧是相对于relationship关系函数的。

下面开始解释。

一对多关系

假设有两个表,ParentChildChild通过字段Child.parent_idParent形成多对一关系,也就是一个Parent可以有多个Child

class Child(Model):
    __tablename__ = 'child'
    id = Column(Integer(), primary_key=True)

    parent_id = Column(Integer(), ForeignKey('parent.id'))

    # SELECT ... FROM parent WHERE parent.id = ?
    parent = relationship(
        'Parent',
        back_populates='children',
        # foreign_keys=[parent_id]  # not required
    )

    def __repr__(self):
        return '<Child {}>'.format(self.id)


class Parent(Model):
    __tablename__ = 'parent'
    id = Column(Integer(), primary_key=True)

    # SELECT ... FROM child WHERE ? = child.parent_id
    children = relationship(
        'Child',
        back_populates='parent',
        cascade="all, delete-orphan",
        # foreign_keys=[Child.parent_id]  # not required
    )

    def __repr__(self):
        return '<Parent {}>'.format(self.id)

显而易见,对于关系Parent.children来说,外键Child.parent_id是远程侧,使用Parent模型查询,会生成SELECT ... FROM child WHERE ? = child.parent_id这样的SQL语句

对于关系Child.parent,外键Child.parent_id是本地侧,会生成SELECT ... FROM parent WHERE parent.id = ?查询语句。

自关联表

如果是自关联表,那么就是这样子:

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

    # SELECT ... FROM node WHERE ? = node.parent_id
    children = relationship(
        'Node',
        back_populates='parent',
        cascade='all',
        # foreign_keys=[parent_id]  # not required
    )
    # SELECT ... FROM node WHERE node.id = ?
    parent = relationship('Node', back_populates='children', remote_side=[id])

    # 等价于
    # children = relationship(
    #     "Node",
    #     backref=backref('parent', remote_side=[id]),
    #     cascade='all',
    #     # lazy='dynamic',
    # )

    def __repr__(self):
        return '<Node {}>'.format(self.id)

Node表有parent_id外键,对于Node.children来说,Node.parent_id是远程侧,调用时会生成SELECT ... FROM node WHERE ? = node.parent_id查询语句。

但是,对于关系Node.parent来说,按道理是在另一个模型里的,如果是在另一个模型会自动检测出来。但现在同一模型里面,所以如果不设置remote_side岂不是和Node.children一样?所以我们就要手动设置该关系函数的远程侧的字段,即Node.id。对应查询语句:SELECT ... FROM node WHERE node.id = ?

总结

从一对多关系开始说明,到解释自关联关系中的relationship的远程侧问题,应该说很清楚了,虽说省略了一些知识点。

完整测试代码: