The Practice Database

SQL · Lesson 3 of 42 7 min read Updated

Every example in this course runs against one small database called shop. It belongs to an imaginary online store that sells computer accessories, audio gear, storage and books to customers across India. It also stores the store's own staff.

Spend ten minutes on this page now. When you know the tables and a few of their rows by heart, the results of later queries will make sense at a glance, and you will spot mistakes in your own queries much faster.

The five tables

TableOne row isRows
customersA person who has signed up with the store12
productsAn item the store sells12
ordersOne order placed by a customer15
order_itemsOne product line inside an order22
employeesA member of the store's staff10

You can download the file that creates all of this from shop.sql and load it into your own MySQL server, as shown in Install MySQL & Run Queries.

customers

ColumnTypeMeaning
customer_idINTEGER, primary keyUnique number for each customer, 1 to 12
first_nameVARCHAR(40), requiredFirst name, such as Aarav or Priya
last_nameVARCHAR(40), requiredSurname
cityVARCHAR(40), requiredCity, such as Rohtak, Jaipur or Kochi
stateVARCHAR(40), requiredState or union territory
emailVARCHAR(80), optionalEmail address; NULL for customers 4 and 9
joined_onDATEThe day the customer signed up

Primary key means the value is unique in every row and never empty. Required (NOT NULL in SQL) means the column must always have a value. VARCHAR(40) is text of up to 40 characters. NULL means "no value", and you will learn to handle it in NULL Values.

products

ColumnTypeMeaning
product_idINTEGER, primary keyUnique number for each product, 1 to 12
product_nameVARCHAR(60), requiredName, such as Wireless Mouse
categoryVARCHAR(30), requiredAccessories, Displays, Audio, Storage or Books
priceDECIMAL(10,2), requiredCurrent selling price in rupees
stockINTEGER, requiredUnits in the warehouse; the HD Webcam has 0

Here is the whole table. Run it and keep it open while you read the next lessons.

SQLRun it yourself
SELECT * FROM products;
Output
product_id | product_name                | category    | price | stock
-----------+-----------------------------+-------------+-------+------
1          | Wireless Mouse              | Accessories | 799   | 120
2          | Mechanical Keyboard         | Accessories | 3499  | 45
3          | 27-inch Monitor             | Displays    | 15999 | 18
4          | USB-C Hub                   | Accessories | 1899  | 60
5          | Laptop Stand                | Accessories | 1299  | 75
6          | Noise-Cancelling Headphones | Audio       | 8999  | 25
7          | Bluetooth Speaker           | Audio       | 2499  | 40
8          | HD Webcam                   | Accessories | 2199  | 0
9          | 1TB External SSD            | Storage     | 6499  | 30
10         | 64GB Pen Drive              | Storage     | 549   | 200
11         | Python Basics (Book)        | Books       | 450   | 80
12         | Graphics Tablet             | Accessories | 4599  | 12
(12 rows)

The * means "every column". You will learn more about it in SELECT.

orders

ColumnTypeMeaning
order_idINTEGER, primary keyOrder number, 101 to 115
customer_idINTEGER, requiredWho placed the order; points to customers.customer_id
order_dateDATE, requiredWhen the order was placed, January to May 2025
statusVARCHAR(20), requireddelivered, shipped, pending or cancelled

Customer 1, Aarav from Rohtak, is the store's most frequent buyer:

SQLRun it yourself
SELECT * FROM orders WHERE customer_id = 1;
Output
order_id | customer_id | order_date | status
---------+-------------+------------+----------
101      | 1           | 2025-01-05 | delivered
104      | 1           | 2025-02-02 | delivered
115      | 1           | 2025-05-20 | pending
(3 rows)

order_items

ColumnTypeMeaning
item_idINTEGER, primary keyUnique number for each line, 1 to 22
order_idINTEGER, requiredWhich order this line belongs to
product_idINTEGER, requiredWhich product was bought
quantityINTEGER, requiredHow many units
unit_priceDECIMAL(10,2), requiredPrice of one unit when the order was placed

