MySQLSetting Up a Sample Database

MySQL Sample Databases

The fastest way to learn SQL is to query real, interesting data. MySQL provides several official sample databases: the classic world database with countries and cities, the comprehensive Sakila DVD rental database, and the large-scale employees database.

Each offers different complexity levels and query patterns. This tutorial walks through all three — how to download and import them, and how to start writing meaningful queries.

The World Database

The world database is MySQL's simplest sample database. It contains three tables with real-world data about countries, cities, and languages — perfect for learning joins, aggregations, and filtering.

Schema overview:

  • Country: 239 rows — countries with population, area, GDP, life expectancy, government form, and more

  • City: 4,079 rows — cities with name, country code, district, and population

  • CountryLanguage: 984 rows — languages spoken in each country with percentage of speakers

Importing the World Database

Bash
# Download from dev.mysql.com (or use the direct URL)
wget https://downloads.mysql.com/docs/world-db.tar.gz

# Extract
tar -xzf world-db.tar.gz

# Import the SQL file
mysql -u root -p < world-db/world.sql

# Verify import
mysql -u root -p -e "USE world; SHOW TABLES;"
+-----------------+
| Tables_in_world |
+-----------------+
| city            |
| country         |
| countrylanguage |
+-----------------+
First Queries on World

SQL
USE world;

-- Explore the schema
DESCRIBE country;
DESCRIBE city;

-- Top 10 most populous countries
SELECT Name, Population, Continent
FROM country
ORDER BY Population DESC
LIMIT 10;

-- All cities in Canada
SELECT Name, District, Population
FROM city
WHERE CountryCode = 'CAN'
ORDER BY Population DESC;

-- Average life expectancy by continent
SELECT Continent, ROUND(AVG(LifeExpectancy), 1) AS avg_life_expectancy
FROM country
WHERE LifeExpectancy IS NOT NULL
GROUP BY Continent
ORDER BY avg_life_expectancy DESC;

-- Countries with more than 5 official languages
SELECT co.Name, COUNT(*) AS language_count
FROM country co
JOIN countrylanguage cl ON co.Code = cl.CountryCode
WHERE cl.IsOfficial = 'T'
GROUP BY co.Code, co.Name
HAVING language_count > 5
ORDER BY language_count DESC;
The Sakila Database

Sakila is MySQL's flagship sample database — a fictional DVD rental store. It features a realistic, normalized schema with 16 tables, views, stored procedures, and triggers. Sakila is the go-to database for learning advanced SQL: complex joins, aggregations, window functions, and query optimization.

Key tables:

Table

Rows (approx)

Description

film

1,000

Films with title, description, release year, rating, length

actor

200

Actors with first and last name

film_actor

5,462

Junction table linking films and actors (many-to-many)

category

16

Film categories (Action, Comedy, Drama, etc.)

film_category

1,000

Links films to their category

inventory

4,581

Physical DVD copies — each film can have multiple copies

rental

16,044

Rental transactions — which customer rented which inventory item

customer

599

Customers with address and store

staff

2

Store staff members

store

2

Physical store locations

payment

16,049

Payments for rentals

address

603

Addresses for customers, staff, stores

Importing Sakila

Bash
# Download the Sakila database
wget https://downloads.mysql.com/docs/sakila-db.tar.gz

# Extract
tar -xzf sakila-db.tar.gz

# Import schema first, then data
mysql -u root -p < sakila-db/sakila-schema.sql
mysql -u root -p < sakila-db/sakila-data.sql

# Verify
mysql -u root -p -e "USE sakila; SHOW TABLES;" | head -20
Exploring Sakila's Schema

SQL
USE sakila;

-- See all tables including views
SHOW FULL TABLES;

-- Explore a table's structure
DESCRIBE film;
DESCRIBE rental;

-- Show the CREATE TABLE statement for a complex table
SHOW CREATE TABLE rentalG
Practical Queries on Sakila

SQL
USE sakila;

-- Top 10 most rented films
SELECT f.title, COUNT(r.rental_id) AS times_rented
FROM film f
JOIN inventory i ON f.film_id = i.film_id
JOIN rental r ON i.inventory_id = r.inventory_id
GROUP BY f.film_id, f.title
ORDER BY times_rented DESC
LIMIT 10;

-- Revenue by film category
SELECT c.name AS category, SUM(p.amount) AS total_revenue
FROM category c
JOIN film_category fc ON c.category_id = fc.category_id
JOIN film f ON fc.film_id = f.film_id
JOIN inventory i ON f.film_id = i.film_id
JOIN rental r ON i.inventory_id = r.inventory_id
JOIN payment p ON r.rental_id = p.rental_id
GROUP BY c.category_id, c.name
ORDER BY total_revenue DESC;

-- Customers who have never rented
SELECT c.customer_id, c.first_name, c.last_name, c.email
FROM customer c
LEFT JOIN rental r ON c.customer_id = r.customer_id
WHERE r.rental_id IS NULL;

