Recursive CTEs
A CTE that calls itself: generate number sequences and walk the team_staff reporting hierarchy level by level.
Some questions have no fixed depth: "who reports to whom, all the way down?" or "give me match numbers 1 to N" with no table of numbers. A recursive CTE solves these by repeating a step until there is nothing new to add.
Definition
A recursive CTE is a CTE that refers to itself. It is made of an anchor member (a query that runs once and produces the starting rows),
UNION ALL, and a recursive member (a query that reads the CTE's own previous output and produces the next rows). The recursion stops automatically when the recursive member returns no new rows.
| Part | Role | Runs |
|---|---|---|
| Anchor member | The starting point (e.g. SELECT 1 AS n, or the top boss) | Once |
| UNION ALL | Glues the anchor rows and every new level together | — |
| Recursive member | Refers to the CTE itself and builds the next level | Repeatedly |
| Stop condition | A WHERE (or a join that finds no more matches) | Ends the loop |
Syntax
sql
WITH cte_name AS (
-- 1. anchor member (runs once)
SELECT start_columns
FROM ...
UNION ALL
-- 2. recursive member (refers to cte_name)
SELECT next_columns
FROM cte_name
WHERE stop_condition
)
SELECT * FROM cte_name;Note: SQL Server uses plain
WITH— no RECURSIVE keyword. PostgreSQL and MySQL requireWITH RECURSIVE.
Example — generate match numbers 1 to 5
sql
WITH matches AS (
SELECT 1 AS n -- anchor
UNION ALL
SELECT n + 1 -- recursive
FROM matches
WHERE n < 5 -- stop condition
)
SELECT n AS match_no
FROM matches;| match_no |
|---|
| 1 |
| 2 |
| 3 |
| 4 |
| 5 |
How it works:
| Iteration | Input rows | Output rows |
|---|---|---|
| Anchor | — | 1 |
| 1 | 1 | 2 |
| 2 | 2 | 3 |
| 3 | 3 | 4 |
| 4 | 4 | 5 |
| 5 | 5 | none (5 < 5 is false) → stop |
No table is needed — SQL builds the rows by itself.
Example — walk a hierarchy (team_staff)
The team_staff table stores who reports to whom: boss_id points to the id of the person's boss.
| id | name | boss_id |
|---|---|---|
| 1 | Franchise Owner | NULL |
| 2 | Head Coach | 1 |
| 3 | Captain | 2 |
| 4 | Vice Captain | 3 |
| 5 | Batting Coach | 2 |
sql
WITH org AS (
-- anchor: the top boss (no boss of their own)
SELECT id, name, boss_id, 1 AS level
FROM team_staff
WHERE boss_id IS NULL
UNION ALL
-- recursive: everyone whose boss is already in org
SELECT s.id, s.name, s.boss_id, o.level + 1
FROM team_staff AS s
JOIN org AS o
ON s.boss_id = o.id
)
SELECT id, name, level
FROM org
ORDER BY level, id;| id | name | level |
|---|---|---|
| 1 | Franchise Owner | 1 |
| 2 | Head Coach | 2 |
| 3 | Captain | 3 |
| 5 | Batting Coach | 3 |
| 4 | Vice Captain | 4 |
How it works: the anchor finds the owner (level 1). Round 1 finds everyone whose boss is the owner → Head Coach (level 2). Round 2 finds the Captain and Batting Coach (level 3). Round 3 finds the Vice Captain (level 4). Round 4 finds nobody → stop. Here the join is the stop condition.
Example — show the full reporting path
Add a text column that grows on each level:
sql
WITH org AS (
SELECT id, name, 1 AS level,
CAST(name AS VARCHAR(200)) AS path
FROM team_staff
WHERE boss_id IS NULL
UNION ALL
SELECT s.id, s.name, o.level + 1,
CAST(CONCAT(o.path, ' > ', s.name) AS VARCHAR(200))
FROM team_staff AS s
JOIN org AS o
ON s.boss_id = o.id
)
SELECT id, name, level, path
FROM org
ORDER BY level, id;| id | name | level | path |
|---|---|---|---|
| 1 | Franchise Owner | 1 | Franchise Owner |
| 2 | Head Coach | 2 | Franchise Owner > Head Coach |
| 3 | Captain | 3 | Franchise Owner > Head Coach > Captain |
| 5 | Batting Coach | 3 | Franchise Owner > Head Coach > Batting Coach |
| 4 | Vice Captain | 4 | Franchise Owner > Head Coach > Captain > Vice Captain |
Tip: In SQL Server the anchor and recursive columns must have exactly the same data types. That is why
pathisCASTtoVARCHAR(200)in both parts — without it SQL Server raises "Types don't match between the anchor and the recursive part".
Infinite loops and MAXRECURSION
If the stop condition is missing (SELECT n + 1 FROM matches with no WHERE), the recursion never ends. SQL Server protects you: by default it stops after 100 recursion levels with an error. You can change the limit:
sql
WITH numbers AS (
SELECT 1 AS n
UNION ALL
SELECT n + 1 FROM numbers WHERE n < 500
)
SELECT n FROM numbers
OPTION (MAXRECURSION 1000); -- 0 = no limit (dangerous)Common mistakes
- ❌ Missing stop condition → infinite loop (SQL Server error after 100 levels). ✅ always add
WHERE n < …or a join that eventually finds nothing. - ❌
UNIONinstead ofUNION ALL→ SQL Server requiresUNION ALLbetween anchor and recursive member. - ❌ Different data types in anchor and recursive parts. ✅
CASTboth sides to the same type. - ❌ Writing
WITH RECURSIVEin SQL Server → syntax error. ✅ plainWITH. - ❌ Expecting hierarchy output in a particular order. ✅ add
ORDER BY level(or by path).
Interview tip
Q: Find all employees under a given manager, at any depth.
Use a recursive CTE: the anchor selects the manager (WHERE id = @manager_id), the recursive member joins the table to the CTE on boss_id = cte.id. This is the classic interview use case for recursive CTEs.
Key points to remember
- A recursive CTE refers to itself: anchor member + UNION ALL + recursive member.
- The anchor runs once; the recursive member repeats, each time reading the rows produced in the previous round.
- Recursion stops when the recursive member returns no new rows (a WHERE or a join that finds nothing).
- SQL Server writes plain WITH — no RECURSIVE keyword (PostgreSQL / MySQL need WITH RECURSIVE).
- Classic uses: number / date sequences and hierarchies like who-reports-to-whom.
- SQL Server stops runaway recursion after 100 levels by default; change it with OPTION (MAXRECURSION n).
- Anchor and recursive columns must have matching data types — CAST text columns to the same size.
Try it yourself
Generate match numbers 1 to 5
Runs on a fresh IPL practice database in your browser · edit the query and press Ctrl + Enter
Franchise hierarchy with levels
Runs on a fresh IPL practice database in your browser · edit the query and press Ctrl + Enter
Hierarchy with the full reporting path
Runs on a fresh IPL practice database in your browser · edit the query and press Ctrl + Enter
Practice: overs 1 to 20
Runs on a fresh IPL practice database in your browser · edit the query and press Ctrl + Enter
Revision Flashcards
Tap a card to flip it and see the answer.
