Recursive CTE for hierarchical data in SQL - 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 code snippet.
WITH RECURSIVE Subordinates AS (
SELECT employee_id, manager_id, name
FROM employees
WHERE manager_id IS NULL
UNION ALL
SELECT e.employee_id, e.manager_id, e.name
FROM employees e
INNER JOIN Subordinates s ON e.manager_id = s.employee_id
)
SELECT * FROM Subordinates;
This query finds all employees under top-level managers by repeatedly joining employees to their managers.
- Primary operation: Recursive join between employees and the growing set of subordinates.
- How many times: Once for each level of the hierarchy until all subordinates are found.
Each recursion step adds employees reporting to the current level, repeating until no more are found.
| Input Size (n) | Approx. Operations |
|---|---|
| 10 | About 10 joins, one per employee |
| 100 | About 100 joins, growing with employees |
| 1000 | About 1000 joins, scaling with total employees |
Pattern observation: The work grows roughly in proportion to the total number of employees processed.
Time Complexity: O(n)
This means the time grows linearly with the number of employees in the hierarchy.
[X] Wrong: "Recursive queries always take exponential time because they repeat joins many times."
[OK] Correct: Each employee is processed once in the recursion, so the total work grows linearly, not exponentially.
Understanding recursive queries shows you can handle complex data relationships, a useful skill in many real projects.
"What if the hierarchy has cycles? How would that affect the time complexity of the recursive query?"
Practice
RECURSIVE CTE in SQL when working with hierarchical data?Solution
Step 1: Understand the role of recursive CTEs
Recursive CTEs allow querying hierarchical or connected data by repeatedly joining rows to find related entries.Step 2: Compare options with this purpose
Only To repeatedly query connected rows to explore hierarchical relationships describes this repeated querying of connected rows to explore hierarchies.Final Answer:
To repeatedly query connected rows to explore hierarchical relationships -> Option BQuick Check:
Recursive CTE = repeated connected row queries [OK]
- Confusing recursive CTEs with simple temporary tables
- Thinking recursive CTEs speed up unrelated queries
- Assuming recursive CTEs update data
OrgChart in SQL?Solution
Step 1: Recall recursive CTE syntax
The correct way to start a recursive CTE is usingWITH RECURSIVE Name AS ( ... ).Step 2: Check each option
WITH RECURSIVE OrgChart AS ( ... ) matches the correct syntax. Options A, B, and C use invalid or unrelated SQL commands.Final Answer:
WITH RECURSIVE OrgChart AS ( ... ) -> Option DQuick Check:
Recursive CTE starts with WITH RECURSIVE [OK]
- Using CREATE instead of WITH for CTEs
- Trying to SELECT RECURSIVE directly
- Using DECLARE which is for procedural code
Employees(id, name, manager_id) where manager_id references id, what will the following query return?WITH RECURSIVE Subordinates AS ( SELECT id, name, manager_id FROM Employees WHERE id = 2 UNION ALL SELECT e.id, e.name, e.manager_id FROM Employees e JOIN Subordinates s ON e.manager_id = s.id ) SELECT * FROM Subordinates;
Solution
Step 1: Analyze the base query
The base query selects the employee with id = 2.Step 2: Analyze the recursive part
The recursive part joins employees whose manager_id matches any id found so far, thus finding all subordinates under employee 2.Final Answer:
All employees who report directly or indirectly to employee with id 2 -> Option AQuick Check:
Recursive join finds all subordinates [OK]
- Thinking it returns only the base employee
- Assuming it returns employees without managers
- Believing it returns all employees regardless
WITH RECURSIVE Descendants AS ( SELECT id, parent_id FROM Tree WHERE id = 1 UNION ALL SELECT t.id, t.parent_id FROM Tree t JOIN Descendants d ON t.id = d.parent_id ) SELECT * FROM Descendants;
Solution
Step 1: Understand the tree relationship
Each node'sparent_idpoints to its parent. To find descendants, join where child'sparent_idequals parent'sid.Step 2: Check the JOIN condition
The query incorrectly joins ont.id = d.parent_id. It should bet.parent_id = d.idto find children of current nodes.Final Answer:
The JOIN condition should be ON t.parent_id = d.id, not t.id = d.parent_id -> Option AQuick Check:
Join child parent_id to parent id [OK]
- Reversing join condition columns
- Using base query from recursive CTE itself
- Thinking recursive CTEs can't use JOIN
- Confusing UNION and UNION ALL usage
Parts(id, name, parent_part_id) representing assemblies and subassemblies. Write a recursive CTE query to find the full hierarchy of parts under part with id = 10, including the root part itself, and show the level (depth) of each part in the hierarchy starting at 0 for the root. Which query is correct?Solution
Step 1: Identify the base case and level
The base query selects the root part with id=10 and sets level to 0.Step 2: Check the recursive join and level increment
The recursive part joins parts whereparent_part_idmatches current part's id, and increments level by 1 to track depth.Step 3: Verify options
WITH RECURSIVE PartHierarchy AS ( SELECT id, name, parent_part_id, 0 AS level FROM Parts WHERE id = 10 UNION ALL SELECT p.id, p.name, p.parent_part_id, ph.level + 1 FROM Parts p JOIN PartHierarchy ph ON p.parent_part_id = ph.id ) SELECT * FROM PartHierarchy; correctly implements this logic. WITH RECURSIVE PartHierarchy AS ( SELECT id, name, parent_part_id, 1 AS level FROM Parts WHERE id = 10 UNION ALL SELECT p.id, p.name, p.parent_part_id, ph.level - 1 FROM Parts p JOIN PartHierarchy ph ON p.id = ph.parent_part_id ) SELECT * FROM PartHierarchy; has wrong join direction and decrements level. WITH RECURSIVE PartHierarchy AS ( SELECT id, name, parent_part_id FROM Parts WHERE parent_part_id IS NULL UNION ALL SELECT p.id, p.name, p.parent_part_id FROM Parts p JOIN PartHierarchy ph ON p.id = ph.parent_part_id ) SELECT * FROM PartHierarchy WHERE id = 10; starts from root parts but filters later, missing the correct root. WITH RECURSIVE PartHierarchy AS ( SELECT id, name, parent_part_id, 0 AS level FROM Parts WHERE id = 10 UNION ALL SELECT p.id, p.name, p.parent_part_id, ph.level + 1 FROM Parts p JOIN PartHierarchy ph ON p.id = ph.parent_part_id ) SELECT * FROM PartHierarchy; has wrong join direction.Final Answer:
The query with base WHERE id=10, level 0, JOIN p.parent_part_id=ph.id, level+1 -> Option CQuick Check:
Join on child.parent_part_id = parent.id, level + 1 [OK]
- Reversing join condition (parent to child)
- Starting level at 1 or decrementing level
- Filtering after recursion instead of base case
- Using wrong root selection
Ready to prove it?
Pass the test and this pattern moves unknown
