簡體   English   中英

Oracle SQL 環形自連接

[英]Oracle SQL looped self-join

語境:
假設我有一個表,它有一個引用它自己的 PRIMARY KEY 的 FOREIGN KEY,如下所示:

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


問題:
因此,如您所見,存在循環層次結構:Ringo->George->Paul->John->Ringo->George->Paul->John->etc。

問題:
是否有可以檢測到此類循環的 SQL select?

我知道我可以編寫遞歸 PL/SQL 過程,但我更喜歡使用“純”SQL 的解決方案。

先感謝您

您可以使用帶有 CONNECT_BY_ISCYCLE 偽列的 CONNECT BY 查詢來查找循環 - 請參閱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;

您可以使用connect by nocycleconnect_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

這是一個遞歸 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 |
+ ----------------------+

具有更多 ID 和不同案例的演示: https://dbfiddle.uk/?rdbms=oracle_18&fiddle=f7f924cd8759d67a188b7c11f2d071ef

(這仍然不完美。如果一個非常小的 ID 導致更高的 ID 形成一個循環,例如,如果我們插入一個 ID 0 引用 ID 3 作為父級,查詢將多次顯示循環。這不容易避免,因為我們必須檢測圓圈的最小 ID。我可能會編寫一個小的 PL/SQL function 從 ID 字符串中獲取這個最小 ID。)

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM