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 parents 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.
create tablestatements), some sample data and the expected output based on that data. Formatted text please, no screen shots