MySQL Mastery Guide
Complete guide from beginner to advanced
The Complete MySQL Mastery Guide
A comprehensive journey through MySQL fundamentals to advanced database administration and optimization.
Understanding MySQL: The World's Most Popular Open-Source Database
MySQL is an open-source relational database management system (RDBMS) that uses Structured Query Language (SQL). Originally created by MySQL AB in 1995 and now owned by Oracle Corporation, MySQL has become the world's most popular open-source database, powering millions of web applications worldwide.
MySQL is known for its reliability, performance, and ease of use. It's a core component of the LAMP stack (Linux, Apache, MySQL, PHP/Python/Perl) and is used by major companies like Facebook, Twitter, YouTube, and WordPress for handling massive amounts of data.
Did you know? The name "MySQ" is a combination of "My", the name of co-founder Michael Widenius's daughter, and "SQL", the structured query language. MySQL's dolphin logo is named "Sakila", which was chosen from a list of names suggested by users in a "Name the Dolphin" contest.
1. MySQL Basics & Fundamentals
Getting Started with MySQL
-- Connect to MySQL server
mysql -u root -p
-- Show available databases
SHOW DATABASES;
-- Create a new database
CREATE DATABASE company;
-- Use a specific database
USE company;
-- Show tables in current database
SHOW TABLES;
-- Show current user and database
SELECT USER(), DATABASE();
-- Get MySQL version
SELECT VERSION();
-- Basic SELECT statement
SELECT 'Hello, MySQL!' AS greeting;
-- Working with variables
SET @name = 'John Doe';
SELECT @name;
-- Mathematical operations
SELECT 10 + 5 AS addition,
10 - 5 AS subtraction,
10 * 5 AS multiplication,
10 / 3 AS division,
10 % 3 AS modulus;
-- String operations
SELECT CONCAT('Hello', ' ', 'World') AS greeting,
UPPER('hello') AS upper_case,
LOWER('HELLO') AS lower_case,
LENGTH('MySQL') AS string_length,
SUBSTRING('MySQL', 1, 3) AS substring;
-- Date and time functions
SELECT NOW() AS current_datetime,
CURDATE() AS current_date,
CURTIME() AS current_time,
DAYNAME(NOW()) AS day_name,
MONTHNAME(NOW()) AS month_name;Basic CRUD Operations
-- Create a sample table
CREATE TABLE employees (
id INT AUTO_INCREMENT PRIMARY KEY,
first_name VARCHAR(50) NOT NULL,
last_name VARCHAR(50) NOT NULL,
email VARCHAR(100) UNIQUE,
salary DECIMAL(10,2),
hire_date DATE,
department VARCHAR(50)
);
-- INSERT - Create new records
INSERT INTO employees (first_name, last_name, email, salary, hire_date, department)
VALUES
('John', 'Doe', 'john.doe@company.com', 50000.00, '2020-01-15', 'Engineering'),
('Jane', 'Smith', 'jane.smith@company.com', 60000.00, '2019-03-20', 'Marketing'),
('Bob', 'Johnson', 'bob.johnson@company.com', 55000.00, '2021-06-10', 'Engineering'),
('Alice', 'Brown', 'alice.brown@company.com', 52000.00, '2022-02-28', 'HR');
-- SELECT - Read data (various ways)
SELECT * FROM employees; -- All columns, all rows
SELECT first_name, last_name, salary
FROM employees; -- Specific columns
SELECT first_name, last_name, salary
FROM employees
WHERE department = 'Engineering'; -- With condition
SELECT first_name, last_name, salary
FROM employees
WHERE salary > 55000
ORDER BY salary DESC; -- With ordering
SELECT department, COUNT(*) as employee_count
FROM employees
GROUP BY department; -- Aggregation
-- UPDATE - Modify existing records
UPDATE employees
SET salary = salary * 1.10 -- 10% raise
WHERE department = 'Engineering';
UPDATE employees
SET email = 'john.newemail@company.com'
WHERE id = 1;
-- DELETE - Remove records
DELETE FROM employees
WHERE id = 4; -- Delete specific employee
-- Safe DELETE with SELECT first
SELECT * FROM employees WHERE last_name = 'Johnson'; -- Check what will be deleted
DELETE FROM employees WHERE last_name = 'Johnson';
-- TRUNCATE - Remove all records (faster than DELETE)
TRUNCATE TABLE employees; -- Resets auto_increment counter
-- DROP - Remove entire table
DROP TABLE employees;Working with Multiple Tables
-- Create related tables for a simple e-commerce system
CREATE TABLE customers (
customer_id INT AUTO_INCREMENT PRIMARY KEY,
first_name VARCHAR(50) NOT NULL,
last_name VARCHAR(50) NOT NULL,
email VARCHAR(100) UNIQUE NOT NULL,
phone VARCHAR(20),
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
CREATE TABLE products (
product_id INT AUTO_INCREMENT PRIMARY KEY,
product_name VARCHAR(100) NOT NULL,
description TEXT,
price DECIMAL(10,2) NOT NULL,
stock_quantity INT DEFAULT 0,
category VARCHAR(50),
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
CREATE TABLE orders (
order_id INT AUTO_INCREMENT PRIMARY KEY,
customer_id INT,
order_date TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
total_amount DECIMAL(10,2),
status ENUM('pending', 'processing', 'shipped', 'delivered', 'cancelled') DEFAULT 'pending',
FOREIGN KEY (customer_id) REFERENCES customers(customer_id)
);
CREATE TABLE order_items (
order_item_id INT AUTO_INCREMENT PRIMARY KEY,
order_id INT,
product_id INT,
quantity INT NOT NULL,
unit_price DECIMAL(10,2) NOT NULL,
FOREIGN KEY (order_id) REFERENCES orders(order_id),
FOREIGN KEY (product_id) REFERENCES products(product_id)
);
-- Insert sample data
INSERT INTO customers (first_name, last_name, email, phone) VALUES
('Alice', 'Johnson', 'alice.johnson@email.com', '555-0101'),
('Bob', 'Williams', 'bob.williams@email.com', '555-0102'),
('Carol', 'Davis', 'carol.davis@email.com', '555-0103');
INSERT INTO products (product_name, description, price, stock_quantity, category) VALUES
('Laptop', 'High-performance laptop', 999.99, 50, 'Electronics'),
('Mouse', 'Wireless computer mouse', 29.99, 100, 'Electronics'),
('Keyboard', 'Mechanical keyboard', 79.99, 75, 'Electronics'),
('Book', 'Programming guide', 39.99, 200, 'Books');
INSERT INTO orders (customer_id, total_amount, status) VALUES
(1, 1029.98, 'delivered'),
(2, 79.99, 'processing'),
(1, 29.99, 'shipped');
INSERT INTO order_items (order_id, product_id, quantity, unit_price) VALUES
(1, 1, 1, 999.99), -- Laptop
(1, 2, 1, 29.99), -- Mouse
(2, 3, 1, 79.99), -- Keyboard
(3, 2, 1, 29.99); -- Mouse
-- Query across multiple tables
SELECT
c.first_name,
c.last_name,
o.order_id,
o.order_date,
o.total_amount
FROM customers c
JOIN orders o ON c.customer_id = o.customer_id;
-- Complex query with multiple joins
SELECT
c.first_name,
c.last_name,
o.order_id,
p.product_name,
oi.quantity,
oi.unit_price,
(oi.quantity * oi.unit_price) as item_total
FROM customers c
JOIN orders o ON c.customer_id = o.customer_id
JOIN order_items oi ON o.order_id = oi.order_id
JOIN products p ON oi.product_id = p.product_id
WHERE o.status = 'delivered'
ORDER BY o.order_date DESC;2. Data Types & Operators
Numeric Data Types
-- Integer types
CREATE TABLE integer_types (
-- Signed integers (can be negative)
tiny_int TINYINT, -- 1 byte: -128 to 127
small_int SMALLINT, -- 2 bytes: -32768 to 32767
medium_int MEDIUMINT, -- 3 bytes: -8388608 to 8388607
regular_int INT, -- 4 bytes: -2147483648 to 2147483647
big_int BIGINT, -- 8 bytes: -2^63 to 2^63-1
-- Unsigned integers (only positive)
tiny_int_unsigned TINYINT UNSIGNED, -- 0 to 255
small_int_unsigned SMALLINT UNSIGNED, -- 0 to 65535
int_unsigned INT UNSIGNED, -- 0 to 4294967295
big_int_unsigned BIGINT UNSIGNED, -- 0 to 2^64-1
-- Boolean (alias for TINYINT(1))
is_active BOOLEAN, -- 0 or 1
is_verified TINYINT(1) -- Same as BOOLEAN
);
-- Decimal types for exact precision
CREATE TABLE decimal_types (
-- DECIMAL(precision, scale)
-- precision: total digits, scale: digits after decimal
price DECIMAL(10, 2), -- 10 digits total, 2 after decimal: 12345678.99
tax_rate DECIMAL(5, 4), -- 5 digits total, 4 after decimal: 0.0825
percentage DECIMAL(3, 0), -- 3 digits, no decimal: 100
-- Floating point (approximate)
float_num FLOAT, -- 4 bytes, single precision
double_num DOUBLE, -- 8 bytes, double precision
float_precise FLOAT(10, 2) -- With specified precision
);
-- Insert examples
INSERT INTO integer_types VALUES
(127, 32767, 8388607, 2147483647, 9223372036854775807,
255, 65535, 4294967295, 18446744073709551615, 1, 1);
INSERT INTO decimal_types VALUES
(12345.67, 0.0825, 100, 123.456, 12345.6789, 1234.56);
-- Numeric functions and operations
SELECT
10 + 5 AS addition,
10 - 5 AS subtraction,
10 * 5 AS multiplication,
10 / 3 AS division,
10 % 3 AS modulus,
POW(2, 3) AS power,
SQRT(25) AS square_root,
ROUND(15.756, 2) AS rounded,
FLOOR(15.756) AS floor_value,
CEILING(15.756) AS ceiling_value,
ABS(-15) AS absolute_value,
RAND() AS random_number;String and Text Data Types
-- String and text types
CREATE TABLE string_types (
-- Fixed-length strings (padded with spaces)
char_field CHAR(10), -- Always 10 characters, padded
-- Variable-length strings
varchar_field VARCHAR(255), -- Up to 255 characters
-- Text types for larger content
tiny_text TINYTEXT, -- 255 bytes
regular_text TEXT, -- 65,535 bytes
medium_text MEDIUMTEXT, -- 16,777,215 bytes
long_text LONGTEXT, -- 4,294,967,295 bytes
-- Binary data types
binary_field BINARY(10), -- Fixed-length binary
varbinary_field VARBINARY(255), -- Variable-length binary
blob_field BLOB, -- Binary large object
-- Enum and Set types
status ENUM('active', 'inactive', 'pending'), -- One value from list
tags SET('featured', 'new', 'sale', 'popular') -- Multiple values from list
);
-- Insert examples
INSERT INTO string_types VALUES
('Hello', 'World', 'Short text', 'Regular text content',
'Medium text content here', 'Very long text content...',
UNHEX('48656C6C6F'), UNHEX('576F726C64'), UNHEX('42696E617279'),
'active', 'featured,new');
-- String functions and operations
SELECT
-- Basic string functions
CONCAT('Hello', ' ', 'World') AS concatenation,
CONCAT_WS(', ', 'John', 'Doe', 'CEO') AS concat_with_separator,
LENGTH('MySQL') AS length,
CHAR_LENGTH('MySQL') AS char_length,
-- Case functions
UPPER('hello world') AS uppercase,
LOWER('HELLO WORLD') AS lowercase,
-- Substring and position
SUBSTRING('Hello World', 1, 5) AS substring,
LEFT('Hello World', 5) AS left_part,
RIGHT('Hello World', 5) AS right_part,
LOCATE('World', 'Hello World') AS position,
-- Trim and pad
TRIM(' Hello ') AS trimmed,
LTRIM(' Hello') AS left_trimmed,
RTRIM('Hello ') AS right_trimmed,
LPAD('Hello', 10, '*') AS left_padded,
RPAD('Hello', 10, '*') AS right_padded,
-- Replace and insert
REPLACE('Hello World', 'World', 'MySQL') AS replaced,
INSERT('Hello World', 7, 5, 'MySQL') AS inserted,
-- Formatting
REVERSE('Hello') AS reversed,
REPEAT('Ha', 3) AS repeated;
-- Pattern matching with LIKE
SELECT
first_name,
last_name
FROM employees
WHERE first_name LIKE 'J%'; -- Starts with J
SELECT
product_name
FROM products
WHERE product_name LIKE '%computer%'; -- Contains 'computer'
SELECT
email
FROM customers
WHERE email LIKE '%@gmail.com'; -- Ends with @gmail.comDate, Time, and JSON Data Types
-- Date and time types
CREATE TABLE datetime_types (
-- Date only
birth_date DATE,
-- Time only
meeting_time TIME,
-- Date and time combinations
created_at DATETIME,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
-- Year
birth_year YEAR,
-- JSON data type (MySQL 5.7+)
user_preferences JSON,
metadata JSON
);
-- Insert examples
INSERT INTO datetime_types VALUES
('1990-05-15', '14:30:00', '2023-01-15 10:30:00',
CURRENT_TIMESTAMP, 1990,
'{"theme": "dark", "notifications": true, "language": "en"}',
'{"created_by": "system", "version": 1}');
-- Date and time functions
SELECT
-- Current date/time
NOW() AS current_datetime,
CURDATE() AS current_date,
CURTIME() AS current_time,
CURRENT_TIMESTAMP AS current_ts,
-- Date parts
YEAR(NOW()) AS current_year,
MONTH(NOW()) AS current_month,
DAY(NOW()) AS current_day,
HOUR(NOW()) AS current_hour,
MINUTE(NOW()) AS current_minute,
SECOND(NOW()) AS current_second,
-- Date names
DAYNAME(NOW()) AS day_name,
MONTHNAME(NOW()) AS month_name,
-- Date arithmetic
DATE_ADD(NOW(), INTERVAL 7 DAY) AS next_week,
DATE_SUB(NOW(), INTERVAL 1 MONTH) AS last_month,
DATEDIFF('2023-12-31', '2023-01-01') AS days_diff,
-- Formatting
DATE_FORMAT(NOW(), '%W, %M %e, %Y') AS formatted_date,
TIME_FORMAT(NOW(), '%h:%i %p') AS formatted_time;
-- JSON functions (MySQL 5.7+)
SELECT
-- Create JSON
JSON_OBJECT('name', 'John', 'age', 30) AS json_object,
JSON_ARRAY('apple', 'banana', 'orange') AS json_array,
-- Extract from JSON
JSON_EXTRACT('{"name": "John", "age": 30}', '$.name') AS extracted_name,
user_preferences->>'$.theme' AS theme, -- Shortcut syntax
-- Modify JSON
JSON_SET('{"name": "John"}', '$.age', 30) AS json_set,
JSON_INSERT('{"name": "John"}', '$.city', 'NYC') AS json_insert,
JSON_REMOVE('{"name": "John", "age": 30}', '$.age') AS json_remove,
-- Search in JSON
JSON_SEARCH(user_preferences, 'one', 'dark') AS json_search,
JSON_CONTAINS(user_preferences, '"dark"', '$.theme') AS json_contains,
-- JSON keys and values
JSON_KEYS(user_preferences) AS json_keys,
JSON_LENGTH(user_preferences) AS json_length
FROM datetime_types;
-- Working with JSON data
UPDATE datetime_types
SET user_preferences = JSON_SET(
user_preferences,
'$.notifications', false,
'$.font_size', 'medium'
)
WHERE JSON_EXTRACT(user_preferences, '$.theme') = 'dark';
-- Query JSON data
SELECT *
FROM datetime_types
WHERE JSON_EXTRACT(user_preferences, '$.notifications') = true;3. SQL Queries & Joins
Advanced SELECT Queries
-- Sample data setup
CREATE TABLE employees (
emp_id INT PRIMARY KEY,
first_name VARCHAR(50),
last_name VARCHAR(50),
department VARCHAR(50),
salary DECIMAL(10,2),
hire_date DATE,
manager_id INT
);
INSERT INTO employees VALUES
(1, 'John', 'Doe', 'Engineering', 75000, '2020-01-15', NULL),
(2, 'Jane', 'Smith', 'Engineering', 80000, '2019-03-20', 1),
(3, 'Bob', 'Johnson', 'Marketing', 65000, '2021-06-10', 1),
(4, 'Alice', 'Brown', 'HR', 60000, '2022-02-28', 1),
(5, 'Charlie', 'Wilson', 'Engineering', 90000, '2018-11-05', NULL),
(6, 'Diana', 'Lee', 'Marketing', 70000, '2020-07-12', 5),
(7, 'Edward', 'Davis', 'Sales', 55000, '2023-01-08', 5),
(8, 'Fiona', 'Garcia', 'Engineering', 85000, '2019-09-15', 1);
-- WHERE clause with various operators
SELECT * FROM employees WHERE salary > 70000;
SELECT * FROM employees WHERE department IN ('Engineering', 'Marketing');
SELECT * FROM employees WHERE hire_date BETWEEN '2020-01-01' AND '2021-12-31';
SELECT * FROM employees WHERE last_name LIKE 'D%'; -- Starts with D
SELECT * FROM employees WHERE manager_id IS NULL;
-- Combining conditions
SELECT * FROM employees
WHERE department = 'Engineering'
AND salary > 80000
AND hire_date > '2019-01-01';
SELECT * FROM employees
WHERE department = 'Engineering'
OR department = 'Marketing'
AND salary BETWEEN 60000 AND 80000;
-- ORDER BY with multiple columns
SELECT first_name, last_name, salary, department
FROM employees
ORDER BY department ASC, salary DESC;
-- LIMIT and OFFSET for pagination
SELECT first_name, last_name, salary
FROM employees
ORDER BY salary DESC
LIMIT 5; -- Top 5 highest paid
SELECT first_name, last_name, salary
FROM employees
ORDER BY salary DESC
LIMIT 5 OFFSET 5; -- Next 5 (page 2)
-- DISTINCT for unique values
SELECT DISTINCT department FROM employees;
-- Aggregation functions
SELECT
COUNT(*) AS total_employees,
COUNT(DISTINCT department) AS total_departments,
AVG(salary) AS average_salary,
MAX(salary) AS max_salary,
MIN(salary) AS min_salary,
SUM(salary) AS total_salary_budget
FROM employees;
-- GROUP BY with HAVING
SELECT
department,
COUNT(*) AS employee_count,
AVG(salary) AS avg_salary,
MAX(salary) AS max_salary
FROM employees
GROUP BY department
HAVING AVG(salary) > 65000 -- Filter groups
ORDER BY avg_salary DESC;
-- CASE statements for conditional logic
SELECT
first_name,
last_name,
salary,
CASE
WHEN salary > 80000 THEN 'High'
WHEN salary BETWEEN 60000 AND 80000 THEN 'Medium'
ELSE 'Low'
END AS salary_grade,
CASE department
WHEN 'Engineering' THEN 'Tech'
WHEN 'Marketing' THEN 'Business'
ELSE 'Other'
END AS department_category
FROM employees
ORDER BY salary DESC;JOIN Operations
-- Create related tables
CREATE TABLE departments (
dept_id INT PRIMARY KEY,
dept_name VARCHAR(50) NOT NULL,
location VARCHAR(100)
);
CREATE TABLE employees (
emp_id INT PRIMARY KEY,
first_name VARCHAR(50),
last_name VARCHAR(50),
dept_id INT,
salary DECIMAL(10,2),
hire_date DATE,
FOREIGN KEY (dept_id) REFERENCES departments(dept_id)
);
CREATE TABLE projects (
project_id INT PRIMARY KEY,
project_name VARCHAR(100),
dept_id INT,
budget DECIMAL(12,2),
start_date DATE,
FOREIGN KEY (dept_id) REFERENCES departments(dept_id)
);
-- Insert sample data
INSERT INTO departments VALUES
(1, 'Engineering', 'New York'),
(2, 'Marketing', 'Chicago'),
(3, 'Sales', 'Los Angeles'),
(4, 'HR', 'Boston');
INSERT INTO employees VALUES
(1, 'John', 'Doe', 1, 75000, '2020-01-15'),
(2, 'Jane', 'Smith', 1, 80000, '2019-03-20'),
(3, 'Bob', 'Johnson', 2, 65000, '2021-06-10'),
(4, 'Alice', 'Brown', 2, 60000, '2022-02-28'),
(5, 'Charlie', 'Wilson', NULL, 90000, '2018-11-05'),
(6, 'Diana', 'Lee', 3, 70000, '2020-07-12');
INSERT INTO projects VALUES
(1, 'Website Redesign', 1, 50000, '2023-01-01'),
(2, 'Social Media Campaign', 2, 30000, '2023-02-15'),
(3, 'Product Launch', 1, 75000, '2023-03-01'),
(4, 'Market Research', 2, 25000, '2023-01-20'),
(5, 'Sales Training', 3, 15000, '2023-02-01');
-- INNER JOIN (most common)
SELECT
e.first_name,
e.last_name,
d.dept_name,
d.location
FROM employees e
INNER JOIN departments d ON e.dept_id = d.dept_id;
-- LEFT JOIN (all employees, even without department)
SELECT
e.first_name,
e.last_name,
d.dept_name
FROM employees e
LEFT JOIN departments d ON e.dept_id = d.dept_id;
-- RIGHT JOIN (all departments, even without employees)
SELECT
e.first_name,
e.last_name,
d.dept_name
FROM employees e
RIGHT JOIN departments d ON e.dept_id = d.dept_id;
-- FULL OUTER JOIN (simulated in MySQL)
SELECT
e.first_name,
e.last_name,
d.dept_name
FROM employees e
LEFT JOIN departments d ON e.dept_id = d.dept_id
UNION
SELECT
e.first_name,
e.last_name,
d.dept_name
FROM employees e
RIGHT JOIN departments d ON e.dept_id = d.dept_id;
-- CROSS JOIN (Cartesian product)
SELECT
e.first_name,
d.dept_name
FROM employees e
CROSS JOIN departments d;
-- SELF JOIN (employees and their managers example)
ALTER TABLE employees ADD COLUMN manager_id INT;
UPDATE employees SET manager_id = 1 WHERE emp_id IN (2,3,4);
UPDATE employees SET manager_id = 5 WHERE emp_id = 6;
SELECT
e.first_name AS employee_name,
e.last_name AS employee_last_name,
m.first_name AS manager_name,
m.last_name AS manager_last_name
FROM employees e
LEFT JOIN employees m ON e.manager_id = m.emp_id;
-- Multiple table JOIN
SELECT
e.first_name,
e.last_name,
d.dept_name,
p.project_name,
p.budget
FROM employees e
INNER JOIN departments d ON e.dept_id = d.dept_id
INNER JOIN projects p ON d.dept_id = p.dept_id
ORDER BY e.last_name, p.project_name;
-- Complex JOIN with aggregation
SELECT
d.dept_name,
COUNT(e.emp_id) AS employee_count,
COUNT(p.project_id) AS project_count,
AVG(e.salary) AS avg_salary,
SUM(p.budget) AS total_budget
FROM departments d
LEFT JOIN employees e ON d.dept_id = e.dept_id
LEFT JOIN projects p ON d.dept_id = p.dept_id
GROUP BY d.dept_id, d.dept_name
ORDER BY total_budget DESC;
-- JOIN with subquery
SELECT
e.first_name,
e.last_name,
e.salary,
dept_stats.avg_dept_salary
FROM employees e
INNER JOIN (
SELECT
dept_id,
AVG(salary) AS avg_dept_salary
FROM employees
GROUP BY dept_id
) dept_stats ON e.dept_id = dept_stats.dept_id
WHERE e.salary > dept_stats.avg_dept_salary;Subqueries and Common Table Expressions
-- Sample data setup
CREATE TABLE sales (
sale_id INT PRIMARY KEY,
product_id INT,
salesperson_id INT,
sale_date DATE,
quantity INT,
unit_price DECIMAL(10,2),
region VARCHAR(50)
);
INSERT INTO sales VALUES
(1, 101, 1, '2023-01-15', 2, 25.00, 'North'),
(2, 102, 2, '2023-01-16', 1, 50.00, 'South'),
(3, 101, 1, '2023-02-10', 3, 25.00, 'North'),
(4, 103, 3, '2023-02-15', 2, 75.00, 'East'),
(5, 102, 2, '2023-03-01', 1, 50.00, 'South'),
(6, 101, 3, '2023-03-05', 4, 25.00, 'West'),
(7, 104, 1, '2023-03-10', 1, 100.00, 'North');
-- Scalar subquery (returns single value)
SELECT
product_id,
quantity,
unit_price,
(SELECT AVG(unit_price) FROM sales) AS avg_unit_price
FROM sales
WHERE unit_price > (SELECT AVG(unit_price) FROM sales);
-- Correlated subquery (references outer query)
SELECT
s1.product_id,
s1.region,
s1.quantity
FROM sales s1
WHERE s1.quantity > (
SELECT AVG(s2.quantity)
FROM sales s2
WHERE s2.region = s1.region
);
-- Subquery in WHERE clause
SELECT *
FROM sales
WHERE salesperson_id IN (
SELECT salesperson_id
FROM sales
GROUP BY salesperson_id
HAVING SUM(quantity * unit_price) > 150
);
-- Subquery in FROM clause (derived table)
SELECT
region,
total_sales,
average_sale
FROM (
SELECT
region,
SUM(quantity * unit_price) AS total_sales,
AVG(quantity * unit_price) AS average_sale
FROM sales
GROUP BY region
) regional_stats
WHERE total_sales > 100;
-- EXISTS and NOT EXISTS
SELECT *
FROM sales s1
WHERE EXISTS (
SELECT 1
FROM sales s2
WHERE s2.product_id = s1.product_id
AND s2.quantity > 3
);
-- Common Table Expressions (CTEs) - MySQL 8.0+
WITH regional_sales AS (
SELECT
region,
SUM(quantity * unit_price) AS total_sales,
COUNT(*) AS transaction_count
FROM sales
GROUP BY region
),
sales_performance AS (
SELECT
region,
total_sales,
transaction_count,
total_sales / transaction_count AS avg_transaction_value
FROM regional_sales
)
SELECT
region,
total_sales,
transaction_count,
avg_transaction_value,
CASE
WHEN avg_transaction_value > 50 THEN 'High'
WHEN avg_transaction_value > 25 THEN 'Medium'
ELSE 'Low'
END AS performance_rating
FROM sales_performance
ORDER BY total_sales DESC;
-- Recursive CTE (MySQL 8.0+)
CREATE TABLE categories (
category_id INT PRIMARY KEY,
category_name VARCHAR(50),
parent_id INT
);
INSERT INTO categories VALUES
(1, 'Electronics', NULL),
(2, 'Computers', 1),
(3, 'Laptops', 2),
(4, 'Tablets', 2),
(5, 'Smartphones', 1),
(6, 'Gaming', NULL),
(7, 'Consoles', 6),
(8, 'Games', 6);
WITH RECURSIVE category_tree AS (
-- Anchor member
SELECT
category_id,
category_name,
parent_id,
0 AS level,
CAST(category_name AS CHAR(1000)) AS path
FROM categories
WHERE parent_id IS NULL
UNION ALL
-- Recursive member
SELECT
c.category_id,
c.category_name,
c.parent_id,
ct.level + 1,
CONCAT(ct.path, ' > ', c.category_name)
FROM categories c
INNER JOIN category_tree ct ON c.parent_id = ct.category_id
)
SELECT
category_id,
category_name,
level,
path
FROM category_tree
ORDER BY path;
-- Multiple CTEs in single query
WITH monthly_sales AS (
SELECT
YEAR(sale_date) AS sale_year,
MONTH(sale_date) AS sale_month,
SUM(quantity * unit_price) AS monthly_total
FROM sales
GROUP BY YEAR(sale_date), MONTH(sale_date)
),
running_totals AS (
SELECT
sale_year,
sale_month,
monthly_total,
SUM(monthly_total) OVER (
ORDER BY sale_year, sale_month
) AS running_total
FROM monthly_sales
)
SELECT
sale_year,
sale_month,
monthly_total,
running_total,
LAG(monthly_total) OVER (ORDER BY sale_year, sale_month) AS prev_month_sales,
(monthly_total - LAG(monthly_total) OVER (ORDER BY sale_year, sale_month))
/ LAG(monthly_total) OVER (ORDER BY sale_year, sale_month) * 100 AS growth_percentage
FROM running_totals
ORDER BY sale_year, sale_month;4. DDL & Database Design
Database Design and Normalization
-- Example: E-commerce database design
-- First Normal Form (1NF) - Atomic values, no repeating groups
CREATE TABLE customers_1nf (
customer_id INT PRIMARY KEY,
first_name VARCHAR(50),
last_name VARCHAR(50),
email VARCHAR(100),
phone VARCHAR(20)
);
CREATE TABLE orders_1nf (
order_id INT PRIMARY KEY,
customer_id INT,
order_date DATE,
-- Instead of having multiple product columns, we create separate table
FOREIGN KEY (customer_id) REFERENCES customers_1nf(customer_id)
);
CREATE TABLE order_items_1nf (
order_id INT,
product_id INT,
quantity INT,
unit_price DECIMAL(10,2),
PRIMARY KEY (order_id, product_id),
FOREIGN KEY (order_id) REFERENCES orders_1nf(order_id)
);
-- Second Normal Form (2NF) - No partial dependencies
-- Move product details to separate table
CREATE TABLE products_2nf (
product_id INT PRIMARY KEY,
product_name VARCHAR(100),
category_id INT,
price DECIMAL(10,2)
);
CREATE TABLE categories_2nf (
category_id INT PRIMARY KEY,
category_name VARCHAR(50),
description TEXT
);
-- Update order_items to reference products
ALTER TABLE order_items_1nf
ADD FOREIGN KEY (product_id) REFERENCES products_2nf(product_id);
ALTER TABLE products_2nf
ADD FOREIGN KEY (category_id) REFERENCES categories_2nf(category_id);
-- Third Normal Form (3NF) - No transitive dependencies
-- Remove derived and transitive dependencies
CREATE TABLE customers_3nf (
customer_id INT PRIMARY KEY,
first_name VARCHAR(50),
last_name VARCHAR(50),
email VARCHAR(100),
phone VARCHAR(20),
address_id INT -- Reference to address table
);
CREATE TABLE addresses (
address_id INT PRIMARY KEY,
street VARCHAR(100),
city VARCHAR(50),
state VARCHAR(50),
zip_code VARCHAR(20),
country VARCHAR(50)
);
-- Complete normalized e-commerce schema
CREATE TABLE customers (
customer_id INT AUTO_INCREMENT PRIMARY KEY,
first_name VARCHAR(50) NOT NULL,
last_name VARCHAR(50) NOT NULL,
email VARCHAR(100) UNIQUE NOT NULL,
phone VARCHAR(20),
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
CREATE TABLE addresses (
address_id INT AUTO_INCREMENT PRIMARY KEY,
customer_id INT NOT NULL,
address_type ENUM('billing', 'shipping') NOT NULL,
street VARCHAR(100) NOT NULL,
city VARCHAR(50) NOT NULL,
state VARCHAR(50) NOT NULL,
zip_code VARCHAR(20) NOT NULL,
country VARCHAR(50) DEFAULT 'USA',
is_default BOOLEAN DEFAULT FALSE,
FOREIGN KEY (customer_id) REFERENCES customers(customer_id)
);
CREATE TABLE categories (
category_id INT AUTO_INCREMENT PRIMARY KEY,
category_name VARCHAR(50) UNIQUE NOT NULL,
description TEXT,
parent_category_id INT,
FOREIGN KEY (parent_category_id) REFERENCES categories(category_id)
);
CREATE TABLE products (
product_id INT AUTO_INCREMENT PRIMARY KEY,
product_name VARCHAR(100) NOT NULL,
description TEXT,
sku VARCHAR(50) UNIQUE NOT NULL,
price DECIMAL(10,2) NOT NULL,
cost DECIMAL(10,2),
category_id INT NOT NULL,
stock_quantity INT DEFAULT 0,
is_active BOOLEAN DEFAULT TRUE,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
FOREIGN KEY (category_id) REFERENCES categories(category_id)
);
CREATE TABLE orders (
order_id INT AUTO_INCREMENT PRIMARY KEY,
customer_id INT NOT NULL,
order_date TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
total_amount DECIMAL(10,2) NOT NULL,
status ENUM('pending', 'confirmed', 'shipped', 'delivered', 'cancelled') DEFAULT 'pending',
shipping_address_id INT NOT NULL,
billing_address_id INT NOT NULL,
notes TEXT,
FOREIGN KEY (customer_id) REFERENCES customers(customer_id),
FOREIGN KEY (shipping_address_id) REFERENCES addresses(address_id),
FOREIGN KEY (billing_address_id) REFERENCES addresses(address_id)
);
CREATE TABLE order_items (
order_item_id INT AUTO_INCREMENT PRIMARY KEY,
order_id INT NOT NULL,
product_id INT NOT NULL,
quantity INT NOT NULL CHECK (quantity > 0),
unit_price DECIMAL(10,2) NOT NULL,
line_total DECIMAL(10,2) GENERATED ALWAYS AS (quantity * unit_price) STORED,
FOREIGN KEY (order_id) REFERENCES orders(order_id) ON DELETE CASCADE,
FOREIGN KEY (product_id) REFERENCES products(product_id),
UNIQUE KEY unique_order_product (order_id, product_id)
);
CREATE TABLE payments (
payment_id INT AUTO_INCREMENT PRIMARY KEY,
order_id INT NOT NULL,
payment_method ENUM('credit_card', 'paypal', 'bank_transfer') NOT NULL,
amount DECIMAL(10,2) NOT NULL,
payment_date TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
status ENUM('pending', 'completed', 'failed', 'refunded') DEFAULT 'pending',
transaction_id VARCHAR(100),
FOREIGN KEY (order_id) REFERENCES orders(order_id)
);
-- Indexes for performance
CREATE INDEX idx_customers_email ON customers(email);
CREATE INDEX idx_products_category ON products(category_id);
CREATE INDEX idx_orders_customer_date ON orders(customer_id, order_date);
CREATE INDEX idx_orders_status ON orders(status);
CREATE INDEX idx_order_items_order ON order_items(order_id);
CREATE INDEX idx_order_items_product ON order_items(product_id);Constraints and Data Integrity
-- Comprehensive constraints example
CREATE TABLE employees_constraints (
-- Primary key constraint
employee_id INT AUTO_INCREMENT PRIMARY KEY,
-- NOT NULL constraints
first_name VARCHAR(50) NOT NULL,
last_name VARCHAR(50) NOT NULL,
email VARCHAR(100) NOT NULL,
-- UNIQUE constraint
email VARCHAR(100) UNIQUE,
ssn VARCHAR(11) UNIQUE,
-- CHECK constraints (MySQL 8.0.16+)
age INT CHECK (age >= 18 AND age <= 65),
salary DECIMAL(10,2) CHECK (salary > 0),
-- ENUM constraint
gender ENUM('M', 'F', 'Other') NOT NULL,
status ENUM('active', 'inactive', 'suspended') DEFAULT 'active',
-- DEFAULT constraints
hire_date DATE DEFAULT (CURRENT_DATE),
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
-- Foreign key constraint
department_id INT,
manager_id INT,
FOREIGN KEY (department_id) REFERENCES departments(department_id),
FOREIGN KEY (manager_id) REFERENCES employees_constraints(employee_id)
);
-- Adding constraints to existing tables
ALTER TABLE employees_constraints
ADD CONSTRAINT chk_salary_range CHECK (salary BETWEEN 30000 AND 200000);
ALTER TABLE employees_constraints
ADD CONSTRAINT unique_employee_code UNIQUE (employee_code);
ALTER TABLE employees_constraints
MODIFY COLUMN phone VARCHAR(20) NOT NULL;
-- Complex check constraints
CREATE TABLE products_constraints (
product_id INT PRIMARY KEY,
product_name VARCHAR(100) NOT NULL,
price DECIMAL(10,2) NOT NULL CHECK (price >= 0),
cost DECIMAL(10,2) CHECK (cost >= 0),
markup DECIMAL(10,2) GENERATED ALWAYS AS (price - cost) STORED,
CONSTRAINT chk_price_greater_than_cost CHECK (price >= cost),
CONSTRAINT chk_valid_markup CHECK (markup >= 0)
);
-- Table-level constraints
CREATE TABLE project_assignments (
project_id INT,
employee_id INT,
role VARCHAR(50) NOT NULL,
start_date DATE NOT NULL,
end_date DATE,
allocation_percentage INT CHECK (allocation_percentage BETWEEN 0 AND 100),
-- Composite primary key
PRIMARY KEY (project_id, employee_id),
-- Foreign keys
FOREIGN KEY (project_id) REFERENCES projects(project_id) ON DELETE CASCADE,
FOREIGN KEY (employee_id) REFERENCES employees(employee_id) ON DELETE CASCADE,
-- Table-level check constraint
CONSTRAINT chk_valid_dates CHECK (end_date IS NULL OR end_date > start_date)
);
-- Demonstrating constraint violations
-- This will fail due to NOT NULL constraint
INSERT INTO employees_constraints (first_name, last_name, email)
VALUES (NULL, 'Doe', 'john.doe@email.com');
-- This will fail due to UNIQUE constraint
INSERT INTO employees_constraints (first_name, last_name, email)
VALUES ('John', 'Doe', 'existing@email.com');
INSERT INTO employees_constraints (first_name, last_name, email)
VALUES ('Jane', 'Smith', 'existing@email.com'); -- Duplicate email
-- This will fail due to CHECK constraint
INSERT INTO products_constraints (product_id, product_name, price, cost)
VALUES (1, 'Test Product', 50.00, 60.00); -- Price less than cost
-- This will fail due to FOREIGN KEY constraint
INSERT INTO project_assignments (project_id, employee_id, role, start_date)
VALUES (999, 1, 'Developer', '2023-01-01'); -- Invalid project_id
-- Handling constraints in applications
-- Using INSERT IGNORE to skip rows that violate constraints
INSERT IGNORE INTO employees_constraints (first_name, last_name, email)
VALUES
('John', 'Doe', 'john.doe@email.com'),
('Jane', 'Smith', 'john.doe@email.com'); -- This row will be skipped
-- Using ON DUPLICATE KEY UPDATE for handling duplicates
INSERT INTO employees_constraints (first_name, last_name, email)
VALUES ('John', 'Doe', 'john.doe@email.com')
ON DUPLICATE KEY UPDATE
first_name = VALUES(first_name),
last_name = VALUES(last_name);
-- Viewing constraint information
SELECT
TABLE_NAME,
CONSTRAINT_NAME,
CONSTRAINT_TYPE
FROM information_schema.TABLE_CONSTRAINTS
WHERE TABLE_SCHEMA = DATABASE();
-- Disabling and enabling constraints (be careful!)
SET FOREIGN_KEY_CHECKS = 0; -- Disable foreign key checks
-- Perform operations that might violate constraints
SET FOREIGN_KEY_CHECKS = 1; -- Re-enable foreign key checksViews, Indexes, and Stored Procedures
-- Creating views for simplified data access
CREATE VIEW employee_directory AS
SELECT
e.employee_id,
CONCAT(e.first_name, ' ', e.last_name) AS full_name,
e.email,
e.phone,
d.department_name,
m.first_name AS manager_first_name,
m.last_name AS manager_last_name,
e.hire_date
FROM employees e
LEFT JOIN departments d ON e.department_id = d.department_id
LEFT JOIN employees m ON e.manager_id = m.employee_id
WHERE e.status = 'active';
-- Updatable view
CREATE VIEW active_employees AS
SELECT
employee_id,
first_name,
last_name,
email,
department_id
FROM employees
WHERE status = 'active'
WITH CHECK OPTION; -- Ensures inserts/updates satisfy view condition
-- Materialized view (simulated with table)
CREATE TABLE department_stats (
department_id INT PRIMARY KEY,
department_name VARCHAR(50),
employee_count INT,
avg_salary DECIMAL(10,2),
total_budget DECIMAL(12,2),
last_updated TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
-- Indexes for performance optimization
-- Single-column indexes
CREATE INDEX idx_employees_last_name ON employees(last_name);
CREATE INDEX idx_employees_department ON employees(department_id);
CREATE INDEX idx_employees_hire_date ON employees(hire_date);
-- Composite indexes
CREATE INDEX idx_employees_name_department ON employees(last_name, first_name, department_id);
CREATE INDEX idx_orders_customer_date ON orders(customer_id, order_date DESC);
-- Unique indexes
CREATE UNIQUE INDEX idx_employees_email ON employees(email);
CREATE UNIQUE INDEX idx_products_sku ON products(sku);
-- Partial indexes (MySQL doesn't support directly, but we can use filtered approach)
CREATE INDEX idx_active_products ON products(product_id) WHERE is_active = TRUE;
-- Full-text search indexes
CREATE TABLE articles (
article_id INT PRIMARY KEY,
title VARCHAR(200),
content TEXT,
author VARCHAR(100),
published_date DATE,
FULLTEXT(title, content)
);
-- Using full-text search
SELECT
article_id,
title,
MATCH(title, content) AGAINST('database performance' IN NATURAL LANGUAGE MODE) AS relevance
FROM articles
WHERE MATCH(title, content) AGAINST('database performance' IN NATURAL LANGUAGE MODE)
ORDER BY relevance DESC;
-- Stored procedures
DELIMITER //
CREATE PROCEDURE GetEmployeeDetails(IN emp_id INT)
BEGIN
SELECT
e.employee_id,
CONCAT(e.first_name, ' ', e.last_name) AS full_name,
e.email,
e.phone,
d.department_name,
e.salary,
e.hire_date
FROM employees e
LEFT JOIN departments d ON e.department_id = d.department_id
WHERE e.employee_id = emp_id;
END //
CREATE PROCEDURE UpdateEmployeeSalary(
IN emp_id INT,
IN new_salary DECIMAL(10,2),
OUT old_salary DECIMAL(10,2),
OUT result_message VARCHAR(100)
)
BEGIN
DECLARE current_salary DECIMAL(10,2);
-- Get current salary
SELECT salary INTO current_salary
FROM employees
WHERE employee_id = emp_id;
IF current_salary IS NULL THEN
SET result_message = 'Employee not found';
ELSEIF new_salary <= 0 THEN
SET result_message = 'Invalid salary amount';
ELSE
-- Update salary
UPDATE employees
SET salary = new_salary
WHERE employee_id = emp_id;
SET old_salary = current_salary;
SET result_message = CONCAT('Salary updated from ', current_salary, ' to ', new_salary);
END IF;
END //
CREATE PROCEDURE GetDepartmentStatistics(IN dept_id INT)
BEGIN
SELECT
d.department_name,
COUNT(e.employee_id) AS employee_count,
AVG(e.salary) AS average_salary,
MIN(e.salary) AS min_salary,
MAX(e.salary) AS max_salary,
SUM(e.salary) AS total_budget
FROM departments d
LEFT JOIN employees e ON d.department_id = e.department_id
WHERE d.department_id = dept_id
GROUP BY d.department_id, d.department_name;
END //
DELIMITER ;
-- Using stored procedures
CALL GetEmployeeDetails(1);
CALL GetDepartmentStatistics(1);
SET @old_salary = 0;
SET @message = '';
CALL UpdateEmployeeSalary(1, 80000, @old_salary, @message);
SELECT @old_salary, @message;
-- Functions
DELIMITER //
CREATE FUNCTION CalculateAnnualBonus(
emp_id INT,
bonus_percentage DECIMAL(5,2)
)
RETURNS DECIMAL(10,2)
READS SQL DATA
DETERMINISTIC
BEGIN
DECLARE annual_salary DECIMAL(10,2);
DECLARE bonus_amount DECIMAL(10,2);
SELECT salary * 12 INTO annual_salary
FROM employees
WHERE employee_id = emp_id;
SET bonus_amount = annual_salary * (bonbonus_percentage / 100);
RETURN bonus_amount;
END //
CREATE FUNCTION GetEmployeeTenure(emp_id INT)
RETURNS INT
READS SQL DATA
DETERMINISTIC
BEGIN
DECLARE tenure_years INT;
SELECT TIMESTAMPDIFF(YEAR, hire_date, CURDATE()) INTO tenure_years
FROM employees
WHERE employee_id = emp_id;
RETURN tenure_years;
END //
DELIMITER ;
-- Using functions
SELECT
employee_id,
first_name,
last_name,
salary,
CalculateAnnualBonus(employee_id, 10) AS annual_bonus,
GetEmployeeTenure(employee_id) AS tenure_years
FROM employees;5. Advanced SQL Features
Window Functions
-- Sample data for window functions
CREATE TABLE sales_data (
sale_id INT PRIMARY KEY,
salesperson VARCHAR(50),
region VARCHAR(50),
sale_date DATE,
amount DECIMAL(10,2),
product_category VARCHAR(50)
);
INSERT INTO sales_data VALUES
(1, 'John Doe', 'North', '2023-01-15', 1000.00, 'Electronics'),
(2, 'Jane Smith', 'South', '2023-01-16', 1500.00, 'Furniture'),
(3, 'John Doe', 'North', '2023-02-10', 800.00, 'Electronics'),
(4, 'Bob Johnson', 'East', '2023-02-15', 2000.00, 'Appliances'),
(5, 'Jane Smith', 'South', '2023-03-01', 1200.00, 'Furniture'),
(6, 'John Doe', 'North', '2023-03-05', 900.00, 'Electronics'),
(7, 'Alice Brown', 'West', '2023-03-10', 1800.00, 'Appliances'),
(8, 'Bob Johnson', 'East', '2023-04-01', 2200.00, 'Electronics'),
(9, 'Jane Smith', 'South', '2023-04-05', 1300.00, 'Furniture'),
(10, 'John Doe', 'North', '2023-04-10', 1100.00, 'Electronics');
-- Ranking functions
SELECT
salesperson,
region,
sale_date,
amount,
-- Row number (unique sequential integers)
ROW_NUMBER() OVER (ORDER BY amount DESC) AS row_num,
-- Rank (with gaps for ties)
RANK() OVER (ORDER BY amount DESC) AS rank_amount,
-- Dense rank (no gaps for ties)
DENSE_RANK() OVER (ORDER BY amount DESC) AS dense_rank_amount,
-- Percent rank (relative rank 0-1)
PERCENT_RANK() OVER (ORDER BY amount DESC) AS percent_rank_amount
FROM sales_data;
-- Partitioned window functions
SELECT
salesperson,
region,
sale_date,
amount,
-- Row number within each region
ROW_NUMBER() OVER (PARTITION BY region ORDER BY amount DESC) AS region_row_num,
-- Rank within each salesperson
RANK() OVER (PARTITION BY salesperson ORDER BY amount DESC) AS salesperson_rank,
-- Running total by region
SUM(amount) OVER (
PARTITION BY region
ORDER BY sale_date
ROWS UNBOUNDED PRECEDING
) AS running_total_region,
-- Moving average (3-month)
AVG(amount) OVER (
PARTITION BY salesperson
ORDER BY sale_date
ROWS BETWEEN 2 PRECEDING AND CURRENT ROW
) AS moving_avg_3_month
FROM sales_data;
-- Aggregate window functions
SELECT
salesperson,
region,
sale_date,
amount,
-- Total and average by salesperson
SUM(amount) OVER (PARTITION BY salesperson) AS total_by_salesperson,
AVG(amount) OVER (PARTITION BY salesperson) AS avg_by_salesperson,
-- Percentage of salesperson total
amount / SUM(amount) OVER (PARTITION BY salesperson) * 100 AS pct_of_salesperson_total,
-- Comparison to region average
amount - AVG(amount) OVER (PARTITION BY region) AS diff_from_region_avg,
-- Cumulative percentage
SUM(amount) OVER (
PARTITION BY region
ORDER BY amount DESC
) / SUM(amount) OVER (PARTITION BY region) * 100 AS cumulative_pct
FROM sales_data;
-- LEAD and LAG for accessing previous/next rows
SELECT
salesperson,
sale_date,
amount,
-- Previous sale amount
LAG(amount) OVER (
PARTITION BY salesperson
ORDER BY sale_date
) AS prev_sale_amount,
-- Next sale amount
LEAD(amount) OVER (
PARTITION BY salesperson
ORDER BY sale_date
) AS next_sale_amount,
-- Difference from previous sale
amount - LAG(amount) OVER (
PARTITION BY salesperson
ORDER BY sale_date
) AS diff_from_prev,
-- Percentage growth from previous
(amount - LAG(amount) OVER (
PARTITION BY salesperson
ORDER BY sale_date
)) / LAG(amount) OVER (
PARTITION BY salesperson
ORDER BY sale_date
) * 100 AS growth_pct
FROM sales_data;
-- FIRST_VALUE and LAST_VALUE
SELECT
salesperson,
region,
sale_date,
amount,
-- First sale in region
FIRST_VALUE(amount) OVER (
PARTITION BY region
ORDER BY sale_date
) AS first_sale_in_region,
-- Best sale in region
FIRST_VALUE(amount) OVER (
PARTITION BY region
ORDER BY amount DESC
) AS best_sale_in_region,
-- Last sale in region
LAST_VALUE(amount) OVER (
PARTITION BY region
ORDER BY sale_date
RANGE BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING
) AS last_sale_in_region
FROM sales_data;
-- NTILE for dividing data into buckets
SELECT
salesperson,
amount,
-- Divide into 4 quartiles
NTILE(4) OVER (ORDER BY amount DESC) AS amount_quartile,
-- Divide salespeople into performance tiers
CASE
WHEN NTILE(4) OVER (ORDER BY SUM(amount) OVER (PARTITION BY salesperson) DESC) = 1 THEN 'Top Performer'
WHEN NTILE(4) OVER (ORDER BY SUM(amount) OVER (PARTITION BY salesperson) DESC) = 2 THEN 'High Performer'
WHEN NTILE(4) OVER (ORDER BY SUM(amount) OVER (PARTITION BY salesperson) DESC) = 3 THEN 'Medium Performer'
ELSE 'Low Performer'
END AS performance_tier
FROM sales_data;
-- Complex window function example: Sales trends
WITH monthly_sales AS (
SELECT
salesperson,
DATE_FORMAT(sale_date, '%Y-%m') AS sale_month,
SUM(amount) AS monthly_total
FROM sales_data
GROUP BY salesperson, DATE_FORMAT(sale_date, '%Y-%m')
)
SELECT
salesperson,
sale_month,
monthly_total,
-- Moving average of last 3 months
AVG(monthly_total) OVER (
PARTITION BY salesperson
ORDER BY sale_month
ROWS BETWEEN 2 PRECEDING AND CURRENT ROW
) AS moving_avg_3month,
-- Month-over-month growth
monthly_total - LAG(monthly_total) OVER (
PARTITION BY salesperson
ORDER BY sale_month
) AS mom_growth,
-- Percentage of yearly total
monthly_total / SUM(monthly_total) OVER (
PARTITION BY salesperson, YEAR(sale_date)
) * 100 AS pct_of_year,
-- Rank within month
RANK() OVER (
PARTITION BY sale_month
ORDER BY monthly_total DESC
) AS monthly_rank
FROM monthly_sales
ORDER BY salesperson, sale_month;Advanced Data Manipulation
-- UPSERT operations (INSERT ... ON DUPLICATE KEY UPDATE)
CREATE TABLE products_upsert (
product_id INT AUTO_INCREMENT PRIMARY KEY,
sku VARCHAR(50) UNIQUE NOT NULL,
product_name VARCHAR(100) NOT NULL,
price DECIMAL(10,2) NOT NULL,
stock_quantity INT DEFAULT 0,
last_updated TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
);
-- Basic UPSERT
INSERT INTO products_upsert (sku, product_name, price, stock_quantity)
VALUES ('LAPTOP-001', 'Gaming Laptop', 999.99, 50)
ON DUPLICATE KEY UPDATE
product_name = VALUES(product_name),
price = VALUES(price),
stock_quantity = VALUES(stock_quantity),
last_updated = CURRENT_TIMESTAMP;
-- UPSERT with multiple rows
INSERT INTO products_upsert (sku, product_name, price, stock_quantity)
VALUES
('MOUSE-001', 'Wireless Mouse', 29.99, 100),
('KEYBOARD-001', 'Mechanical Keyboard', 79.99, 75),
('MONITOR-001', '27-inch Monitor', 199.99, 30)
ON DUPLICATE KEY UPDATE
product_name = VALUES(product_name),
price = VALUES(price),
stock_quantity = stock_quantity + VALUES(stock_quantity); -- Add to existing stock
-- REPLACE (DELETE + INSERT)
REPLACE INTO products_upsert (sku, product_name, price, stock_quantity)
VALUES ('LAPTOP-001', 'Updated Gaming Laptop', 899.99, 60);
-- INSERT IGNORE (skip duplicates)
INSERT IGNORE INTO products_upsert (sku, product_name, price, stock_quantity)
VALUES
('LAPTOP-001', 'Another Laptop', 799.99, 40), -- This will be skipped
('TABLET-001', 'New Tablet', 299.99, 25); -- This will be inserted
-- Bulk operations with CTEs
WITH new_products AS (
SELECT 'LAPTOP-002' AS sku, 'Business Laptop' AS product_name, 799.99 AS price, 40 AS stock_quantity
UNION ALL
SELECT 'TABLET-002' AS sku, 'Drawing Tablet' AS product_name, 399.99 AS price, 20 AS stock_quantity
UNION ALL
SELECT 'PHONE-001' AS sku, 'Smartphone' AS product_name, 599.99 AS price, 100 AS stock_quantity
)
INSERT INTO products_upsert (sku, product_name, price, stock_quantity)
SELECT sku, product_name, price, stock_quantity
FROM new_products
ON DUPLICATE KEY UPDATE
product_name = VALUES(product_name),
price = VALUES(price),
stock_quantity = VALUES(stock_quantity);
-- Conditional updates with CASE
UPDATE products_upsert
SET price = CASE
WHEN stock_quantity > 50 THEN price * 0.9 -- 10% discount for high stock
WHEN stock_quantity < 10 THEN price * 1.1 -- 10% increase for low stock
ELSE price
END,
stock_quantity = CASE
WHEN product_name LIKE '%Laptop%' THEN stock_quantity + 10 -- Restock laptops
ELSE stock_quantity
END
WHERE price > 500;
-- Multi-table updates
CREATE TABLE product_categories (
product_id INT PRIMARY KEY,
category_name VARCHAR(50),
is_featured BOOLEAN DEFAULT FALSE
);
-- Update based on join
UPDATE products_upsert p
JOIN product_categories pc ON p.product_id = pc.product_id
SET p.price = p.price * 0.85 -- 15% discount for featured products
WHERE pc.is_featured = TRUE;
-- Update with subquery
UPDATE products_upsert
SET stock_quantity = stock_quantity + (
SELECT COALESCE(SUM(quantity), 0)
FROM order_items oi
WHERE oi.product_id = products_upsert.product_id
AND oi.created_date >= DATE_SUB(NOW(), INTERVAL 30 DAY)
)
WHERE product_id IN (SELECT product_id FROM product_categories WHERE is_featured = TRUE);
-- Advanced DELETE operations
-- Delete with join
DELETE p
FROM products_upsert p
LEFT JOIN order_items oi ON p.product_id = oi.product_id
WHERE p.stock_quantity = 0
AND oi.order_item_id IS NULL; -- Never been ordered
-- Delete using subquery
DELETE FROM products_upsert
WHERE product_id IN (
SELECT product_id
FROM (
SELECT product_id
FROM products_upsert
WHERE stock_quantity = 0
AND last_updated < DATE_SUB(NOW(), INTERVAL 6 MONTH)
) AS old_products
);
-- Temporary tables for complex operations
CREATE TEMPORARY TABLE temp_sales_summary AS
SELECT
salesperson,
COUNT(*) AS sale_count,
SUM(amount) AS total_sales,
AVG(amount) AS avg_sale_amount
FROM sales_data
GROUP BY salesperson;
-- Use temporary table in operations
UPDATE sales_commissions sc
JOIN temp_sales_summary tss ON sc.salesperson = tss.salesperson
SET sc.commission_amount = tss.total_sales * sc.commission_rate
WHERE sc.commission_calculated = FALSE;
-- Clean up temporary table
DROP TEMPORARY TABLE temp_sales_summary;
-- Data archiving with INSERT ... SELECT
CREATE TABLE sales_archive LIKE sales_data;
-- Archive old sales data
INSERT INTO sales_archive
SELECT *
FROM sales_data
WHERE sale_date < DATE_SUB(NOW(), INTERVAL 2 YEAR);
-- Delete archived data
DELETE FROM sales_data
WHERE sale_date < DATE_SUB(NOW(), INTERVAL 2 YEAR);
-- MERGE-like operations (using multiple statements)
START TRANSACTION;
-- Archive records that will be updated
INSERT INTO products_archive
SELECT * FROM products_upsert
WHERE sku IN ('LAPTOP-001', 'MOUSE-001');
-- Update the records
UPDATE products_upsert
SET price = price * 0.9,
last_updated = NOW()
WHERE sku IN ('LAPTOP-001', 'MOUSE-001');
COMMIT;
-- Complex data transformation example
-- Update product prices based on multiple factors
UPDATE products_upsert p
LEFT JOIN (
SELECT
product_id,
COUNT(*) AS order_count,
AVG(quantity) AS avg_order_quantity
FROM order_items
GROUP BY product_id
) oi_stats ON p.product_id = oi_stats.product_id
LEFT JOIN product_categories pc ON p.product_id = pc.product_id
SET p.price = CASE
WHEN pc.is_featured = TRUE AND oi_stats.order_count > 10 THEN p.price * 1.15 -- Popular featured: +15%
WHEN oi_stats.order_count > 20 THEN p.price * 1.10 -- Very popular: +10%
WHEN oi_stats.order_count < 5 AND p.stock_quantity > 50 THEN p.price * 0.85 -- Slow moving: -15%
WHEN p.stock_quantity < 10 THEN p.price * 1.05 -- Low stock: +5%
ELSE p.price
END,
p.last_updated = NOW()
WHERE p.price != CASE
WHEN pc.is_featured = TRUE AND oi_stats.order_count > 10 THEN p.price * 1.15
WHEN oi_stats.order_count > 20 THEN p.price * 1.10
WHEN oi_stats.order_count < 5 AND p.stock_quantity > 50 THEN p.price * 0.85
WHEN p.stock_quantity < 10 THEN p.price * 1.05
ELSE p.price
END; -- Only update if price actually changes6. Performance & Optimization
Query Optimization and Execution Plans
-- Sample large table for optimization examples
CREATE TABLE large_orders (
order_id INT AUTO_INCREMENT PRIMARY KEY,
customer_id INT NOT NULL,
order_date DATETIME NOT NULL,
total_amount DECIMAL(12,2) NOT NULL,
status ENUM('pending', 'processing', 'shipped', 'delivered', 'cancelled') NOT NULL,
shipping_method VARCHAR(50),
payment_method VARCHAR(50),
region VARCHAR(50),
salesperson_id INT,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
INDEX idx_customer_id (customer_id),
INDEX idx_order_date (order_date),
INDEX idx_status (status),
INDEX idx_region_status (region, status)
);
-- Insert sample data (1 million rows)
-- Note: In practice, you'd use a script to generate this data
INSERT INTO large_orders (customer_id, order_date, total_amount, status, shipping_method, payment_method, region, salesperson_id)
SELECT
FLOOR(RAND() * 10000) + 1 AS customer_id,
DATE_ADD('2020-01-01', INTERVAL FLOOR(RAND() * 1460) DAY) AS order_date,
ROUND(RAND() * 1000 + 10, 2) AS total_amount,
ELT(FLOOR(RAND() * 5) + 1, 'pending', 'processing', 'shipped', 'delivered', 'cancelled') AS status,
ELT(FLOOR(RAND() * 4) + 1, 'standard', 'express', 'overnight', 'international') AS shipping_method,
ELT(FLOOR(RAND() * 3) + 1, 'credit_card', 'paypal', 'bank_transfer') AS payment_method,
ELT(FLOOR(RAND() * 4) + 1, 'North', 'South', 'East', 'West') AS region,
FLOOR(RAND() * 100) + 1 AS salesperson_id
FROM
(SELECT 1 UNION SELECT 2 UNION SELECT 3 UNION SELECT 4 UNION SELECT 5) a,
(SELECT 1 UNION SELECT 2 UNION SELECT 3 UNION SELECT 4 UNION SELECT 5) b,
(SELECT 1 UNION SELECT 2 UNION SELECT 3 UNION SELECT 4 UNION SELECT 5) c,
(SELECT 1 UNION SELECT 2 UNION SELECT 3 UNION SELECT 4 UNION SELECT 5) d,
(SELECT 1 UNION SELECT 2 UNION SELECT 3 UNION SELECT 4 UNION SELECT 5) e,
(SELECT 1 UNION SELECT 2 UNION SELECT 3 UNION SELECT 4 UNION SELECT 5) f,
(SELECT 1 UNION SELECT 2 UNION SELECT 3 UNION SELECT 4 UNION SELECT 5) g,
(SELECT 1 UNION SELECT 2 UNION SELECT 3 UNION SELECT 4 UNION SELECT 5) h;
-- Analyze table statistics
ANALYZE TABLE large_orders;
-- Using EXPLAIN to understand query execution
EXPLAIN FORMAT=JSON
SELECT
customer_id,
COUNT(*) AS order_count,
SUM(total_amount) AS total_spent
FROM large_orders
WHERE order_date BETWEEN '2023-01-01' AND '2023-12-31'
AND status = 'delivered'
AND region = 'North'
GROUP BY customer_id
HAVING total_spent > 1000
ORDER BY total_spent DESC
LIMIT 10;
-- Simple EXPLAIN output
EXPLAIN
SELECT * FROM large_orders
WHERE customer_id = 1234
AND order_date > '2023-01-01';
-- Common performance issues and solutions
-- 1. Missing indexes
-- Slow query (full table scan)
EXPLAIN SELECT * FROM large_orders WHERE region = 'North' AND status = 'pending';
-- Add composite index
CREATE INDEX idx_region_status ON large_orders(region, status);
-- 2. Index not being used
-- Function on indexed column prevents index usage
EXPLAIN SELECT * FROM large_orders WHERE YEAR(order_date) = 2023;
-- Rewrite to use index
EXPLAIN SELECT * FROM large_orders WHERE order_date BETWEEN '2023-01-01' AND '2023-12-31';
-- 3. Using OR conditions
EXPLAIN SELECT * FROM large_orders WHERE region = 'North' OR region = 'South';
-- Rewrite using IN
EXPLAIN SELECT * FROM large_orders WHERE region IN ('North', 'South');
-- Or use UNION
EXPLAIN
SELECT * FROM large_orders WHERE region = 'North'
UNION ALL
SELECT * FROM large_orders WHERE region = 'South';
-- 4. Subquery optimization
-- Correlated subquery (slow)
EXPLAIN
SELECT customer_id, total_amount
FROM large_orders lo1
WHERE total_amount > (
SELECT AVG(total_amount)
FROM large_orders lo2
WHERE lo2.customer_id = lo1.customer_id
);
-- Rewrite with window function (MySQL 8.0+)
EXPLAIN
SELECT customer_id, total_amount
FROM (
SELECT
customer_id,
total_amount,
AVG(total_amount) OVER (PARTITION BY customer_id) AS avg_customer_amount
FROM large_orders
) AS customer_stats
WHERE total_amount > avg_customer_amount;
-- Query performance analysis
-- Enable performance schema (if not already enabled)
-- SET GLOBAL performance_schema = ON;
-- Check slow queries
SELECT
query,
db,
exec_count,
avg_latency,
max_latency,
rows_sent,
rows_examined
FROM sys.statement_analysis
ORDER BY avg_latency DESC
LIMIT 10;
-- Identify tables with full scans
SELECT
object_schema,
object_name,
index_name,
count_star,
avg_timer_wait
FROM performance_schema.table_io_waits_summary_by_table
WHERE index_name IS NULL -- Full table scans
ORDER BY count_star DESC
LIMIT 10;
-- Monitor index usage
SELECT
OBJECT_SCHEMA,
OBJECT_NAME,
INDEX_NAME,
COUNT_READ,
COUNT_FETCH,
COUNT_INSERT,
COUNT_UPDATE,
COUNT_DELETE
FROM performance_schema.table_io_waits_summary_by_index_usage
WHERE OBJECT_SCHEMA = DATABASE()
ORDER BY COUNT_READ DESC;
-- Query tuning techniques
-- 1. Force index usage (when optimizer chooses wrong index)
SELECT * FROM large_orders FORCE INDEX (idx_order_date)
WHERE order_date BETWEEN '2023-01-01' AND '2023-12-31'
AND customer_id = 1234;
-- 2. Use covering indexes
EXPLAIN
SELECT order_id, customer_id, order_date
FROM large_orders
WHERE order_date BETWEEN '2023-01-01' AND '2023-12-31';
-- Create covering index
CREATE INDEX idx_covering_order_date ON large_orders(order_date, customer_id, order_id);
-- 3. Batch processing for large updates
-- Instead of single large update, break into batches
SET @batch_size = 1000;
SET @offset = 0;
WHILE @offset < (SELECT COUNT(*) FROM large_orders) DO
UPDATE large_orders
SET status = 'processed'
WHERE status = 'pending'
LIMIT @batch_size
OFFSET @offset;
SET @offset = @offset + @batch_size;
-- Add delay if needed: DO SLEEP(1);
END WHILE;
-- 4. Use temporary tables for complex queries
CREATE TEMPORARY TABLE temp_customer_stats AS
SELECT
customer_id,
COUNT(*) AS order_count,
SUM(total_amount) AS total_spent,
MAX(order_date) AS last_order_date
FROM large_orders
WHERE order_date >= DATE_SUB(NOW(), INTERVAL 1 YEAR)
GROUP BY customer_id;
-- Then use the temporary table
SELECT
customer_id,
order_count,
total_spent
FROM temp_customer_stats
WHERE total_spent > 1000
ORDER BY total_spent DESC;
-- Clean up
DROP TEMPORARY TABLE temp_customer_stats;Indexing Strategies and Best Practices
-- Different index types and their use cases
-- 1. B-tree indexes (default for most cases)
CREATE TABLE products_indexed (
product_id INT AUTO_INCREMENT PRIMARY KEY,
sku VARCHAR(50) UNIQUE,
product_name VARCHAR(200) NOT NULL,
category_id INT NOT NULL,
price DECIMAL(10,2) NOT NULL,
stock_quantity INT DEFAULT 0,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
is_active BOOLEAN DEFAULT TRUE,
-- Single column indexes
INDEX idx_category_id (category_id),
INDEX idx_price (price),
INDEX idx_created_at (created_at),
-- Composite indexes
INDEX idx_category_price (category_id, price),
INDEX idx_active_created (is_active, created_at),
-- Covering indexes
INDEX idx_covering_product (category_id, price, product_name),
-- Partial indexes (simulated with condition)
INDEX idx_active_products (product_id) WHERE is_active = TRUE
);
-- 2. Unique indexes
CREATE UNIQUE INDEX idx_unique_sku ON products_indexed(sku);
CREATE UNIQUE INDEX idx_unique_product_name ON products_indexed(product_name);
-- 3. Full-text indexes (for text search)
CREATE TABLE articles_ft (
article_id INT AUTO_INCREMENT PRIMARY KEY,
title VARCHAR(500) NOT NULL,
content TEXT NOT NULL,
author VARCHAR(100),
published_date DATE,
FULLTEXT(title, content)
);
-- Using full-text search
SELECT
article_id,
title,
MATCH(title, content) AGAINST('database performance' IN NATURAL LANGUAGE MODE) AS relevance
FROM articles_ft
WHERE MATCH(title, content) AGAINST('database performance' IN NATURAL LANGUAGE MODE)
ORDER BY relevance DESC;
-- Boolean mode search
SELECT
article_id,
title
FROM articles_ft
WHERE MATCH(title, content) AGAINST('+MySQL -Oracle' IN BOOLEAN MODE);
-- 4. Spatial indexes (for geographic data)
CREATE TABLE locations (
location_id INT AUTO_INCREMENT PRIMARY KEY,
location_name VARCHAR(100) NOT NULL,
coordinates POINT NOT NULL,
SPATIAL INDEX(coordinates)
);
-- Insert spatial data
INSERT INTO locations (location_name, coordinates)
VALUES
('Office', ST_GeomFromText('POINT(40.7128 -74.0060)')),
('Warehouse', ST_GeomFromText('POINT(40.7589 -73.9851)'));
-- Spatial query
SELECT
location_name,
ST_AsText(coordinates) AS coordinates
FROM locations
WHERE ST_Distance_Sphere(coordinates, ST_GeomFromText('POINT(40.7128 -74.0060)')) < 1000;
-- Index analysis and maintenance
-- Show index statistics
SELECT
TABLE_NAME,
INDEX_NAME,
COLUMN_NAME,
SEQ_IN_INDEX,
CARDINALITY,
INDEX_TYPE
FROM information_schema.STATISTICS
WHERE TABLE_SCHEMA = DATABASE()
AND TABLE_NAME = 'products_indexed'
ORDER BY TABLE_NAME, INDEX_NAME, SEQ_IN_INDEX;
-- Analyze index usage
SELECT
OBJECT_TYPE,
OBJECT_SCHEMA,
OBJECT_NAME,
INDEX_NAME,
COUNT_READ,
COUNT_FETCH,
COUNT_INSERT,
COUNT_UPDATE,
COUNT_DELETE
FROM performance_schema.table_io_waits_summary_by_index_usage
WHERE OBJECT_SCHEMA = DATABASE()
AND OBJECT_NAME = 'products_indexed'
ORDER BY COUNT_READ DESC;
-- Identify unused indexes
SELECT
OBJECT_SCHEMA,
OBJECT_NAME,
INDEX_NAME
FROM performance_schema.table_io_waits_summary_by_index_usage
WHERE INDEX_NAME IS NOT NULL
AND COUNT_STAR = 0
AND OBJECT_SCHEMA NOT IN ('mysql', 'performance_schema', 'information_schema')
ORDER BY OBJECT_SCHEMA, OBJECT_NAME;
-- Index fragmentation analysis
SELECT
TABLE_NAME,
INDEX_NAME,
ROUND(stat_value * @@innodb_page_size / 1024 / 1024, 2) AS index_size_mb
FROM mysql.innodb_index_stats
WHERE database_name = DATABASE()
AND stat_name = 'size'
ORDER BY stat_value * @@innodb_page_size DESC;
-- Rebuild fragmented indexes
OPTIMIZE TABLE products_indexed;
-- Or for specific indexes
ALTER TABLE products_indexed ENGINE=InnoDB;
-- Indexing best practices
-- 1. Choose the right columns to index
-- Index columns used in WHERE, JOIN, ORDER BY, GROUP BY
CREATE INDEX idx_orders_customer_date ON large_orders(customer_id, order_date);
CREATE INDEX idx_products_category_active ON products_indexed(category_id, is_active);
-- 2. Composite index column order
-- Put most selective columns first
CREATE INDEX idx_selective_first ON large_orders(region, status, order_date);
-- 3. Avoid over-indexing
-- Each index adds overhead for INSERT/UPDATE/DELETE
-- 4. Use covering indexes
-- Include all columns needed in the query
CREATE INDEX idx_covering_order_details ON large_orders(
customer_id,
order_date,
total_amount,
status
);
-- 5. Consider index on foreign keys
ALTER TABLE order_items ADD INDEX idx_product_id (product_id);
-- 6. Partial indexes (where supported)
-- MySQL doesn't support true partial indexes, but we can simulate
CREATE INDEX idx_active_high_price
ON products_indexed(price)
WHERE is_active = TRUE AND price > 100;
-- Indexing anti-patterns
-- 1. Indexing low-cardinality columns
CREATE INDEX idx_gender ON users(gender); -- Usually bad (only 2-3 values)
-- 2. Too many indexes on frequently updated tables
-- Each index slows down INSERT/UPDATE/DELETE operations
-- 3. Indexing very long columns
CREATE INDEX idx_long_content ON articles(content(100)); -- Prefix index
-- 4. Redundant indexes
CREATE INDEX idx_a_b ON table1(a, b);
CREATE INDEX idx_a ON table1(a); -- Redundant, first index can be used
-- Advanced indexing scenarios
-- 1. Descending indexes (MySQL 8.0+)
CREATE INDEX idx_orders_date_desc ON large_orders(order_date DESC);
-- 2. Functional indexes (MySQL 8.0+)
CREATE INDEX idx_month_year ON large_orders((YEAR(order_date)), (MONTH(order_date)));
-- 3. Multi-valued indexes (MySQL 8.0.17+ for JSON arrays)
CREATE TABLE users_mv (
user_id INT PRIMARY KEY,
name VARCHAR(100),
tags JSON,
INDEX idx_tags ((CAST(tags AS CHAR(255) ARRAY)))
);
-- Index monitoring and tuning
-- Slow query log analysis
-- Enable slow query log
SET GLOBAL slow_query_log = 1;
SET GLOBAL long_query_time = 2; -- Queries taking more than 2 seconds
SET GLOBAL log_queries_not_using_indexes = 1;
-- Analyze slow queries
SELECT
query,
db,
exec_count,
avg_latency,
rows_sent,
rows_examined,
rows_sent / rows_examined AS selectivity
FROM sys.statement_analysis
WHERE rows_examined > 1000
ORDER BY avg_latency DESC
LIMIT 10;
-- Index suggestions based on query patterns
SELECT
TABLE_SCHEMA,
TABLE_NAME,
COLUMN_NAME,
DATA_TYPE,
COUNT(*) AS query_count
FROM information_schema.COLUMNS c
JOIN mysql.slow_log sl ON sl.db = c.TABLE_SCHEMA
WHERE sl.query_text LIKE CONCAT('%', c.COLUMN_NAME, '%')
AND sl.query_text LIKE '%WHERE%'
GROUP BY TABLE_SCHEMA, TABLE_NAME, COLUMN_NAME
ORDER BY query_count DESC;7. Database Administration
User Management and Security
-- User management and privileges
-- View current users
SELECT user, host, authentication_string FROM mysql.user;
-- Create new user
CREATE USER 'app_user'@'localhost' IDENTIFIED BY 'secure_password123';
CREATE USER 'readonly_user'@'%' IDENTIFIED BY 'readonly_pass';
CREATE USER 'admin_user'@'192.168.1.%' IDENTIFIED BY 'admin_pass';
-- Grant privileges
-- Application user (limited privileges)
GRANT SELECT, INSERT, UPDATE, DELETE ON company.* TO 'app_user'@'localhost';
-- Read-only user
GRANT SELECT ON company.* TO 'readonly_user'@'%';
-- Admin user (all privileges on specific database)
GRANT ALL PRIVILEGES ON company.* TO 'admin_user'@'192.168.1.%';
-- Grant specific table privileges
GRANT SELECT, INSERT ON company.products TO 'app_user'@'localhost';
GRANT SELECT ON company.salaries TO 'hr_user'@'localhost';
-- Grant stored procedure privileges
GRANT EXECUTE ON PROCEDURE company.CalculateBonus TO 'app_user'@'localhost';
-- View user privileges
SHOW GRANTS FOR 'app_user'@'localhost';
SELECT * FROM mysql.user WHERE user = 'app_user';
-- Revoke privileges
REVOKE DELETE ON company.* FROM 'app_user'@'localhost';
REVOKE ALL PRIVILEGES ON company.salaries FROM 'hr_user'@'localhost';
-- Modify users
ALTER USER 'app_user'@'localhost' IDENTIFIED BY 'new_secure_password123';
RENAME USER 'old_user'@'localhost' TO 'new_user'@'localhost';
-- Lock and unlock users
ALTER USER 'app_user'@'localhost' ACCOUNT LOCK;
ALTER USER 'app_user'@'localhost' ACCOUNT UNLOCK;
-- Drop users
DROP USER 'old_user'@'localhost';
-- Roles (MySQL 8.0+)
CREATE ROLE 'read_only';
CREATE ROLE 'data_entry';
CREATE ROLE 'admin';
-- Grant privileges to roles
GRANT SELECT ON company.* TO 'read_only';
GRANT SELECT, INSERT, UPDATE ON company.* TO 'data_entry';
GRANT ALL PRIVILEGES ON company.* TO 'admin';
-- Assign roles to users
GRANT 'read_only' TO 'readonly_user'@'%';
GRANT 'data_entry' TO 'app_user'@'localhost';
GRANT 'admin' TO 'admin_user'@'192.168.1.%';
-- Set default roles
SET DEFAULT ROLE 'data_entry' TO 'app_user'@'localhost';
-- Security best practices
-- Password validation plugin (MySQL 8.0+)
INSTALL COMPONENT 'file://component_validate_password';
SET GLOBAL validate_password.policy = MEDIUM;
-- Secure connections
CREATE USER 'secure_user'@'%' IDENTIFIED BY 'password' REQUIRE SSL;
-- Password expiration
ALTER USER 'app_user'@'localhost' PASSWORD EXPIRE INTERVAL 90 DAY;
-- Login attempts limitation
CREATE USER 'limited_user'@'localhost'
IDENTIFIED BY 'password'
WITH MAX_QUERIES_PER_HOUR 1000
MAX_UPDATES_PER_HOUR 100
MAX_CONNECTIONS_PER_HOUR 50
MAX_USER_CONNECTIONS 10;
-- Audit logging (Enterprise feature or using triggers)
CREATE TABLE security_audit (
audit_id INT AUTO_INCREMENT PRIMARY KEY,
user_name VARCHAR(50),
action_type VARCHAR(50),
table_name VARCHAR(50),
record_id INT,
old_values JSON,
new_values JSON,
changed_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
ip_address VARCHAR(45)
);
-- Example audit trigger
DELIMITER //
CREATE TRIGGER audit_employee_changes
AFTER UPDATE ON employees
FOR EACH ROW
BEGIN
IF NEW.salary != OLD.salary OR NEW.department_id != OLD.department_id THEN
INSERT INTO security_audit (
user_name,
action_type,
table_name,
record_id,
old_values,
new_values,
ip_address
)
VALUES (
USER(),
'UPDATE',
'employees',
NEW.employee_id,
JSON_OBJECT(
'salary', OLD.salary,
'department_id', OLD.department_id
),
JSON_OBJECT(
'salary', NEW.salary,
'department_id', NEW.department_id
),
-- Get client IP (requires additional setup)
'unknown'
);
END IF;
END //
DELIMITER ;
-- Database backup and recovery strategies
-- Logical backup with mysqldump
-- Command line: mysqldump -u root -p company > company_backup.sql
-- Partial backups
-- Backup specific tables
-- mysqldump -u root -p company employees departments > hr_tables.sql
-- Backup with conditions
-- mysqldump -u root -p company --where="salary>50000" employees > high_salary_employees.sql
-- Backup stored procedures and functions
-- mysqldump -u root -p --routines --no-data company > schema_and_routines.sql
-- Physical backup (file system level)
-- Requires MySQL to be stopped or using special tools
-- Point-in-time recovery with binary logs
-- Enable binary logging in my.cnf:
-- log_bin = /var/log/mysql/mysql-bin.log
-- expire_logs_days = 7
-- View binary logs
SHOW BINARY LOGS;
SHOW BINLOG EVENTS IN 'mysql-bin.000001';
-- Backup automation with events
CREATE EVENT daily_backup
ON SCHEDULE EVERY 1 DAY
STARTS '2023-01-01 02:00:00'
DO
BEGIN
-- This would typically call an external script
-- For demonstration, we'll just log
INSERT INTO backup_log (backup_type, status, completed_at)
VALUES ('scheduled', 'completed', NOW());
END;
-- Recovery procedures
-- Restore from logical backup
-- mysql -u root -p company < company_backup.sql
-- Point-in-time recovery
-- mysqlbinlog mysql-bin.000001 --start-datetime="2023-01-15 10:00:00" | mysql -u root -p
-- Disaster recovery planning
-- Replication setup
-- On master:
-- server_id = 1
-- log_bin = mysql-bin
-- binlog_do_db = company
-- On slave:
-- server_id = 2
-- relay_log = mysql-relay-bin
-- read_only = 1
-- Monitor replication
SHOW SLAVE STATUS\G
-- High availability with group replication (MySQL 8.0+)
-- Requires special configuration for multi-master replication
-- Performance monitoring
-- Show process list
SHOW PROCESSLIST;
SELECT * FROM information_schema.PROCESSLIST;
-- Show current locks
SHOW ENGINE INNODB STATUS;
SELECT * FROM information_schema.INNODB_LOCKS;
SELECT * FROM information_schema.INNODB_LOCK_WAITS;
-- Monitor table sizes
SELECT
TABLE_SCHEMA,
TABLE_NAME,
ROUND((DATA_LENGTH + INDEX_LENGTH) / 1024 / 1024, 2) AS size_mb,
TABLE_ROWS
FROM information_schema.TABLES
WHERE TABLE_SCHEMA = DATABASE()
ORDER BY (DATA_LENGTH + INDEX_LENGTH) DESC;
-- Monitor connection usage
SHOW STATUS LIKE 'Threads_connected';
SHOW VARIABLES LIKE 'max_connections';
-- Buffer pool usage
SHOW STATUS LIKE 'Innodb_buffer_pool%';
-- Maintenance operations
-- Optimize tables
OPTIMIZE TABLE large_orders, products, customers;
-- Check and repair tables
CHECK TABLE large_orders;
REPAIR TABLE large_orders; -- Only for MyISAM
-- Update statistics
ANALYZE TABLE large_orders, products, customers;
-- Clean up old data
PURGE BINARY LOGS BEFORE '2023-01-01 00:00:00';
-- Reset performance schema if needed
-- TRUNCATE TABLE performance_schema.events_waits_history_long;Backup, Recovery, and High Availability
-- Comprehensive backup and recovery strategies
-- 1. Logical backups with mysqldump
-- Full database backup
-- mysqldump -u root -p --single-transaction --routines --triggers --events company > full_backup.sql
-- Backup with compression
-- mysqldump -u root -p company | gzip > company_backup_$(date +%Y%m%d).sql.gz
-- Backup specific schemas
-- mysqldump -u root -p --databases company hr finance > multiple_dbs.sql
-- Backup with master data for replication
-- mysqldump -u root -p --single-transaction --master-data=2 company > backup_with_binlog_info.sql
-- 2. MySQL Enterprise Backup (for InnoDB)
-- Requires commercial license
-- mysqlbackup --backup-dir=/backups --user=root --password backup
-- 3. File system snapshots (LVM, ZFS, etc.)
-- Requires coordination with MySQL
-- Backup automation script example
-- Save as /scripts/mysql_backup.sh
DELIMITER //
CREATE PROCEDURE CreateBackupLog()
BEGIN
INSERT INTO backup_audit (
backup_type,
backup_file,
file_size,
status,
created_at
) VALUES (
'scheduled',
CONCAT('company_backup_', DATE_FORMAT(NOW(), '%Y%m%d_%H%i%s'), '.sql'),
0,
'started',
NOW()
);
END //
CREATE PROCEDURE UpdateBackupLog(IN backup_file VARCHAR(255), IN file_size BIGINT)
BEGIN
UPDATE backup_audit
SET status = 'completed',
file_size = file_size,
completed_at = NOW()
WHERE backup_file = backup_file;
END //
DELIMITER ;
-- Create backup audit table
CREATE TABLE backup_audit (
audit_id INT AUTO_INCREMENT PRIMARY KEY,
backup_type ENUM('scheduled', 'manual', 'emergency'),
backup_file VARCHAR(255),
file_size BIGINT,
status ENUM('started', 'completed', 'failed'),
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
completed_at TIMESTAMP NULL,
error_message TEXT
);
-- Recovery procedures
-- 1. Full database recovery
-- mysql -u root -p < full_backup.sql
-- 2. Partial recovery (specific tables)
-- mysql -u root -p company < employees_backup.sql
-- 3. Point-in-time recovery
-- Step 1: Restore last full backup
-- mysql -u root -p < full_backup.sql
-- Step 2: Apply binary logs since backup
-- mysqlbinlog --start-datetime="2023-01-15 10:00:00" mysql-bin.000001 mysql-bin.000002 | mysql -u root -p
-- 4. Table-level recovery from backup
-- Create temporary database for extraction
CREATE DATABASE temp_recovery;
-- mysql -u root -p temp_recovery < full_backup.sql
-- mysqldump -u root -p temp_recovery specific_table > recovered_table.sql
-- mysql -u root -p company < recovered_table.sql
-- DROP DATABASE temp_recovery;
-- Disaster recovery testing
-- Test recovery procedure
CREATE DATABASE recovery_test;
-- mysql -u root -p recovery_test < full_backup.sql
-- Verify data integrity
SELECT
TABLE_NAME,
TABLE_ROWS
FROM information_schema.TABLES
WHERE TABLE_SCHEMA = 'recovery_test';
-- Compare with production
SELECT
'production' as source,
COUNT(*) as row_count
FROM company.employees
UNION ALL
SELECT
'recovery_test' as source,
COUNT(*) as row_count
FROM recovery_test.employees;
-- High availability setups
-- 1. Master-Slave replication
-- On master configuration:
-- server_id = 1
-- log_bin = /var/log/mysql/mysql-bin.log
-- binlog_do_db = company
-- On slave configuration:
-- server_id = 2
-- relay_log = /var/log/mysql/mysql-relay-bin.log
-- read_only = 1
-- Setup replication
-- On master:
CREATE USER 'repl'@'%' IDENTIFIED BY 'repl_password';
GRANT REPLICATION SLAVE ON *.* TO 'repl'@'%';
SHOW MASTER STATUS;
-- On slave:
CHANGE MASTER TO
MASTER_HOST='master_host',
MASTER_USER='repl',
MASTER_PASSWORD='repl_password',
MASTER_LOG_FILE='mysql-bin.000001',
MASTER_LOG_POS=107;
START SLAVE;
-- Monitor replication
SHOW SLAVE STATUS\G
-- 2. Master-Master replication
-- Both servers act as masters
-- Requires careful conflict resolution
-- 3. Group Replication (MySQL 8.0+)
-- Provides built-in high availability
-- Configuration example:
-- plugin_load_add='group_replication.so'
-- group_replication_group_name="aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa"
-- group_replication_start_on_boot=off
-- group_replication_local_address= "node1:33061"
-- group_replication_group_seeds= "node1:33061,node2:33061,node3:33061"
-- Start group replication
SET GLOBAL group_replication_bootstrap_group=ON;
START GROUP_REPLICATION;
SET GLOBAL group_replication_bootstrap_group=OFF;
-- 4. MySQL InnoDB Cluster (MySQL 8.0+)
-- Provides complete high availability solution
-- Uses Group Replication + MySQL Router
-- Monitoring and maintenance
-- Replication monitoring
SELECT
channel_name,
service_state,
last_error_number,
last_error_message
FROM performance_schema.replication_connection_status;
-- Lag monitoring
SHOW SLAVE STATUS;
SELECT
NOW() - MAX(ts) AS replication_lag_seconds
FROM company.replication_heartbeat;
-- Automated failover procedures
-- Failover script concept
DELIMITER //
CREATE PROCEDURE PromoteSlaveToMaster()
BEGIN
-- Stop slave
STOP SLAVE;
-- Reset slave
RESET SLAVE ALL;
-- Make writable
SET GLOBAL read_only = 0;
-- Update application configuration
-- This would typically call an external script
-- Log the promotion
INSERT INTO failover_events (event_type, old_master, new_master, occurred_at)
VALUES ('promotion', @old_master_host, @@hostname, NOW());
END //
CREATE PROCEDURE SetupReplicationToNewMaster(IN new_master_host VARCHAR(255))
BEGIN
-- Stop current replication
STOP SLAVE;
-- Configure new master
CHANGE MASTER TO
MASTER_HOST = new_master_host,
MASTER_USER = 'repl',
MASTER_PASSWORD = 'repl_password',
MASTER_AUTO_POSITION = 1;
-- Start replication
START SLAVE;
-- Log the setup
INSERT INTO failover_events (event_type, new_master, occurred_at)
VALUES ('replication_setup', new_master_host, NOW());
END //
DELIMITER ;
-- Backup verification
-- Create backup verification procedure
DELIMITER //
CREATE PROCEDURE VerifyBackup(IN backup_file VARCHAR(255))
BEGIN
DECLARE backup_row_count INT;
DECLARE production_row_count INT;
-- Count rows in backup (would need to restore to temp database)
SELECT COUNT(*) INTO backup_row_count
FROM temp_restore.employees;
-- Count rows in production
SELECT COUNT(*) INTO production_row_count
FROM company.employees;
-- Compare
IF backup_row_count = production_row_count THEN
UPDATE backup_audit
SET status = 'verified',
completed_at = NOW()
WHERE backup_file = backup_file;
ELSE
UPDATE backup_audit
SET status = 'verification_failed',
error_message = CONCAT('Row count mismatch: backup=', backup_row_count, ' production=', production_row_count)
WHERE backup_file = backup_file;
END IF;
END //
DELIMITER ;
-- Emergency procedures
-- Database corruption recovery
-- Check for corruption
CHECK TABLE company.employees EXTENDED;
-- Repair if needed (for MyISAM)
REPAIR TABLE company.employees;
-- For InnoDB, use innodb_force_recovery
-- Set in my.cnf: innodb_force_recovery = 1 (then 2, 3, etc. if needed)
-- Data recovery from corrupted tables
-- Try to dump data
-- mysqldump -u root -p company employees > recovered_data.sql
-- Performance under recovery
-- Monitor recovery progress
SHOW ENGINE INNODB STATUS;
-- Set recovery settings
SET GLOBAL innodb_force_recovery = 1;
-- Restart MySQL and attempt data recovery
-- Then remove innodb_force_recovery and restart normally
-- Documentation and runbooks
-- Create maintenance documentation table
CREATE TABLE maintenance_runbooks (
runbook_id INT AUTO_INCREMENT PRIMARY KEY,
procedure_name VARCHAR(100),
description TEXT,
steps JSON,
estimated_duration INT,
risk_level ENUM('low', 'medium', 'high'),
last_performed TIMESTAMP NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
-- Example runbook entry
INSERT INTO maintenance_runbooks (
procedure_name,
description,
steps,
estimated_duration,
risk_level
) VALUES (
'Monthly Database Maintenance',
'Routine maintenance including optimization and statistics update',
JSON_ARRAY(
'Backup database',
'Check table integrity',
'Optimize fragmented tables',
'Update statistics',
'Verify backup',
'Document results'
),
120, -- 2 hours
'low'
);8. Programming & Integration
Stored Procedures, Functions, and Triggers
-- Stored procedures for complex business logic
DELIMITER //
-- Procedure with parameters and error handling
CREATE PROCEDURE ProcessEmployeeBonus(
IN emp_id INT,
IN bonus_percentage DECIMAL(5,2),
OUT bonus_amount DECIMAL(10,2),
OUT result_message VARCHAR(200)
)
BEGIN
DECLARE current_salary DECIMAL(10,2);
DECLARE employee_exists INT DEFAULT 0;
DECLARE EXIT HANDLER FOR SQLEXCEPTION
BEGIN
ROLLBACK;
SET result_message = 'Error processing bonus';
SET bonus_amount = 0;
END;
-- Check if employee exists
SELECT COUNT(*) INTO employee_exists
FROM employees
WHERE employee_id = emp_id;
IF employee_exists = 0 THEN
SET result_message = 'Employee not found';
SET bonus_amount = 0;
ELSEIF bonus_percentage <= 0 OR bonus_percentage > 50 THEN
SET result_message = 'Invalid bonus percentage';
SET bonus_amount = 0;
ELSE
START TRANSACTION;
-- Get current salary
SELECT salary INTO current_salary
FROM employees
WHERE employee_id = emp_id
FOR UPDATE; -- Lock the row
-- Calculate bonus
SET bonus_amount = (current_salary * 12) * (bonus_percentage / 100);
-- Insert bonus record
INSERT INTO employee_bonuses (
employee_id,
bonus_amount,
bonus_percentage,
calculated_date
) VALUES (
emp_id,
bonus_amount,
bonus_percentage,
CURDATE()
);
-- Update employee record
UPDATE employees
SET last_bonus_date = CURDATE(),
last_bonus_amount = bonus_amount
WHERE employee_id = emp_id;
COMMIT;
SET result_message = CONCAT(
'Bonus processed successfully: $',
ROUND(bonus_amount, 2)
);
END IF;
END //
-- Function for calculated fields
CREATE FUNCTION CalculateEmployeeTenure(emp_id INT)
RETURNS INT
READS SQL DATA
DETERMINISTIC
BEGIN
DECLARE tenure_years INT;
SELECT TIMESTAMPDIFF(YEAR, hire_date, CURDATE())
INTO tenure_years
FROM employees
WHERE employee_id = emp_id;
RETURN tenure_years;
END //
-- Function with complex logic
CREATE FUNCTION GetEmployeePerformanceRating(emp_id INT)
RETURNS VARCHAR(20)
READS SQL DATA
DETERMINISTIC
BEGIN
DECLARE avg_salary DECIMAL(10,2);
DECLARE emp_salary DECIMAL(10,2);
DECLARE performance_rating VARCHAR(20);
-- Get department average salary
SELECT AVG(salary) INTO avg_salary
FROM employees e
WHERE e.department_id = (
SELECT department_id
FROM employees
WHERE employee_id = emp_id
);
-- Get employee salary
SELECT salary INTO emp_salary
FROM employees
WHERE employee_id = emp_id;
-- Determine performance rating
IF emp_salary > avg_salary * 1.2 THEN
SET performance_rating = 'Excellent';
ELSEIF emp_salary > avg_salary THEN
SET performance_rating = 'Good';
ELSEIF emp_salary > avg_salary * 0.8 THEN
SET performance_rating = 'Average';
ELSE
SET performance_rating = 'Needs Improvement';
END IF;
RETURN performance_rating;
END //
-- Triggers for data integrity
-- Before insert trigger
CREATE TRIGGER before_employee_insert
BEFORE INSERT ON employees
FOR EACH ROW
BEGIN
-- Set default hire date if not provided
IF NEW.hire_date IS NULL THEN
SET NEW.hire_date = CURDATE();
END IF;
-- Validate email format
IF NEW.email NOT LIKE '%_@__%.__%' THEN
SIGNAL SQLSTATE '45000'
SET MESSAGE_TEXT = 'Invalid email format';
END IF;
-- Set created timestamp
SET NEW.created_at = NOW();
END //
-- After update trigger for audit trail
CREATE TRIGGER after_employee_update
AFTER UPDATE ON employees
FOR EACH ROW
BEGIN
-- Only log if important fields changed
IF NEW.salary != OLD.salary OR NEW.department_id != OLD.department_id THEN
INSERT INTO employee_audit (
employee_id,
changed_field,
old_value,
new_value,
changed_by,
changed_at
)
VALUES (
NEW.employee_id,
'salary',
OLD.salary,
NEW.salary,
USER(),
NOW()
);
IF NEW.department_id != OLD.department_id THEN
INSERT INTO employee_audit (
employee_id,
changed_field,
old_value,
new_value,
changed_by,
changed_at
)
VALUES (
NEW.employee_id,
'department_id',
OLD.department_id,
NEW.department_id,
USER(),
NOW()
);
END IF;
END IF;
END //
-- Instead of delete trigger (using soft delete)
CREATE TRIGGER before_employee_delete
BEFORE DELETE ON employees
FOR EACH ROW
BEGIN
-- Instead of deleting, mark as inactive
UPDATE employees
SET status = 'terminated',
termination_date = CURDATE()
WHERE employee_id = OLD.employee_id;
-- Prevent actual deletion
SIGNAL SQLSTATE '45000'
SET MESSAGE_TEXT = 'Employees are soft-deleted. Use termination procedure.';
END //
-- Complex stored procedure with cursors
CREATE PROCEDURE GenerateDepartmentReport(IN dept_id INT)
BEGIN
DECLARE done INT DEFAULT FALSE;
DECLARE emp_id INT;
DECLARE emp_name VARCHAR(100);
DECLARE emp_salary DECIMAL(10,2);
DECLARE total_salary DECIMAL(12,2) DEFAULT 0;
DECLARE employee_count INT DEFAULT 0;
DECLARE emp_cursor CURSOR FOR
SELECT employee_id, CONCAT(first_name, ' ', last_name), salary
FROM employees
WHERE department_id = dept_id
AND status = 'active';
DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = TRUE;
-- Create temporary table for report
CREATE TEMPORARY TABLE dept_report (
employee_id INT,
employee_name VARCHAR(100),
salary DECIMAL(10,2),
salary_rank INT,
department_avg DECIMAL(10,2)
);
OPEN emp_cursor;
read_loop: LOOP
FETCH emp_cursor INTO emp_id, emp_name, emp_salary;
IF done THEN
LEAVE read_loop;
END IF;
SET total_salary = total_salary + emp_salary;
SET employee_count = employee_count + 1;
INSERT INTO dept_report (employee_id, employee_name, salary)
VALUES (emp_id, emp_name, emp_salary);
END LOOP;
CLOSE emp_cursor;
-- Calculate rankings and averages
UPDATE dept_report
SET salary_rank = (
SELECT COUNT(*) + 1
FROM dept_report dr2
WHERE dr2.salary > dept_report.salary
),
department_avg = total_salary / NULLIF(employee_count, 0);
-- Return the report
SELECT * FROM dept_report ORDER BY salary_rank;
-- Clean up
DROP TEMPORARY TABLE dept_report;
END //
DELIMITER ;
-- Using stored procedures and functions
-- Call procedure with output parameters
SET @bonus_amt = 0;
SET @message = '';
CALL ProcessEmployeeBonus(1, 10, @bonus_amt, @message);
SELECT @bonus_amt, @message;
-- Use function in queries
SELECT
employee_id,
first_name,
last_name,
salary,
CalculateEmployeeTenure(employee_id) AS tenure_years,
GetEmployeePerformanceRating(employee_id) AS performance
FROM employees
WHERE status = 'active';
-- Event scheduler for automated tasks
-- Enable event scheduler
SET GLOBAL event_scheduler = ON;
-- Create event for daily maintenance
DELIMITER //
CREATE EVENT daily_maintenance
ON SCHEDULE EVERY 1 DAY
STARTS '2023-01-01 02:00:00'
DO
BEGIN
-- Update employee tenure
UPDATE employees
SET tenure_years = CalculateEmployeeTenure(employee_id)
WHERE status = 'active';
-- Archive old audit records
INSERT INTO employee_audit_archive
SELECT * FROM employee_audit
WHERE changed_at < DATE_SUB(NOW(), INTERVAL 1 YEAR);
DELETE FROM employee_audit
WHERE changed_at < DATE_SUB(NOW(), INTERVAL 1 YEAR);
-- Log maintenance completion
INSERT INTO maintenance_log (task_name, completed_at)
VALUES ('daily_maintenance', NOW());
END //
DELIMITER ;
-- View events
SHOW EVENTS;
SELECT * FROM information_schema.EVENTS;Application Integration and Best Practices
-- Application integration patterns
-- 1. Connection pooling and management
-- Use connection pools in your application
-- Set appropriate timeout values
SET GLOBAL wait_timeout = 600;
SET GLOBAL interactive_timeout = 600;
-- 2. Prepared statements for security and performance
-- Using prepared statements in MySQL
PREPARE employee_stmt FROM
'SELECT * FROM employees WHERE department_id = ? AND salary > ?';
SET @dept_id = 1;
SET @min_salary = 50000;
EXECUTE employee_stmt USING @dept_id, @min_salary;
DEALLOCATE PREPARE employee_stmt;
-- 3. Transaction management
-- Basic transaction
START TRANSACTION;
UPDATE accounts SET balance = balance - 100 WHERE account_id = 1;
UPDATE accounts SET balance = balance + 100 WHERE account_id = 2;
-- Check for errors in application code
-- If no errors:
COMMIT;
-- If errors:
ROLLBACK;
-- 4. Error handling in stored procedures
DELIMITER //
CREATE PROCEDURE TransferFunds(
IN from_account INT,
IN to_account INT,
IN amount DECIMAL(10,2),
OUT success BOOLEAN,
OUT error_msg VARCHAR(200)
)
BEGIN
DECLARE from_balance DECIMAL(10,2);
DECLARE EXIT HANDLER FOR SQLEXCEPTION
BEGIN
ROLLBACK;
SET success = FALSE;
SET error_msg = 'Database error during transfer';
END;
START TRANSACTION;
-- Check if from account exists and has sufficient funds
SELECT balance INTO from_balance
FROM accounts
WHERE account_id = from_account
FOR UPDATE;
IF from_balance IS NULL THEN
ROLLBACK;
SET success = FALSE;
SET error_msg = 'Source account not found';
ELSEIF from_balance < amount THEN
ROLLBACK;
SET success = FALSE;
SET error_msg = 'Insufficient funds';
ELSE
-- Perform transfer
UPDATE accounts SET balance = balance - amount
WHERE account_id = from_account;
UPDATE accounts SET balance = balance + amount
WHERE account_id = to_account;
-- Log the transaction
INSERT INTO transactions (from_account, to_account, amount, transaction_date)
VALUES (from_account, to_account, amount, NOW());
COMMIT;
SET success = TRUE;
SET error_msg = NULL;
END IF;
END //
DELIMITER ;
-- 5. Bulk operations for better performance
-- Bulk insert
INSERT INTO order_items (order_id, product_id, quantity, unit_price)
VALUES
(1, 101, 2, 25.00),
(1, 102, 1, 50.00),
(1, 103, 3, 15.00);
-- Bulk update using CASE
UPDATE products
SET price = CASE
WHEN category_id = 1 THEN price * 0.9 -- 10% discount
WHEN category_id = 2 THEN price * 1.1 -- 10% increase
ELSE price
END,
stock_quantity = CASE
WHEN stock_quantity < 10 THEN stock_quantity + 20 -- Restock
ELSE stock_quantity
END;
-- 6. Pagination for large result sets
-- Efficient pagination using keyset (cursor) pagination
SELECT *
FROM orders
WHERE order_id > ? -- Last seen order_id
AND customer_id = ?
ORDER BY order_id
LIMIT 20;
-- Traditional LIMIT/OFFSET (less efficient for large offsets)
SELECT *
FROM orders
WHERE customer_id = ?
ORDER BY order_date DESC
LIMIT 20 OFFSET 40; -- Page 3
-- 7. Full-text search integration
-- Create full-text index
CREATE TABLE product_search (
product_id INT PRIMARY KEY,
product_name VARCHAR(200),
description TEXT,
category VARCHAR(100),
FULLTEXT(product_name, description, category)
);
-- Search with relevance scoring
SELECT
product_id,
product_name,
MATCH(product_name, description, category) AGAINST('wireless bluetooth speaker' IN NATURAL LANGUAGE MODE) AS relevance
FROM product_search
WHERE MATCH(product_name, description, category) AGAINST('wireless bluetooth speaker' IN NATURAL LANGUAGE MODE)
ORDER BY relevance DESC;
-- 8. JSON data handling
-- Store and query JSON data
CREATE TABLE user_preferences (
user_id INT PRIMARY KEY,
preferences JSON,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
INSERT INTO user_preferences (user_id, preferences)
VALUES (1, '{
"theme": "dark",
"notifications": {
"email": true,
"push": false,
"sms": true
},
"language": "en",
"font_size": "medium"
}');
-- Query JSON data
SELECT
user_id,
preferences->>'$.theme' AS theme,
preferences->>'$.notifications.email' AS email_notifications,
JSON_EXTRACT(preferences, '$.language') AS language
FROM user_preferences
WHERE preferences->>'$.theme' = 'dark';
-- Update JSON data
UPDATE user_preferences
SET preferences = JSON_SET(
preferences,
'$.theme', 'light',
'$.font_size', 'large'
)
WHERE user_id = 1;
-- 9. Application-level caching strategies
-- Query result caching (using application cache)
-- Cache frequently accessed, rarely changed data
SELECT
product_id,
product_name,
price,
category
FROM products
WHERE is_active = TRUE
AND category_id = ?;
-- Cache this result for 1 hour in application cache
-- 10. Monitoring and logging
-- Create application log table
CREATE TABLE application_logs (
log_id INT AUTO_INCREMENT PRIMARY KEY,
log_level ENUM('DEBUG', 'INFO', 'WARN', 'ERROR'),
message TEXT,
context JSON,
user_id INT,
ip_address VARCHAR(45),
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
INDEX idx_log_level (log_level),
INDEX idx_created_at (created_at)
);
-- Log slow queries
INSERT INTO application_logs (log_level, message, context, created_at)
SELECT
'WARN',
'Slow query detected',
JSON_OBJECT(
'query', query,
'execution_time', query_time,
'rows_examined', rows_examined
),
NOW()
FROM mysql.slow_log
WHERE query_time > 10; -- Queries taking more than 10 seconds
-- 11. Database versioning and migrations
-- Create migrations table
CREATE TABLE schema_migrations (
version VARCHAR(50) PRIMARY KEY,
description TEXT,
applied_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
-- Example migration
INSERT INTO schema_migrations (version, description)
VALUES ('20230115001', 'Add email verification to users table');
ALTER TABLE users
ADD COLUMN email_verified BOOLEAN DEFAULT FALSE,
ADD COLUMN verification_token VARCHAR(100);
-- 12. Data validation and sanitization
-- Use constraints and triggers for data validation
ALTER TABLE users
ADD CONSTRAINT chk_valid_email
CHECK (email REGEXP '^[A-Za-z0-9._%-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,4}$');
-- Trigger for additional validation
DELIMITER //
CREATE TRIGGER validate_user_data
BEFORE INSERT ON users
FOR EACH ROW
BEGIN
-- Ensure username is alphanumeric
IF NEW.username REGEXP '[^a-zA-Z0-9]' THEN
SIGNAL SQLSTATE '45000'
SET MESSAGE_TEXT = 'Username must be alphanumeric';
END IF;
-- Set default values
IF NEW.created_at IS NULL THEN
SET NEW.created_at = NOW();
END IF;
END //
DELIMITER ;
-- 13. Performance monitoring integration
-- Create performance metrics table
CREATE TABLE performance_metrics (
metric_id INT AUTO_INCREMENT PRIMARY KEY,
metric_name VARCHAR(100),
metric_value DECIMAL(15,4),
recorded_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
INDEX idx_metric_name (metric_name),
INDEX idx_recorded_at (recorded_at)
);
-- Log query performance
INSERT INTO performance_metrics (metric_name, metric_value)
SELECT
'query_execution_time',
AVG(query_time)
FROM mysql.slow_log
WHERE start_time > DATE_SUB(NOW(), INTERVAL 1 HOUR);
-- 14. Backup integration
-- Create backup status table
CREATE TABLE backup_status (
backup_id INT AUTO_INCREMENT PRIMARY KEY,
backup_type ENUM('FULL', 'INCREMENTAL', 'LOG'),
status ENUM('STARTED', 'COMPLETED', 'FAILED'),
file_path VARCHAR(500),
file_size BIGINT,
started_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
completed_at TIMESTAMP NULL,
error_message TEXT
);
-- Update backup status
INSERT INTO backup_status (backup_type, status, started_at)
VALUES ('FULL', 'STARTED', NOW());
-- After backup completes
UPDATE backup_status
SET status = 'COMPLETED',
completed_at = NOW(),
file_size = 1024000 -- Example size
WHERE backup_id = LAST_INSERT_ID();
-- 15. Security best practices
-- Use application-specific users with limited privileges
CREATE USER 'web_app'@'%' IDENTIFIED BY 'secure_password';
GRANT SELECT, INSERT, UPDATE ON company.* TO 'web_app'@'%';
GRANT EXECUTE ON PROCEDURE company.TransferFunds TO 'web_app'@'%';
-- Enable SSL for secure connections
ALTER USER 'web_app'@'%' REQUIRE SSL;
-- Regular security audits
CREATE TABLE security_audit (
audit_id INT AUTO_INCREMENT PRIMARY KEY,
action_type VARCHAR(50),
user_name VARCHAR(50),
table_name VARCHAR(50),
query_text TEXT,
ip_address VARCHAR(45),
audit_timestamp TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);💻 Hands-On Practice Exercises
Beginner Level
- 1Create a database for a library system with books, authors, and members
- 2Write queries to find the most borrowed books and active members
- 3Design a student grade management system with courses and grades
- 4Practice basic CRUD operations with proper constraints
- 5Create views for commonly accessed data combinations
Intermediate Level
- 1Design and normalize an e-commerce database with products, orders, and customers
- 2Write complex queries with multiple joins and aggregations
- 3Create stored procedures for business logic like order processing
- 4Implement triggers for audit trails and data validation
- 5Optimize queries using proper indexing strategies
Advanced Level
- 1Design a multi-tenant SaaS application database
- 2Implement database partitioning for large tables
- 3Set up master-slave replication for high availability
- 4Create comprehensive backup and recovery procedures
- 5Design and implement a data warehouse with star schema
🛠️ Essential MySQL Tools
MySQL Workbench
Visual tool for database design, development, and administration
- Visual SQL Development
- Database Design & Modeling
- Server Administration
- Data Migration
GUI for writing queries, designing ER diagrams, and managing database servers
phpMyAdmin
Web-based MySQL administration tool
- Web Interface
- User Management
- Import/Export
- Query Browser
Browser-based database management, ideal for web hosting environments
MySQL Shell
Advanced client and code editor for MySQL
- JavaScript & Python Support
- X DevAPI
- AdminAPI
- SQL & NoSQL
Command-line interface with modern features for developers and DBAs
Percona Toolkit
Advanced command-line tools for MySQL
- Query Analysis
- Schema Management
- Data Synchronization
- Performance Tools
Expert tools for database optimization, monitoring, and maintenance
🎯 MySQL Career Paths
Database Developer
Stored Procedures, Query Optimization, Database Design
Database Administrator
Performance Tuning, Backup/Recovery, Security, High Availability
Data Architect
Database Design, Data Modeling, Enterprise Architecture
Continue Your MySQL Journey!
You've now explored the comprehensive landscape of MySQL database management, from basic SQL queries to advanced administration and optimization techniques. MySQL continues to be one of the world's most popular and reliable database systems.
Continue exploring advanced topics like database clustering, cloud database services, big data integration, and stay updated with the latest MySQL features and best practices in the ever-evolving database landscape.