-- Average rental duration by rating
SELECT f.rating, ROUND(AVG(DATEDIFF(r.return_date, r.rental_date)), 1) AS avg_days
FROM film f
JOIN inventory i ON f.film_id = i.film_id
JOIN rental r ON i.inventory_id = r.inventory_id
WHERE r.return_date IS NOT NULL
GROUP BY f.rating
ORDER BY avg_days DESC;
Tip
Sakila also includes pre-built views like customer_list, film_list, and staff_list. Run SELECT * FROM customer_list LIMIT 5; to see them in action — they demonstrate how views simplify complex joins.
The Employees Database

The employees database is the largest of the three official samples — about 160MB of data with nearly 4 million rows across 6 tables. It simulates a company's HR system and is ideal for testing query performance, indexes, and handling real-scale data.

Schema:

Table

Rows (approx)

Description

employees

300,024

Employee records with name, birth date, hire date, gender

departments

9

Company departments

dept_emp

331,603

Which department each employee works in (with date ranges)

dept_manager

24

Department managers (with date ranges)

titles

443,308

Job titles held by each employee (with date ranges)

salaries

2,844,047

Salary history for each employee (with date ranges)

Importing the Employees Database

Bash
# Clone from GitHub (official source)
git clone https://github.com/datacharmer/test_db.git
cd test_db

# Import (takes 1-3 minutes due to size)
mysql -u root -p < employees.sql

# Verify the import with the included test script
mysql -u root -p -t < test_employees_md5.sql
+----------------------+
| INFO                 |
+----------------------+
| TESTING INSTALLATION |
+----------------------+
...
+--------------+
| computation  |
+--------------+
| OK           |
+--------------+
Queries on the Employees Database

SQL
USE employees;

-- Current employees in each department
SELECT d.dept_name, COUNT(*) AS employee_count
FROM departments d
JOIN dept_emp de ON d.dept_no = de.dept_no
WHERE de.to_date = '9999-01-01'  -- current assignments
GROUP BY d.dept_no, d.dept_name
ORDER BY employee_count DESC;

-- Average salary by department (current salaries only)
SELECT d.dept_name, ROUND(AVG(s.salary), 2) AS avg_salary
FROM departments d
JOIN dept_emp de ON d.dept_no = de.dept_no
JOIN salaries s ON de.emp_no = s.emp_no
WHERE de.to_date = '9999-01-01'
  AND s.to_date = '9999-01-01'
GROUP BY d.dept_no, d.dept_name
ORDER BY avg_salary DESC;

-- Employees who have held more than 3 different titles
SELECT e.emp_no, e.first_name, e.last_name, COUNT(*) AS title_count
FROM employees e
JOIN titles t ON e.emp_no = t.emp_no
GROUP BY e.emp_no, e.first_name, e.last_name
HAVING title_count > 3
ORDER BY title_count DESC
LIMIT 10;
Note
The employees database uses to_date = '9999-01-01' as a sentinel value to indicate "current" (no end date). This pattern — using a far-future date instead of NULL for open-ended ranges — is common in temporal data modeling.
Exploring Any Database Schema

Whether using a sample database or your own, these queries help you quickly understand any unfamiliar schema:

SQL
-- List all tables with row counts
SELECT
  TABLE_NAME,
  TABLE_ROWS,
  ROUND(DATA_LENGTH / 1024 / 1024, 2) AS data_mb,
  ROUND(INDEX_LENGTH / 1024 / 1024, 2) AS index_mb,
  TABLE_COMMENT
FROM information_schema.TABLES
WHERE TABLE_SCHEMA = 'sakila'
ORDER BY DATA_LENGTH DESC;

-- List all columns in a database with their types
SELECT
  TABLE_NAME,
  COLUMN_NAME,
  COLUMN_TYPE,
  IS_NULLABLE,
  COLUMN_DEFAULT,
  COLUMN_KEY
FROM information_schema.COLUMNS
WHERE TABLE_SCHEMA = 'sakila'
ORDER BY TABLE_NAME, ORDINAL_POSITION;

-- List all foreign key relationships in a database
SELECT
  TABLE_NAME,
  COLUMN_NAME,
  REFERENCED_TABLE_NAME,
  REFERENCED_COLUMN_NAME
FROM information_schema.KEY_COLUMN_USAGE
WHERE TABLE_SCHEMA = 'sakila'
  AND REFERENCED_TABLE_NAME IS NOT NULL
ORDER BY TABLE_NAME;

-- Find all indexes in a database
SELECT
  TABLE_NAME,
  INDEX_NAME,
  GROUP_CONCAT(COLUMN_NAME ORDER BY SEQ_IN_INDEX) AS columns,
  INDEX_TYPE,
  NON_UNIQUE
FROM information_schema.STATISTICS
WHERE TABLE_SCHEMA = 'sakila'
GROUP BY TABLE_NAME, INDEX_NAME
ORDER BY TABLE_NAME;
Tip
The information_schema database is a virtual read-only database that describes all objects on the server. Querying it is the programmatic way to inspect schemas — much more flexible than SHOW commands when you need to filter, join, or export schema information.