15 SQL JOIN Practice Problems with Solutions

JOINs are one of the most important — and most searched — topics in SQL. This practice set covers INNER JOIN, LEFT JOIN, and multi-table JOINs with 15 beginner-friendly problems and full solutions. Click any problem to reveal the solution and explanation.

Part 1 — INNER JOIN (Problems 1–5)

INNER JOIN returns only the rows that have a match in both tables.

Problem 1 Show each order along with the customer's full name. Return the order ID, order date, and customer full name. Tables: orders (order_id, customer_id, order_date, total_usd) · customers (customer_id, full_name, email, city)
Show solution ↓

Solution

SELECT o.order_id, o.order_date, c.full_name
FROM orders o
INNER JOIN customers c ON o.customer_id = c.customer_id;

Explanation

INNER JOIN connects the two tables on the shared customer_id column. Only orders that have a matching customer record are returned. The table aliases o and c are shorthand to avoid writing the full table name each time.

Problem 2 Show each product's name and the category it belongs to. Return the product name and category name. Tables: products (product_id, product_name, category_id, price_usd) · categories (category_id, category_name)
Show solution ↓

Solution

SELECT p.product_name, c.category_name
FROM products p
INNER JOIN categories c ON p.category_id = c.category_id;

Explanation

The products table stores a category_id but not the category name. INNER JOIN pulls in the matching category name from the categories table. Products without a matching category_id would be excluded — but with clean data, all products should match.

Problem 3 List every employee along with the name of the department they work in. Return the employee's full name and department name. Tables: employees (employee_id, full_name, department_id, salary) · departments (department_id, department_name)
Show solution ↓

Solution

SELECT e.full_name, d.department_name
FROM employees e
INNER JOIN departments d ON e.department_id = d.department_id;

Explanation

The employees table stores a department_id as a reference. INNER JOIN uses that ID to look up the matching department name in the departments table. Employees without a matching department_id would not appear in the results.

Problem 4 Show each booking along with the name of the hotel it was made at. Return the booking ID, check-in date, and hotel name. Tables: bookings (booking_id, hotel_id, guest_name, check_in_date, nights) · hotels (hotel_id, hotel_name, city, star_rating)
Show solution ↓

Solution

SELECT b.booking_id, b.check_in_date, h.hotel_name
FROM bookings b
INNER JOIN hotels h ON b.hotel_id = h.hotel_id;

Explanation

INNER JOIN links each booking to its hotel using the shared hotel_id. Only bookings with a valid matching hotel record are included in the results.

Problem 5 Show each course enrollment along with the student's full name. Return the enrollment ID, course name, and student full name. Tables: enrollments (enrollment_id, student_id, course_name, enrollment_date) · students (student_id, full_name, email)
Show solution ↓

Solution

SELECT e.enrollment_id, e.course_name, s.full_name
FROM enrollments e
INNER JOIN students s ON e.student_id = s.student_id;

Explanation

INNER JOIN connects enrollments to students via the shared student_id. Only enrollments that have a matching student record are returned — any enrollment with an invalid student_id would be excluded.

Part 2 — LEFT JOIN (Problems 6–10)

LEFT JOIN returns all rows from the left table, plus matching rows from the right table. If there's no match, the right table columns return NULL.

Problem 6 Show all customers and any orders they have placed. Return the customer's full name and order ID. Include customers who have never placed an order. Tables: customers (customer_id, full_name, email, city) · orders (order_id, customer_id, order_date, total_usd)
Show solution ↓

Solution

SELECT c.full_name, o.order_id
FROM customers c
LEFT JOIN orders o ON c.customer_id = o.customer_id;

Explanation

LEFT JOIN keeps all rows from the left table (customers) regardless of whether they have a match in the right table (orders). Customers with no orders will still appear, but their order_id will show as NULL.

Problem 7 Show all employees and the project they are assigned to, if any. Return the employee's full name and project name. Include employees with no project assignment. Tables: employees (employee_id, full_name, department_id, salary) · project_assignments (assignment_id, employee_id, project_name, start_date)
Show solution ↓

Solution

SELECT e.full_name, p.project_name
FROM employees e
LEFT JOIN project_assignments p ON e.employee_id = p.employee_id;

Explanation

LEFT JOIN ensures every employee appears in the results. Employees assigned to a project will show the project name. Employees with no assignment will show NULL in the project_name column.

Problem 8 Show all products and any reviews they have received. Return the product name and review text. Include products with no reviews. Tables: products (product_id, product_name, category_id, price_usd) · reviews (review_id, product_id, review_text, rating, review_date)
Show solution ↓

Solution

SELECT p.product_name, r.review_text
FROM products p
LEFT JOIN reviews r ON p.product_id = r.product_id;

Explanation

LEFT JOIN keeps all products in the results. Products with reviews will show the review text. Products that have never been reviewed will appear with NULL in the review_text column.

Problem 9 Show all students and any course they are enrolled in. Return the student's full name and course name. Include students who are not enrolled in any course. Tables: students (student_id, full_name, email) · enrollments (enrollment_id, student_id, course_name, enrollment_date)
Show solution ↓

