#sql #oracle #oracle11g #hierarchical-data
#sql #Oracle #oracle11g #иерархия-данные
Вопрос:
У меня есть таблица с родительскими / дочерними идентификаторами, и я пытаюсь получить полный список всех уровней родителей и дочерних элементов для данного идентификатора.
В принципе, для данного идентификатора пройдите весь путь вниз и весь путь вверх по иерархии.
Я пробовал connect by, но, может быть, рекурсивный 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
Пример:
Дочерний элемент | Родительский |
---|---|
abc | null |
mno | abc |
def | abc |
123 | abc |
qrs | 123 |
789 | def |
xyz | 123 |
Для 123 желаемый результат:
- xyz > 123 > abc
- qrs > 123 > abc
Для abc желаемый результат:
- xyz > 123 > abc
- 789 > def> abc
- qrs > 123 > abc
- mno > abc
Вот моя попытка. Это кажется немного хакерским, поскольку full_hier является конкатенацией подстрокой дочерних и родительских путей. Кроме того, я получаю дополнительные результаты, которые я не уверен, как отфильтровать (например: возвращается 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
Спасибо, что взглянули, я ценю это!
Комментарии:
1. Под «дочерними элементами» вы подразумеваете «строгие» дочерние элементы (не включая сам данный идентификатор в качестве собственного дочернего элемента)? Тогда на самом деле это не «на всех уровнях», а «на уровнях> 1» — данный идентификатор является его собственным дочерним элементом на уровне = 0. Затем, если данный идентификатор является листом (без строгих дочерних элементов), запрос ничего не вернет (без строк)?
2. О, на самом деле вы также не хотите показывать строку для дочернего элемента, который не является листом. Похоже, вы хотите показать только пути для конечных потомков данного идентификатора, которые не равны заданному идентификатору. (Другими словами, если данный идентификатор уже является листом, тогда не возвращайте строки). Это все?
Ответ №1:
Другой метод.
На этот раз с помощью рекурсивных 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 )
ПУТЬ |
---|
qrs> 123> abc |
xyz> 123> abc |
Демонстрация в db<>fiddle здесь
Комментарии:
1. Это отлично работает, спасибо! Он работает довольно быстро с набором записей ~ 500 тыс.
Ответ №2:
Если я правильно понимаю, учитывая любой идентификатор (в качестве входных данных — ниже я использую переменную bind в запросе), вам нужно найти всех его конечных потомков, а затем для каждого такого листа показать полный путь от листа до корня иерархии.
Один из способов сделать это — дважды пройти иерархию: сначала начать с заданного идентификатора и найти всех его конечных потомков, затем пройти в противоположном направлении, чтобы найти все «полные пути».
Хотя это может выглядеть (незначительно) более элегантно, оно будет значительно менее эффективным. Лучший подход — это то, что вы уже пробовали.
Ниже я использую 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
;
Комментарии:
1. Я довольно новичок в Oracle, я гораздо лучше знаком с presto — я проверю объявление:i_child . Будет ли это работать при просмотре списка идентификаторов или мне нужно будет запустить цикл отдельных идентификаторов?
Ответ №3:
Вот метод, который сначала спускается вниз по дереву, чтобы получить корневого родителя (родителей) из выбранного.
Затем использует эти корни для подъема обратно.
Затем результирующие пути фильтруются по выбранному дочернему элементу.
И остаются те, в которых конечный дочерний элемент не является родительским.
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 )
ПУТЬ | База | Уровень | ДОЧЕРНИЙ элемент |
---|---|---|---|
> qrs> 123> abc | 123 | 3 | qrs |
> xyz> 123> abc | 123 | 3 | xyz |
Демонстрация в db<>fiddle здесь
Комментарии:
1. Я пробовал что-то подобное, но оно работало более 5 минут, поэтому я отказался от него. Тем не менее, он хорошо работал с образцами данных. Спасибо за выбор!