Question presented to candidate: "A bad data import left three managers reporting to each other in a circle, and now our org-chart query never finishes. How do you make a recursive CTE safe against cycles, how do you find the people caught in the loop, and how do you control whether the result comes out depth-first or breadth-first?"
What a strong answer should cover:
- Why it loops: with
UNION ALLevery iteration finds rows again, the working table never becomes empty, and PostgreSQL and SQLite have no built-in recursion limit - Portable cycle detection: carry the visited path (a delimited string, or an array in PostgreSQL), flag a row whose id is already in the path, and stop expanding flagged rows
- PostgreSQL 14+
CYCLE id SET is_cycle USING path, which generates that bookkeeping for you, andSEARCH DEPTH FIRST/SEARCH BREADTH FIRST BY ... SET ordercolfor the output order - Cheaper partial defences:
UNION(stops only when whole rows repeat) and a depth guard (bounds the work but returns repeated rows) - A data-quality query that lists the employees who are on a loop, and preventing it at write time
- Search order: breadth-first = order by depth; depth-first = order by the path; SQLite can also pick the queue order with
ORDER BYinside the recursive member
Clarifying questions expected:
- Do you want the query to tolerate the bad rows and still return everything reachable, or to report them?
- Which order does the consumer need: level by level, or each manager followed by their whole team?
Code / implementation expected: Yes — a recursive walk with path-based cycle detection, plus the PostgreSQL CYCLE / SEARCH clauses or an equivalent ordering.
Schema used by the examples: