By Matthew Rathbone · October 24, 2026

The fastest way to get comfortable with SQL is to write queries and check them against a real answer. These problems are grouped by difficulty: beginner, intermediate, joins and advanced. Every problem has a hidden solution and the exact result it returns, so you can compare your output row by row.

Set up the practice database

All problems use four tables from a tiny online shop: customers, products, orders and order_items. The script below works in SQLite and, with minor type changes, in PostgreSQL, MySQL and SQL Server. The quickest route is a free SQLite database in a desktop client such as Beekeeper Studio: create a new SQLite database, open a query tab, paste the script and run it.

CREATE TABLE customers (id INTEGER PRIMARY KEY, name TEXT, country TEXT, signup_date TEXT);
CREATE TABLE products (id INTEGER PRIMARY KEY, name TEXT, category TEXT, price REAL);
CREATE TABLE orders (id INTEGER PRIMARY KEY, customer_id INTEGER, order_date TEXT, status TEXT);
CREATE TABLE order_items (order_id INTEGER, product_id INTEGER, quantity INTEGER);

INSERT INTO customers VALUES
  (1, 'Ana Silva', 'Brazil', '2025-01-15'),
  (2, 'Ben Carter', 'USA', '2025-02-03'),
  (3, 'Chloe Dubois', 'France', '2025-02-20'),
  (4, 'Dev Patel', 'India', '2025-03-11'),
  (5, 'Emma Jones', 'USA', '2025-04-02'),
  (6, 'Felix Wagner', 'Germany', '2025-05-18'),
  (7, 'Grace Kim', 'USA', '2025-06-07'),
  (8, 'Hiro Tanaka', 'Japan', '2025-06-30');

INSERT INTO products VALUES
  (1, 'Laptop', 'Electronics', 899.00),
  (2, 'Headphones', 'Electronics', 129.50),
  (3, 'Desk Lamp', 'Home', 34.99),
  (4, 'Notebook', 'Stationery', 4.25),
  (5, 'Pen Set', 'Stationery', 12.00),
  (6, 'Office Chair', 'Home', 189.00),
  (7, 'Webcam', 'Electronics', 59.90),
  (8, 'Backpack', 'Accessories', 45.00);

INSERT INTO orders VALUES
  (1, 1, '2025-07-01', 'shipped'),
  (2, 2, '2025-07-03', 'shipped'),
  (3, 2, '2025-07-15', 'cancelled'),
  (4, 3, '2025-07-20', 'shipped'),
  (5, 5, '2025-08-02', 'shipped'),
  (6, 1, '2025-08-09', 'pending'),
  (7, 4, '2025-08-12', 'shipped'),
  (8, 5, '2025-08-21', 'shipped'),
  (9, 7, '2025-09-04', 'shipped'),
  (10, 2, '2025-09-10', 'shipped'),
  (11, 3, '2025-09-18', 'pending'),
  (12, 7, '2025-09-25', 'cancelled');

INSERT INTO order_items VALUES
  (1, 1, 1), (1, 4, 3),
  (2, 2, 1), (2, 7, 1),
  (3, 6, 1),
  (4, 3, 2), (4, 5, 2),
  (5, 8, 1), (5, 4, 5),
  (6, 2, 2),
  (7, 1, 1), (7, 2, 1),
  (8, 5, 3),
  (9, 6, 1), (9, 3, 1),
  (10, 4, 10), (10, 8, 2),
  (11, 7, 1),
  (12, 1, 1);

How the tables connect: orders.customer_id points at customers.id, and each row of order_items links one order to one product through order_id and product_id.

New to SQL? Do the free interactive lessons first. They run in your browser with no setup, and give hints when you get stuck. Keep our SQL cheat sheet open as a reference while you work through the problems below.

How to use these problems

  1. Read the problem and write your own query before opening the solution.
  2. Run it and compare your result with the one shown. Row order matters only where the problem asks for sorting.
  3. If your answer differs, read the solution, then re-run yours and find out why it differed. That is where the learning happens.
  4. There is usually more than one correct query. If yours returns the same rows, it is right.

Beginner: SELECT, WHERE, ORDER BY

Problem 1: List every product.

Return all columns and all rows from products.

Show solution
SELECT * FROM products;

Result (8 rows):

idnamecategoryprice
1LaptopElectronics899.0
2HeadphonesElectronics129.5
3Desk LampHome34.99
4NotebookStationery4.25
5Pen SetStationery12.0
6Office ChairHome189.0
7WebcamElectronics59.9
8BackpackAccessories45.0

Problem 2: Names and prices only.

Return just the name and price of each product.

Show solution
SELECT name, price FROM products;

Result (8 rows):

nameprice
Laptop899.0
Headphones129.5
Desk Lamp34.99
Notebook4.25
Pen Set12.0
Office Chair189.0
Webcam59.9
Backpack45.0

Problem 3: Customers from the USA.

Return the name of every customer whose country is USA.

Show solution
SELECT name FROM customers WHERE country = 'USA';

Result (3 rows):

name
Ben Carter
Emma Jones
Grace Kim

Problem 4: Products over $100.

