Python 如何编写一个SQLAlchemy查询来返回图中节点的所有子代?

Python 如何编写一个SQLAlchemy查询来返回图中节点的所有子代?,python,graph,orm,sqlalchemy,many-to-many,Python,Graph,Orm,Sqlalchemy,Many To Many,我正在开发一个应用程序,在这个应用程序中,我的数据库对象通常有多个父对象和多个子对象,我希望创建一个SQLAlchemy查询,该查询将返回对象的所有子对象 意识到我基本上是在尝试将一个图存储在SQL数据库中,我发现设置一个图在很大程度上帮助了我,但是我在编写返回一个节点的所有子代的查询时遇到了困难。我试着去做,这看起来是正确的方法,但是在让它工作时遇到了问题。我认为我的情况与示例不同,因为在我的示例中,对Node.child(和Node.parent)的查询返回插入指令的列表,而不是ORM对象

我正在开发一个应用程序,在这个应用程序中,我的数据库对象通常有多个父对象和多个子对象,我希望创建一个SQLAlchemy查询,该查询将返回对象的所有子对象

意识到我基本上是在尝试将一个图存储在SQL数据库中,我发现设置一个图在很大程度上帮助了我,但是我在编写返回一个节点的所有子代的查询时遇到了困难。我试着去做,这看起来是正确的方法,但是在让它工作时遇到了问题。我认为我的情况与示例不同,因为在我的示例中,对
Node.child
(和
Node.parent
)的查询返回插入指令的列表,而不是ORM对象

在任何情况下,下面的代码将建立一个简单的有向无环断开连接图,如下所示(其中方向推断为从较高的行到较低的行):

我要找的是一些帮助来编写一个查询,它将为我提供一个节点的所有后代

  • get\u子体(d)
    应该返回g、h、i

  • get\u子体(b)
    应返回d、e、g、h、i

示例代码:

来自sqlalchemy.orm的
导入别名
从sqlalchemy导入列、外键、整数、表、文本
从sqlalchemy导入创建引擎
从sqlalchemy.ext.declarative导入声明性基础
从sqlalchemy.orm导入关系
从sqlalchemy.orm导入sessionmaker
engine=create_engine('sqlite://:memory:',echo=True)
会话=会话生成器(绑定=引擎)
会话=会话()
Base=声明性_Base()
关联表=表('association\u table',Base.metadata,
列('parent_id',Integer,ForeignKey('node.id'),primary_key=True),
列('child_id',Integer,ForeignKey('node.id'),primary_key=True))
类节点(基):
__tablename_uu='node'
id=列(整数,主键=True)
属性_1=列(文本)
属性_2=列(整数)
# http://docs.sqlalchemy.org/en/latest/orm/join_conditions.html#self-参考多对多关系
child=关系('节点',
次要=关联表,
primaryjoin=id==association\u table.c.parent\u id,
secondaryjoin=id==association\u table.c.child\u id,
backref='parent'
)
Base.metadata.create_all(引擎)
a=节点(属性_1='a',属性_2=1)
b=节点(属性_1='b',属性_2=2)
c=节点(属性_1='c',属性_2=3)
d=节点(属性_1='d',属性_2=4)
e=节点(属性_1='e',属性_2=5)
f=节点(属性_1='f',属性_2=6)
g=节点(属性_1='g',属性_2=7)
h=节点(属性_1='h',属性_2=8)
i=节点(属性_1='i',属性_2=9)
session.add_all([a,b,c,d,e,f,g,h,i])
a、 附加(d)
b、 附加(d)
d、 附加子项(g)
d、 附加(h)
g、 child.append(一)
b、 child.append(e)
e、 附加(h)
c、 附加(f)
session.commit()
session.close()
解决方案 下面是一个非常简单的自引用多对多递归CTE查询,它将返回查找
b
的所有后代所需的结果:

nodealias=别名(节点)
子体=session.query(节点)\
.filter(Node.id==b.id)\
.cte(name=“后代”,recursive=True)
后代=后代.union(
session.query(nodealias)\
.join(子代,nodealias.parent)
)
测试

会话中项目的
。查询(子体):
打印(item.property_1,item.property_2)
收益率:

b 2
d 4
e 5
g 7
h 8
i 9
这是
b
及其所有子体的正确列表

完整的工作示例代码 此示例为
节点
类添加了一个方便的函数,用于返回对象的所有子体,同时还计算从对象自身到其所有子体的路径:

来自sqlalchemy.orm的
导入别名
从sqlalchemy导入列、外键、整数、表、文本
从sqlalchemy导入创建引擎
从sqlalchemy.ext.declarative导入声明性基础
从sqlalchemy.orm导入关系
从sqlalchemy.orm导入sessionmaker
引擎=创建引擎('sqlite://',echo=True)
会话=会话生成器(绑定=引擎)
会话=会话()
Base=声明性_Base()
关联表=表('association\u table',Base.metadata,
列('parent_id',Integer,ForeignKey('node.id'),primary_key=True),
列('child_id',Integer,ForeignKey('node.id'),primary_key=True))
类节点(基):
__tablename_uu='node'
id=列(整数,主键=True)
属性_1=列(文本)
属性_2=列(整数)
# http://docs.sqlalchemy.org/en/latest/orm/join_conditions.html#self-参考多对多关系
child=关系('节点',
次要=关联表,
primaryjoin=id==association\u table.c.parent\u id,
secondaryjoin=id==association\u table.c.child\u id,
backref='parent'
)
def子代_节点(自身):
nodealias=别名(节点)
子体=session.query(Node.id,Node.property_1,(self.property_1+'/'+Node.property_1)。标签('path'))。过滤器(Node.parent.contains(self))\
.cte(递归=真)
后代=后代.union(
session.query(nodealias.id,nodealias.property_1,(subjections.c.path+'/'+nodealias.property_1)。label('path'))。join(subjects,nodealias.parent)
)
返回session.query(subjects.c.property_1,subjects.c.path).all()
Base.metadata.create_all(引擎)
a=节点(属性_1='a',属性_2=1)
b=节点(属性_1='b',属性_2=2)
c=节点(属性_1='c',属性_2=3)
d=节点(属性_1='d',属性_2=4)
e=N
b 2
d 4
e 5
g 7
h 8
i 9