Postgres - 如何使用 UNION ALL 实现 UNION 行为?

zem*_*rco 3 sql postgresql recursion

parent我有一张带有和id 的表child

create table if not exists stack (
    parent int,
    child int
)
Run Code Online (Sandbox Code Playgroud)

每个父母可以有多个孩子,每个孩子又可以有多个孩子。

insert into stack (parent, child) values
    (1,2),
    (2,3),
    (3,4),
    (4,5),
    (5,6),
    (6,7),
    (7,8),
    (8,9),
    (9,null),
    (1,7),
    (7,8),
    (8,9),
    (9,null);
Run Code Online (Sandbox Code Playgroud)

数据看起来像这样。

|parent|child|
|------|-----|
|1     |2    |
|2     |3    |
|3     |4    |
|4     |5    |
|5     |6    |
|6     |7    |
|7     |8    |
|8     |9    |
|9     |NULL |
|1     |7    |
|7     |8    |
|8     |9    |
|9     |NULL |
Run Code Online (Sandbox Code Playgroud)

我想找到所有的孩子。我可以将递归 cte 与UNION ALL.

|parent|child|
|------|-----|
|1     |2    |
|2     |3    |
|3     |4    |
|4     |5    |
|5     |6    |
|6     |7    |
|7     |8    |
|8     |9    |
|9     |NULL |
|1     |7    |
|7     |8    |
|8     |9    |
|9     |NULL |
Run Code Online (Sandbox Code Playgroud)

这给了我我想要达到的结果。

|child|
|-----|
|2    |
|7    |
|3    |
|8    |
|4    |
|9    |
|5    |
|NULL |
|6    |
Run Code Online (Sandbox Code Playgroud)

不过,我想包括深度/级别以及每个节点的路径。我可以使用不同的递归 cte 来完成此操作。

with recursive cte as (
select
    child
from
    stack
where
      stack.parent = 1
union
select
    stack.child
from
    cte
left join stack on
    cte.child = stack.parent
where
    cte.child is not null
  )
select * from cte;
Run Code Online (Sandbox Code Playgroud)

这给了我这些数据。

|parent|child|level|path                |
|------|-----|-----|--------------------|
|1     |2    |0    |{1,2}               |
|1     |7    |0    |{1,7}               |
|2     |3    |1    |{1,2,3}             |
|7     |8    |1    |{1,7,8}             |
|7     |8    |1    |{1,7,8}             |
|3     |4    |2    |{1,2,3,4}           |
|8     |9    |2    |{1,7,8,9}           |
|8     |9    |2    |{1,7,8,9}           |
|8     |9    |2    |{1,7,8,9}           |
|8     |9    |2    |{1,7,8,9}           |
|4     |5    |3    |{1,2,3,4,5}         |
|9     |     |3    |{1,7,8,9,}          |
|9     |     |3    |{1,7,8,9,}          |
|9     |     |3    |{1,7,8,9,}          |
|9     |     |3    |{1,7,8,9,}          |
|9     |     |3    |{1,7,8,9,}          |
|9     |     |3    |{1,7,8,9,}          |
|9     |     |3    |{1,7,8,9,}          |
|9     |     |3    |{1,7,8,9,}          |
|5     |6    |4    |{1,2,3,4,5,6}       |
|6     |7    |5    |{1,2,3,4,5,6,7}     |
|7     |8    |6    |{1,2,3,4,5,6,7,8}   |
|7     |8    |6    |{1,2,3,4,5,6,7,8}   |
|8     |9    |7    |{1,2,3,4,5,6,7,8,9} |
|8     |9    |7    |{1,2,3,4,5,6,7,8,9} |
|8     |9    |7    |{1,2,3,4,5,6,7,8,9} |
|8     |9    |7    |{1,2,3,4,5,6,7,8,9} |
|9     |     |8    |{1,2,3,4,5,6,7,8,9,}|
|9     |     |8    |{1,2,3,4,5,6,7,8,9,}|
|9     |     |8    |{1,2,3,4,5,6,7,8,9,}|
|9     |     |8    |{1,2,3,4,5,6,7,8,9,}|
|9     |     |8    |{1,2,3,4,5,6,7,8,9,}|
|9     |     |8    |{1,2,3,4,5,6,7,8,9,}|
|9     |     |8    |{1,2,3,4,5,6,7,8,9,}|
|9     |     |8    |{1,2,3,4,5,6,7,8,9,}|
Run Code Online (Sandbox Code Playgroud)

我的问题是我有很多重复的数据。我希望获得与UNION查询相同的结果,但具有级别和路径。

我尝试过类似的东西

|child|
|-----|
|2    |
|7    |
|3    |
|8    |
|4    |
|9    |
|5    |
|NULL |
|6    |
Run Code Online (Sandbox Code Playgroud)

或者

with recursive cte as (
select
    parent,
    child,
    0 as level,
    array[parent,
    child] as path
from
    stack
where
      stack.parent = 1
union all
select
    stack.parent,
    stack.child,
    cte.level + 1,
    cte.path || stack.child
from
    cte
left join stack on
    cte.child = stack.parent
where
    cte.child is not null
  )
select * from cte;
Run Code Online (Sandbox Code Playgroud)

但第一个不会改变任何东西,第二个返回错误。

ERROR: recursive reference to query "cte" must not appear within a subquery
Run Code Online (Sandbox Code Playgroud)

有任何想法吗?非常感谢!

Tho*_*ner 6

您的问题是表数据不合适。例如,您的表包含 8 是 7 的直接子代两次的信息。我建议您删除重复的数据并对数据对实施唯一约束。

如果由于某种原因无法这样做,请使查询中的行不同:

with recursive
  good_stack as (select distinct * from stack)
 ,cte as 
  (
    select
      parent,
      child,
      0 as level,
      array[parent,
      child] as path
    from good_stack
    where good_stack.parent = 1
    union all
    select
      good_stack.parent,
      good_stack.child,
      cte.level + 1,
      cte.path || good_stack.child
    from cte
    left join good_stack on cte.child = good_stack.parent
    where cte.child is not null and good_stack.child is not null
  )
select * from cte;
Run Code Online (Sandbox Code Playgroud)

演示:https ://dbfiddle.uk/?rdbms=postgres_13&fiddle=acb1d7a1a1d26c3fd9caf0e7dedc12b2

(您还可以使列不可为空。条目 9|null 不添加任何信息。如果表缺少这些条目,则 9 仍然没有子项。)