SQL course menu
Intermediate SQL Interview Questions and Answers
Intermediate SQL interview questions with worked answers: correlated subqueries, EXISTS vs IN, UNION vs UNION ALL, CASE, conditional aggregation, CTEs, views, indexes, and common query patterns.
On this page
- Concepts
- 1. What is a correlated subquery?
- 2. EXISTS vs IN: what is the difference?
- 3. UNION vs UNION ALL?
- 4. What is a CTE and why use one?
- 5. What is a view? Does it improve performance?
- 6. What is an index, and what is the trade-off?
- Queries
- 7. List every customer with their number of orders, including customers with none.
- 8. Find customers who have not ordered since 1 February 2026.
- 9. Show monthly revenue by region as columns (a pivot).
- 10. Find the most expensive product in each category.
- 11. Revenue and order count per customer city, including cities with no orders.
- 12. How many distinct customers ordered each month?
- Tips
These questions are typical for data analyst, BI, and backend developer interviews once the basics are covered. They test whether you can combine ideas: grouping with joins, subqueries with filters, and knowing which tool fits which problem.
The examples use the course's sample tables: employees, customers, orders and monthly_sales. See SQL Database Basics for their contents.
Concepts#
1. What is a correlated subquery?#
Show solution
A subquery that refers to a column of the outer query, so it is evaluated once per outer row. Example: employees who earn more than their own department's average:
SELECT name, department, salary
FROM employees e
WHERE salary > (
SELECT AVG(salary)
FROM employees e2
WHERE e2.department = e.department
);name | department | salary
------+-------------+-------
Meera | Engineering | 81000
Priya | Sales | 61000See Subqueries.
2. EXISTS vs IN: what is the difference?#
Show solution
Both filter rows based on another query. IN compares a value with a list; EXISTS checks whether a correlated subquery returns any row. The important practical difference is with negation: NOT IN returns no rows if the subquery yields a NULL, while NOT EXISTS is unaffected. Modern optimisers often execute IN and EXISTS similarly. See EXISTS and IN.
3. UNION vs UNION ALL?#
Show solution
Both stack the results of queries with the same number of columns. UNION removes duplicate rows, which requires extra work; UNION ALL keeps every row and is faster. Use UNION ALL unless you specifically need de-duplication, and always when the result will be counted or summed. See UNION ALL.
4. What is a CTE and why use one?#
Show solution
A Common Table Expression (WITH name AS (...)) is a named, temporary result used within one statement. It makes multi-step queries readable top to bottom, can be referenced more than once, and can be recursive for hierarchies. See CTEs.
5. What is a view? Does it improve performance?#
Show solution
A view is a saved query that can be queried like a table. It simplifies complex logic, standardises definitions, and can hide sensitive columns. A normal view does not store data or make queries faster; it re-runs its query every time. A materialized view stores the result and must be refreshed. See Views.
6. What is an index, and what is the trade-off?#
Show solution
An index is a sorted structure (usually a B-tree) that lets the database find rows without scanning the whole table. It speeds up filters, joins and sorts on the indexed columns, but slows down inserts, updates and deletes, and uses storage. Composite indexes are used from the leftmost column. See Indexes.
Queries#
7. List every customer with their number of orders, including customers with none.#
Show hint
LEFT JOIN from customers, and count a column from orders, not *.
Show solution
SELECT c.name, COUNT(o.order_id) AS orders
FROM customers c
LEFT JOIN orders o ON o.customer_id = c.customer_id
GROUP BY c.customer_id, c.name
ORDER BY orders DESC, c.name;name | orders
------+-------
Asha | 2
Ravi | 1
John | 0
Meera | 0COUNT(*) would report 1 for John and Meera, because the left join still produces a row for them.
8. Find customers who have not ordered since 1 February 2026.#
Show solution
SELECT c.name
FROM customers c
WHERE NOT EXISTS (
SELECT 1
FROM orders o
WHERE o.customer_id = c.customer_id
AND o.order_date >= '2026-02-01'
);name
-----
Meera
JohnAsha (order on 11 February) and Ravi (20 February) are excluded. This "inactive customers" pattern is common in retention analysis.
9. Show monthly revenue by region as columns (a pivot).#
Show hint
Conditional aggregation: SUM(CASE WHEN region = 'North' THEN revenue ELSE 0 END).
Show solution
SELECT month,
SUM(CASE WHEN region = 'North' THEN revenue ELSE 0 END) AS north,
SUM(CASE WHEN region = 'South' THEN revenue ELSE 0 END) AS south
FROM monthly_sales
GROUP BY month
ORDER BY month;month | north | south
--------+-------+------
2026-01 | 120 | 90
2026-02 | 150 | 90
2026-03 | 110 | 130
2026-04 | 170 | 125See CASE.
10. Find the most expensive product in each category.#
Show solution
With a correlated subquery:
SELECT p.category, p.name, p.price
FROM products p
WHERE p.price = (
SELECT MAX(p2.price)
FROM products p2
WHERE p2.category = p.category
)
ORDER BY p.category;category | name | price
------------+------------+------
Electronics | Laptop | 55000
Furniture | Desk Chair | 7000
Stationery | Notebook | 60Be ready to also write it with ROW_NUMBER() or RANK() (see Advanced SQL Questions), and to explain that this version returns every product tied for the top price.
11. Revenue and order count per customer city, including cities with no orders.#
Show solution
SELECT c.city,
COUNT(o.order_id) AS orders,
COALESCE(SUM(o.amount), 0) AS revenue
FROM customers c
LEFT JOIN orders o ON o.customer_id = c.customer_id
GROUP BY c.city
ORDER BY revenue DESC, c.city;city | orders | revenue
--------+--------+--------
Delhi | 1 | 560
Mumbai | 2 | 370
Chennai | 0 | 0
Pune | 0 | 012. How many distinct customers ordered each month?#
Show solution
-- SQLite; use DATE_TRUNC('month', order_date) in PostgreSQL
SELECT strftime('%Y-%m', order_date) AS month,
COUNT(DISTINCT customer_id) AS active_customers
FROM orders
GROUP BY month
ORDER BY month;month | active_customers
--------+-----------------
2026-01 | 1
2026-02 | 2
2026-03 | 1COUNT(DISTINCT ...) matters: a customer with two orders in a month is still one active customer.
Tips#
- When you write a join, say what one row of the result represents ("one row per order").
- After any join followed by
SUM, check for fan-out (rows multiplied by a one-to-many join). See Multiple JOINs. - Offer a second approach (subquery vs window function vs join). Interviewers love candidates who know trade-offs.
Next: Advanced SQL Questions.
Finished this lesson?