CTE (Common Table Expression) is a temporary result set defined within the execution scope of a single SELECT
, INSERT
, UPDATE
, or DELETE
statement.
It’s similar to a subquery, but:
- It improves readability and modularity
- Supports recursive operations
- Makes complex queries easier to debug
Basic Syntax of a CTE:
WITH cte_name AS (
SELECT ...
FROM ...
WHERE ...
)
SELECT *
FROM cte_name;
You can also chain multiple CTEs:
WITH cte1 AS (...),
cte2 AS (...)
SELECT ...
FROM cte2;
Why Business & Data Analysts Should Use CTEs
Advantage | Benefit for Analysts |
---|---|
✅ Readability | Break down complex logic into named steps |
✅ Reusability | Avoid repeating the same logic multiple times |
✅ Debuggability | Easier to test parts of queries |
✅ Recursion | Solve hierarchical data problems (e.g. org charts) |
✅ Compatibility | Works in PostgreSQL, SQL Server, Snowflake, BigQuery |
Real-World Use Cases for CTEs
Let’s walk through business-relevant CTE examples, each with SQL code and analyst-focused explanations.
Use Case 1: Top-N Customers by Revenue
Problem: Find the top 5 customers by revenue in the last year.
WITH customer_sales AS (
SELECT
customer_id,
SUM(order_total) AS total_revenue
FROM orders
WHERE order_date >= DATEADD(year, -1, GETDATE())
GROUP BY customer_id
)
SELECT *
FROM customer_sales
ORDER BY total_revenue DESC
LIMIT 5;
Why CTE helps: You separate aggregation logic from the final filter/order, making the query easier to manage.
Use Case 2: Filtered Aggregations
Problem: Compare sales from new vs returning customers.
WITH first_orders AS (
SELECT customer_id, MIN(order_date) AS first_order
FROM orders
GROUP BY customer_id
),
classified_orders AS (
SELECT o.order_id, o.customer_id, o.order_total,
CASE WHEN o.order_date = f.first_order THEN 'New' ELSE 'Returning' END AS customer_type
FROM orders o
JOIN first_orders f ON o.customer_id = f.customer_id
)
SELECT customer_type, SUM(order_total) AS total_sales
FROM classified_orders
GROUP BY customer_type;
Why CTE helps: You clearly separate logic into order classification and summary aggregation.
Use Case 3: Recursive CTE for Hierarchies
Problem: Retrieve an entire management chain from an employee table.
WITH RECURSIVE org_chart AS (
SELECT employee_id, manager_id, name, 1 AS level
FROM employees
WHERE employee_id = 101 -- starting employee
UNION ALL
SELECT e.employee_id, e.manager_id, e.name, oc.level + 1
FROM employees e
JOIN org_chart oc ON e.employee_id = oc.manager_id
)
SELECT * FROM org_chart;
Why CTE helps: Recursive CTEs are ideal for organizational structures, product category trees, and dependency chains.
Use Case 4: Breaking Down Funnel Steps
Problem: Analyze conversion rates in a marketing funnel.
WITH pageviews AS (
SELECT user_id FROM events WHERE event_name = 'PageView'
),
signups AS (
SELECT user_id FROM events WHERE event_name = 'Signup'
),
purchases AS (
SELECT user_id FROM events WHERE event_name = 'Purchase'
)
SELECT
(SELECT COUNT(*) FROM pageviews) AS total_views,
(SELECT COUNT(*) FROM signups) AS total_signups,
(SELECT COUNT(*) FROM purchases) AS total_purchases;
Why CTE helps: Separate each funnel step as a clean building block, great for dashboards and presentations.
Use Case 5: Simplify Repetitive Joins
Problem: Join the same derived table to multiple other tables.
WITH sales_summary AS (
SELECT product_id, SUM(sales_amount) AS total_sales
FROM sales
GROUP BY product_id
)
SELECT p.product_name, s.total_sales
FROM products p
JOIN sales_summary s ON p.product_id = s.product_id;
Why CTE helps: You avoid repeating subqueries for common aggregations or filters.
CTE vs Subquery: What’s the Difference?
Feature | CTE | Subquery |
---|---|---|
Reusability | ✅ Named, reusable | ❌ One-time use only |
Readability | ✅ Better for complex logic | 😐 Harder to read/debug |
Nesting | ❌ Can’t be nested easily | ✅ Can nest deeply |
Recursion | ✅ Supported | ❌ Not supported |
Compatibility | ✅ Widely supported | ✅ Universal |
Best Practices When Using CTEs
- Keep each CTE focused on one task
- Avoid creating too many levels (no more than 3–4 chained CTEs)
- Comment each CTE with what it does
- Use CTEs for clarity, not for performance optimization (unless recursive)
- Use indexes on base tables for large CTEs to improve performance
✅ Summary
Concept | Why It Matters for Analysts |
---|---|
CTE Basics | Create temporary named query results |
Modularity | Write complex logic in readable blocks |
Real Use Cases | Customer revenue, funnel analysis, org charts |
Recursive CTE | Solve hierarchical problems elegantly |
Best Practices | Keep it clean, reusable, and documented |
Discover more from Daily BI Talks
Subscribe to get the latest posts sent to your email.