A recursive common-table expression (CTE) in MySQL repeatedly executes a subquery that references itself until a termination condition is met, enabling elegant traversal of hierarchies or generation of sequences.
Recursive common-table expressions (CTEs) were introduced in MySQL 8.0.1 and finally gave MySQL developers a standards-compliant way to traverse hierarchical data, generate running totals, and solve problems that previously required stored procedures or application-side loops. By declaring a WITH RECURSIVE
query, you can define an anchor member (your starting rows) and a recursive member (the part that calls itself). MySQL automatically iterates until no new rows are produced or a loop-prevention limit is hit.
Hierarchical data is everywhere: org charts, folder structures, bill-of-materials, dependency graphs, network hops—the list goes on. Recursive CTEs let you handle these patterns entirely in SQL, keeping business logic close to the data and minimizing round-trips. For data engineers and analysts, this means:
WITH RECURSIVE cte_name (col_list) AS (
-- Anchor member
SELECT ...
UNION ALL
-- Recursive member
SELECT ... FROM cte_name JOIN ...
)
SELECT * FROM cte_name;
The rules:
UNION ALL
is required (the ANSI spec allows UNION
, but MySQL insists on ALL
).SET @@cte_max_recursion_depth = n;
Suppose you have an employees
table with columns emp_id
, emp_name
, and manager_id
. You want the full reporting chain for employee 7.
-- Anchor: start with the employee
WITH RECURSIVE hierarchy AS (
SELECT emp_id, emp_name, manager_id, 0 AS depth
FROM employees
WHERE emp_id = 7
UNION ALL
-- Recursive: add the manager each loop
SELECT e.emp_id, e.emp_name, e.manager_id, h.depth + 1
FROM employees AS e
JOIN hierarchy AS h ON e.emp_id = h.manager_id
)
SELECT * FROM hierarchy;
The query returns the employee, her manager, the manager’s manager, and so on, until manager_id
becomes NULL
. The depth
column shows hierarchy level and is handy for ordering:
SELECT * FROM hierarchy ORDER BY depth;
Need a quick tally table for a date dimension or pagination? Use a recursive CTE:
WITH RECURSIVE nums AS (
SELECT 1 AS n
UNION ALL
SELECT n + 1 FROM nums WHERE n < 1000
)
SELECT n FROM nums;
You now have integers 1–1000 without creating a physical table.
Recursive members usually join the CTE back to the base table. Make sure columns such as manager_id
are indexed.
Filter the anchor or recursive member to avoid ballooning data. Even a simple WHERE depth < 10
can prevent runaway recursion.
If the data contains cycles (e.g., an employee managing herself), the query may exceed cte_max_recursion_depth
. Clean the data or add a break condition.
ALL
to avoid the costly deduplication step.ORDER BY
disrupts recursion and is disallowed in MySQL.Forgetting a termination condition can exceed the recursion depth. Always ensure the recursive member shrinks the problem space.
The anchor and recursive queries must project the exact number and order of columns. MySQL throws 1248: Every select must have the same number of columns
.
MySQL will error out; switch to UNION ALL
.
Galaxy’s modern SQL editor provides syntax highlighting, auto-completion, and an AI copilot that recognizes recursion patterns. Type WITH RECURSIVE
and Galaxy auto-suggests anchor/recursive scaffolding. You can:
Because Galaxy runs queries natively against MySQL, execution plans and run-time metrics appear in a panel, helping you fine-tune recursion depth and indexes without leaving the editor.
Recursive CTEs unlock elegant, set-based solutions for hierarchical queries in MySQL 8+. By understanding the anchor-recursive pattern, guarding against run-away loops, and leveraging tools like Galaxy, you can write maintainable SQL that stays performant—even as your data grows.
Recursive CTEs let data engineers traverse hierarchies, generate sequences, and perform graph-like calculations inside a single query—reducing round-trips, simplifying ETL pipelines, and aligning MySQL with ANSI SQL standards found in other major databases.
Recursive CTEs arrived in MySQL 8.0.1. Older 5.x or 5.7 installations do not support them.
Set SET @@cte_max_recursion_depth = N;
for the session, or add a depth column and a WHERE depth < N
condition inside the recursive member.
Use a recursive CTE when the hierarchy is variable-depth or unknown at query time. Self-joins work for fixed depths (e.g., 3 levels) but become unwieldy beyond that.
Yes. Galaxy’s SQL editor supports full MySQL 8 syntax, autocompletes WITH RECURSIVE
, and lets you store queries in Collections for team reuse.