By Fatskills Exam Guides Team — the exam nerds behind 28,500+ quizzes and 2.1M practice questions across 500+ global exams.
(Correlated vs. Non-Correlated in SELECT, FROM, and WHERE)
Subqueries are SQL’s Swiss Army knife—they let you nest one query inside another to break down complex problems into smaller, manageable pieces. In data analysis, you’ll use them daily to: - Filter data dynamically (e.g., "Show me all customers who spent more than the average order value").- Join tables without explicit JOIN syntax (e.g., "Get product details for items in a specific category").- Calculate aggregates on the fly (e.g., "Compare each employee’s salary to their department’s average").
JOIN
Why this matters in production:- Legacy systems often rely on subqueries because they’re more readable than complex joins (or because the original dev didn’t know better).- Performance pitfalls lurk here: A poorly written subquery can turn a 1-second query into a 10-minute disaster.- Certifications (PL-300, Google Data Analytics, etc.) test this heavily—you’ll see questions like: "Which subquery type is most efficient for filtering rows based on a dynamic condition?"
Real-world scenario:You’re analyzing e-commerce data. Your boss asks: "Show me all orders where the customer’s lifetime value (LTV) is in the top 10% of all customers." You can’t answer this with a simple WHERE clause—you need a subquery to calculate the 90th percentile LTV first.
WHERE
sql SELECT customer_id, order_date FROM orders WHERE order_date > (SELECT MAX(order_date) FROM orders WHERE YEAR(order_date) = 2022);
sql SELECT e.name, e.salary FROM employees e WHERE e.salary > (SELECT AVG(salary) FROM employees WHERE department_id = e.department_id);
SELECT
sql SELECT product_name, price, (SELECT AVG(price) FROM products WHERE category_id = p.category_id) AS avg_category_price FROM products p;
FROM
sql SELECT c.customer_name, s.monthly_sales FROM customers c JOIN ( SELECT customer_id, SUM(amount) AS monthly_sales FROM orders WHERE order_date BETWEEN '2023-01-01' AND '2023-01-31' GROUP BY customer_id ) s ON c.customer_id = s.customer_id;
sql SELECT product_name FROM products WHERE product_id IN (SELECT product_id FROM order_items WHERE quantity > 10);
EXISTS
IN
Example: sql SELECT customer_name FROM customers c WHERE EXISTS (SELECT 1 FROM orders o WHERE o.customer_id = c.customer_id);
sql SELECT customer_name FROM customers c WHERE EXISTS (SELECT 1 FROM orders o WHERE o.customer_id = c.customer_id);
IN:
NULL
sql SELECT product_name FROM products WHERE category_id IN (1, 2, 3);
ANY
SOME
ALL
TRUE
Example: sql SELECT product_name FROM products WHERE price > ANY (SELECT price FROM products WHERE category_id = 5);
sql SELECT product_name FROM products WHERE price > ANY (SELECT price FROM products WHERE category_id = 5);
ALL:
sql SELECT customer_name FROM customers WHERE customer_id = ALL (SELECT customer_id FROM orders WHERE order_date > '2023-01-01');
customers
orders
order_items
"Show me all customers who placed orders in 2023, along with their total spend and how it compares to the average customer spend in their state."
SELECT state, AVG(total_spend) AS avg_state_spend FROM ( SELECT c.state, c.customer_id, SUM(o.amount) AS total_spend FROM customers c JOIN orders o ON c.customer_id = o.customer_id WHERE o.order_date BETWEEN '2023-01-01' AND '2023-12-31' GROUP BY c.state, c.customer_id ) customer_spend GROUP BY state;
Expected output:| state | avg_state_spend | |-------|-----------------| | CA | 1250.50 | | NY | 980.75 | | TX | 1100.00 |
SELECT c.customer_id, c.name, c.state, SUM(o.amount) AS total_spend, ( SELECT AVG(total_spend) FROM ( SELECT SUM(o2.amount) AS total_spend FROM orders o2 JOIN customers c2 ON o2.customer_id = c2.customer_id WHERE c2.state = c.state AND o2.order_date BETWEEN '2023-01-01' AND '2023-12-31' GROUP BY c2.customer_id ) state_spend ) AS avg_state_spend, CASE WHEN SUM(o.amount) > ( SELECT AVG(total_spend) FROM ( SELECT SUM(o2.amount) AS total_spend FROM orders o2 JOIN customers c2 ON o2.customer_id = c2.customer_id WHERE c2.state = c.state AND o2.order_date BETWEEN '2023-01-01' AND '2023-12-31' GROUP BY c2.customer_id ) state_spend ) THEN 'Above Average' ELSE 'Below Average' END AS spend_category FROM customers c JOIN orders o ON c.customer_id = o.customer_id WHERE o.order_date BETWEEN '2023-01-01' AND '2023-12-31' GROUP BY c.customer_id, c.name, c.state;
Expected output:| customer_id | name | state | total_spend | avg_state_spend | spend_category | |-------------|-----------|-------|-------------|-----------------|-----------------| | 1 | Alice | CA | 1500.00 | 1250.50 | Above Average | | 2 | Bob | NY | 800.00 | 980.75 | Below Average |
WITH state_avg AS ( SELECT c.state, AVG(SUM(o.amount)) AS avg_state_spend FROM customers c JOIN orders o ON c.customer_id = o.customer_id WHERE o.order_date BETWEEN '2023-01-01' AND '2023-12-31' GROUP BY c.state, c.customer_id GROUP BY c.state ) SELECT c.customer_id, c.name, c.state, SUM(o.amount) AS total_spend, s.avg_state_spend, CASE WHEN SUM(o.amount) > s.avg_state_spend THEN 'Above Average' ELSE 'Below Average' END AS spend_category FROM customers c JOIN orders o ON c.customer_id = o.customer_id JOIN state_avg s ON c.state = s.state WHERE o.order_date BETWEEN '2023-01-01' AND '2023-12-31' GROUP BY c.customer_id, c.name, c.state, s.avg_state_spend;
Why this works:- Replaced the correlated subquery with a CTE (Common Table Expression) for better readability and performance.- The database executes the CTE once, not per row.
LIMIT
TOP
WHERE product_id IN (SELECT product_id FROM top_products LIMIT 10)
sql SELECT customer_id, (SELECT MAX(order_date) FROM orders WHERE customer_id = c.customer_id) AS last_order_date FROM customers c;
WITH
sql SELECT * FROM ( SELECT customer_id, SUM(amount) AS total_spend FROM orders GROUP BY customer_id ) AS customer_totals;
sql -- Get the top 10% of customers by spend SELECT customer_id FROM customers WHERE total_spend > ( SELECT PERCENTILE_CONT(0.9) WITHIN GROUP (ORDER BY total_spend) FROM customer_spend );
SELECT *
GROUP BY
NOT IN
NOT EXISTS
WHERE column IS NOT NULL
Trap: Correlated subqueries are slower but sometimes necessary.
"What’s the difference between IN and EXISTS?"
Trap: IN fails with NULL values.
"Rewrite this query to avoid a correlated subquery:" sql SELECT name FROM employees e WHERE salary > (SELECT AVG(salary) FROM employees WHERE department_id = e.department_id);
sql SELECT name FROM employees e WHERE salary > (SELECT AVG(salary) FROM employees WHERE department_id = e.department_id);
Answer: sql WITH dept_avg AS ( SELECT department_id, AVG(salary) AS avg_salary FROM employees GROUP BY department_id ) SELECT e.name FROM employees e JOIN dept_avg d ON e.department_id = d.department_id WHERE e.salary > d.avg_salary;
sql WITH dept_avg AS ( SELECT department_id, AVG(salary) AS avg_salary FROM employees GROUP BY department_id ) SELECT e.name FROM employees e JOIN dept_avg d ON e.department_id = d.department_id WHERE e.salary > d.avg_salary;
"When would you use a subquery in FROM instead of a JOIN?"
OR
AND
"Find all products that have never been ordered." Tables:- products (product_id, product_name) - order_items (order_item_id, order_id, product_id)
products
SELECT product_id, product_name FROM products WHERE product_id NOT IN (SELECT product_id FROM order_items WHERE product_id IS NOT NULL);
OR (better, handles NULL):
SELECT product_id, product_name FROM products p WHERE NOT EXISTS (SELECT 1 FROM order_items o WHERE o.product_id = p.product_id);
Why it works:- NOT IN fails if order_items.product_id contains NULL.- NOT EXISTS is safer and often faster.
order_items.product_id
WHERE column IN (SELECT ...)
WHERE column = (SELECT ... WHERE outer.column = inner.column)
SELECT (SELECT ...) AS alias
FROM (SELECT ...) AS alias
WHERE EXISTS (SELECT 1 ...)
WHERE column > ANY (SELECT ...)
WHERE column > ALL (SELECT ...)
WHERE column NOT IN (SELECT ...)
WITH alias AS (SELECT ...) SELECT ... FROM alias
Join 4M+ learners. Unlock unlimited quizzes, wrong-answer tracking, flashcards + reminders, study guides, and 1-on-1 challenges.