Oracle SQL петлевое самостоятельное соединение - PullRequest
3 голосов
/ 03 апреля 2020

Контекст: Допустим, у меня есть таблица с FOREIGN KEY, которая ссылается на собственный PRIMARY KEY, например:

|---------------------|------------------|------------------|
|          ID         |       NAME       |     PARENT_ID    |
|---------------------|------------------|------------------|
|          01         |       John       |         04       |
|---------------------|------------------|------------------|
|          02         |       Paul       |         01       |
|---------------------|------------------|------------------|
|          03         |       George     |         02       |
|---------------------|------------------|------------------|
|          04         |       Ringo      |         03       |
|---------------------|------------------|------------------|

Проблема: Итак, как вы видите, существует петлевая иерархия: Рин go -> Джордж-> Пол-> Джон-> Рин go -> Джордж-> Пол-> Джон-> et c.

Вопрос: Есть ли выбор SQL, который может обнаруживать такие циклы?

Я знаю, что могу написать рекурсивную процедуру PL / SQL, но я предпочитаю решение с "чистым" SQL.

Спасибо заранее

Ответы [ 3 ]

3 голосов
/ 03 апреля 2020

Вы можете использовать запрос CONNECT BY с псевдостолбцом CONNECT_BY_ISCYCLE для поиска циклов - см. пример из Oracle документов :

SELECT last_name "Employee", CONNECT_BY_ISCYCLE "Cycle"
FROM employees
WHERE level <= 3 
AND   department_id = 80
START WITH last_name = 'King'
CONNECT BY NOCYCLE PRIOR employee_id = manager_id;
1 голос
/ 03 апреля 2020

Вы можете сделать это с connect by nocycle и connect_by_iscycle. Для вашей структуры таблицы это будет выглядеть следующим образом:

select id, name, parent_id, connect_by_iscycle
from mytable
connect by nocycle id = prior parent_id
start with id = 4

connect by nocycle заставляет запрос прекратить итерацию, когда встречается cyle, и псевдостолбец connect_by_iscycle содержит флаг, указывающий, в какой точке он произошло, как показано в этой демонстрации :

ID | NAME   | PARENT_ID | CONNECT_BY_ISCYCLE
-: | :----- | --------: | -----------------:
 4 | Ringo  |         3 |                  0
 3 | George |         2 |                  0
 2 | Paul   |         1 |                  0
 1 | John   |         4 |                  1  --> cycle detected here
0 голосов
/ 03 апреля 2020

Вот решение с рекурсивным cte:

with cte (id, parent_id, ids) as
(
  select id, parent_id, to_char(id) from mytable
  union all
  select t.id, t.parent_id, ids || ' -> ' || t.id
  from cte
  join mytable t on t.id = cte.parent_id
)
cylce id set cycle to 1 default 0
select ids as cycling_ids
from cte
where cycle = 1
order by ids;

Результат:

+ ----------------------+
| CYCLING_IDS           |
+ ----------------------+
| 1 -> 4 -> 3 -> 2 -> 1 |
| 2 -> 1 -> 4 -> 3 -> 2 |
| 3 -> 2 -> 1 -> 4 -> 3 |
| 4 -> 3 -> 2 -> 1 -> 4 |
+ ----------------------+

Если вы хотите увидеть каждый цикл только один раз (что я предполагаю), запомните минимум ID на цикл и показывать только один цикл на минимальный ID:

with cte (id, parent_id, ids, min_id) as
(
  select id, parent_id, to_char(id), id from mytable
  union all
  select t.id, t.parent_id, ids || ' -> ' || t.id, least(t.id, cte.min_id)
  from cte
  join mytable t on t.id = cte.parent_id
)
cycle id set cycle to 1 default 0
select min(ids) as cycling_ids
from cte
where cycle = 1
group by min_id
order by min_id;

Результат:

+ ----------------------+
| CYCLING_IDS           |
+ ----------------------+
| 1 -> 4 -> 3 -> 2 -> 1 |
+ ----------------------+

Демонстрация с большим количеством идентификаторов и различными случаями: https://dbfiddle.uk/?rdbms=oracle_18&fiddle=f7f924cd8759d67a188b7c11f2d071ef

(Это все еще не идеально. Если очень маленький идентификатор приводит к более высоким идентификаторам, образующим цикл, например, если мы вставили идентификатор 0, ссылающийся на идентификатор 3 также как родитель, запрос отобразил бы цикл более одного раза. Этого нелегко избежать, так как мы должны были бы обнаружить минимальный идентификатор в круге . Я бы, вероятно, написал небольшую функцию PL / SQL, чтобы получить этот минимальный идентификатор из строки идентификаторов.)

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...