Blog›SQL Practice Questions (With Answers, Easy to Hard)
SQL SkillsCareer
SQL Practice Questions Easy to Hard, With Answers
Ten practice questions with worked solutions, from your first WHERE clause to window functions. Try each one before you reveal the answer, because that is where the learning actually happens.
CR
Conor Robertson
July 18, 2026 · 9 min read
Frequently asked questions
The questions people ask about practising SQL.
Attempt real questions against a real database before looking at the answer, and start from a prompt rather than a tutorial. Reading a solution feels productive but teaches almost nothing. The pattern that works is: read the question, write your own query, run it, then compare against the worked answer and understand any gap. Space your practice out and revisit the patterns you got wrong. That is exactly how the ten questions on this page are built, and how QueryCase's 54 guided cases are structured.
Filtering with WHERE, aggregation with GROUP BY, the difference between WHERE and HAVING, all the JOIN types (especially finding rows that do not match, the anti-join), and window functions (ROW_NUMBER, RANK, SUM OVER, LAG). Top-N-per-group and period-over-period change come up constantly. The ten questions here cover that whole surface in order.
There is no magic number, but consistency beats volume. A handful of questions a day across a few weeks, revisiting the ones you got wrong, will take you further than a hundred in one sitting. What matters is that you attempt each one yourself before reading the answer, and that you understand why the answer works rather than memorising it.
Yes. The first three are genuinely beginner level (SELECT, WHERE, GROUP BY, ORDER BY and LIMIT) and each question says what it is testing. They then ladder up through joins, HAVING and window functions, so you can start at the top and stop wherever it gets hard, then come back.
QueryCase's Rookie rank is free: eight guided cases, an exam and a cheat sheet, no credit card, and you can solve the first case before making an account. You write real SQL against a real database in the browser (DuckDB compiled to WebAssembly) with nothing to install.
Ready to practice on real data?
QueryCase teaches SQL through mystery cases on real datasets. Music, sports, film, and more. Your first cases are completely free.
Most SQL practice fails for one boring reason: people read answers instead of writing queries. You skim a solution, it makes sense, you feel like you learned something, and then a week later you are staring at a blank editor with no idea where to start. Recognising a correct query and being able to produce one are completely different skills, and only one of them gets you through an interview or a real task at work.
So here is the deal with the ten questions below. Each one is a prompt and a schema. Write your query first. Only then reveal the answer and compare. If your query matched, great, move on. If it did not, the gap between yours and the worked solution is the single most valuable thing on this page, more valuable than the solution itself.
They ladder from a first WHERE clause up to window functions, which is roughly the order these ideas show up in real work, and almost exactly what a data-analyst interview walks through.
How to use this page
Read the question. Look at the schema. Write your own query somewhere, even if you are not sure. Then hit Reveal solution and compare. The point is not to collect answers, it is to notice where your instinct and the worked query diverge.
Ten questions, easy to hard
Every solution is written in standard SQL (it runs as-is in PostgreSQL or DuckDB) and each one has been checked by hand. The schema for each question sits right above it, so there is no scrolling back and forth.
EasyQuestion 1
List the name and salary of every employee in the Sales department.
SELECT name, salary
FROM employees
WHERE department = 'Sales';
The most basic shape there is: pick columns, filter rows. If you can write this without thinking, the filtering muscle is there.
What it tests
SELECTWHERE
EasyQuestion 2
How many customers are there in each country? Show the busiest country first.
customers(id, name, country, created_at)
SQLSolution
SELECT country, COUNT(*) AS customers
FROM customers
GROUP BY country
ORDER BY customers DESC;
Counting rows per group is the single most common aggregation in analytics. The ORDER BY on the counted column is the part people forget.
What it tests
GROUP BYCOUNTORDER BY
EasyQuestion 3
What are the five most expensive products?
products(id, name, category, price)
SQLSolution
SELECT name, price
FROM products
ORDER BY price DESCLIMIT5;
ORDER BY plus LIMIT is how every "top N" question starts. Sort descending, take the first few. Nothing exotic, but you will type it a thousand times.
What it tests
ORDER BYLIMIT
MediumQuestion 4
What was the total revenue for each month of 2024?
orders(id, customer_id, amount, order_date)
SQLSolution
SELECTDATE_TRUNC('month', order_date) AS month,
SUM(amount) AS revenue
FROM orders
WHERE order_date >='2024-01-01'AND order_date <'2025-01-01'GROUP BY1ORDER BY1;
Two things at once: filter to a date range, then bucket by month. DATE_TRUNC does the bucketing so rows in the same month collapse together. This is the backbone of every revenue chart.
What it tests
Date filteringDATE_TRUNCSUMGROUP BY
MediumQuestion 5
Which customers have never placed an order?
customers(id, name, country, created_at)
orders(id, customer_id, amount, order_date)
SQLApproach 1: LEFT JOIN, keep the non-matches
SELECT c.id, c.name
FROM customers c
LEFT JOIN orders o ON o.customer_id = c.id
WHERE o.customer_id IS NULL;
SQLApproach 2: NOT EXISTS
SELECT id, name
FROM customers c
WHERE NOT EXISTS (
SELECT1FROM orders o
WHERE o.customer_id = c.id
);
Finding the absence of something trips people up more than any other pattern. The trick is a LEFT JOIN that keeps every customer, then keeping only the rows where no order matched. NOT EXISTS is the other clean way.
What it tests
LEFT JOINNULL handlingAnti-join
MediumQuestion 6
Which departments pay an average salary above 50,000?
SELECT department, AVG(salary) AS avg_salary
FROM employees
GROUP BY department
HAVINGAVG(salary) >50000ORDER BY avg_salary DESC;
The classic WHERE-vs-HAVING test. You cannot filter on AVG(salary) in WHERE because the average does not exist yet when WHERE runs. HAVING filters after the grouping, once the average has been calculated.
What it tests
GROUP BYAVGHAVING
MediumQuestion 7
Show each employee next to their department's average salary, without collapsing the rows.
SELECT
name,
department,
salary,
AVG(salary) OVER (PARTITION BY department) AS dept_avg
FROM employees;
This is where GROUP BY stops being enough. GROUP BY would give you one row per department. You want every employee AND the department average beside them, which is exactly what a window function does: it computes across a group without folding the rows away.
What it tests
Window functionsPARTITION BYAVG OVER
HardQuestion 8
Find the three highest-paid employees within each department.
WITH ranked AS (
SELECT
name, department, salary,
ROW_NUMBER() OVER (
PARTITION BY department
ORDER BY salary DESC
) AS rn
FROM employees
)
SELECT name, department, salary
FROM ranked
WHERE rn <=3;
"Top N per group" is the interview question that separates people who have used window functions from people who have not. Number each employee within their department by salary, then keep the first three. You cannot do this with GROUP BY alone.
What it tests
ROW_NUMBERPARTITION BYCTEs
HardQuestion 9
Calculate the month-over-month percentage change in revenue.
orders(id, customer_id, amount, order_date)
SQLSolution
WITH monthly AS (
SELECTDATE_TRUNC('month', order_date) AS month,
SUM(amount) AS revenue
FROM orders
GROUP BY1
)
SELECT
month,
revenue,
ROUND(
(revenue - LAG(revenue) OVER (ORDER BY month))
/ LAG(revenue) OVER (ORDER BY month) * 100, 2
) AS pct_change
FROM monthly
ORDER BY month;
Period-over-period is one of the most requested things in any analytics role, and LAG is the elegant answer. It reaches back to the previous row so you can compare this month to last, with no self-join. Roll the monthly totals up first in a CTE, then compare.
What it tests
LAGCTEsDATE_TRUNCPeriod comparison
HardQuestion 10
Produce a running total of daily sales, ordered by date.
sales(id, sale_date, amount)
SQLSolution
SELECT
sale_date,
amount,
SUM(amount) OVER (ORDER BY sale_date) AS running_total
FROM sales
ORDER BY sale_date;
A running total is a windowed SUM with an ORDER BY inside the OVER clause. The ordering turns the sum into a cumulative one: each row adds itself to everything before it. Change nothing but that ORDER BY and a plain total becomes a running one.
What it tests
SUM OVERRunning totalWindow frame
The patterns underneath the questions
If you look back at those ten, they are not really ten separate things to memorise. They are a handful of patterns that recombine endlessly:
Filter, then aggregate. Questions 1, 2, 4 and 6. Almost every reporting query is some version of "narrow the rows down, then count or sum what is left."
Find what is missing. Question 5. The anti-join (a LEFT JOIN with a NULL check, or NOT EXISTS) is how you answer any "who has never" or "which of these has no matching" question, and it trips people up far more than it should.
Filter after grouping. Question 6. WHERE cannot see an aggregate because the aggregate does not exist yet when WHERE runs. HAVING filters after the grouping happens. This is the single most common SQL interview gotcha.
Compute across a group without collapsing it. Questions 7, 8, 9 and 10. The moment you need every row and something calculated across its group (a department average, a rank, a previous value, a running total), you have left behind and you need a window function.
GROUP BY
Notice that the difficulty is not really about syntax getting harder. It is about the same operations combining in more steps. That is why practising the patterns beats memorising queries: once you can see which pattern a question is asking for, writing it is mechanical.
Why "read the answer" does not count as practice
There is a real reason attempting the query first matters, and it is not just discipline.
When you read a worked solution, your brain runs a cheap recognition process: "yes, that looks right." Recognition is easy and it fades fast. When you write a query from a blank editor, you run a much more expensive retrieval process: you have to actually reconstruct the pattern from memory and commit to it. Retrieval is what builds durable recall, which is the difference between nodding along to a HAVING explanation and reaching for HAVING under interview pressure without thinking.
This is also why passive resources (video courses, cheat sheets you scroll) feel productive but leave you unable to write anything. They are all recognition, no retrieval.
You do not learn SQL by understanding a query. You learn it by producing one from nothing, being wrong, and closing the gap.
If you want the concepts behind these questions in more depth, we have written the hard ones up in full:
The trouble with a list of ten questions is that it ends. Real fluency comes from doing this steadily against a real database, not memorising a page. That is what QueryCase is built for: 54 guided cases that ladder exactly like the questions above, each one a mystery you crack by writing genuine SQL, with drills between topics and an exam at the end of every rank. It runs entirely in your browser, so there is nothing to install and no database to set up.
The first rank is free, and you can solve the opening case before you even make an account. Start there, and the difference between reading SQL and writing it will close faster than any list of answers can manage.
SQL Practice Questions (With Answers, Easy to Hard)