SQLAlchemy中的多重自引用关系

12 投票
2 回答
5924 浏览
提问于 2025-04-16 11:41

我有一个数据库模型,需要建立一个一对多的关系和两个一对一的关系。下面是我做的模型,但它出现了错误。

class Page(Base):
    __tablename__ = 'pages'
    id          = Column(Integer, primary_key=True)
    title       = Column(String(100), nullable=False)
    content     = Column(Text, nullable=False)

    parent_id   = Column(Integer, ForeignKey("pages.id"), nullable=True)
    children    = relationship("Page", backref=backref("parent", remote_side=id))

    next_id     = Column(Integer, ForeignKey("pages.id"), nullable=True)
    next        = relationship("Page", backref=backref("prev", remote_side=id, uselist=False))

    prev_id     = Column(Integer, ForeignKey("pages.id"), nullable=True)
    prev        = relationship("Page", backref=backref("next", remote_side=id, uselist=False))

    def __init__(self, title, content, parent_id=None, next_id=None, prev_id=None):
        self.title = title
        self.content = content
        self.parent_id = parent_id
        self.next_id = next_id
        self.prev_id = prev_id

    def __repr__(self):
        return '<Page "%r">' % self.title

每当我尝试对数据库进行任何操作时,就会出现以下错误。

ArgumentError: Could not determine join condition between parent/child tables on relationship Page.children. Specify a 'primaryjoin' expression. If 'secondary' is present, 'secondaryjoin' is needed as well.

奇怪的是,在没有“下一个”和“上一个”这两列的时候,它是可以正常工作的。有人知道问题出在哪里吗?

2 个回答

5

你可以使用 foreign_keys

class Page(Base):
    __tablename__ = 'pages'
    id          = Column(Integer, primary_key=True)
    title       = Column(String(100), nullable=False)
    content     = Column(Text, nullable=False)

    parent_id   = Column(Integer, ForeignKey("pages.id"), nullable=True)
    parent      = relationship("Page",
                    foreign_keys=[parent_id],
                    remote_side=[id],
                    backref=backref("children" ))

    next_id     = Column(Integer, ForeignKey("pages.id"), nullable=True)
    next        = relationship("Page", 
                    foreign_keys=[next_id],
                    remote_side=[id], 
                    backref=backref("prev", uselist=False))
22

这个话题虽然老旧,但因为实在让人困惑,所以我还是写下来吧。
你不需要单独的“prev”列,因为你已经有了作为“next”的反向引用。
另外,由于你有多个外键指向同一个目标,你需要手动指定主连接。

class Page(Base):
    __tablename__ = 'pages'
    id          = Column(Integer, primary_key=True)
    title       = Column(String(100), nullable=False)
    content     = Column(Text, nullable=False)

    parent_id   = Column(Integer, ForeignKey("pages.id"), nullable=True)
    parent      = relationship("Page",
                    primaryjoin=('pages.c.id==pages.c.parent_id'),
                    remote_side='Page.id',
                    backref=backref("children" ))

    next_id     = Column(Integer, ForeignKey("pages.id"), nullable=True)
    next        = relationship("Page", 
                    primaryjoin=('pages.c.next_id==pages.c.id'),
                    remote_side='Page.id', 
                    backref=backref("prev", uselist=False))

我注意到了一些小问题或者奇怪的行为:
- 你只能使用 remote_side="Page.id",不能用 remote_side=[id]remote_side=["Page.id"],否则就不管用了(sqlalchemy 0.6.6)。这点让我很烦。
- 看起来你应该总是用 remote_side 和主键,不管你的实际远程端是什么。remote_side="Pages.next_id" 总是会产生奇怪的错误,即使看起来是合适的。
- primaryjoin 表达式让人困惑,因为它不使用别名,但这其实是正确的做法。绑定引擎知道哪个表达式需要替换成参数(这点太隐晦了,和简单明了的原则有点背道而驰)。

撰写回答