SQL practice questions with answers
Practise SQL and MySQL questions at your own pace: joins, GROUP BY, NULLs, subqueries, window functions and indexes. Pick an answer to see whether you are right and why. Nothing is timed or ranked here; for that, play the Daily Quiz at 4 PM IST.
Which clause sorts the result?
easy
- GROUP BY
- ORDER BY
- SORT BY
- HAVING
Answer: B. ORDER BY
Explanation: ORDER BY col ASC (the default) or DESC. GROUP BY groups rows for aggregates; it does not guarantee any order.
What does this return?
easy
-- orders(id, customer, amount): -- (1, Asha, 500) (2, Ravi, 300) (3, Asha, 200) (4, Meera, NULL) SELECT COUNT(DISTINCT customer) FROM orders;- 4
- 3
- 2
- NULL
Answer: B. 3
Explanation: Asha appears twice, so there are 3 distinct customers: Asha, Ravi and Meera.
Which keyword limits the number of rows returned in MySQL?
easy
- TOP
- LIMIT
- FIRST
- ROWNUM
Answer: B. LIMIT
Explanation: MySQL and PostgreSQL use LIMIT n (with OFFSET for paging). SQL Server uses TOP; Oracle uses FETCH FIRST or ROWNUM.
Which statement changes existing rows?
easy
- INSERT
- UPDATE
- ALTER
- SELECT
Answer: B. UPDATE
Explanation: UPDATE changes data in rows; ALTER changes the table structure. Always check the WHERE clause before running an UPDATE.
What does this return?
medium
-- orders(id, customer, amount): -- (1, Asha, 500) (2, Ravi, 300) (3, Asha, 200) (4, Meera, NULL) SELECT customer, SUM(amount) AS total FROM orders GROUP BY customer ORDER BY total DESC LIMIT 1;- Asha 700
- Asha 500
- Ravi 300
- Meera NULL
Answer: A. Asha 700
Explanation: Asha: 500 + 200 = 700, Ravi 300, Meera NULL (SUM of only NULLs is NULL). In MySQL NULLs sort last in DESC order, so Asha is first.
What does this return?
medium
-- orders(id, customer, amount): -- (1, Asha, 500) (2, Ravi, 300) (3, Asha, 200) (4, Meera, NULL) SELECT COUNT(*) FROM orders WHERE amount > 250 OR amount IS NULL;- 2
- 3
- 4
- 1
Answer: B. 3
Explanation: 500 and 300 are above 250, and Meera's NULL matches IS NULL: 3 rows. A plain amount > 250 alone would skip the NULL row.
Which query lists customers who have never placed an order?
medium
- SELECT c.name FROM customers c LEFT JOIN orders o ON o.customer_id = c.id WHERE o.id IS NULL
- SELECT c.name FROM customers c JOIN orders o ON o.customer_id = c.id
- SELECT name FROM customers WHERE id = NULL
- SELECT name FROM orders WHERE amount = 0
Answer: A. SELECT c.name FROM customers c LEFT JOIN orders o ON o.customer_id = c.id WHERE o.id IS NULL
Explanation: An anti-join: the LEFT JOIN keeps every customer, and customers with no matching order have NULLs on the orders side. NOT EXISTS works too.
What is a correlated subquery?
medium
- A subquery in the FROM clause
- A subquery that refers to columns of the outer query, so it is evaluated per outer row
- A subquery that returns one value
- A JOIN written in reverse
Answer: B. A subquery that refers to columns of the outer query, so it is evaluated per outer row
Explanation: For example, WHERE salary > (SELECT AVG(salary) FROM employees e2 WHERE e2.dept = e1.dept). It is often rewritten as a join or a window function.
What does LAG(amount) OVER (ORDER BY id) return for the first row?
hard
- 0
- NULL
- The first row's amount
- An error
Answer: B. NULL
Explanation: LAG reads the previous row; the first row has none, so NULL (or the default you pass as the third argument). LEAD reads the next row.
Which query finds each customer's largest order, ties included?
hard
- SELECT * FROM (SELECT *, DENSE_RANK() OVER (PARTITION BY customer ORDER BY amount DESC) r FROM orders) t WHERE r = 1
- SELECT customer, MAX(id) FROM orders GROUP BY customer
- SELECT * FROM orders ORDER BY amount DESC LIMIT 1
- SELECT DISTINCT customer, amount FROM orders
Answer: A. SELECT * FROM (SELECT *, DENSE_RANK() OVER (PARTITION BY customer ORDER BY amount DESC) r FROM orders) t WHERE r = 1
Explanation: Partitioning ranks orders within each customer. MAX(id) finds the latest order, not the largest.
EXPLAIN shows "type: ALL" for a query on a 10-million-row table. What does it mean?
hard
- All indexes are used
- A full table scan: every row is read, usually because no usable index exists for the filter
- The query returns all rows
- The query is cached
Answer: B. A full table scan: every row is read, usually because no usable index exists for the filter
Explanation: Add an index on the filtered column, and avoid wrapping that column in a function in the WHERE clause.
Which make an index unusable for a WHERE filter on the column? (Select all that apply)
hard
- WHERE YEAR(created_at) = 2026
- WHERE name LIKE '%kumar'
- WHERE created_at >= '2026-01-01'
- WHERE CAST(phone AS UNSIGNED) = 98765
Answer: A. WHERE YEAR(created_at) = 2026; B. WHERE name LIKE '%kumar'; D. WHERE CAST(phone AS UNSIGNED) = 98765
Explanation: Functions on the column and leading wildcards hide the sorted order the index relies on. A plain range comparison can use it.
From past Daily Quizzes
Which clause filters groups after GROUP BY?
SQL · easy
- WHERE
- HAVING
- ORDER BY
- LIMIT
Answer: B. HAVING
Explanation: WHERE filters rows before grouping; HAVING filters the groups, so it can use aggregates like COUNT(*) > 5.
What does the running total return for the third row?
SQL · hard
SELECT d, amt, SUM(amt) OVER (ORDER BY d) AS running FROM sales; -- rows (d, amt): ('2026-01-01', 10), ('2026-01-02', 20), ('2026-01-02', 5), ('2026-01-03', 7)- 30
- 35
- 5
- 42
Answer: B. 35
Explanation: With ORDER BY and no frame clause the default frame is RANGE … CURRENT ROW, which includes all peers with the same d. Both 2026-01-02 rows show 10 + 20 + 5 = 35. Use ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW for a strict row-by-row total.
Which JOIN returns every row from the left table, with NULLs where there is no match on the right?
SQL · easy
- INNER JOIN
- LEFT JOIN
- CROSS JOIN
- SELF JOIN
Answer: B. LEFT JOIN
Explanation: LEFT (OUTER) JOIN keeps all left rows. INNER JOIN keeps only rows that match on both sides.
What does this return?
SQL · easy
SELECT name FROM employees WHERE name LIKE 'M%'; -- names: Asha, Ravi, Meera, Kabir, Neha- Meera
- Meera, Neha
- Asha, Meera
- No rows
Answer: A. Meera
Explanation: 'M%' matches names starting with M. % is any run of characters and _ is exactly one.
What does this return?
SQL · hard
SELECT * FROM a WHERE id NOT IN (SELECT a_id FROM b); -- a.id: 1, 2, 3 -- b.a_id: 1, NULL- 2, 3
- No rows
- 1
- 1, 2, 3
Answer: B. No rows
Explanation: id NOT IN (1, NULL) means id <> 1 AND id <> NULL. Comparing with NULL is unknown, so no row qualifies. NOT EXISTS does not have this trap.
What does this recursive CTE return?
SQL · hard
WITH RECURSIVE n(x) AS ( SELECT 1 UNION ALL SELECT x * 2 FROM n WHERE x < 20 ) SELECT SUM(x) FROM n;- 31
- 63
- 30
- 62
Answer: B. 63
Explanation: Rows: 1, 2, 4, 8, 16, then 32 (16 < 20 still passes, producing 32; 32 stops the recursion). 1+2+4+8+16+32 = 63.
What does this return?
SQL · medium
-- Table employees(id, name, dept, salary): -- (1, Asha, Eng, 90) -- (2, Ravi, Eng, 70) -- (3, Meera, Sales, 60) -- (4, Kabir, Sales, 60) -- (5, Neha, HR, NULL) SELECT dept, COUNT(*) AS n FROM employees GROUP BY dept HAVING COUNT(*) > 1 ORDER BY dept;- Eng 2, Sales 2
- Eng 2, HR 1, Sales 2
- Sales 2
- Eng 2
Answer: A. Eng 2, Sales 2
Explanation: Eng and Sales have 2 employees each; HR has 1 and is filtered out by HAVING.
Table A has 3 rows and table B has 4 rows. How many rows does SELECT * FROM A CROSS JOIN B return?
SQL · medium
- 7
- 12
- 4
- 3
Answer: B. 12
Explanation: A cross join pairs every row of A with every row of B: 3 × 4 = 12 (a Cartesian product).
Which statements are true about a VIEW? (Select all that apply)
SQL · medium
- It is a stored query
- By default it does not store a copy of the data
- It always makes queries faster
- It can hide columns from users
Answer: A. It is a stored query; B. By default it does not store a copy of the data; D. It can hide columns from users
Explanation: A view is a named SELECT that runs when queried, so it is not automatically faster (materialized views, which store results, are a different feature). Views are often used to expose only some columns.