Recursive CTEs help you explore connected data step-by-step, like finding all friends of a friend in a network.
Recursive CTE for graph traversal in PostgreSQL
WITH RECURSIVE cte_name(column_list) AS ( -- Anchor member: starting point of recursion SELECT initial_columns FROM table_name WHERE condition UNION ALL -- Recursive member: references cte_name to find connected rows SELECT next_columns FROM table_name JOIN cte_name ON join_condition WHERE recursive_condition ) SELECT * FROM cte_name;
The anchor member is the base query that starts the recursion.
The recursive member references the CTE itself to find connected rows.
WITH RECURSIVE friends_cte(friend_id) AS ( SELECT friend_id FROM friendships WHERE user_id = 1 UNION ALL SELECT f.friend_id FROM friendships f JOIN friends_cte fc ON f.user_id = fc.friend_id WHERE f.friend_id != 1 ) SELECT * FROM friends_cte;
WITH RECURSIVE ancestors_cte(id, parent_id) AS ( SELECT id, parent_id FROM family WHERE id = 5 UNION ALL SELECT f.id, f.parent_id FROM family f JOIN ancestors_cte a ON f.parent_id = a.id ) SELECT * FROM ancestors_cte;
This query finds all nodes you can reach starting from node 1 by following edges step-by-step.
CREATE TABLE edges ( from_node INT, to_node INT ); INSERT INTO edges VALUES (1, 2), (1, 3), (2, 4), (3, 5), (5, 6); -- Find all nodes reachable from node 1 WITH RECURSIVE reachable_nodes(node) AS ( -- Start from node 1 SELECT 1 AS node UNION -- Find nodes connected to already found nodes SELECT e.to_node FROM edges e JOIN reachable_nodes r ON e.from_node = r.node ) SELECT DISTINCT node FROM reachable_nodes ORDER BY node;
Time complexity depends on the number of edges and nodes; it can be expensive for large graphs.
Space complexity grows with the number of reachable nodes stored during recursion.
Common mistake: forgetting to include a stopping condition or DISTINCT, which can cause infinite loops or duplicates.
Use recursive CTEs when you need to explore hierarchical or connected data stepwise; for simple joins, recursion is not needed.
Recursive CTEs let you explore connected data step-by-step.
They have an anchor part and a recursive part that references itself.
Useful for graphs, trees, and hierarchical data traversal.
Practice
WITH RECURSIVE clause in PostgreSQL when working with graph data?Solution
Step 1: Understand the purpose of recursive CTEs
Recursive CTEs allow a query to call itself repeatedly, which is useful for exploring connected data like graph nodes.Step 2: Identify the correct use case
UsingWITH RECURSIVElets you traverse graph structures step-by-step by referencing the CTE inside itself.Final Answer:
To repeatedly execute a query that references itself to explore connected nodes -> Option CQuick Check:
Recursive CTE = self-referencing query [OK]
- Confusing recursive CTE with temporary tables
- Thinking it automatically optimizes performance
- Using it for unrelated table joins
path in PostgreSQL?Solution
Step 1: Recall the syntax for recursive CTEs
The correct syntax starts withWITH RECURSIVE cte_name AS ( ... ), where the CTE references itself inside.Step 2: Match the syntax options
WITH RECURSIVE path AS (SELECT ... UNION ALL SELECT ... FROM path ...) correctly usesWITH RECURSIVE path AS (SELECT ... UNION ALL SELECT ... FROM path ...). Other options misuse keywords or order.Final Answer:
WITH RECURSIVE path AS (SELECT ... UNION ALL SELECT ... FROM path ...) -> Option DQuick Check:
WITH RECURSIVE + CTE name + self-reference [OK]
- Swapping RECURSIVE and CTE name order
- Using CREATE instead of WITH
- Omitting UNION ALL between parts
edges(source INT, target INT) with data:(1, 2), (2, 3), (3, 4), (2, 5)What will the following query return?
WITH RECURSIVE path AS (SELECT source, target FROM edges WHERE source = 1 UNION ALL SELECT e.source, e.target FROM edges e JOIN path p ON e.source = p.target) SELECT * FROM path ORDER BY source, target;
Solution
Step 1: Identify the anchor and recursive parts
The anchor selects edges where source=1: (1,2). The recursive part joins edges where source matches previous target.Step 2: Trace recursive expansion
From (1,2), next edges with source=2 are (2,3) and (2,5). From (2,3), next edge with source=3 is (3,4). No further edges from 5 or 4.Final Answer:
Rows: (1,2), (2,3), (2,5), (3,4) -> Option AQuick Check:
Recursive join expands all reachable edges [OK]
- Ignoring multiple edges from same source
- Stopping recursion too early
- Misordering results
WITH RECURSIVE path(node) AS (SELECT 1 UNION ALL SELECT node + 1 FROM path WHERE node < 3) SELECT * FROM path;
What is wrong if this query runs forever without stopping?
Solution
Step 1: Analyze the recursive condition
The recursive part adds 1 to node while node < 3, but due to UNION ALL accumulating duplicates of rows satisfying the condition, recursion continues indefinitely.Step 2: Identify why it loops infinitely
If the termination condition is incorrect or not reached, recursion never stops. Here, the condition is present but ineffective against duplicates, causing infinite loop.Final Answer:
Missing a proper termination condition in the recursive part -> Option BQuick Check:
Infinite recursion = no stop condition [OK]
- Confusing UNION and UNION ALL effects
- Ignoring anchor query correctness
- Forgetting to select from CTE
employees(id INT, manager_id INT) representing a company hierarchy. How would you write a recursive CTE to find all employees under manager with id = 10, including indirect reports?Solution
Step 1: Identify the anchor query
The anchor selects employees directly reporting to manager 10:WHERE manager_id = 10.Step 2: Understand the recursive join
The recursive part joins employees whosemanager_idmatches any previously found employeeid, thus finding indirect reports.Final Answer:
WITH RECURSIVE subordinates AS ( SELECT id FROM employees WHERE manager_id = 10 UNION ALL SELECT e.id FROM employees e JOIN subordinates s ON e.manager_id = s.id ) SELECT * FROM subordinates; -> Option AQuick Check:
Join on e.manager_id = s.id to find subordinates [OK]
- Swapping join conditions (id vs manager_id)
- Starting anchor with wrong WHERE clause
- Selecting wrong columns in recursive part
Ready to prove it?
Pass the test and this pattern moves unknown
