如何在SQLAlchemy中声明性地设置带有递归外键的表及其关系?

0 投票
1 回答
5561 浏览
提问于 2025-04-17 00:20

假设我有一个叫“nodes”的表,用来存储一个树形结构。每个节点都有一个主键id和一个parent_id列。显然,我想要访问每个节点的父节点属性,也就是它们之间的关系。

import sqlalchemy, sqlalchemy.orm, sqlalchemy.ext.declarative
engine = sqlalchemy.create_engine('sqlite:///PATHTOMYDATABASE', echo=True)
Base = sqlalchemy.ext.declarative.declarative_base()
class Node(Base):
    __tablename__ = "nodes"
    id = sqlalchemy.Column(sqlalchemy.Integer, primary_key=True) 
    parent_id = sqlalchemy.Column(sqlalchemy.Integer, sqlalchemy.ForeignKey("nodes.id"))
    parent = sqlalchemy.orm.relationship("Node", backref=sqlalchemy.orm.backref("childs"))
Base.metadata.create_all(engine)

但是当我这样做的时候,我遇到了一个错误:

sqlalchemy.exc.InvalidRequestError: 表 'nodes' 已经在这个MetaData实例中定义。请指定 'useexisting=True' 来重新定义现有表对象的选项和列。

我不太明白我应该在什么地方设置这个选项 'useexisting=True'。这样做是对的吗?

补充:实际上,这个错误是间接来自原始脚本的另一部分。如果把数据库路径换成临时数据库 :memory:,就没有问题了。感谢TokenMacGuy。

所以上面的例子可以看作是一个有效的示例。

1 个回答

5

可能是因为某种原因,你已经为那个表注册了一个类,或者你可能间接地定义了这个表两次。这种情况我在使用Python命令行时最常遇到;SQLAlchemy会记住表的定义和类的映射,时间比你想象的要长(比实际的MetaData实例的生命周期要长)。确保你只定义表一次(如果你在Python解释器中,可以退出并重新启动它)。其他可能导致这个问题的原因包括表的反射,或者某个地方调用了reload(),但没有先清空sys.modules

如果你在使用表的反射,你需要在__table_args__类变量中传递useexisting=True选项:

class Node(Base):
    __tablename__ = "nodes"
    __table_args__ = {"useexisting": True}

但只有在你确定你是在定义表之前故意这样做的情况下,才可以这么做。

如果这些都不能解决问题,请将你的模块简化到一个最小的示例,展示出问题所在,然后把整个内容发到你的回答中。

撰写回答