Return the name and price of products that cost more than 100, most expensive first.

Show solution
SELECT name, price FROM products WHERE price > 100 ORDER BY price DESC;

Result (3 rows):

nameprice
Laptop899.0
Office Chair189.0
Headphones129.5

Problem 5: Cheapest three products.

Return the three cheapest products (name and price).

Show solution
SELECT name, price FROM products ORDER BY price ASC LIMIT 3;

Result (3 rows):

nameprice
Notebook4.25
Pen Set12.0
Desk Lamp34.99

Problem 6: Unique countries.

List each country that has at least one customer, once, in alphabetical order.

Show solution
SELECT DISTINCT country FROM customers ORDER BY country;

Result (6 rows):

country
Brazil
France
Germany
India
Japan
USA

Problem 7: Names starting with a letter.

Return products whose name starts with the letter P or the letter W.

Show solution
SELECT name FROM products WHERE name LIKE 'P%' OR name LIKE 'W%';

Result (2 rows):

name
Pen Set
Webcam

Problem 8: Stationery or Home.

Return the name and category of every product in the Stationery or Home categories.

Show solution
SELECT name, category FROM products WHERE category IN ('Stationery', 'Home');

Result (4 rows):

namecategory
Desk LampHome
NotebookStationery
Pen SetStationery
Office ChairHome

Intermediate: aggregates and GROUP BY

Problem 9: Count the customers.

How many customers are there?

Show solution
SELECT COUNT(*) AS customer_count FROM customers;

Result (1 row):

customer_count
8

Problem 10: Average product price.

What is the average product price, rounded to 2 decimal places?

Show solution
SELECT ROUND(AVG(price), 2) AS avg_price FROM products;

Result (1 row):

avg_price
171.71

Problem 11: Customers per country.

For each country, count its customers. Show the busiest countries first.

Show solution
SELECT country, COUNT(*) AS customers
FROM customers
GROUP BY country
ORDER BY customers DESC, country;

Result (6 rows):

countrycustomers
USA3
Brazil1
France1
Germany1
India1
Japan1

Problem 12: Orders per status.

Count the orders in each status.

Show solution
SELECT status, COUNT(*) AS orders
FROM orders
GROUP BY status
ORDER BY orders DESC, status;

Result (3 rows):

statusorders
shipped8
cancelled2
pending2

Problem 13: Categories with more than one product.

List each product category that has more than one product, with its product count.

Show solution
SELECT category, COUNT(*) AS products
FROM products
GROUP BY category
HAVING COUNT(*) > 1
ORDER BY products DESC, category;

Result (3 rows):

categoryproducts
Electronics3
Home2
Stationery2

Problem 14: Price range per category.

For each category show the cheapest and the most expensive price.

Show solution
SELECT category, MIN(price) AS cheapest, MAX(price) AS priciest
FROM products
GROUP BY category
ORDER BY category;

Result (4 rows):

categorycheapestpriciest
Accessories45.045.0
Electronics59.9899.0
Home34.99189.0
Stationery4.2512.0

Problem 15: Orders placed in August 2025.

How many orders were placed in August 2025?

Show solution
SELECT COUNT(*) AS august_orders
FROM orders
WHERE order_date >= '2025-08-01'
  AND order_date < '2025-09-01';

Result (1 row):

august_orders
4

Problem 16: Label each product.

Return each product's name and price, plus a tier column: 'budget' under 20, 'mid' from 20 up to 100, otherwise 'premium'.

Show solution
SELECT name, price,
  CASE
    WHEN price < 20 THEN 'budget'
    WHEN price < 100 THEN 'mid'
    ELSE 'premium'
  END AS tier
FROM products
ORDER BY price;

Result (8 rows):

namepricetier
Notebook4.25budget
Pen Set12.0budget
Desk Lamp34.99mid
Backpack45.0mid
Webcam59.9mid
Headphones129.5premium
Office Chair189.0premium
Laptop899.0premium

Joins

Problem 17: Who placed each order?

Return each order id and the name of the customer who placed it.

Show solution
SELECT o.id AS order_id, c.name
FROM orders o
JOIN customers c ON c.id = o.customer_id
ORDER BY o.id;

Result (12 rows):

order_idname
1Ana Silva
2Ben Carter
3Ben Carter
4Chloe Dubois
5Emma Jones
6Ana Silva
7Dev Patel
8Emma Jones
9Grace Kim
10Ben Carter
11Chloe Dubois
12Grace Kim

Problem 18: Customers who never ordered.

List the customers who have not placed any order.

Show solution
SELECT c.name
FROM customers c
LEFT JOIN orders o ON o.customer_id = c.id
WHERE o.id IS NULL;

Result (2 rows):

name
Felix Wagner
Hiro Tanaka

Problem 19: Items in each order.

For order 1, list each product name and the quantity ordered.

Show solution
SELECT p.name, oi.quantity
FROM order_items oi
JOIN products p ON p.id = oi.product_id
WHERE oi.order_id = 1;

Result (2 rows):

namequantity
Laptop1
Notebook3

Problem 20: Line totals.

For every order line, return the order id, product name and line total (price x quantity) for orders 1 to 3, sorted by order id then product name.

