【问题标题】:Recursive parent child query - postgresql递归父子查询 - postgresql
【发布时间】:2021-07-06 17:51:58
【问题描述】:

我正在尝试准备一个递归查询,该查询将在单个表中生成有关父子关系的数据。

这是一些测试数据:

CREATE TABLE test
(
  id INTEGER,
  parent INTEGER
);


INSERT INTO test (id, parent) VALUES
  (2, 1),
  (3, 1),
  (7, 2),
  (8, 3),
  (9, 3),
  (10, 8),
  (11, 8);
    1       
   / \     
  2   3    
 /   / \
7   8   9  
   / \
 10   11
Excepted results:
--  id |    ancestry | parent | 
--   1 |         {1} |      1 |  
--   2 |       {1,2} |      1 |       
--   3 |       {1,3} |      1 |                 
--   7 |     {1,2,7} |      2 |                 
--   8 |     {1,3,8} |      3 |                 
--   9 |     {1,3,9} |      3 |                 
--  10 |  {1,3,8,10} |      8 |                 
--  11 |  {1,3,8,11} |      8 |       

#Query 1:返回孩子 11 - {1,3,8,11} 的所有父母。这里的问题是我不知道如何标记 11 的第一个父母是 8。

WITH RECURSIVE c AS (
   SELECT 11 AS id
   UNION
   SELECT t.parent
   FROM test AS t
   JOIN c ON c.id = t.id
)
SELECT * FROM c;

提前致谢。

【问题讨论】:

    标签: postgresql parent-child


    【解决方案1】:

    您可以携带一个计数器来查看层次结构中的位置。

    WITH RECURSIVE
    c
    AS
    (
    SELECT 11 AS id,
           0 AS n
    UNION ALL
    SELECT t.parent,
           c.n + 1
           FROM test AS t
                INNER JOIN c
                           ON c.id = t.id
    )
    SELECT *
           FROM c
           ORDER BY n;
    

    8 在您的示例中将有 1,因此将其指定为第一父级。
    db<>fiddle


    编辑:

    基于此,您可以将锚集扩展到所有节点。有点遗憾的是没有 1 的条目。快速解决方法是 UNION ALL 也为其创建记录(但您应该通过将记录 (1, NULL) 插入到 test 中来永久解决此问题。)。有第三列承载层次结构开始的实际叶子和最后的GROUP BY。使用array_agg() 获取祖先数组。 (过滤NULL 节点,一旦您将(1, NULL) 插入test,就会出现在其中。)要获得叶子的直接父节点,您可以在计数器的id 过滤上使用max()(或min())为 1。

    WITH RECURSIVE
    c
    AS
    (
    SELECT 1 AS leaf,
           1 AS parent,
           0 AS n
    UNION ALL
    SELECT id AS leaf,
           id AS parent,
           0 AS n
           FROM test
    UNION ALL
    SELECT leaf,
           t.parent,
           c.n + 1
           FROM test AS t
                INNER JOIN c
                           ON c.parent = t.id
    )
    SELECT leaf AS id,
           array_agg(parent ORDER BY n DESC) FILTER (WHERE parent IS NOT NULL) AS ancestry,
           max(parent) FILTER (WHERE n = 1) AS parent
           FROM c
           GROUP BY leaf
           ORDER BY leaf;
    

    db<>fiddle

    【讨论】: