Skip to content
PHP2Shell: The PHP Bug
Akshay Thakare

CTEs vs Subqueries vs Temp Tables

When to use each, what the optimizer does with them, and how to write long transformations that stay readable.

SQLBeginner2 min readUpdated 11 Oct 2026

Same logic, three shapes

SQL.sql
-- subquery
SELECT * FROM (SELECT customer_id, SUM(amount) AS total FROM orders GROUP BY 1) t WHERE total > 1000;
 
-- CTE
WITH totals AS (
  SELECT customer_id, SUM(amount) AS total FROM orders GROUP BY 1
)
SELECT * FROM totals WHERE total > 1000;
 
-- temp table
CREATE TEMPORARY TABLE totals AS
SELECT customer_id, SUM(amount) AS total FROM orders GROUP BY 1;
SELECT * FROM totals WHERE total > 1000;

How they differ

SubqueryCTETemp table
ReadabilityPoor when nestedReads top to bottomGood, split into statements
Reuse in one queryRepeat itReference by nameReference by name
Stored?NoUsually inlined by the optimizerYes, for the session
Reuse across statementsNoNoYes

Most modern engines inline a CTE like a view. Writing a CTE does not by itself cache the result.

Rules of thumb

  • Use CTEs to name steps: cleaned, deduped, daily, final. Each should do one thing.
  • Use a temp table when several later statements read the same expensive result, or to stage data before a MERGE.
  • Avoid deep nesting. If you indent past three levels, switch to CTEs.
SQL.sql
WITH cleaned AS (...),
     deduped AS (... QUALIFY ROW_NUMBER() OVER (...) = 1),
     daily   AS (SELECT business_date, SUM(net) AS net FROM deduped GROUP BY 1)
SELECT * FROM daily;

Finished this note?

Share