Результаты подключения не всегда могут быть интуитивно понятными.
Ниже запросы демонстрируют различные подходы для обнаружения циклов, начинающихся с id = 3
для графика на рисунке.
create table graph (id, id_parent) as
(select 2, 1 from dual
union all select 3, 1 from dual
union all select 4, 3 from dual
union all select 5, 4 from dual
union all select 3, 5 from dual)
SQL> select level lvl, graph.*, connect_by_iscycle cycle
2 from graph
3 start with id = 3
4 connect by nocycle prior id = id_parent;
LVL ID ID_PARENT CYCLE
---------- ---------- ---------- ----------
1 3 1 0
2 4 3 0
3 5 4 1
1 3 5 0
2 4 3 0
3 5 4 1
6 rows selected.
SQL> select level lvl, graph.*, connect_by_iscycle cycle
2 from graph
3 start with id = 3
4 connect by nocycle prior id = id_parent
5 and prior id_parent is not null;
LVL ID ID_PARENT CYCLE
---------- ---------- ---------- ----------
1 3 1 0
2 4 3 0
3 5 4 0
4 3 5 1
1 3 5 0
2 4 3 0
3 5 4 1
7 rows selected.
SQL> with t(id, id_parent) as
2 (select *
3 from graph
4 where id = 3
5 union all
6 select g.id, g.id_parent
7 from t
8 join graph g
9 on t.id = g.id_parent)
10 search depth first by id set ord
11 cycle id set cycle to 1 default 0
12 select * from t;
ID ID_PARENT ORD C
---------- ---------- ---------- -
3 1 1 0
4 3 2 0
5 4 3 0
3 5 4 1
3 5 5 0
4 3 6 0
5 4 7 0
3 5 8 1
8 rows selected.
Узел с id = 3
имеет двух родителей, поэтому в этом примере Oracle проходит два цикла.
(1, 3) -> (3, 4) -> (4, 5) -> (5, 3)
и
(5, 3) -> (3, 4) -> (4, 5)
Край (5, 3) отсутствует в результате первого запроса и первого цикла.
В то же время в третьем запросе и втором цикле дважды появляется грань (5, 3).
Почему так? Вы можете проверить описание логики обнаружения цикла в ответе, предоставленном Quassnoi. На простом английском языке это означает, что
(1) connect by обнаруживает цикл, если идентификатор ребенка для текущей строки является частью
Посещенные идентификаторы
(2) rec с обнаруживает цикл, если ID для текущей строки является частью идентификаторов
Посетил до сих пор
Результат второго запроса выглядит наиболее естественным, хотя есть дополнительный предикат and prior id_parent is not null
. В этом случае
(3) обнаруживает цикл, если идентификатор для текущей строки является частью родительских идентификаторов
Посетил до сих пор
Все эти условия реализованы в столбцах cnt1, cnt2, cnt3 в следующем запросе.
SQL> with t(id, id_parent, path_id, path_id_parent, cnt1, cnt2, cnt3) as
2 (select g.*,
3 cast('->' || g.id as varchar2(4000)),
4 cast('->' || g.id_parent as varchar2(4000)),
5 0,
6 0,
7 0
8 from graph g
9 where id = 3
10 union all
11 select g.id,
12 g.id_parent,
13 t.path_id || '->' || g.id,
14 t.path_id_parent || '->' || g.id_parent,
15 regexp_count(t.path_id || '->', '->' ||
16 (select id from graph c where c.id_parent = g.id) || '->'),
17 regexp_count(t.path_id || '->', '->' || g.id || '->'),
18 regexp_count(t.path_id_parent || '->', '->' || g.id || '->')
19 from t
20 join graph g
21 on t.id = g.id_parent
22 -- and t.cnt1 = 0
23 -- and t.cnt2 = 0
24 -- and t.cnt3 = 0
25 )
26 search depth first by id set ord
27 cycle id set cycle to 1 default 0
28 select * from t;
ID ID_PARENT PATH_ID PATH_ID_PARENT CNT1 CNT2 CNT3 ORD C
---------- ---------- --------------- --------------- ---- ---- ---- ---------- -
3 1 ->3 ->1 0 0 0 1 0
4 3 ->3->4 ->1->3 0 0 0 2 0
5 4 ->3->4->5 ->1->3->4 1 0 0 3 0
3 5 ->3->4->5->3 ->1->3->4->5 1 1 1 4 1
3 5 ->3 ->5 0 0 0 5 0
4 3 ->3->4 ->5->3 0 0 0 6 0
5 4 ->3->4->5 ->5->3->4 1 0 1 7 0
3 5 ->3->4->5->3 ->5->3->4->5 1 1 1 8 1
8 rows selected.
Если вы раскомментируете фильтр с помощью cnt1 / cnt2 / cnt3 и удаляете «идентификатор цикла установлен равным 1 по умолчанию 0», тогда запрос вернет результат в виде соответствующего запроса выше. Другими словами вы можете избежать cycle clause
и реализовать любую логику обнаружения циклов, которую считаете более интуитивной .
Дополнительные сведения об обходе иерархий и обнаружении циклов можно найти в книге Oracle SQL Revealed .