Skip to main content

VillageSQL is a drop-in replacement for MySQL with extensions.

All examples in this guide work on VillageSQL. Install Now →
A Common Table Expression (CTE) is a named temporary result set defined at the top of a query with the WITH keyword. CTEs are fully supported in MySQL 8.4 and 9.x.

Basic CTE Syntax

A practical example — find the top 5 customers by total spend, then join their names:
You can define multiple CTEs in one WITH clause, and each can reference the ones defined before it:

CTEs vs Subqueries

Use CTEs when the subquery is complex, when you reference the same result more than once, or when nesting would make the query hard to follow. For a comparison of when inline subqueries are better, see Subqueries vs JOINs in MySQL.

Recursive CTEs

A recursive CTE references itself. This is the standard SQL pattern for querying hierarchical data — org charts, category trees, threaded comments.
The RECURSIVE keyword is required — without it, MySQL doesn’t recognize the self-reference and returns ERROR 1146: Table 'org_chart' doesn't exist. A recursive CTE has two parts joined by UNION ALL:
  1. Anchor member — the base case (employees with no manager)
  2. Recursive member — the step case (reports of employees already in the result)
MySQL stops when the recursive member returns zero rows. If your data has cycles (employee A reports to B, B reports to A), the query runs until it hits cte_max_recursion_depth (default: 1000) and errors. Guard against this with a depth column and a WHERE depth < N condition.

Frequently Asked Questions

Are CTEs faster than subqueries?

Usually the same. The MySQL optimizer often inlines a CTE the same way it would a subquery. CTEs referenced multiple times may be materialized into a temporary table, which can be faster (avoids re-executing the subquery) or slower (extra I/O). If you suspect unwanted materialization, check EXPLAIN for “MATERIALIZED” in the extra column.

Can I use CTEs in INSERT, UPDATE, or DELETE?

Yes. CTEs work with DML statements:

What’s the recursion depth limit?

The default is 1000 iterations (cte_max_recursion_depth). For deeper hierarchies, increase the session variable: SET SESSION cte_max_recursion_depth = 5000. If you hit the limit on normal data, check that your recursive member has a proper termination condition and that the data doesn’t contain cycles.

Troubleshooting

See also