Using multiple CTEs helps break down complex queries into smaller, easy parts. It makes your query clearer and easier to understand.
When you want to organize your query into steps for better clarity.
When you need to reuse intermediate results multiple times in one query.
When you want to separate different calculations or filters before the final result.
When you want to improve readability of a long query by naming parts.
When you want to avoid repeating the same subquery multiple times.
Syntax
SQL
WITH cte1 AS (
-- first query here
),
cte2 AS (
-- second query here
)
SELECT * FROM cte1 JOIN cte2 ON cte1.id = cte2.id;
Each CTE is separated by a comma.
You write all CTEs after WITH and before the main SELECT.
Examples
This example uses two CTEs to get employee names and their salaries, then joins them.
SQL
WITH first_cte AS (
SELECT id, name FROM employees
),
second_cte AS (
SELECT id, salary FROM salaries
)
SELECT first_cte.name, second_cte.salary
FROM first_cte
JOIN second_cte ON first_cte.id = second_cte.id;
This example calculates total sales per product and then joins with product names.
SQL
WITH sales_cte AS (
SELECT product_id, SUM(amount) AS total_sales FROM sales GROUPBY product_id
),
products_cte AS (
SELECT id, product_name FROM products
)
SELECT products_cte.product_name, sales_cte.total_sales
FROM sales_cte
JOIN products_cte ON sales_cte.product_id = products_cte.id;
Sample Program
This query uses two CTEs: one to find employees with salary over 50000, and another to count employees per department. Then it joins them to show high earners with their department size.
SQL
WITH
high_salary AS (
SELECT id, name, salary, department_id FROM employees WHERE salary > 50000
),
department_count AS (
SELECT department_id, COUNT(*) AS num_employees FROM employees GROUPBY department_id
)
SELECT h.name, h.salary, d.num_employees
FROM high_salary h
JOIN department_count d ON h.department_id = d.department_id
ORDERBY h.salary DESC;
OutputSuccess
Important Notes
Make sure the columns you join on exist and match in both CTEs.
CTEs are temporary and only exist during the query execution.
You can have as many CTEs as you want, but keep queries readable.
Summary
Multiple CTEs let you split a query into named parts for clarity.
Separate each CTE with a comma after WITH.
Use CTEs to organize, reuse, and simplify complex queries.
Practice
(1/5)
1. What is the main purpose of using multiple CTEs (Common Table Expressions) in a single SQL query?
easy
A. To automatically create indexes on the tables used
B. To break down complex queries into simpler, named parts for better readability
C. To permanently store intermediate results in the database
D. To speed up the database engine by running queries in parallel
Solution
Step 1: Understand the role of CTEs
CTEs are temporary named result sets used inside a query to organize complex logic.
Step 2: Identify the benefit of multiple CTEs
Using multiple CTEs lets you split a big query into smaller, readable parts without permanent storage or indexing.
Final Answer:
To break down complex queries into simpler, named parts for better readability -> Option B
Quick Check:
Multiple CTEs improve query clarity = D [OK]
Hint: Multiple CTEs split queries into named parts for clarity [OK]
Common Mistakes:
Thinking CTEs create permanent tables
Assuming CTEs improve query speed automatically
Confusing CTEs with indexes
2. Which of the following is the correct syntax to define two CTEs named cte1 and cte2 in one SQL query?
easy
A. WITH cte1 AS (SELECT * FROM table1), cte2 AS (SELECT * FROM table2) SELECT * FROM cte1 JOIN cte2 ON cte1.id = cte2.id;
B. WITH cte1 AS (SELECT * FROM table1) cte2 AS (SELECT * FROM table2) SELECT * FROM cte1 JOIN cte2 ON cte1.id = cte2.id;
C. WITH cte1 (SELECT * FROM table1); WITH cte2 (SELECT * FROM table2); SELECT * FROM cte1 JOIN cte2 ON cte1.id = cte2.id;
D. WITH cte1 = (SELECT * FROM table1), cte2 = (SELECT * FROM table2) SELECT * FROM cte1 JOIN cte2 ON cte1.id = cte2.id;
Solution
Step 1: Recall correct CTE syntax
Multiple CTEs are separated by commas after WITH, each defined as name AS (query).
Step 2: Check each option
WITH cte1 AS (SELECT * FROM table1), cte2 AS (SELECT * FROM table2) SELECT * FROM cte1 JOIN cte2 ON cte1.id = cte2.id; correctly uses commas and AS keyword; others miss commas or use wrong syntax.
Final Answer:
WITH cte1 AS (SELECT * FROM table1), cte2 AS (SELECT * FROM table2) SELECT * FROM cte1 JOIN cte2 ON cte1.id = cte2.id; -> Option A
Quick Check:
Multiple CTEs separated by commas = B [OK]
Hint: Separate multiple CTEs with commas after WITH [OK]
Common Mistakes:
Omitting commas between CTEs
Using semicolons between CTEs
Using equals sign instead of AS
3. Given the following SQL query with multiple CTEs, what will be the output?
WITH cte1 AS (SELECT 1 AS num UNION ALL SELECT 2), cte2 AS (SELECT num FROM cte1 WHERE num = 2) SELECT * FROM cte2;
medium
A. 1 and 2
B. 1
C. 2
D. No rows returned
Solution
Step 1: Analyze cte1 content
cte1 returns two rows with num values 1 and 2.
Step 2: Analyze cte2 filtering
cte2 selects only rows from cte1 where num = 2, so only one row with num=2.
Final Answer:
2 -> Option C
Quick Check:
cte2 filters num=2 = A [OK]
Hint: Trace each CTE step-by-step to find final output [OK]
Common Mistakes:
Assuming cte2 returns all rows from cte1
Confusing UNION ALL with UNION
Ignoring the WHERE filter in cte2
4. Identify the error in the following SQL query using multiple CTEs:
WITH cte1 AS (SELECT id, name FROM users) SELECT * FROM cte1; WITH cte2 AS (SELECT id FROM orders) SELECT * FROM cte2;
medium
A. CTEs cannot select from tables
B. CTEs must be named with different names
C. Missing JOIN condition between cte1 and cte2
D. Multiple WITH clauses are not allowed in one query
Solution
Step 1: Review CTE syntax rules
Only one WITH clause is allowed per query; multiple CTEs must be separated by commas inside one WITH.
Step 2: Identify the error in the query
The query uses two separate WITH clauses, which is invalid syntax.
Final Answer:
Multiple WITH clauses are not allowed in one query -> Option D
Quick Check:
Only one WITH clause per query = A [OK]
Hint: Use one WITH clause with commas for multiple CTEs [OK]
Common Mistakes:
Writing multiple WITH clauses in one query
Forgetting commas between CTEs
Assuming CTEs create permanent tables
5. You want to calculate the average order amount per customer and then find customers whose average order is above 100. Which of the following queries correctly uses multiple CTEs to achieve this?
hard
A. WITH avg_orders AS (SELECT customer_id, AVG(amount) AS avg_amount FROM orders GROUP BY customer_id), high_avg AS (SELECT customer_id FROM avg_orders WHERE avg_amount > 100) SELECT * FROM high_avg;
B. WITH avg_orders AS (SELECT customer_id, AVG(amount) AS avg_amount FROM orders), high_avg AS (SELECT customer_id FROM avg_orders WHERE avg_amount > 100) SELECT * FROM high_avg;
C. WITH avg_orders AS (SELECT customer_id, amount FROM orders GROUP BY customer_id), high_avg AS (SELECT customer_id FROM avg_orders WHERE amount > 100) SELECT * FROM high_avg;
D. WITH avg_orders AS (SELECT customer_id, AVG(amount) AS avg_amount FROM orders GROUP BY customer_id) SELECT * FROM avg_orders WHERE avg_amount > 100;
Solution
Step 1: Calculate average order per customer
WITH avg_orders AS (SELECT customer_id, AVG(amount) AS avg_amount FROM orders GROUP BY customer_id), high_avg AS (SELECT customer_id FROM avg_orders WHERE avg_amount > 100) SELECT * FROM high_avg; correctly groups orders by customer_id and calculates AVG(amount).
Step 2: Filter customers with avg_amount > 100
WITH avg_orders AS (SELECT customer_id, AVG(amount) AS avg_amount FROM orders GROUP BY customer_id), high_avg AS (SELECT customer_id FROM avg_orders WHERE avg_amount > 100) SELECT * FROM high_avg; uses a second CTE to filter customers with average above 100, then selects from it.
Final Answer:
WITH avg_orders AS (SELECT customer_id, AVG(amount) AS avg_amount FROM orders GROUP BY customer_id), high_avg AS (SELECT customer_id FROM avg_orders WHERE avg_amount > 100) SELECT * FROM high_avg; -> Option A
Quick Check:
Multiple CTEs for calculation and filtering = C [OK]
Hint: Use one CTE to calculate, second to filter results [OK]
Common Mistakes:
Missing GROUP BY when calculating averages
Not filtering on aggregated column
Incorrect CTE definitions without commas
Recommended
Ready to prove it?
Pass the test and this pattern moves unknownstrongwhile earning +1 Grip toward your certificate.