Common Table Expressions (CTE)
CTEs break complex queries into simple, readable steps using WITH
.
Quick Start
-- Instead of complex nested queries
WITH high_earners AS (
SELECT * FROM employees WHERE salary > 70000
)
SELECT department, COUNT(*)
FROM high_earners
GROUP BY department;
Result: Clean, readable code that's easy to debug.
When to Use CTE
✅ Use CTE when:
- Query has multiple steps
- You need the same subquery twice
- Query is hard to read
❌ Skip CTE when:
- Simple one-step query
- Performance is critical
Three Essential Patterns
1. Filter → Analyze
WITH filtered_data AS (
SELECT * FROM sales WHERE date >= '2023-01-01'
)
SELECT product, SUM(amount)
FROM filtered_data
GROUP BY product;
2. Multiple Steps
WITH step1 AS (
SELECT department, AVG(salary) as avg_sal FROM employees GROUP BY department
),
step2 AS (
SELECT * FROM step1 WHERE avg_sal > 70000
)
SELECT * FROM step2;
3. Use Same Data Twice
WITH dept_stats AS (
SELECT department, AVG(salary) as avg_sal FROM employees GROUP BY department
)
SELECT d1.department, d1.avg_sal
FROM dept_stats d1
JOIN dept_stats d2 ON d1.avg_sal > d2.avg_sal;
Advanced: Recursive CTE
Recursive CTEs solve problems where you need to repeatedly apply the same logic. Think of it like climbing stairs - you start at step 1, then keep going up one step at a time.
-- Generate a sequence (useful for reports, testing, or filling gaps)
WITH RECURSIVE countdown AS (
-- Base case: where we start
SELECT 10 as num, 'Starting countdown' as message
UNION ALL
-- Recursive case: what we do repeatedly
SELECT num - 1, CONCAT('Count: ', CAST(num - 1 AS VARCHAR))
FROM countdown
WHERE num > 1 -- Stop condition: when to stop
)
SELECT num, message FROM countdown;
Result: Numbers from 10 down to 1 with messages.
Real-world example: Generate missing months for a sales report
- Start: January 2024
- Repeat: Add next month
- Stop: When we reach December 2024
Key insight: Recursion = Start somewhere + Repeat an action + Know when to stop.
That's it. Start with simple CTEs, add complexity only when needed.