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.

WITH high_value AS ( SELECT customer_id, SUM(total) AS total_spend FROM orders GROUP BY customer_id HAVING SUM(total) > 100 ) SELECT * FROM high_value; -- the CTE is usable only here

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.

CREATE TEMP TABLE high_value AS SELECT customer_id, SUM(total) AS total_spend FROM orders GROUP BY customer_id HAVING SUM(total) > 100; SELECT COUNT(*) FROM high_value; -- still usable SELECT AVG(total_spend) FROM high_value; -- and again, later

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.

CTE Query 1 (WITH …) gone gone gone Temp table CREATE TEMP TABLE Query 2 Query 3 session ends A CTE vanishes after its query; a temp table stays available across many queries until the session ends.
The CTE exists only inside its own query; the temporary table persists across statements for the whole session.

Side by Side

DimensionCTETemporary table
ScopeOne statement onlyWhole session (or transaction)
StorageIn-memory while the query runsA real table, on disk or in memory
ReusabilitySingle use within its queryReusable across many statements
IndexingNot possibleCan be indexed
StatisticsUsually none for the optimizerCan carry statistics that help planning
Setup costNone — declared inlineA real write; CREATE + INSERT
CleanupAutomatic at end of queryEnd of session, or explicit DROP
Best forReadable, complex single queriesMulti-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, no DROP, 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?

Need it across multiple statements? NO Use a CTE clean, inline, single-query YES Use a temporary table reuse · index · debug …also pick a temp table if you need an index or to debug between steps.
One statement favours a CTE; many statements favour a temporary table.

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:

-- Query 1 WITH high_value AS ( SELECT customer_id, SUM(total) AS spend FROM orders GROUP BY customer_id HAVING SUM(total) > 100 ) SELECT COUNT(*) FROM high_value; -- Query 2: the SAME CTE must be written all over again WITH high_value AS ( SELECT customer_id, SUM(total) AS spend FROM orders GROUP BY customer_id HAVING SUM(total) > 100 ) SELECT AVG(spend) FROM high_value;

With a temporary table — declared once

The temporary table is built a single time and serves both queries:

CREATE TEMP TABLE high_value AS SELECT customer_id, SUM(total) AS spend FROM orders GROUP BY customer_id HAVING SUM(total) > 100; SELECT COUNT(*) FROM high_value; -- Query 1 SELECT AVG(spend) FROM high_value; -- Query 2 — no repetition

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.

A note on performance

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

  1. PostgreSQL Global Development GroupWITH Queries (Common Table Expressions)postgresql.org/docs/current/queries-with.html
  2. PostgreSQL Global Development GroupCREATE TABLE (TEMPORARY)postgresql.org/docs/current/sql-createtable.html
  3. Oracle CorporationMySQL 8.0 Reference Manual: CREATE TEMPORARY TABLEdev.mysql.com/doc/refman/8.0/en/create-temporary-table.html
  4. MicrosoftWITH common_table_expression (Transact-SQL)learn.microsoft.com/en-us/sql/…/with-common-table-expression
  5. Amazon Web ServicesRedshift Developer Guide: CREATE TABLEdocs.aws.amazon.com/redshift/…/r_CREATE_TABLE_NEW.html
← Previous article Next article →
← Back to all articles