Postgresql 使用查询递归查找父级

Postgresql 使用查询递归查找父级,postgresql,recursive-query,Postgresql,Recursive Query,我正在使用postgresql。我的桌子如下所示 parent_id child_id ---------------------- 101 102 103 104 104 105 105 106 我想编写一个sql查询,它将给出输入的最终父级 i、 假设我将106作为输入传递,那么它的输出将是103 (106 --> 105 --> 104 --> 103) 这里有一个完整的例子。首先是DDL: …还有一些数据

我正在使用postgresql。我的桌子如下所示

parent_id    child_id
----------------------
101       102
103       104
104       105
105       106   
我想编写一个sql查询,它将给出输入的最终父级

i、 假设我将106作为输入传递,那么它的输出将是103

(106 --> 105 --> 104 --> 103)

这里有一个完整的例子。首先是DDL:

…还有一些数据

test=> INSERT INTO node (label, parent_id) VALUES ('n1',NULL),('n2',1),('n3',2),('n4',3);
INSERT 0 4
test=> INSERT INTO node (label) VALUES ('garbage1'),('garbage2'), ('garbage3');
INSERT 0 3
test=> INSERT INTO node (label,parent_id) VALUES ('garbage4',6);
INSERT 0 1
test=> SELECT * FROM node;
id |  label   | parent_id 
----+----------+-----------
 1 | n1       |          
 2 | n2       |         1
 3 | n3       |         2
 4 | n4       |         3
 5 | garbage1 |          
 6 | garbage2 |          
 7 | garbage3 |          
 8 | garbage4 |         6
(8 rows)
这将对节点中的每个id执行递归查询:

test=> WITH RECURSIVE nodes_cte(id, label, parent_id, depth, path) AS (
 SELECT tn.id, tn.label, tn.parent_id, 1::INT AS depth, tn.id::TEXT AS path 
 FROM node AS tn 
 WHERE tn.parent_id IS NULL
UNION ALL
 SELECT c.id, c.label, c.parent_id, p.depth + 1 AS depth, 
        (p.path || '->' || c.id::TEXT) 
 FROM nodes_cte AS p, node AS c 
 WHERE c.parent_id = p.id
)
SELECT * FROM nodes_cte AS n ORDER BY n.id ASC;
id |  label   | parent_id | depth |    path    
----+----------+-----------+-------+------------
 1 | n1       |           |     1 | 1
 2 | n2       |         1 |     2 | 1->2
 3 | n3       |         2 |     3 | 1->2->3
 4 | n4       |         3 |     4 | 1->2->3->4
 5 | garbage1 |           |     1 | 5
 6 | garbage2 |           |     1 | 6
 7 | garbage3 |           |     1 | 7
 8 | garbage4 |         6 |     2 | 6->8
(8 rows)
这将获取node.id=1的所有子代:

test=> WITH RECURSIVE nodes_cte(id, label, parent_id, depth, path) AS (
 SELECT tn.id, tn.label, tn.parent_id, 1::INT AS depth, tn.id::TEXT AS path FROM node AS tn WHERE tn.id = 1
UNION ALL                   
 SELECT c.id, c.label, c.parent_id, p.depth + 1 AS depth, (p.path || '->' || c.id::TEXT) FROM nodes_cte AS p, node AS c WHERE c.parent_id = p.id
)                                                                
SELECT * FROM nodes_cte AS n;
id | label | parent_id | depth |    path    
----+-------+-----------+-------+------------
 1 | n1    |           |     1 | 1
 2 | n2    |         1 |     2 | 1->2
 3 | n3    |         2 |     3 | 1->2->3
 4 | n4    |         3 |     4 | 1->2->3->4
(4 rows)
以下内容将获取id为4的节点的路径:

test=> WITH RECURSIVE nodes_cte(id, label, parent_id, depth, path) AS (
 SELECT tn.id, tn.label, tn.parent_id, 1::INT AS depth, tn.id::TEXT AS path 
 FROM node AS tn 
 WHERE tn.parent_id IS NULL
UNION ALL
 SELECT c.id, c.label, c.parent_id, p.depth + 1 AS depth, 
        (p.path || '->' || c.id::TEXT) 
 FROM nodes_cte AS p, node AS c 
 WHERE c.parent_id = p.id
)
SELECT * FROM nodes_cte AS n WHERE n.id = 4;
id | label | parent_id | depth |    path    
----+-------+-----------+-------+------------
 4 | n4    |         3 |     4 | 1->2->3->4
(1 row)
假设您希望将搜索范围限制为深度小于3的子体。请注意,深度尚未增加:

test=> WITH RECURSIVE nodes_cte(id, label, parent_id, depth, path) AS (
  SELECT tn.id, tn.label, tn.parent_id, 1::INT AS depth, tn.id::TEXT AS path 
  FROM node AS tn WHERE tn.id = 1
UNION ALL
  SELECT c.id, c.label, c.parent_id, p.depth + 1 AS depth, 
         (p.path || '->' || c.id::TEXT) 
  FROM nodes_cte AS p, node AS c 
  WHERE c.parent_id = p.id AND p.depth < 2
)
SELECT * FROM nodes_cte AS n;
 id | label | parent_id | depth | path 
----+-------+-----------+-------+------
  1 | n1    |           |     1 | 1
  2 | n2    |         1 |     2 | 1->2
(2 rows)
我建议使用数组数据类型而不是字符串来演示路径,但箭头更能说明父子关系。

用于创建公共表表达式CTE。对于非递归项,获取子项位于父项正下方的行:

SELECT
   c.child_id,
   c.parent_id
FROM
   mytable c
LEFT JOIN
   mytable p ON c.parent_id = p.child_id
WHERE
   p.child_id IS NULL

 child_id | parent_id
----------+-----------
      102 |       101
      104 |       103
对于递归项,您需要这些子项的子项

WITH RECURSIVE tree(child, root) AS (
   SELECT
      c.child_id,
      c.parent_id
   FROM
      mytable c
   LEFT JOIN
      mytable p ON c.parent_id = p.child_id
   WHERE
      p.child_id IS NULL
   UNION
   SELECT
      child_id,
      root
   FROM
      tree
   INNER JOIN
      mytable on tree.child = mytable.parent_id
)
SELECT * FROM tree;

 child | root
-------+------
   102 |  101
   104 |  103
   105 |  103
   106 |  103
查询CTE时,您可以筛选子项:

WITH RECURSIVE tree(child, root) AS (...) SELECT root FROM tree WHERE child = 106;

 root
------
  103

如果您使用的是postgres,请删除sql server和oracle标记。请。linq-to-sql不支持postgresql,因此也删除了该标记。是否可以在自身多对多中使用此方法?假设有很多父节点,但有一个类似子树id的字段,也就是说,节点a是这个子树中B的子节点,如果节点B在另一个子树中,但a不在其中,不要在B下显示a。我希望我说了我的目的!如何从上一个孩子那里得到所有的父母?这真的很优雅。这似乎是递归CTE的理想教学示例。因此,很容易为我的用例进行修改。
WITH RECURSIVE tree(child, root) AS (...) SELECT root FROM tree WHERE child = 106;

 root
------
  103