ποΈ SQL Β· Topic 15 of 20
Subqueries & CTEs β SQL Questions with Answers
30 practice questions on Subqueries & CTEs, each with the correct answer and a clear explanation of why it is right. Read through them as revision notes, or take this topic as an interactive quiz to test yourself.
1 easy Β· 14 medium Β· 15 hard Β· Part of SQL: Databases from basics to window functions - 20 topics.
Medium questions (14)
Q2. medium
What is a scalar subquery?
SELECT name, (SELECT AVG(marks) FROM students) AS class_avg FROM students;
- A A subquery that returns exactly ONE value (one row, one column)
- B A subquery returning many rows
- C A subquery with no result
- D A subquery in FROM
Answer: A β A subquery that returns exactly ONE value (one row, one column)
Scalar subqueries can be used anywhere a single value is expected, such as = comparisons or the SELECT list. Returning more than one row causes an error.
Scalar subquery
Q3. medium
What does this find?
SELECT name FROM products WHERE price > (SELECT AVG(price) FROM products);
- A Products priced above the overall average
- B The most expensive product
- C All products
- D An error
Answer: A β Products priced above the overall average
The subquery computes one average value, and the outer query compares each product to it. A classic 'above average' pattern that a single query cannot express with an aggregate in WHERE.
Scalar subqueryAVG
Q6. medium
What does EXISTS with a correlated subquery test?
SELECT c.name FROM customers c
WHERE EXISTS (SELECT 1 FROM orders o WHERE o.customer_id = c.id);
- A Whether at least one matching row exists for the current outer row - it stops at the first match
- B The count of rows
- C Whether a table exists
- D Nothing
Answer: A β Whether at least one matching row exists for the current outer row - it stops at the first match
EXISTS is a semi-join: it returns the outer row if the subquery finds anything. It is efficient because it short-circuits and does not care what the subquery selects.
EXISTSSemi-join
Q8. medium
What does a subquery in the FROM clause create?
SELECT dept, avg_sal FROM (
SELECT dept, AVG(salary) AS avg_sal FROM employees GROUP BY dept
) t WHERE avg_sal > 50000;
- A A derived table (inline view) that the outer query treats as a regular table
- B A permanent table
- C An index
- D A join
Answer: A β A derived table (inline view) that the outer query treats as a regular table
A derived table must be given an alias. It is handy for pre-aggregating or filtering before the outer query works on the result.
Derived tableInline view
Q9. medium
What is a CTE (Common Table Expression)?
WITH dept_avg AS (
SELECT dept, AVG(salary) AS avg_sal FROM employees GROUP BY dept
)
SELECT * FROM dept_avg WHERE avg_sal > 50000;
- A A named temporary result defined with WITH, usable in the query that follows
- B A permanent table
- C A type of index
- D A backup
Answer: A β A named temporary result defined with WITH, usable in the query that follows
CTEs improve readability by naming and separating steps. They exist only for the duration of the query, acting like a labelled subquery you can reference by name.
CTEWITH
Q10. medium
What is the main advantage of a CTE over a nested subquery?
- A Readability - complex logic is broken into named, top-to-bottom steps
- B It is always faster
- C It uses less storage
- D It prevents errors
Answer: A β Readability - complex logic is broken into named, top-to-bottom steps
Deeply nested subqueries are hard to follow. CTEs read like a sequence of named steps, and one CTE can be referenced multiple times in the same query.
CTEReadability
Q14. medium
What does this scalar subquery in SELECT do?
SELECT name, (SELECT COUNT(*) FROM orders o WHERE o.customer_id = c.id) AS order_count FROM customers c;
- A Shows each customer with their order count via a correlated subquery
- B Counts all orders once
- C Returns an error
- D Lists all orders
Answer: A β Shows each customer with their order count via a correlated subquery
The correlated subquery runs per customer, counting their orders. A LEFT JOIN with GROUP BY often does this more efficiently, but the subquery form is readable.
Correlated subquerySELECT
Q16. medium
Where can subqueries appear in a statement?
- A In SELECT, FROM, WHERE, HAVING - and even in INSERT/UPDATE/DELETE
- B Only in WHERE
- C Only in FROM
- D Only in SELECT
Answer: A β In SELECT, FROM, WHERE, HAVING - and even in INSERT/UPDATE/DELETE
Subqueries are versatile. A scalar one in SELECT computes a value per row; one in FROM is a derived table; one in WHERE filters; and they drive many INSERT ... SELECT operations.
SubqueriesPlacement
Q18. medium
Are CTEs materialised (computed once) or inlined?
- A It depends on the database - some materialise them, others inline them into the main query
- B Always materialised
- C Always inlined
- D They are never computed
Answer: A β It depends on the database - some materialise them, others inline them into the main query
PostgreSQL historically materialised CTEs (an optimisation fence) but now can inline them; MySQL and SQL Server generally inline. Behaviour affects performance, so test on your platform.
CTEOptimisation
Q20. medium
What must a derived table (FROM subquery) always have?
- A An ALIAS - a name by which the outer query refers to it
- B An index
- C A primary key
- D A WHERE clause
Answer: A β An ALIAS - a name by which the outer query refers to it
Most databases require an alias for a subquery in FROM, e.g. ) AS t. Without it you get a syntax error.
Derived tableAlias
Q23. medium
Can a CTE be referenced more than once in the main query?
- A Yes - define it once with WITH and use its name multiple times
- B No, only once
- C Only in subqueries
- D Only with UNION
Answer: A β Yes - define it once with WITH and use its name multiple times
This is a readability win over subqueries: a self join on a computed set can reference the same CTE twice by name instead of repeating the subquery.
CTEReuse
Q25. medium
How do you filter a GROUP BY result using a subquery threshold?
- A Compute the threshold in a subquery and compare in HAVING (or wrap in a CTE and filter outside)
- B Use WHERE with the aggregate
- C It cannot be done
- D Use ORDER BY
Answer: A β Compute the threshold in a subquery and compare in HAVING (or wrap in a CTE and filter outside)
For example, HAVING SUM(total) > (SELECT AVG(order_total) FROM ...). A CTE that pre-aggregates then a plain WHERE is often clearer.
HAVINGSubquery
Q27. medium
What does WHERE col = (SELECT MAX(col) FROM t) return?
- A The row(s) holding the maximum value - including ties
- B Only one row always
- C All rows
- D An error
Answer: A β The row(s) holding the maximum value - including ties
Unlike ORDER BY ... LIMIT 1, comparing to the MAX subquery returns every tied maximum. Useful when ties matter.
SubqueryMAXTies
Q29. medium
Does a subquery in SELECT run for every output row?
- A A correlated one does; a non-correlated scalar subquery is typically evaluated once and reused
- B Always once
- C Always per row
- D It never runs
Answer: A β A correlated one does; a non-correlated scalar subquery is typically evaluated once and reused
Correlation is the key: if the subquery references the outer row it recomputes per row; if it is constant, the optimiser can compute it a single time.
Correlated subqueryPerformance
Hard questions (15)
Q4. hard
What is a correlated subquery?
SELECT e.name FROM employees e
WHERE e.salary > (SELECT AVG(salary) FROM employees WHERE dept = e.dept);
- A A subquery that REFERENCES a column from the outer query, so it re-runs for each outer row
- B A subquery that runs once
- C A subquery with no WHERE
- D Two unrelated queries
Answer: A β A subquery that REFERENCES a column from the outer query, so it re-runs for each outer row
Correlated subqueries depend on the current outer row, executing repeatedly. Powerful but potentially slow - often rewritable as a join or window function.
Correlated subqueryPerformance
Q5. hard
How does a correlated subquery differ from a regular (non-correlated) one in execution?
- A A non-correlated subquery runs ONCE; a correlated one runs for EACH outer row
- B They run identically
- C Correlated runs once
- D Non-correlated never runs
Answer: A β A non-correlated subquery runs ONCE; a correlated one runs for EACH outer row
Because it references the outer row, the correlated subquery is conceptually re-evaluated per row. Optimisers can sometimes transform it into a join, but be mindful of the performance model.
Correlated subqueryExecution
Q7. hard
Why is EXISTS often preferred over IN for large subqueries?
- A EXISTS can stop at the first match and handles NULLs safely; IN may materialise the whole list and mishandle NULLs
- B IN is always faster
- C They are identical
- D EXISTS is invalid
Answer: A β EXISTS can stop at the first match and handles NULLs safely; IN may materialise the whole list and mishandle NULLs
IN with a subquery containing NULL can behave unexpectedly (especially NOT IN). EXISTS/NOT EXISTS avoid the NULL trap and often perform better on big data.
EXISTSINNULL
Q11. hard
Can you define MULTIPLE CTEs in one query?
WITH sales AS (...),
ranked AS (SELECT *, RANK() OVER (...) FROM sales)
SELECT * FROM ranked WHERE rnk <= 3;
- A Yes - separate them with commas after a single WITH, and later CTEs can reference earlier ones
- B No, only one
- C Only two
- D Only with UNION
Answer: A β Yes - separate them with commas after a single WITH, and later CTEs can reference earlier ones
Chaining CTEs builds a readable pipeline: WITH a AS (...), b AS (SELECT ... FROM a) SELECT ... FROM b. Each step names an intermediate result.
CTEChaining
Q12. hard
What is a RECURSIVE CTE used for?
WITH RECURSIVE nums AS (
SELECT 1 AS n
UNION ALL
SELECT n + 1 FROM nums WHERE n < 10
)
SELECT * FROM nums;
- A Traversing hierarchies or generating sequences - it references ITSELF until a stopping condition
- B Nothing special
- C Faster joins
- D Sorting data
Answer: A β Traversing hierarchies or generating sequences - it references ITSELF until a stopping condition
WITH RECURSIVE has an anchor (starting rows) and a recursive part that repeatedly joins back to the CTE - ideal for org charts, category trees and number series.
Recursive CTEHierarchies
Q13. hard
What are the two parts of a recursive CTE?
- A The anchor member (base rows) and the recursive member (which references the CTE), joined by UNION ALL
- B SELECT and FROM
- C WHERE and HAVING
- D Two subqueries
Answer: A β The anchor member (base rows) and the recursive member (which references the CTE), joined by UNION ALL
The anchor runs once to seed the result; the recursive member runs repeatedly, each time using the previous iteration's rows, until it produces no new rows.
Recursive CTEStructure
Q15. hard
How do you find the customer(s) with the maximum total spend?
WITH t AS (SELECT customer_id, SUM(total) s FROM orders GROUP BY 1)
SELECT * FROM t WHERE s = (SELECT MAX(s) FROM t);
- A Use a subquery for the max: WHERE total = (SELECT MAX(total) FROM ...) after aggregating
- B ORDER BY total LIMIT 1 always suffices
- C MAX(customer)
- D It is impossible
Answer: A β Use a subquery for the max: WHERE total = (SELECT MAX(total) FROM ...) after aggregating
Comparing to a MAX subquery returns ALL tied top spenders, whereas LIMIT 1 arbitrarily picks one. A CTE that computes totals first makes this clean.
SubqueryTies
Q17. hard
What does WHERE col IN (SELECT ...) do, and its risk?
- A Keeps rows whose col matches any value the subquery returns; risky as NOT IN with a NULL returns no rows
- B It always works safely
- C It joins tables
- D It sorts rows
Answer: A β Keeps rows whose col matches any value the subquery returns; risky as NOT IN with a NULL returns no rows
IN is fine, but its negation NOT IN silently returns nothing if the subquery yields a NULL. Prefer NOT EXISTS for anti-membership tests.
INNOT INNULL
Q19. hard
How can a correlated subquery often be rewritten for speed?
- A As a JOIN or a window function that computes the value once per group instead of per row
- B It cannot be rewritten
- C As a CROSS JOIN
- D As a UNION
Answer: A β As a JOIN or a window function that computes the value once per group instead of per row
For example, 'salary above department average' via a correlated subquery can become AVG(salary) OVER (PARTITION BY dept) - one pass instead of many.
Window functionsPerformance
Q21. hard
What does this recursive CTE compute?
WITH RECURSIVE t AS (SELECT 1 n UNION ALL SELECT n*2 FROM t WHERE n < 100) SELECT * FROM t;
- A Powers of 2 up to 128 (1, 2, 4, ... doubling until n >= 100)
- B Numbers 1 to 100
- C An infinite loop
- D An error
Answer: A β Powers of 2 up to 128 (1, 2, 4, ... doubling until n >= 100)
The anchor seeds 1; each step doubles the previous value while it is under 100, yielding 1, 2, 4, 8, 16, 32, 64, 128 (128 is produced from 64, which was < 100).
Recursive CTESequences
Q22. hard
What prevents a recursive CTE from looping forever?
- A A termination condition in the recursive member's WHERE - and databases also enforce a max recursion depth
- B Nothing - they always stop
- C The LIMIT clause only
- D They cannot loop
Answer: A β A termination condition in the recursive member's WHERE - and databases also enforce a max recursion depth
You must ensure the recursive part eventually produces no rows. Cyclic data (A manages B manages A) can loop, so many databases cap recursion depth as a safety net.
Recursive CTETermination
Q24. hard
What does a subquery returning multiple rows require in a comparison?
- A An operator like IN, ANY or ALL - a bare = expects a single value and errors
- B Nothing special
- C Only =
- D A join
Answer: A β An operator like IN, ANY or ALL - a bare = expects a single value and errors
= (SELECT ...) fails if the subquery returns many rows. Use IN for membership, or > ANY / > ALL for range comparisons against the set.
INANYALL
Q26. hard
When would you choose a CTE over a temporary table?
- A For a single query's readability without persistence; temp tables suit reuse across multiple statements or very large intermediate data
- B CTEs are always better
- C Temp tables cannot be indexed
- D They are identical
Answer: A β For a single query's readability without persistence; temp tables suit reuse across multiple statements or very large intermediate data
A CTE lives only within one statement. If you need the intermediate result across several queries, or want to index it, a temporary table is the better tool.
CTETemp tables
Q28. hard
What is a common readability pattern for a multi-step analysis?
- A Chained CTEs: filter in one, aggregate in the next, rank in the next, then SELECT the final result
- B One giant nested subquery
- C Many separate queries copied by hand
- D CROSS JOINs
Answer: A β Chained CTEs: filter in one, aggregate in the next, rank in the next, then SELECT the final result
Building a pipeline of named CTEs mirrors how you think about the problem and makes each step testable. It is the modern idiom for complex analytical SQL.
CTEPipelines
Q30. hard
How do you find the second-highest salary using a subquery?
- A SELECT MAX(salary) FROM emp WHERE salary < (SELECT MAX(salary) FROM emp)
- B SELECT MAX(salary) FROM emp
- C ORDER BY salary LIMIT 2
- D It is impossible
Answer: A β SELECT MAX(salary) FROM emp WHERE salary < (SELECT MAX(salary) FROM emp)
The subquery finds the top salary; the outer MAX finds the highest below it - the second highest. A famous interview question; window functions (DENSE_RANK) generalise it to Nth.
SubqueryNth highestInterview