Recursive CTE for hierarchical data in PostgreSQL - Time & Space Complexity
When working with hierarchical data, recursive queries help us find all related items step-by-step.
We want to know how the time to get all related data grows as the hierarchy gets bigger.
Analyze the time complexity of the following recursive query.
WITH RECURSIVE subordinates AS (
SELECT employee_id, manager_id
FROM employees
WHERE manager_id IS NULL
UNION ALL
SELECT e.employee_id, e.manager_id
FROM employees e
JOIN subordinates s ON e.manager_id = s.employee_id
)
SELECT * FROM subordinates;
This query finds all employees under the top-level managers by repeatedly joining employees to their managers.
- Primary operation: Recursive join between employees and subordinates.
- How many times: Once per level of the hierarchy until all employees are found.
Each recursion step adds employees reporting to the previous level.
| Input Size (n) | Approx. Operations |
|---|---|
| 10 | About 10 joins to find all employees. |
| 100 | About 100 joins as it explores each employee once. |
| 1000 | About 1000 joins, one per employee in the hierarchy. |
Pattern observation: The work grows roughly in direct proportion to the number of employees.
Time Complexity: O(n)
This means the query time grows linearly with the number of employees in the hierarchy.
[X] Wrong: "Recursive queries always take exponential time because they repeat work."
[OK] Correct: The query visits each employee once, so work grows linearly, not exponentially.
Understanding recursive queries helps you handle real-world data like org charts or file systems efficiently.
What if the hierarchy had cycles? How would that affect the time complexity of the recursive query?
Practice
WITH RECURSIVE clause in PostgreSQL when working with hierarchical data?Solution
Step 1: Understand the purpose of recursive CTEs
Recursive CTEs allow querying hierarchical data by starting with a base case and repeatedly adding related rows.Step 2: Compare options with this purpose
Only To repeatedly query related rows and build a hierarchy step-by-step describes this step-by-step building of hierarchy, while others describe unrelated SQL features.Final Answer:
To repeatedly query related rows and build a hierarchy step-by-step -> Option AQuick Check:
Recursive CTE = step-by-step hierarchy building [OK]
- Confusing recursive CTE with temporary tables
- Thinking recursive CTEs update data
- Assuming recursive CTEs join unrelated tables
tree in PostgreSQL?Solution
Step 1: Recall correct recursive CTE syntax
The correct syntax starts withWITH RECURSIVE cte_name AS ( ... ).Step 2: Match options to syntax
Only WITH RECURSIVE tree AS (SELECT ... UNION ALL SELECT ...) matches this exact syntax; others misplace the keyword RECURSIVE.Final Answer:
WITH RECURSIVE tree AS (SELECT ... UNION ALL SELECT ...) -> Option BQuick Check:
WITH RECURSIVE before CTE name [OK]
- Placing RECURSIVE after CTE name
- Using RECURSIVE inside parentheses
- Omitting RECURSIVE keyword
employees(id, name, manager_id) with data:1, 'Alice', NULL2, 'Bob', 13, 'Carol', 2What will the following query return?
WITH RECURSIVE emp_tree AS (SELECT id, name, manager_id FROM employees WHERE id = 1 UNION ALL SELECT e.id, e.name, e.manager_id FROM employees e JOIN emp_tree et ON e.manager_id = et.id) SELECT name FROM emp_tree ORDER BY id;
Solution
Step 1: Understand base case and recursion
The base case selects employee with id=1 (Alice). The recursive part adds employees whose manager_id matches ids in emp_tree.Step 2: Trace recursion
First iteration adds Bob (manager_id=1), second adds Carol (manager_id=2). So all three are included.Final Answer:
Alice, Bob, Carol -> Option DQuick Check:
Recursive CTE collects all subordinates [OK]
- Ignoring base case rows
- Confusing manager_id direction
- Assuming only direct reports are included
WITH RECURSIVE cte AS (SELECT id, parent_id FROM items WHERE parent_id IS NULL UNION ALL SELECT i.id, i.parent_id FROM items i JOIN cte ON i.parent_id = cte.id) SELECT * FROM cte;
It returns no rows. What is the most likely cause?
Solution
Step 1: Check base case condition
The base case selects rows where parent_id IS NULL. If none exist, base returns zero rows.Step 2: Understand recursion depends on base
Without base rows, recursion never starts, so no rows returned.Final Answer:
The base case SELECT returns no rows because no parent_id is NULL -> Option AQuick Check:
Base case must return rows to start recursion [OK]
- Assuming JOIN condition is wrong without checking data
- Thinking UNION vs UNION ALL causes no rows
- Believing ORDER BY affects recursion output
folders(id, name, parent_id) representing nested folders. Write a recursive CTE query to list all folders with their full path from the root, separated by '/'. Which query correctly achieves this?Solution
Step 1: Identify correct base case and path initialization
The base case must select root folders (parent_id IS NULL) and set path to just the folder name without leading '/'. The recursive part builds path as parent_path || '/' || name.Step 2: Compare options with correct logic
The correct query uses parent_id IS NULL for base, name AS path, and fp.path || '/' || f.name for recursion (PostgreSQL string concat). Distractors fail by using invalid + concat, adding extra '/' (causing '//'), or starting from non-roots (parent_id IS NOT NULL).Final Answer:
WITH RECURSIVE folder_paths AS (SELECT id, name, parent_id, name AS path FROM folders WHERE parent_id IS NULL UNION ALL SELECT f.id, f.name, f.parent_id, fp.path || '/' || f.name FROM folders f JOIN folder_paths fp ON f.parent_id = fp.id) SELECT id, path FROM folder_paths ORDER BY path; -> Option CQuick Check:
Use base root rows + '||' concat for paths [OK]
- Using '+' instead of '||' for string concatenation
- Starting recursion from non-root folders
- Adding extra '/' at path start
Ready to prove it?
Pass the test and this pattern moves unknown