Solution

SELECT s.full_name, e.course_name
FROM students s
LEFT JOIN enrollments e ON s.student_id = e.student_id;

Explanation

LEFT JOIN returns every student regardless of enrollment status. Students enrolled in a course show the course name. Students with no enrollment show NULL for course_name.

Problem 10 Show all hotels and any bookings made at each hotel. Return the hotel name and booking ID. Include hotels that have no bookings. Tables: hotels (hotel_id, hotel_name, city, star_rating) · bookings (booking_id, hotel_id, guest_name, check_in_date, nights)
Show solution ↓

Solution

SELECT h.hotel_name, b.booking_id
FROM hotels h
LEFT JOIN bookings b ON h.hotel_id = b.hotel_id;

Explanation

LEFT JOIN keeps all hotels in the output. Hotels with bookings will show the corresponding booking IDs. Hotels with no bookings will still appear but with NULL in the booking_id column.

Part 3 — Multi-Table JOINs (Problems 11–15)

You can chain multiple JOINs in a single query to combine data from three or more tables.

Problem 11 Show each order with the customer's full name and the city they live in. Return the order ID, customer full name, and city. Tables: orders (order_id, customer_id, order_date, total_usd) · customers (customer_id, full_name, city_id) · cities (city_id, city_name)
Show solution ↓

Solution

SELECT o.order_id, c.full_name, ci.city_name
FROM orders o
INNER JOIN customers c ON o.customer_id = c.customer_id
INNER JOIN cities ci ON c.city_id = ci.city_id;

Explanation

The first INNER JOIN connects orders to customers. The second INNER JOIN then connects customers to cities using the city_id. Each JOIN adds another table to the chain, letting you pull columns from all three tables in one query.

Problem 12 Show each product with its category name and supplier name. Return the product name, category name, and supplier name. Tables: products (product_id, product_name, category_id, supplier_id, price_usd) · categories (category_id, category_name) · suppliers (supplier_id, supplier_name)
Show solution ↓

Solution

SELECT p.product_name, c.category_name, s.supplier_name
FROM products p
INNER JOIN categories c ON p.category_id = c.category_id
INNER JOIN suppliers s ON p.supplier_id = s.supplier_id;

Explanation

Two INNER JOINs extend the products table with data from both categories and suppliers. Each JOIN uses a different foreign key column from the products table to connect to the right lookup table.

Problem 13 Show each employee with their department name and their manager's full name. Return the employee full name, department name, and manager full name. Tables: employees (employee_id, full_name, department_id, manager_id) · departments (department_id, department_name)
Show solution ↓

Solution

SELECT e.full_name, d.department_name, m.full_name AS manager_name
FROM employees e
INNER JOIN departments d ON e.department_id = d.department_id
INNER JOIN employees m ON e.manager_id = m.employee_id;

Explanation

The first INNER JOIN pulls in the department name. The second INNER JOIN joins the employees table to itself — using a different alias (m for manager) — to look up the manager's name from the same table. This is called a self-join.

Problem 14 Show all students, the course they are enrolled in if any, and the instructor for that course if one exists. Return the student full name, course name, and instructor name. Tables: students (student_id, full_name, email) · enrollments (enrollment_id, student_id, course_id) · courses (course_id, course_name, instructor_name)
Show solution ↓

Solution

SELECT s.full_name, c.course_name, c.instructor_name
FROM students s
LEFT JOIN enrollments e ON s.student_id = e.student_id
LEFT JOIN courses c ON e.course_id = c.course_id;

Explanation

LEFT JOIN is used for both joins so that students with no enrollment still appear in the results. If a student has no enrollment, both course_name and instructor_name will be NULL. Chaining LEFT JOINs preserves all rows from the leftmost table throughout.

Problem 15 Show each booking with the guest's full name, the hotel name, and the city the hotel is in. Return the booking ID, guest full name, hotel name, and city name. Tables: bookings (booking_id, guest_id, hotel_id, check_in_date) · guests (guest_id, full_name, email) · hotels (hotel_id, hotel_name, city_id) · cities (city_id, city_name)
Show solution ↓

Solution

SELECT b.booking_id, g.full_name, h.hotel_name, ci.city_name
FROM bookings b
INNER JOIN guests g ON b.guest_id = g.guest_id
INNER JOIN hotels h ON b.hotel_id = h.hotel_id
INNER JOIN cities ci ON h.city_id = ci.city_id;

Explanation

Three INNER JOINs chain four tables together. The first links bookings to guests, the second links bookings to hotels, and the third links hotels to cities. Each JOIN adds one more table to the query, letting you pull together data spread across multiple tables in a single result set.

Ready to practice JOINs interactively? Write real queries and get instant feedback in the SQL Drills interactive exercise tool.

Practice JOINs Interactively →

Looking for more practice? Try the LEFT JOIN exercises or head back to the SQL Drills Learn hub for more practice sets and guides.