Basic CTEs in PostgreSQL

From the PostgreSQL Advanced Features cheat sheet ยท CTEs & Advanced Queries ยท verified Jul 2026

Basic CTEs

Simplify complex queries with named subqueries

sql
-- Simple CTE
WITH high_value_customers AS (
  SELECT customer_id, SUM(amount) as total
  FROM orders
  GROUP BY customer_id
  HAVING SUM(amount) > 1000
)
SELECT c.name, hvc.total
FROM customers c
JOIN high_value_customers hvc ON c.id = hvc.customer_id;

-- Multiple CTEs
WITH 
  active_users AS (
    SELECT * FROM users WHERE status = 'active'
  ),
  recent_orders AS (
    SELECT * FROM orders WHERE created_at > NOW() - INTERVAL '30 days'
  )
SELECT u.name, COUNT(o.id) as order_count
FROM active_users u
LEFT JOIN recent_orders o ON u.id = o.user_id
GROUP BY u.name;

-- DETAILED_TAB:
-- CTE with INSERT/UPDATE/DELETE
WITH deleted AS (
  DELETE FROM old_records
  WHERE created_at < NOW() - INTERVAL '1 year'
  RETURNING *
)
INSERT INTO archive_table
SELECT * FROM deleted;

-- Materialized CTE (PostgreSQL 12+)
WITH expensive_calculation AS MATERIALIZED (
  -- This runs once and results are stored
  SELECT complex_function(data) as result
  FROM large_table
)
SELECT * FROM expensive_calculation
WHERE result > 100;
๐ŸŸข Essential - CTEs make complex queries readable
๐Ÿ’ก CTEs are like named temporary tables
๐Ÿ“Œ Can reference other CTEs defined earlier
โšก MATERIALIZED forces CTE to run once
๐Ÿ”— Related: Views for permanent named queries
ctewithsubqueries

Continue with PostgreSQL Advanced Features

Save the full cheat sheet or work through every related task.

More PostgreSQL tasks

Back to the full PostgreSQL Advanced Features cheat sheet