【问题标题】:Recursive CTE with subclause in SQLAlchemySQLAlchemy中带有子句的递归CTE
【发布时间】:2018-07-07 15:37:36
【问题描述】:

我正在尝试在 SQLAlchemy 中实现一个 CTE,它从父/子树中的给定节点返回根节点。

我能够按照Recursive CTE Query 中的示例在 SQLAlchemy 中生成 CTE 递归。

但我不确定如何在其上附加附加条件。即:

WITH ...
SELECT parent_id from parents
WHERE parent_id NOT IN (SELECT child_id FROM node_parent_relation );

我想这样做,以便只返回根节点。

查询在 SQL 中工作。

WITH RECURSIVE parents( child_id, parent_id ) 
AS (
  -- get leaf children
  SELECT child_id, parent_id
  FROM node_parent_relation
  WHERE child_id = '4415b581-0a31-43e8-a69d-d74caeccacd2'

  UNION ALL

  -- get all parents  
  SELECT t.child_id, t.parent_id
  FROM parents p
  JOIN node_parent_relation t
  ON p.parent_id = t.child_id
)
SELECT parent_id from parents
WHERE parent_id NOT IN (SELECT child_id FROM node_parent_relation );

但我可以看到如何将这个附加子句添加到我的 SQLAlchemy 语句中。

我想这将涉及在某处添加这些语句。

sub_query = db.session.query(NodeParentRelation.child_id)
[...].filter([...].parent_id.in_(sub_query))

这是我的 SQLAlchemy 代码目前的样子:

parent = aliased(NodeParentRelation)
child = aliased(NodeParentRelation)

sub_query = db.session.query(NodeParentRelation.child_id)
top_query = db.session.query(NodeParentRelation.child_id,
                             NodeParentRelation.parent_id)\
                         .filter(NodeParentRelation.child_id == self.id)\
                         .cte(recursive=True)

bottom_query = db.session.query(child.child_id, child.parent_id)\
                         .join(parent, parent.parent_id == child.child_id)

query = top_query.union(bottom_query)

作为参考,这是当前正在从上述 SQLAlchemy 语句构造的 SQL 查询。

WITH RECURSIVE anon_1(child_id, parent_id) AS
(SELECT node_parent_relation.child_id AS child_id, node_parent_relation.parent_id AS parent_id
FROM node_parent_relation
WHERE node_parent_relation.child_id = %(child_id_1)s UNION SELECT node_parent_relation_1.child_id AS node_parent_relation_1_child_id, node_parent_relation_1.parent_id AS node_parent_relation_1_parent_id
FROM node_parent_relation AS node_parent_relation_1 JOIN node_parent_relation AS node_parent_relation_2 ON node_parent_relation_2.parent_id = node_parent_relation_1.child_id)
 SELECT anon_1.child_id AS anon_1_child_id, anon_1.parent_id AS anon_1_parent_id
FROM anon_1

【问题讨论】:

    标签: python sqlalchemy common-table-expression


    【解决方案1】:

    当前查询根本没有在递归步骤中引用 CTE。您需要 CTE 本身的别名来解决这个问题:

    In [5]: top_query = db.session.query(NodeParentRelation.child_id,
       ...:                              NodeParentRelation.parent_id)\
       ...:                          .filter(NodeParentRelation.child_id == 1)\
       ...:                          .cte(recursive=True)
       ...:                              
    
    In [6]: parents = db.aliased(top_query)
    
    In [7]: t = db.aliased(NodeParentRelation)
    
    In [11]: query = top_query.union_all(
        ...:     db.session.query(t.child_id, t.parent_id).
        ...:         join(parents, t.child_id == parents.c.parent_id))
    

    其余的都是相当标准的东西:

    In [14]: db.session.query(query.c.parent_id).\
        ...:     filter(query.c.parent_id.notin_(
        ...:         db.session.query(NodeParentRelation.child_id)))
    Out[14]: <sqlalchemy.orm.query.Query at 0x7f56bf55b668>
    
    In [15]: print(_)
    WITH RECURSIVE anon_1(child_id, parent_id) AS 
    (SELECT node_parent_relation.child_id AS child_id, node_parent_relation.parent_id AS parent_id 
    FROM node_parent_relation 
    WHERE node_parent_relation.child_id = ? UNION ALL SELECT node_parent_relation_1.child_id AS node_parent_relation_1_child_id, node_parent_relation_1.parent_id AS node_parent_relation_1_parent_id 
    FROM node_parent_relation AS node_parent_relation_1 JOIN anon_1 AS anon_2 ON node_parent_relation_1.child_id = anon_2.parent_id)
     SELECT anon_1.parent_id AS anon_1_parent_id 
    FROM anon_1 
    WHERE anon_1.parent_id NOT IN (SELECT node_parent_relation.child_id AS node_parent_relation_child_id 
    FROM node_parent_relation)
    

    虽然我个人更喜欢不存在:

    In [25]: db.session.query(query.c.parent_id).\
        ...:     filter(~db.session.query(NodeParentRelation).
        ...:         filter(NodeParentRelation.child_id == query.c.parent_id).
        ...:         exists())
        ...:         
    

    【讨论】:

    • 这非常好。我唯一需要修改的是db.session.query(NodeParentRelation),因为我在表中有其他列。
    • 您能解释一下为什么使用“aliased”吗?
    • 别名不是严格要求的,但确实可以更轻松地检查查询,以防出现问题。
    猜你喜欢
    • 2022-01-20
    • 1970-01-01
    • 1970-01-01
    • 2017-08-30
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-02-17
    • 2014-05-19
    相关资源
    最近更新 更多