Oracle层次结构获取每个id的所有children和所有parents

Oracle Hierarchy to get all children and all parents of each id

我有一个 table 和 parent/child 个 ID,我正在尝试获取 parents 和 children 所有级别的完整列表给定 id.
基本上,对于给定的 id,一直向下并一直向上。

我试过连接方式,但也许递归 CTE 会更好?

select 'abc' as child, null as parent from dual union all
select 'mno' as child, 'abc' as parent from dual union all
select 'def' as child, 'abc' as parent from dual union all
select '123' as child, 'abc' as parent from dual union all
select 'qrs' as child, '123' as parent from dual union all
select '789' as child, 'def' as parent from dual union all
select 'xyz' as child, '123' as parent from dual 

例如:

Child Parent
abc null
mno abc
def abc
123 abc
qrs 123
789 def
xyz 123

对于 123,期望的输出:

对于 abc,期望的输出:

这是我的尝试。 full_hier 是 child 和 parent 路径的串联 + 子字符串,这似乎有点老套。另外,我得到了我不确定如何过滤掉的额外结果(例如:def > abc 被返回,但我不想要它,因为它在 789 > def > abc 中被捕获)。

select 
    connect_by_root child,
    substr(sys_connect_by_path(child, '>' ),2) as child_hier
    , substr(sys_connect_by_path(parent, '>' ),2) as parent_hier
    , case 
        when parent is null then substr(sys_connect_by_path(child, '>' ),2)
        else substr(sys_connect_by_path(child, '>' ),2) ||  substr(substr(sys_connect_by_path(parent, '>' ),2), instr(substr(sys_connect_by_path(parent, '>' ),2),'>',1,1)) 
    end as full_hier
    , level
from   
    (
        select 'abc' as child, null as parent from dual union all
        select 'mno' as child, 'abc' as parent from dual union all
        select 'def' as child, 'abc' as parent from dual union all
        select '123' as child, 'abc' as parent from dual union all
        select 'qrs' as child, '123' as parent from dual union all
        select '789' as child, 'def' as parent from dual union all
        select 'xyz' as child, '123' as parent from dual 
    ) table_name
where 1=1
--and connect_by_isleaf = 1
--and connect_by_root child in ('123')
and child = 'abc'
connect by child = prior parent 
--connect_by prior parent = child

感谢观看,非常感谢!

如果我理解正确,给定任何 id(作为输入 - 下面我在查询中使用绑定变量),您需要找到它的所有叶子后代,然后对于每个这样的叶子,显示来自的完整路径叶子到层次结构的根。

一种方法是遍历层次结构两次:首先从给定的 id 开始并找到它的所有叶子后代,然后以相反的方向遍历以找到所有“完整路径”。

虽然这看起来(略微)更优雅,但效率会大大降低。更好的方法是按照您已经尝试过的方法。

下面我使用了 with 子句(并在子查询声明中给出了列名 - 这仅在 Oracle 11.2 之后才受支持,如果您的版本是 11.1,则需要将别名移动到 select 子句,就像你在尝试时所做的那样)。

with
  table_name (child, parent) as (
    select 'abc', null  from dual union all
    select 'mno', 'abc' from dual union all
    select 'def', 'abc' from dual union all
    select '123', 'abc' from dual union all
    select 'qrs', '123' from dual union all
    select '789', 'def' from dual union all
    select 'xyz', '123' from dual   
  )
, a (ancestor_path) as (
    select  sys_connect_by_path(child, '>')
    from    table_name
    where   connect_by_isleaf = 1
    start   with child = :i_child
    connect by child = prior parent
  )
, d (descendant_path) as (
    select  substr(sys_connect_by_path(child, '>'), 2)
    from    table_name
    where   connect_by_isleaf = 1
    start   with parent = :i_child
    connect by parent = prior child
  )
select d.descendant_path || a.ancestor_path as full_path
from   d cross join a
;

这里有一种方法,它首先沿着树下降,从所选树中获取根 parent(s)。

然后利用这些树根重新爬上去。

然后根据所选 child.

过滤生成的路径

剩下的是那些最后 child 不是 parent 的那些。

create table test_hierarchy (
 child varchar(3),
 parent varchar(3),
 primary key (child)
);

insert into test_hierarchy (child, parent)
select 'abc' as child, null as parent from dual union all
select 'mno' as child, 'abc' as parent from dual union all
select 'def' as child, 'abc' as parent from dual union all
select '123' as child, 'abc' as parent from dual union all
select 'qrs' as child, '123' as parent from dual union all
select '789' as child, 'def' as parent from dual union all
select 'xyz' as child, '123' as parent from dual;
with cte (base) as (
 select '123' from dual
)
select *
from
(
  select 
    sys_connect_by_path(child,'>') as path
  , cte.base
  , level
  , connect_by_root child as child
  from test_hierarchy
  cross join cte
  where child in (
    select connect_by_root child 
    from test_hierarchy
    where child in (select base from cte)
    and connect_by_root parent is null
    connect by prior child = parent 
  ) 
  connect by prior parent = child
) q
where path like '%'||base||'%'
  and not exists (
    select 1 
    from test_hierarchy t
    where t.parent = q.child
  ) 
PATH BASE LEVEL CHILD
>qrs>123>abc 123 3 qrs
>xyz>123>abc 123 3 xyz

演示 db<>fiddle here

另一种方法。
这次通过递归 CTE。

with cte_init (base) as (
    select '123' as base 
    from dual
), 
rcte_hierarchy_down (base, lvl, child, parent) as (
   select 
     child as base
   , 0 as lvl
   , child
   , parent
   from test_hierarchy
   where child in (select base from cte_init) 
   
   union all
   
   select 
     cte.base
   , cte.lvl-1
   , t.child
   , t.parent
   from rcte_hierarchy_down cte
   join test_hierarchy t 
     on t.child = cte.parent
), 
rcte_hierarchy_up (lvl, child, parent, path) as (
   select 
   1 as lvl
   , child
   , parent
   , child||'>'||parent as path
   from test_hierarchy h
   where parent in (select child 
                    from rcte_hierarchy_down 
                    where parent is null) 
   
   union all
   
   select 
     cte.lvl+1
   , t.child
   , t.parent
   , t.child||'>'||cte.path
   from rcte_hierarchy_up cte
   join test_hierarchy t 
     on t.parent = cte.child
)
select distinct h.path
from rcte_hierarchy_up h
join cte_init i on h.path like '%'||i.base||'%'
and not exists (
    select 1 
    from test_hierarchy t
    where t.parent = h.child
  )
PATH
qrs>123>abc
xyz>123>abc

演示 db<>fiddle here