What is a CTE in SQL? Explained in one query

Published Aug 2, 2026·6 min read·Every query runnable in your browser

A CTE is a subquery with a name. That is the entire concept. WITH engineers AS (…) defines it, and the main query selects FROM engineers as if a table called engineers existed. It lives for one statement, then it's gone. Everything else people say about CTEs — readability, reuse, recursion — is a consequence of being able to name a step.

In this post

  1. The one query that explains it
  2. What the name buys you
  3. The scoping rule that trips everyone
  4. Multiple CTEs = a pipeline with named steps
  5. When a plain subquery is fine
  6. FAQ

The one query that explains it

A CTE, in full — runs on SQL Quest's employees table
WITH engineers AS (
  SELECT name, salary, hire_date
  FROM employees
  WHERE department = 'Engineering'
)
SELECT name, salary
FROM engineers
WHERE salary > 90000
ORDER BY salary DESC;

Read it top to bottom: first define what "engineers" means (the Engineering rows), then ask the actual question (who earns above 90k). The database sees the same work as a nested subquery — but the human sees a recipe with named ingredients.

Could you write this as one WHERE department = 'Engineering' AND salary > 90000? Absolutely — and for something this small you should. The CTE earns its keep when the steps multiply; the point here is only the shape.

What the name buys you

What the name does NOT buy you: speed. Modern engines (PostgreSQL 12+, MySQL 8+, SQL Server, SQLite) inline the CTE as if you had written the subquery by hand. A CTE is a readability tool. If someone tells you CTEs are slow, they are quoting PostgreSQL ≤11, which materialized every CTE — that behavior is history.

The scoping rule that trips everyone

A CTE exposes only the columns its own SELECT lists. Not the underlying table's columns — its own. This is the single most common CTE error, and it looks like this:

✗ Fails — hire_date is not in the CTE
WITH engineers AS (
  SELECT name, salary FROM employees WHERE department = 'Engineering'
)
SELECT name, salary, hire_date   -- ✗ no such column: hire_date
FROM engineers;

hire_date obviously exists in employees — but engineers is not employees. It is the two-column result you defined, and hire_date was not one of them. The fix is mechanical: add hire_date to the CTE's SELECT.

Real-world cost of this rule: a SQL Quest user prepping for an Amazon interview lost 45 minutes to exactly this error — the query looked right because the column does exist in the base table. If your "no such column" error names a column you can see in the table, check what the CTE actually selected.

Multiple CTEs = a pipeline with named steps

Two CTEs, comma-separated — the second uses the first
WITH engineers AS (
  SELECT name, salary FROM employees WHERE department = 'Engineering'
),
seniors AS (
  SELECT name, salary FROM engineers WHERE salary > 90000
)
SELECT COUNT(*) AS senior_count, ROUND(AVG(salary)) AS avg_salary
FROM seniors;

One WITH, commas between definitions, and each CTE can use the ones above it. This is how a gnarly report stays maintainable: filter → enrich → aggregate, each step named, each step testable on its own by changing the final SELECT.

▶ Practice: Give a Query a Name (WITH)

When a plain subquery is fine

Honesty over dogma: not everything needs a CTE.

FAQ

What does CTE stand for?

Common Table Expression. The name is worse than the thing: "named subquery" describes it better than all three words combined.

Can a CTE be used in UPDATE or DELETE?

Yes in most engines — e.g. WITH stale AS (SELECT id FROM …) DELETE FROM t WHERE id IN (SELECT id FROM stale). PostgreSQL goes further and allows data-modifying CTEs themselves; treat that as an advanced tool.

How is a CTE different from a view?

Scope. A view is a saved, named query that lives in the database schema and every query can use. A CTE exists for one statement only. Same idea — naming a query — at two lifetimes.

How is a CTE different from a temp table?

A temp table persists for your whole session and is written to storage; a CTE evaporates when the statement ends. If three separate queries need the same intermediate result, compute it once into a temp table. If one query does, use a CTE.

You now know what a CTE is. Make it muscle memory.

The challenge takes two minutes, runs in your browser, and the Coach explains why a wrong CTE is wrong — including the scoping trap above.

Practice CTEs — Free →