Two of SQL's most useful tools for handling intermediate results look deceptively similar: the Common Table Expression (CTE) and the temporary table. Both let you name a chunk of computed data and build on it. But they differ in lifespan, storage, and capability — and choosing the wrong one can cost you readability or performance. This guide lays the two side by side so you always know which to reach for.
A Quick Recap of Each Tool
A CTE is a named temporary result set defined with the WITH keyword. It exists only for the duration of the single query that contains it. You write WITH name AS ( ... ) and then reference name in the query that immediately follows. When that query finishes, the CTE is gone — nothing persists.
A temporary table is a real table that lives only temporarily — typically for your whole session — and is private to your connection. You create it, fill it, and can then query it across many separate statements until the session ends or you drop it.
The Core Difference: Lifespan
Almost every distinction between the two flows from one fact: a CTE lives for one query; a temporary table lives for the whole session.
Side by Side
| Dimension | CTE | Temporary table |
|---|---|---|
| Scope | One statement only | Whole session (or transaction) |
| Storage | In-memory while the query runs | A real table, on disk or in memory |
| Reusability | Single use within its query | Reusable across many statements |
| Indexing | Not possible | Can be indexed |
| Statistics | Usually none for the optimizer | Can carry statistics that help planning |
| Setup cost | None — declared inline | A real write; CREATE + INSERT |
| Cleanup | Automatic at end of query | End of session, or explicit DROP |
| Best for | Readable, complex single queries | Multi-step workflows, reuse, tuning |
When to Reach for a CTE
A CTE is the better choice when:
- The logic lives in one query. If you only need the intermediate result once, within a single statement, a CTE keeps everything in one clean, readable place.
- Readability is the priority. CTEs let you name each step and read a complex query top to bottom instead of untangling nested subqueries.
- You want zero setup or cleanup. There is no
CREATE, noDROP, no write to manage — the CTE appears and disappears with the query. - You need recursion. Recursive CTEs (with
WITH RECURSIVE) handle hierarchies and trees — a capability temporary tables do not have on their own.
When to Reach for a Temporary Table
A temporary table earns its place when:
- You reuse the result across statements. Compute an expensive aggregation once, then query it from several angles without recomputing — the CTE would have to be re-declared in every statement.
- You need an index. For repeated joins or lookups against a large intermediate result, an index on a temporary table can transform performance. A CTE cannot be indexed.
- You are debugging a multi-step process. Because the data sits in a real table, you can inspect it between stages with a simple
SELECT *. - The optimizer needs help. A temporary table can carry statistics, which sometimes lets the planner make better decisions than it would for an inlined subquery or CTE.
A Simple Decision Rule
When you are unsure, this single question resolves most cases: do you need the intermediate result in more than one statement?
The Same Problem, Both Ways
To make the trade-off concrete, here is one task solved with each tool: from a group of high-value customers, return both the count and the average spend.
With a CTE — but note the repetition
A CTE is scoped to one query, so to use the same group in two statements you must declare it twice:
With a temporary table — declared once
The temporary table is built a single time and serves both queries:
For a single query, the CTE version would be cleaner — no setup, no cleanup. The moment you need the result in a second statement, the temporary table avoids repeating yourself and, for large data, avoids recomputing the aggregation twice.
For one-off queries, a CTE and the equivalent subquery usually perform the same — the engine optimizes them together. A temporary table only pays off when its one-time write cost is repaid by repeated reuse, indexing, or clearer multi-step logic. Building a temporary table you read just once is usually wasted effort.
The Mental Model
Reach for a CTE when you want clean, readable logic inside a single query — and especially when you need recursion. Reach for a temporary table when an intermediate result must persist across several statements, be queried repeatedly, carry an index, or be inspected step by step while you debug.
One statement, lean and readable: CTE. Many statements, reused or indexed: temporary table. With that single question in mind — "do I need this in more than one query?" — you will almost always reach for the right tool on the first try.
Main References
- PostgreSQL Global Development Group — WITH Queries (Common Table Expressions) — postgresql.org/docs/current/queries-with.html
- PostgreSQL Global Development Group — CREATE TABLE (TEMPORARY) — postgresql.org/docs/current/sql-createtable.html
- Oracle Corporation — MySQL 8.0 Reference Manual: CREATE TEMPORARY TABLE — dev.mysql.com/doc/refman/8.0/en/create-temporary-table.html
- Microsoft — WITH common_table_expression (Transact-SQL) — learn.microsoft.com/en-us/sql/…/with-common-table-expression
- Amazon Web Services — Redshift Developer Guide: CREATE TABLE — docs.aws.amazon.com/redshift/…/r_CREATE_TABLE_NEW.html