MySQL in MySQL is best learned by connecting the rule to an order-management database. Start with the smallest SQL statement, observe the output, and then add one realistic constraint so the concept becomes practical.
The key habit for this lesson is to watch table rows, constraints, and indexes as it changes. That makes the topic easier to debug, easier to explain in interviews, and easier to use in real code without memorizing isolated syntax.
A subquery (also called an inner query or nested query) is a SELECT statement embedded inside another SQL statement. Subqueries can appear in the WHERE, FROM, SELECT, and HAVING clauses. They are categorized as scalar (returns one value), row (returns one row), or table (returns multiple rows/columns) subqueries.
-- Scalar subquery: returns a single value
-- Find products priced above the average price
SELECT name, price
FROM products
WHERE price > (SELECT AVG(price) FROM products)
ORDER BY price DESC;
-- IN with subquery: find customers who have placed at least one order
SELECT customer_id, first_name, last_name
FROM customers
WHERE customer_id IN (
SELECT DISTINCT customer_id FROM orders
);
-- NOT IN with subquery: find customers who have NEVER placed an order
SELECT customer_id, first_name, last_name
FROM customers
WHERE customer_id NOT IN (
SELECT DISTINCT customer_id FROM orders WHERE customer_id IS NOT NULL
);
-- Subquery with comparison: find the most expensive product
SELECT name, price
FROM products
WHERE price = (SELECT MAX(price) FROM products);
A subquery in the FROM clause creates a temporary result set called a derived table. It must be given an alias.
-- Derived table: customers with their order count and total spent
SELECT
d.customer_id,
d.order_count,
d.total_spent,
CONCAT(c.first_name, ' ', c.last_name) AS customer_name
FROM (
SELECT
customer_id,
COUNT(*) AS order_count,
SUM(total) AS total_spent
FROM orders
GROUP BY customer_id
) AS d
INNER JOIN customers c ON d.customer_id = c.customer_id
WHERE d.order_count >= 2
ORDER BY d.total_spent DESC;
-- Subquery in SELECT clause (scalar subquery per row)
-- Show each product with the count of times it has been ordered
SELECT
p.product_id,
p.name,
p.price,
(
SELECT SUM(oi.quantity)
FROM order_items oi
WHERE oi.product_id = p.product_id
) AS total_ordered
FROM products p
ORDER BY total_ordered DESC;
A correlated subquery references columns from the outer query - it's re-executed for each row of the outer query. EXISTS returns TRUE if the subquery returns at least one row, and is often more efficient than IN for large datasets.
-- EXISTS: find customers who have at least one delivered order
SELECT c.customer_id, c.first_name, c.last_name
FROM customers c
WHERE EXISTS (
SELECT 1
FROM orders o
WHERE o.customer_id = c.customer_id -- correlated: references outer query
AND o.status = 'delivered'
);
-- NOT EXISTS: find customers with no orders at all
SELECT c.customer_id, c.first_name, c.last_name
FROM customers c
WHERE NOT EXISTS (
SELECT 1 FROM orders o WHERE o.customer_id = c.customer_id
);
-- Correlated subquery: find each customer's most recent order
SELECT
c.customer_id,
c.first_name,
o.order_id,
o.total,
o.ordered_at
FROM customers c
INNER JOIN orders o ON c.customer_id = o.customer_id
WHERE o.ordered_at = (
SELECT MAX(o2.ordered_at)
FROM orders o2
WHERE o2.customer_id = c.customer_id -- correlated
);
A CTE (introduced with the WITH clause in MySQL 8.0) is a named temporary result set that exists only for the duration of a single query. CTEs make complex queries more readable and can be referenced multiple times in the same query. Recursive CTEs can traverse hierarchical data.
-- Simple CTE: customer order summary
WITH customer_summary AS (
SELECT
customer_id,
COUNT(*) AS order_count,
SUM(total) AS total_spent
FROM orders
GROUP BY customer_id
)
SELECT
c.first_name,
c.last_name,
cs.order_count,
cs.total_spent
FROM customers c
INNER JOIN customer_summary cs ON c.customer_id = cs.customer_id
WHERE cs.total_spent > 100
ORDER BY cs.total_spent DESC;
-- Multiple CTEs in one query
WITH
top_products AS (
SELECT product_id, SUM(quantity) AS total_sold
FROM order_items
GROUP BY product_id
ORDER BY total_sold DESC
LIMIT 3
),
product_revenue AS (
SELECT product_id, SUM(quantity * unit_price) AS revenue
FROM order_items
GROUP BY product_id
)
SELECT
p.name,
tp.total_sold,
pr.revenue
FROM products p
INNER JOIN top_products tp ON p.product_id = tp.product_id
INNER JOIN product_revenue pr ON p.product_id = pr.product_id
ORDER BY tp.total_sold DESC;
Use MySQL when the program needs a clear answer to a specific problem, not because the keyword looks familiar. In a real MySQL task, first name the input, then name the transformation, then name the output. This small discipline shows whether the topic is being used correctly or only copied from an example.
A reliable practice flow is: create the smallest working SQL statement, add one normal case, add one edge case such as missing, repeated, empty, or boundary input, and then confirm the result with result set and EXPLAIN output. If the result surprises you, reduce the code until the behavior is visible again.
The most common trap here is copying the syntax before understanding the behavior. Avoid it by writing one sentence before the code that explains why MySQL is the right choice. After the code runs, verify the lesson by doing this: change one input and explain the changed output.
Copying the syntax before understanding the behavior.
Write the expected behavior first, then make the example prove it.
Practicing only the perfect input.
Also test missing, repeated, empty, or boundary input before considering the lesson complete.
Looking only at the final output.
Trace table rows, constraints, and indexes through each important step.
Use it when the problem matches the behavior shown in the example and when the result can be verified through result set and EXPLAIN output.
Start with a tiny case, then test missing, repeated, empty, or boundary input. The main warning sign is copying the syntax before understanding the behavior.
Trace table rows, constraints, and indexes, predict the result, run the example, and compare your prediction with the actual output.
Explore 500+ free tutorials across 20+ languages and frameworks.