Common Table Expressions (CTEs) in SQL

Common Table Expressions (CTEs) in SQL — A Beginner's Guide

A Common Table Expression (CTE) is a named, temporary result set you can reference within a single query. Think of it as giving a subquery a name and using that name like a regular table — it makes complex queries dramatically more readable.

๐Ÿ“˜ Basic Syntax

WITH high_earners AS (
  SELECT employee_id, name, salary
  FROM employees
  WHERE salary > 80000
)
SELECT * FROM high_earners
WHERE name LIKE 'A%';

The WITH ... AS (...) block defines the CTE, and it can then be queried just like a table in the main SELECT below it.

๐ŸŽฏ Why Use a CTE Instead of a Subquery?

Compare the same logic without a CTE:

-- Without CTE — nested and harder to read
SELECT * FROM (
  SELECT employee_id, name, salary
  FROM employees
  WHERE salary > 80000
) high_earners
WHERE name LIKE 'A%';

Functionally identical, but for anything more complex than this toy example, nested subqueries get hard to follow fast. CTEs let you name each logical step, top to bottom, like a recipe rather than a deeply nested set of parentheses.

๐Ÿงช Multiple CTEs in One Query

WITH regional_sales AS (
  SELECT region, SUM(amount) AS total_sales
  FROM orders
  GROUP BY region
),
top_regions AS (
  SELECT region
  FROM regional_sales
  WHERE total_sales > 100000
)
SELECT o.*
FROM orders o
JOIN top_regions t ON o.region = t.region;

Each CTE can reference the ones defined before it, letting you break a complex transformation into clear, named steps.

๐Ÿ” Recursive CTEs

CTEs can also reference themselves, which is how you handle hierarchical data (org charts, category trees, bill-of-materials structures):

WITH RECURSIVE org_chart AS (
  -- Anchor: top-level employees (no manager)
  SELECT employee_id, name, manager_id, 1 AS level
  FROM employees
  WHERE manager_id IS NULL

  UNION ALL

  -- Recursive part: employees whose manager is already in org_chart
  SELECT e.employee_id, e.name, e.manager_id, oc.level + 1
  FROM employees e
  JOIN org_chart oc ON e.manager_id = oc.employee_id
)
SELECT * FROM org_chart
ORDER BY level;

This walks down the management hierarchy level by level — something that's genuinely difficult to express any other way in plain SQL.

⚠️ Common Mistakes

  • Assuming a CTE is materialized/cached like a temp table — in most databases (including BigQuery), a CTE is re-evaluated each time it's referenced, so referencing the same CTE multiple times can mean repeated computation
  • Forgetting the RECURSIVE keyword when writing a self-referencing CTE — syntax requirements vary slightly by database
  • Building deeply nested CTEs when a simple intermediate table would be clearer and better for reuse across multiple queries

๐Ÿ”— Related Posts

  • SQL JOIN Types Explained (INNER, LEFT, RIGHT, FULL) with Examples
  • SQL Window Functions Explained: ROW_NUMBER, RANK, LAG, LEAD

Comments

Popular Posts

BigQuery MERGE Statement – Explained

LRT Masjid Jamek-Bandaraya

Kavalkaran - MGR Tamil movie Part 4