An order can contain several products, so each product in an order gets its own row here. Order 101 has two lines: two wireless mice and one pen drive.

SQLRun it yourself
SELECT * FROM order_items WHERE order_id = 101;
Output
item_id | order_id | product_id | quantity | unit_price
--------+----------+------------+----------+-----------
1       | 101      | 1          | 2        | 799
2       | 101      | 10         | 1        | 549
(2 rows)

Why store unit_price when products already has a price? Because prices change. If the mouse costs more next year, old orders must still show what the customer actually paid.

employees

ColumnTypeMeaning
employee_idINTEGER, primary keyUnique number for each employee, 1 to 10
first_nameVARCHAR(40), requiredFirst name
last_nameVARCHAR(40), requiredSurname
departmentVARCHAR(30), requiredManagement, Engineering, Sales or Support
job_titleVARCHAR(40), requiredSuch as Developer or Sales Manager
salaryINTEGER, requiredMonthly salary in rupees
manager_idINTEGER, optionalThe employee_id of this person's manager; NULL for the chief executive
hire_dateDATE, requiredThe day the person joined

The tables are connected by matching ID values. A column that points to the primary key of another table is called a foreign key.

  • orders.customer_id points to customers.customer_id. One customer can have many orders.
  • order_items.order_id points to orders.order_id. One order can have many items.
  • order_items.product_id points to products.product_id. One product can appear in many orders.
  • employees.manager_id points to employees.employee_id in the same table. This builds the reporting chain: Rajesh (1) manages Neha (2), Simran (6) and Deepak (9).

So order_items is the bridge between orders and products. To answer "which products did Priya buy?", you go from customers to orders to order_items to products. You will do exactly that in INNER JOIN. The employees table is not linked to orders; it is used for the self-join and salary examples.

A few rows are special, and later lessons rely on them:

RowWhy it matters
Customers 4 (Ananya) and 9 (Arjun)No email address (NULL)
Customer 12 (Divya, Pune)Has never placed an order
Product 12 (Graphics Tablet)Has never been ordered
Product 8 (HD Webcam)Out of stock
Order 105Cancelled
Employee 1 (Rajesh)Has no manager (NULL)

The browser runs SQLite, so numbers look slightly different

The Try-it editor runs SQLite, not MySQL. The queries are the same, but SQLite prints some numbers differently. Prices are stored in whole rupees, so the browser shows 799 where MySQL shows 799.00. Averages show more decimal places too:

SQLRun it yourself
SELECT AVG(price) FROM products;
Output
AVG(price)
-----------------
4107.416666666667
(1 row)

MySQL gives 4107.416667 for the same query. The value is the same; only the number of decimals shown is different. Everywhere else in the course, the results are identical.

Key points

  • The shop database has five tables: customers, products, orders, order_items and employees.
  • Each table has an integer primary key that is unique in every row.
  • Foreign keys link the tables: customers to orders, orders to order items, order items to products.
  • employees.manager_id points back into the same table to show who reports to whom.
  • Customers 4 and 9 have no email, customer 12 has no orders, product 12 has never been ordered.
  • The browser shows 799 where MySQL shows 799.00, and averages show more decimals.

Exercise

Using only SELECT * FROM ... WHERE ... like the examples above, find out two things. First, which order lines belong to order 110? Second, look up the product IDs from those lines in the products table and work out what the customer bought.

Show solution
SQL
SELECT * FROM order_items WHERE order_id = 110;

Order 110 has two lines: product 3 with quantity 2 and product 1 with quantity 1. In the products table, product 3 is the 27-inch Monitor and product 1 is the Wireless Mouse, so the customer bought two monitors and one mouse. A join would do this lookup for you in one query.

Quick Quiz

1. Which table records how many units of a product were bought in an order?

2. What does orders.customer_id point to?

3. Why does order_items store unit_price as well?

4. The browser shows a price as 799. What does MySQL show for the same value?

Learn SQL with a mentor

Free lessons get you started. Our PHP & MySQL Development training adds real projects and code reviews from working developers.

  • Project-based learning
  • Mentor code reviews
  • Free counselling call