Here's a solution that works with arbitrary depth.
Store all your relationships in one table:
Table t
Parent | Child
------ | -----
B | A
C | B
A | C
E | D
F | E
Then you can use this WITH RECURSIVE
query to find cycles:
WITH RECURSIVE working(parent, last_visited, already_visited, cycle_detected) AS (
SELECT parent, child, ARRAY[parent], false FROM t
UNION ALL
SELECT t.parent, t.child, already_visited || t.parent, t.parent = ANY(already_visited)
FROM t
JOIN working ON working.last_visited = t.parent
WHERE NOT cycle_detected
)
SELECT parent, already_visited FROM working WHERE cycle_detected
Fiddle
It will give you the parent
s that are part of a cycle, and also the cycle they are in:
A | A,C,B,A
B | B,A,C,B
C | C,B,A,C
It works like this (because that is what the keyword RECURSIVE
instructs Postgres to do):
- Run the first
SELECT
, selecting all entries from table t
and placing them in a temporary table named working
.
- Then run the second
SELECT
, joining the working
table with table t
to find the children of each entry. Those children are added to the array of already seen children.
- Now run the second
SELECT
again and again, as long as entries are added to the working
table.
- A cycle is detected when one of the entries visits a child that it has visited before (
t.parent = ANY(already_visited)
) in this case cycle_detected
is set to true and no more children are added to the entry.