CTEs (WITH clause) — recursive CTEs
WHAT is a CTE first?
WHY use one? It makes nested queries readable (top-to-bottom instead of inside-out) and lets you reference the same subquery multiple times.
WHAT makes it recursive?
A normal CTE can't refer to itself. A recursive CTE can — that self-reference is what creates the loop.
HOW the execution actually works (derivation from first principles)
Don't trust the word "recursive" — it's really iteration under the hood. Here is the exact algorithm the engine runs:
- Run the anchor query. Put its rows into the result and into a working table .
- Repeat for step :
- Run the recursive member, but every mention of
cte_namereads only from (the rows produced last round). - Call the new rows .
- Append to the final result .
- Run the recursive member, but every mention of
- Stop when an iteration produces zero new rows ().
So the total result is the union of every round:

Worked example 1: count 1 → 5
WITH RECURSIVE nums AS (
SELECT 1 AS n -- anchor
UNION ALL
SELECT n + 1 FROM nums -- recursive member
WHERE n < 5 -- stop guard
)
SELECT n FROM nums;| Round | Working table | New rows? |
|---|---|---|
| anchor | {1} |
yes |
{2} |
yes | |
{3} |
yes | |
{4} |
yes | |
{5} |
yes | |
{} (6 fails n<5) |
stop |
Result: 1,2,3,4,5.
- Why
WHERE n < 5? It's the base-case-of-induction in reverse: without it, never empties → infinite loop. - Why
UNION ALLnotUNION? We know rows are unique here, andUNION ALLskips the dedupe sort → faster.
Worked example 2: walk an org chart (find all reports of CEO)
Table emp(id, name, manager_id) where manager_id points to the boss (NULL for CEO).
WITH RECURSIVE chain AS (
SELECT id, name, manager_id, 1 AS lvl -- anchor: the CEO
FROM emp
WHERE manager_id IS NULL
UNION ALL
SELECT e.id, e.name, e.manager_id, c.lvl + 1
FROM emp e
JOIN chain c ON e.manager_id = c.id -- e reports to someone we've found
)
SELECT id, name, lvl FROM chain ORDER BY lvl;- Why join
e.manager_id = c.id? Each round adds people whose manager was discovered in the previous round → we descend one level of the tree per iteration. - Why the
lvlcolumn? Dual-coding the depth: is CEO, direct reports, etc. Lets you filter/limit depth.
Worked example 3: detect a path in a graph (with cycle guard)
edges(src, dst). Find everything reachable from node 'A', avoiding infinite loops:
WITH RECURSIVE reach AS (
SELECT 'A' AS node, ARRAY['A'] AS path
UNION ALL
SELECT e.dst, r.path || e.dst
FROM edges e
JOIN reach r ON e.src = r.node
WHERE e.dst <> ALL(r.path) -- cycle guard: don't revisit
)
SELECT DISTINCT node FROM reach;- Why the
patharray +<> ALL? A graph can have cycles → might never empty. We carry the visited set so we refuse to re-enter a node, forcing termination.
Common mistakes (steel-manned)
Recall Feynman: explain to a 12-year-old
Imagine a treasure hunt. The first clue (the anchor) is handed to you. Each clue tells you where the next clues are. You only ever look at the clue you just found to get the next one — not all your old clues. You keep going until a clue leads to a dead end (no new clue) and then you stop. A recursive CTE is the database doing this treasure hunt automatically: "here's the start, here's how to get the next step, keep going until there's nothing new."
Active-recall flashcards
What keyword introduces a CTE?
WITH (add RECURSIVE for self-referencing ones)What are the two members of a recursive CTE?
During iteration, which rows can the recursive member see?
What is the exact termination condition?
Difference between UNION and UNION ALL in a recursive CTE?
UNION dedupes each round (can act as a cycle guard, slower); UNION ALL keeps all rows (faster, loops forever on cycles).How do you prevent infinite loops on a cyclic graph?
UNION) and exclude already-visited nodes, or impose a depth limit.Why can't a plain (non-recursive) CTE reference itself?
WITH RECURSIVE enables the self-reference that creates the iteration.Where should ORDER BY / LIMIT go for a recursive CTE?
Connections
- Common Table Expressions (WITH clause) — the non-recursive parent concept
- SQL Joins — the recursive member almost always joins the CTE to a base table
- UNION vs UNION ALL — set semantics drive termination
- Tree and Graph Data Structures — what recursive CTEs traverse
- Window Functions — the other "advanced read" SQL tool
- Mathematical Induction — anchor = base case, recursive member = inductive step
Concept Map
Hinglish (regional understanding)
Intuition Hinglish mein samjho
Bhai, recursive CTE ka funda simple hai: SQL me normally loop nahi hota, but WITH RECURSIVE use karke hum loop bana lete hain. Do hisse hote hain — anchor (starting point, ek baar chalta hai) aur recursive member (jo khud CTE ko reference karke baar-baar chalta hai). Jaise treasure hunt — pehli clue di gayi (anchor), uske baad har clue agli clue batati hai, aur jab koi nayi clue nahi milti to ruk jaate ho.
Sabse important baat jo log galat samajhte hain: recursive part ke andar jab tum CTE ka naam likhte ho, wo poora result nahi dikhata — sirf pichle round me jo nayi rows bani thi wahi dikhata hai (working table). Isi wajah se loop terminate hota hai. Agar har baar pura result re-scan hota to kabhi khatam hi nahi hota. Loop tab rukta hai jab kisi round me zero nayi rows banti hain.
Practical use? Org chart (kaun kiska boss), folder tree, ya graph me path nikalna — yahan recursion zaroori hai kyunki depth pehle se pata nahi hoti. Graph me cycle ho to infinite loop ka danger hota hai, isliye ek path array rakhte ho ya UNION use karte ho taaki visited node dobara na ghuse. Aur dhyan rakho: UNION ALL fast hai par cycle pe phasega; UNION duplicate hata deta hai (free cycle-guard) par thoda slow. Stop condition (WHERE n < 5 jaisa) bhulna sabse common mistake hai — uske bina query hang!