The Practice Database
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
| Table | One row is | Rows |
|---|---|---|
customers | A person who has signed up with the store | 12 |
products | An item the store sells | 12 |
orders | One order placed by a customer | 15 |
order_items | One product line inside an order | 22 |
employees | A member of the store's staff | 10 |
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
| Column | Type | Meaning |
|---|---|---|
customer_id | INTEGER, primary key | Unique number for each customer, 1 to 12 |
first_name | VARCHAR(40), required | First name, such as Aarav or Priya |
last_name | VARCHAR(40), required | Surname |
city | VARCHAR(40), required | City, such as Rohtak, Jaipur or Kochi |
state | VARCHAR(40), required | State or union territory |
email | VARCHAR(80), optional | Email address; NULL for customers 4 and 9 |
joined_on | DATE | The 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
| Column | Type | Meaning |
|---|---|---|
product_id | INTEGER, primary key | Unique number for each product, 1 to 12 |
product_name | VARCHAR(60), required | Name, such as Wireless Mouse |
category | VARCHAR(30), required | Accessories, Displays, Audio, Storage or Books |
price | DECIMAL(10,2), required | Current selling price in rupees |
stock | INTEGER, required | Units 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.
SELECT * FROM products;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
| Column | Type | Meaning |
|---|---|---|
order_id | INTEGER, primary key | Order number, 101 to 115 |
customer_id | INTEGER, required | Who placed the order; points to customers.customer_id |
order_date | DATE, required | When the order was placed, January to May 2025 |
status | VARCHAR(20), required | delivered, shipped, pending or cancelled |
Customer 1, Aarav from Rohtak, is the store's most frequent buyer:
SELECT * FROM orders WHERE customer_id = 1;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
| Column | Type | Meaning |
|---|---|---|
item_id | INTEGER, primary key | Unique number for each line, 1 to 22 |
order_id | INTEGER, required | Which order this line belongs to |
product_id | INTEGER, required | Which product was bought |
quantity | INTEGER, required | How many units |
unit_price | DECIMAL(10,2), required | Price 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.
SELECT * FROM order_items WHERE order_id = 101;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
| Column | Type | Meaning |
|---|---|---|
employee_id | INTEGER, primary key | Unique number for each employee, 1 to 10 |
first_name | VARCHAR(40), required | First name |
last_name | VARCHAR(40), required | Surname |
department | VARCHAR(30), required | Management, Engineering, Sales or Support |
job_title | VARCHAR(40), required | Such as Developer or Sales Manager |
salary | INTEGER, required | Monthly salary in rupees |
manager_id | INTEGER, optional | The employee_id of this person's manager; NULL for the chief executive |
hire_date | DATE, required | The day the person joined |
How the tables link
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_idpoints tocustomers.customer_id. One customer can have many orders.order_items.order_idpoints toorders.order_id. One order can have many items.order_items.product_idpoints toproducts.product_id. One product can appear in many orders.employees.manager_idpoints toemployees.employee_idin 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:
| Row | Why 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 105 | Cancelled |
| 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:
SELECT AVG(price) FROM products;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
shopdatabase has five tables:customers,products,orders,order_itemsandemployees. - 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_idpoints 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
799where MySQL shows799.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
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?
Each row of order_items is one product line in an order, with its quantity and unit_price.
2. What does orders.customer_id point to?
It is a foreign key: each order stores the ID of the customer who placed it.
3. Why does order_items store unit_price as well?
Prices change over time, so each order line records the price at the moment of sale.
4. The browser shows a price as 799. What does MySQL show for the same value?
The column is DECIMAL(10,2), so MySQL always prints two decimal places; SQLite prints the whole number.
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
