AdvancedintermediateCommonly used

How to use CTEs (WITH clause) in SQL

Write cleaner, reusable subqueries using Common Table Expressions.

Basic CTEAll databases
WITH active_users AS (
  SELECT id, name, email
  FROM users
  WHERE active = true
)
SELECT *
FROM active_users
WHERE country = 'Kenya';

The WITH clause defines a named temporary result set. The main query uses it like a table.

When to use: Replacing complex subqueries with readable named steps.

Multiple CTEsAll databases
WITH
  active_users AS (
    SELECT id, name
    FROM users
    WHERE active = true
  ),
  high_value_orders AS (
    SELECT user_id, SUM(total) AS total_spent
    FROM orders
    WHERE status = 'completed'
    GROUP BY user_id
    HAVING SUM(total) > 1000
  )
SELECT
  u.name,
  o.total_spent
FROM active_users u
INNER JOIN high_value_orders o ON u.id = o.user_id;

Chain multiple CTEs. Each can reference CTEs defined before it.

CTE vs subqueryAll databases
-- Subquery version (harder to read):
SELECT name, total_spent
FROM (
  SELECT u.name, SUM(o.total) AS total_spent
  FROM users u
  INNER JOIN orders o ON u.id = o.user_id
  GROUP BY u.id, u.name
) AS user_totals
WHERE total_spent > 1000;

-- CTE version (cleaner):
WITH user_totals AS (
  SELECT u.name, SUM(o.total) AS total_spent
  FROM users u
  INNER JOIN orders o ON u.id = o.user_id
  GROUP BY u.id, u.name
)
SELECT name, total_spent
FROM user_totals
WHERE total_spent > 1000;

CTEs and subqueries are often equivalent. CTEs are easier to read and debug.

Recursive CTE — hierarchy traversalAll databases
WITH RECURSIVE org_chart AS (
  -- Base case: top-level employees
  SELECT id, name, manager_id, 0 AS level
  FROM employees
  WHERE manager_id IS NULL

  UNION ALL

  -- Recursive case: employees reporting to someone
  SELECT e.id, e.name, e.manager_id, oc.level + 1
  FROM employees e
  INNER JOIN org_chart oc ON e.manager_id = oc.id
)
SELECT level, name
FROM org_chart
ORDER BY level, name;

Recursive CTEs traverse hierarchies like org charts, category trees, or comment threads.

When to use: Querying tree or graph structures stored in a self-referencing table.

Common Mistakes

  • CTEs are not necessarily cached — in PostgreSQL, each CTE reference may re-execute the query
  • Infinite recursion in recursive CTEs — always have a base case and termination condition
← Back to SQL Reference