Show solution
SELECT oi.order_id, p.name, p.price * oi.quantity AS line_total
FROM order_items oi
JOIN products p ON p.id = oi.product_id
WHERE oi.order_id <= 3
ORDER BY oi.order_id, p.name;

Result (5 rows):

order_idnameline_total
1Laptop899.0
1Notebook12.75
2Headphones129.5
2Webcam59.9
3Office Chair189.0

Problem 21: Orders per customer, including zero.

Return every customer with the number of orders they placed (0 if none). Sort by count descending, then name.

Show solution
SELECT c.name, COUNT(o.id) AS orders
FROM customers c
LEFT JOIN orders o ON o.customer_id = c.id
GROUP BY c.id, c.name
ORDER BY orders DESC, c.name;

Result (8 rows):

nameorders
Ben Carter3
Ana Silva2
Chloe Dubois2
Emma Jones2
Grace Kim2
Dev Patel1
Felix Wagner0
Hiro Tanaka0

Problem 22: Revenue per customer (shipped only).

Total spent per customer on shipped orders only, highest first. Skip customers with no shipped orders.

Show solution
SELECT c.name, SUM(p.price * oi.quantity) AS total_spent
FROM customers c
JOIN orders o ON o.customer_id = c.id
JOIN order_items oi ON oi.order_id = o.id
JOIN products p ON p.id = oi.product_id
WHERE o.status = 'shipped'
GROUP BY c.id, c.name
ORDER BY total_spent DESC;

Result (6 rows):

nametotal_spent
Dev Patel1028.5
Ana Silva911.75
Ben Carter321.9
Grace Kim223.99
Emma Jones102.25
Chloe Dubois93.98

Advanced: subqueries, CTEs and window functions

Problem 23: Above-average products.

Return products priced above the average product price.

Show solution
SELECT name, price
FROM products
WHERE price > (SELECT AVG(price) FROM products)
ORDER BY price DESC;

Result (2 rows):

nameprice
Laptop899.0
Office Chair189.0

Problem 24: Products never ordered.

Which products have never appeared in any order?

Show solution
SELECT name
FROM products
WHERE id NOT IN (SELECT product_id FROM order_items);

Result (0 rows):

name

Problem 25: Best-selling category.

Total units sold per category (all order statuses except cancelled), most units first.

Show solution
SELECT p.category, SUM(oi.quantity) AS units
FROM order_items oi
JOIN orders o ON o.id = oi.order_id
JOIN products p ON p.id = oi.product_id
WHERE o.status <> 'cancelled'
GROUP BY p.category
ORDER BY units DESC, p.category;

Result (4 rows):

categoryunits
Stationery23
Electronics8
Home4
Accessories3

Problem 26: Rank products by price within category.

For each product, show its rank by price inside its category (1 = most expensive).

Show solution
SELECT category, name, price,
  RANK() OVER (PARTITION BY category ORDER BY price DESC) AS price_rank
FROM products
ORDER BY category, price_rank;

Result (8 rows):

categorynamepriceprice_rank
AccessoriesBackpack45.01
ElectronicsLaptop899.01
ElectronicsHeadphones129.52
ElectronicsWebcam59.93
HomeOffice Chair189.01
HomeDesk Lamp34.992
StationeryPen Set12.01
StationeryNotebook4.252

Problem 27: Each customer's first order.

Return each customer's earliest order date. Customers with no orders should not appear.

Show solution
SELECT c.name, MIN(o.order_date) AS first_order
FROM customers c
JOIN orders o ON o.customer_id = c.id
GROUP BY c.id, c.name
ORDER BY first_order;

Result (6 rows):

namefirst_order
Ana Silva2025-07-01
Ben Carter2025-07-03
Chloe Dubois2025-07-20
Emma Jones2025-08-02
Dev Patel2025-08-12
Grace Kim2025-09-04

Problem 28: Running total of orders.

Using a CTE, count orders per month and add a running total.

Show solution
WITH monthly AS (
  SELECT substr(order_date, 1, 7) AS month, COUNT(*) AS orders
  FROM orders
  GROUP BY substr(order_date, 1, 7)
)
SELECT month, orders,
  SUM(orders) OVER (ORDER BY month) AS running_total
FROM monthly
ORDER BY month;

Result (3 rows):

monthordersrunning_total
2025-0744
2025-0848
2025-09412

What to practice next

FAQ

Where can I practice SQL for free?

SQL Easy has free interactive lessons that run in your browser with instant feedback. For open-ended practice like the problems above, use any free database client with SQLite.

Which database should I use for these problems?

SQLite is the easiest because it needs no server. The queries are standard SQL, so they also work in PostgreSQL and MySQL. Only the substr month trick in problem 28 differs by database.

How many SQL problems should I solve to get good?

Aim for consistency over volume: a few problems a day for two weeks covers all the core patterns (filtering, aggregating, joining, subqueries). Redo the ones you got wrong a few days later.

Are these problems good for interview preparation?

The intermediate, join and advanced sections cover the patterns that come up most in analyst and engineering screens: anti-joins, aggregating across joins, and window functions.