BaseCodeByte
MySQL

MySQL Lessons

Work through the complete MySQL stack: querying, joins, indexes, schema design, constraints, views and stored procedures.

Course outline

Tracks and lessons

Track 01beginner

Module 1: MySQL Basics

Work through the complete MYSQL stack: querying, joins, indexes, schema design, constraints, views, stored.

01MySQL SQLWhat is MySQL? MySQL is the world's most popular open-source relational database management system (RDBMS). It was created in. SQL (Structured Query Language) is the language. MySQL is a. Open-source — free to use under GPL license Cross-platform — Wbeginner

What is MySQL? MySQL is the world's most popular open-source relational database management system (RDBMS). It was created in. SQL (Structured Query Language) is the language. MySQL is a. Open-source — free to use under GPL license Cross-platform — W

Example
-- Connect via CLI
mysql -u root -p
mysql -u username -p -h hostname database_name

-- Show version
SELECT VERSION();

-- Show current user
SELECT USER();
Quiz

MySQL is:

SQL vs MySQL:

MySQL's default storage engine is:

02MySQL SELECTThe SELECT Statement SELECT is the most fundamental MySQL command — it retrieves data from one or more tables. MySQL processes clauses in this order (different from how you. FROM / JOIN WHERE SELECT column1, column2, ... FROM table_name; -- Select albeginner

The SELECT Statement SELECT is the most fundamental MySQL command — it retrieves data from one or more tables. MySQL processes clauses in this order (different from how you. FROM / JOIN WHERE SELECT column1, column2, ... FROM table_name; -- Select al

Example
SELECT column1, column2, ...
FROM table_name;

-- Select all columns
SELECT * FROM customers;

-- Select specific columns
SELECT first_name, last_name, email
FROM customers;

-- With calculation
SELECT
    product_name,
    price,
    price * 1.1 AS price_with_tax
FROM products;

-- With string function
SELECT
    CONCAT(first_name, ' ', last_name) AS full_name,
    UPPER(email) AS email_upper
FROM customers;
Quiz

SELECT * means:

MySQL uses backticks (`) for:

Which is NOT a valid SELECT clause?

03MySQL SELECT DISTINCTSELECT DISTINCT — Remove Duplicates DISTINCT filters out duplicate rows from the result set. It applies to all columns in the. -- Get unique cities (ignores duplicates) SELECT DISTINCT city FROM customers; -- DISTINCT applies to the COMBINATION of cobeginner

SELECT DISTINCT — Remove Duplicates DISTINCT filters out duplicate rows from the result set. It applies to all columns in the. -- Get unique cities (ignores duplicates) SELECT DISTINCT city FROM customers; -- DISTINCT applies to the COMBINATION of co

Example
-- Get unique cities (ignores duplicates)
SELECT DISTINCT city FROM customers;

-- DISTINCT applies to the COMBINATION of columns
SELECT DISTINCT city, country FROM customers;
-- Returns unique city+country pairs

-- Count distinct values
SELECT COUNT(DISTINCT city) AS unique_cities
FROM customers;

-- DISTINCT with ORDER BY
SELECT DISTINCT category
FROM products
ORDER BY category;
Quiz

SELECT DISTINCT city, country returns:

COUNT(DISTINCT column) counts:

DISTINCT is applied:

04MySQL WHEREWHERE Clause — Filtering Rows WHERE filters rows BEFORE they are returned. Only rows where the condition is TRUE are included. SELECT column1, column2 FROM table_name WHERE condition; -- Comparison operators WHERE price = 99.99 -- equal WHERE price !beginner

WHERE Clause — Filtering Rows WHERE filters rows BEFORE they are returned. Only rows where the condition is TRUE are included. SELECT column1, column2 FROM table_name WHERE condition; -- Comparison operators WHERE price = 99.99 -- equal WHERE price !

Example
SELECT column1, column2
FROM table_name
WHERE condition;

-- Comparison operators
WHERE price = 99.99       -- equal
WHERE price != 99.99      -- not equal (also: <>)
WHERE price > 100         -- greater than
WHERE price >= 100        -- greater or equal
WHERE price < 50          -- less than
WHERE price <= 50         -- less or equal

-- String comparison
WHERE name = 'Alice'       -- exact match (case-insensitive by default)
WHERE status = 'active'

-- Range
WHERE price BETWEEN 10 AND 100  -- inclusive!
WHERE age BETWEEN 18 AND 65

-- NULL check (NEVER use = NULL!)
WHERE email IS NULL
WHERE email IS NOT NULL

-- Multiple conditions (AND, OR, NOT)
WHERE price > 100 AND category = 'Electronics'
WHERE city = 'NYC' OR city = 'LA'
WHERE NOT status = 'deleted'
Quiz

To check if a column is NULL in MySQL:

WHERE price BETWEEN 10 AND 50 includes:

WHERE price > 100 AND category = 'Electronics':

05MySQL ORDER BYORDER BY — Sorting Results ORDER BY sorts the result set. Without ORDER BY, MySQL makes NO guarantee about row order. -- Sort ascending (default) SELECT * FROM products ORDER BY price; SELECT * FROM products ORDER BY price ASC; -- Sort descending SELbeginner

ORDER BY — Sorting Results ORDER BY sorts the result set. Without ORDER BY, MySQL makes NO guarantee about row order. -- Sort ascending (default) SELECT * FROM products ORDER BY price; SELECT * FROM products ORDER BY price ASC; -- Sort descending SEL

Example
-- Sort ascending (default)
SELECT * FROM products ORDER BY price;
SELECT * FROM products ORDER BY price ASC;

-- Sort descending
SELECT * FROM products ORDER BY price DESC;

-- Sort by multiple columns
SELECT * FROM employees
ORDER BY department ASC, salary DESC;
-- Sorts by dept A→Z, then by salary high→low within each dept

-- Sort by column alias
SELECT name, price * 0.9 AS discounted
FROM products
ORDER BY discounted DESC;

-- Sort by expression
SELECT * FROM orders ORDER BY YEAR(created_at) DESC, MONTH(created_at);

-- Sort by column position (avoid in production)
SELECT name, age, salary FROM employees ORDER BY 3 DESC;  -- 3 = salary
Quiz

Default sort order without ASC/DESC is:

ORDER BY dept ASC, salary DESC means:

Without ORDER BY, MySQL guarantees:

Track 02beginner

Module 2: Filtering & Logic

Work through the complete MYSQL stack: querying, joins, indexes, schema design, constraints, views, stored.

01MySQL ANDAND Operator — All Conditions Must Be True AND combines multiple conditions. A row is returned only when ALL conditions evaluate to TRUE. -- Basic AND SELECT * FROM products WHERE category = 'Electronics' AND price 500 AND customer_country = 'US'; --beginner

AND Operator — All Conditions Must Be True AND combines multiple conditions. A row is returned only when ALL conditions evaluate to TRUE. -- Basic AND SELECT * FROM products WHERE category = 'Electronics' AND price 500 AND customer_country = 'US'; --

Example
-- Basic AND
SELECT * FROM products
WHERE category = 'Electronics' AND price < 100;

-- Multiple AND conditions
SELECT * FROM orders
WHERE status = 'shipped'
  AND total > 500
  AND customer_country = 'US';

-- AND with parentheses (precedence)
SELECT * FROM products
WHERE (category = 'Electronics' OR category = 'Computers')
  AND price BETWEEN 100 AND 500
  AND in_stock = 1;

-- AND with date range
SELECT * FROM orders
WHERE created_at >= '2026-01-01'
  AND created_at < '2026-04-01'
  AND status != 'cancelled';
Quiz

AND returns TRUE when:

TRUE AND NULL evaluates to:

WHERE a = 1 AND b = 2 AND c = 3 returns rows where:

02MySQL OROR Operator — Any Condition Can Be True OR returns a row if ANY of the conditions is TRUE. OR has lower precedence than. -- Basic OR SELECT * FROM products WHERE category = 'Electronics' OR category = 'Computers'; -- Better with IN (cleaner for multibeginner

OR Operator — Any Condition Can Be True OR returns a row if ANY of the conditions is TRUE. OR has lower precedence than. -- Basic OR SELECT * FROM products WHERE category = 'Electronics' OR category = 'Computers'; -- Better with IN (cleaner for multi

Example
-- Basic OR
SELECT * FROM products
WHERE category = 'Electronics' OR category = 'Computers';

-- Better with IN (cleaner for multiple same-column ORs)
SELECT * FROM products
WHERE category IN ('Electronics', 'Computers', 'Accessories');

-- Mixed AND/OR — use parentheses!
SELECT * FROM orders
WHERE (status = 'pending' OR status = 'processing')
  AND total > 100;

-- Without parentheses (wrong intent!):
-- status='pending' OR (status='processing' AND total>100)
SELECT * FROM orders
WHERE status = 'pending' OR status = 'processing' AND total > 100;
Quiz

OR returns FALSE only when:

AND has __ precedence than OR:

Best way to filter multiple values of the same column:

03MySQL NOTNOT Operator — Negate a Condition NOT reverses the truth value of a condition. Use it with any condition or with IN. -- Basic NOT SELECT * FROM products WHERE NOT discontinued; -- NOT with comparison SELECT * FROM employees WHERE NOT department = 'HRbeginner

NOT Operator — Negate a Condition NOT reverses the truth value of a condition. Use it with any condition or with IN. -- Basic NOT SELECT * FROM products WHERE NOT discontinued; -- NOT with comparison SELECT * FROM employees WHERE NOT department = 'HR

Example
-- Basic NOT
SELECT * FROM products WHERE NOT discontinued;

-- NOT with comparison
SELECT * FROM employees WHERE NOT department = 'HR';
-- Equivalent to:
SELECT * FROM employees WHERE department != 'HR';
SELECT * FROM employees WHERE department <> 'HR';

-- NOT IN
SELECT * FROM products
WHERE category NOT IN ('Deprecated', 'Draft', 'Archive');

-- NOT LIKE
SELECT * FROM customers
WHERE email NOT LIKE '%@spam.com';

-- NOT BETWEEN
SELECT * FROM products
WHERE price NOT BETWEEN 100 AND 500;

-- NOT NULL
SELECT * FROM users WHERE phone IS NOT NULL;

-- NOT EXISTS
SELECT * FROM customers c
WHERE NOT EXISTS (
    SELECT 1 FROM orders o WHERE o.customer_id = c.id
);  -- customers with no orders
Quiz

NOT IN ('A', 'B') is equivalent to:

IS NOT NULL checks that:

NOT EXISTS returns the outer row when:

04MySQL INSERT INTOINSERT INTO — Adding Rows INSERT INTO adds one or more rows to a table. It is a DML (Data Manipulation. -- Insert specifying columns (recommended!) INSERT INTO customers (first_name, last_name, email, city) VALUES ('Alice', 'Smith', 'alice@lab.dev', beginner

INSERT INTO — Adding Rows INSERT INTO adds one or more rows to a table. It is a DML (Data Manipulation. -- Insert specifying columns (recommended!) INSERT INTO customers (first_name, last_name, email, city) VALUES ('Alice', 'Smith', 'alice@lab.dev',

Example
-- Insert specifying columns (recommended!)
INSERT INTO customers (first_name, last_name, email, city)
VALUES ('Alice', 'Smith', 'alice@lab.dev', 'New York');

-- Insert all columns (column order must match table definition)
INSERT INTO customers
VALUES (NULL, 'Bob', 'Jones', 'bob@lab.dev', 'LA', NOW());

-- Insert multiple rows (efficient — one round trip to DB)
INSERT INTO products (name, price, category) VALUES
    ('Laptop', 999.99, 'Electronics'),
    ('Mouse', 29.99, 'Electronics'),
    ('Desk', 349.00, 'Furniture');

-- Insert from SELECT (copy data from another table)
INSERT INTO archive_orders (order_id, customer_id, total)
SELECT id, customer_id, total
FROM orders
WHERE YEAR(created_at) < 2024;
Quiz

INSERT INTO specifying column names is recommended because:

To get the auto-increment ID of the last insert:

INSERT IGNORE does what with duplicate key violations?

05MySQL NULL ValuesNULL in MySQL — The Absence of Value NULL means unknown or missing. It is NOT zero, NOT an empty string, NOT false. NULL. -- NULL arithmetic NULL + 1 -- NULL NULL * 0 -- NULL (not 0!) NULL = NULL -- NULL (not TRUE!) NULL != NULL -- NULL -- Correct NUbeginner

NULL in MySQL — The Absence of Value NULL means unknown or missing. It is NOT zero, NOT an empty string, NOT false. NULL. -- NULL arithmetic NULL + 1 -- NULL NULL * 0 -- NULL (not 0!) NULL = NULL -- NULL (not TRUE!) NULL != NULL -- NULL -- Correct NU

Example
-- NULL arithmetic
NULL + 1        -- NULL
NULL * 0        -- NULL (not 0!)
NULL = NULL     -- NULL (not TRUE!)
NULL != NULL    -- NULL

-- Correct NULL comparisons
WHERE email IS NULL      -- ✓
WHERE email IS NOT NULL  -- ✓
WHERE email = NULL       -- ✗ always NULL (never matches!)
Quiz

NULL = NULL in MySQL evaluates to:

COALESCE(a, b, c) returns:

COUNT(*) vs COUNT(col):

06MySQL UPDATEUPDATE — Modifying Existing Rows UPDATE changes data in existing rows. Always use WHERE — without it, ALL rows in the. -- Basic UPDATE (always add WHERE!) UPDATE customers SET email = 'newemail@lab.dev' WHERE id = 1; -- Update multiple columns UPDATEbeginner

UPDATE — Modifying Existing Rows UPDATE changes data in existing rows. Always use WHERE — without it, ALL rows in the. -- Basic UPDATE (always add WHERE!) UPDATE customers SET email = 'newemail@lab.dev' WHERE id = 1; -- Update multiple columns UPDATE

Example
-- Basic UPDATE (always add WHERE!)
UPDATE customers
SET email = 'newemail@lab.dev'
WHERE id = 1;

-- Update multiple columns
UPDATE products
SET price = 899.99,
    stock = stock - 1,
    updated_at = NOW()
WHERE id = 42;

-- Update based on calculation
UPDATE employees
SET salary = salary * 1.10     -- 10% raise
WHERE department = 'Engineering'
  AND performance_rating = 'Excellent';

-- Update with subquery
UPDATE products
SET price = price * 0.9        -- 10% discount
WHERE id IN (
    SELECT product_id FROM promotions
    WHERE active = 1
);

-- ⚠️ DANGEROUS — updates ALL rows:
UPDATE products SET discount = 0;  -- no WHERE!
Quiz

UPDATE without WHERE clause:

Best practice before a large UPDATE:

UPDATE products SET price = price * 0.9 WHERE id = 5:

07MySQL DELETEDELETE — Removing Rows DELETE removes rows from a table. Like UPDATE, always use WHERE unless you intend to delete. -- Delete specific rows (always use WHERE!) DELETE FROM customers WHERE id = 42; -- Delete based on condition DELETE FROM orders WHEREbeginner

DELETE — Removing Rows DELETE removes rows from a table. Like UPDATE, always use WHERE unless you intend to delete. -- Delete specific rows (always use WHERE!) DELETE FROM customers WHERE id = 42; -- Delete based on condition DELETE FROM orders WHERE

Example
-- Delete specific rows (always use WHERE!)
DELETE FROM customers WHERE id = 42;

-- Delete based on condition
DELETE FROM orders
WHERE status = 'cancelled'
  AND created_at < '2024-01-01';

-- Delete with LIMIT (safe for large tables)
DELETE FROM logs
WHERE created_at < '2025-01-01'
LIMIT 10000;  -- batch delete

-- ⚠️ Delete ALL rows (keeps table structure):
DELETE FROM session_tokens;

-- TRUNCATE is faster for deleting all rows:
TRUNCATE TABLE session_tokens;
-- Differences from DELETE:
-- 1. Cannot use WHERE
-- 2. Cannot roll back (in most cases)
-- 3. Resets AUTO_INCREMENT counter
-- 4. Much faster (doesn't log row-by-row)
Quiz

DELETE vs TRUNCATE:

DELETE FROM table with no WHERE clause:

To batch-delete large amounts safely, use:

08MySQL LIMITLIMIT — Restricting Result Count LIMIT is applied last — after WHERE, GROUP BY, HAVING, ORDER BY. Essential for pagination and. -- Get first N rows SELECT * FROM products ORDER BY price LIMIT 10; -- Skip M rows, then get N (pagination) SELECT * FROM beginner

LIMIT — Restricting Result Count LIMIT is applied last — after WHERE, GROUP BY, HAVING, ORDER BY. Essential for pagination and. -- Get first N rows SELECT * FROM products ORDER BY price LIMIT 10; -- Skip M rows, then get N (pagination) SELECT * FROM

Example
-- Get first N rows
SELECT * FROM products ORDER BY price LIMIT 10;

-- Skip M rows, then get N (pagination)
SELECT * FROM products
ORDER BY id
LIMIT 10 OFFSET 20;      -- rows 21-30 (page 3 with page size 10)

-- MySQL shorthand: LIMIT offset, count
SELECT * FROM products ORDER BY id LIMIT 20, 10;  -- same as above

-- Top 5 most expensive
SELECT name, price FROM products
ORDER BY price DESC
LIMIT 5;

-- Pagination formula:
-- Page N (0-indexed): LIMIT page_size OFFSET (N * page_size)
-- Page 0: LIMIT 10 OFFSET 0
-- Page 1: LIMIT 10 OFFSET 10
-- Page 2: LIMIT 10 OFFSET 20
Quiz

SELECT * FROM t ORDER BY id LIMIT 10 OFFSET 20 returns:

LIMIT is processed:

For page 3 (0-indexed) with 10 items per page, OFFSET is:

09MySQL MIN and MAXMIN() and MAX() — Extreme Values MIN and MAX are aggregate functions that return the smallest or largest value in a column. -- Basic MIN and MAX SELECT MIN(price) AS cheapest, MAX(price) AS most_expensive FROM products; SELECT MIN(created_at) AS oldebeginner

MIN() and MAX() — Extreme Values MIN and MAX are aggregate functions that return the smallest or largest value in a column. -- Basic MIN and MAX SELECT MIN(price) AS cheapest, MAX(price) AS most_expensive FROM products; SELECT MIN(created_at) AS olde

Example
-- Basic MIN and MAX
SELECT MIN(price) AS cheapest, MAX(price) AS most_expensive
FROM products;

SELECT MIN(created_at) AS oldest_order, MAX(created_at) AS newest_order
FROM orders;

-- With WHERE filter
SELECT MIN(salary) AS min_eng_salary, MAX(salary) AS max_eng_salary
FROM employees
WHERE department = 'Engineering';

-- MIN/MAX per group (combine with GROUP BY)
SELECT department,
       MIN(salary) AS min_sal,
       MAX(salary) AS max_sal,
       MAX(salary) - MIN(salary) AS spread
FROM employees
GROUP BY department;

-- Get the row with the MIN value (subquery approach)
SELECT * FROM products
WHERE price = (SELECT MIN(price) FROM products);

-- Or using ORDER BY + LIMIT (often faster):
SELECT * FROM products ORDER BY price ASC LIMIT 1;
Quiz

MIN() and MAX() applied to a string column:

To get the actual row with the minimum price, use:

MAX(salary) in a GROUP BY query returns:

10MySQL COUNT, AVG, SUMAggregate Functions — Computing Statistics SELECT COUNT(*) FROM orders; -- all rows SELECT COUNT(email) FROM customers; -- non-NULL emails only SELECT COUNT(DISTINCT city) FROM customers; -- unique cities -- COUNT with condition (MySQL 8+ style usingbeginner

Aggregate Functions — Computing Statistics SELECT COUNT(*) FROM orders; -- all rows SELECT COUNT(email) FROM customers; -- non-NULL emails only SELECT COUNT(DISTINCT city) FROM customers; -- unique cities -- COUNT with condition (MySQL 8+ style using

Example
SELECT COUNT(*) FROM orders;         -- all rows
SELECT COUNT(email) FROM customers;  -- non-NULL emails only
SELECT COUNT(DISTINCT city) FROM customers;  -- unique cities

-- COUNT with condition (MySQL 8+ style using filter)
SELECT COUNT(*) FILTER (WHERE status='active') AS active_count
FROM users;

-- Traditional approach:
SELECT SUM(status = 'active') AS active_count FROM users;
Quiz

COUNT(*) vs COUNT(column):

AVG() on a column with some NULLs:

SUM(price > 100) in MySQL:

11MySQL LIKELIKE — Pattern Matching LIKE performs pattern matching on string values. It is case-insensitive by default in MySQL (depends on. -- % matches any sequence of characters (including empty) WHERE name LIKE 'A%' -- starts with A WHERE name LIKE '%son' --beginner

LIKE — Pattern Matching LIKE performs pattern matching on string values. It is case-insensitive by default in MySQL (depends on. -- % matches any sequence of characters (including empty) WHERE name LIKE 'A%' -- starts with A WHERE name LIKE '%son' --

Example
-- % matches any sequence of characters (including empty)
WHERE name LIKE 'A%'        -- starts with A
WHERE name LIKE '%son'      -- ends with son
WHERE name LIKE '%alice%'   -- contains alice
WHERE email LIKE '%@gmail%' -- gmail users

-- _ matches exactly ONE character
WHERE code LIKE 'A_1'       -- A, any char, 1 (e.g. AB1, AC1)
WHERE phone LIKE '___-___-____'  -- format match
WHERE code LIKE '__-__'     -- XX-XX pattern

-- NOT LIKE
WHERE email NOT LIKE '%@spam.com'

-- Case-sensitive LIKE (use BINARY)
WHERE name LIKE BINARY 'alice%'   -- lowercase only

-- Escape the wildcard characters
WHERE filename LIKE '100%'  ESCAPE ''  -- literal percent sign
Quiz

LIKE '%text%' matches strings that:

LIKE 'A_1' matches:

MySQL LIKE is case-sensitive by default?

12MySQL WildcardsMySQL Wildcard Characters Wildcards are used with LIKE (and RLIKE/REGEXP for more power). MySQL has two basic wildcards for. -- Matches any string of 0 or more characters '%' -- matches everything (any or empty string) 'A%' -- starts with A: "A", "ABbeginner

MySQL Wildcard Characters Wildcards are used with LIKE (and RLIKE/REGEXP for more power). MySQL has two basic wildcards for. -- Matches any string of 0 or more characters '%' -- matches everything (any or empty string) 'A%' -- starts with A: "A", "AB

Example
-- Matches any string of 0 or more characters
'%'       -- matches everything (any or empty string)
'A%'      -- starts with A: "A", "AB", "Apple", "A123"
'%A'      -- ends with A: "A", "BA", "ABBA"
'%A%'     -- contains A: "A", "BAD", "CABS"
'A%B'     -- starts A, ends B: "AB", "AxxB", "A1234B"
Quiz

LIKE '___' (3 underscores) matches:

To search for a literal percent sign with LIKE:

REGEXP vs LIKE:

13MySQL ININ Operator — Match Against a List IN tests whether a value matches any value in a list. It's cleaner than multiple OR. -- IN with literal list SELECT * FROM products WHERE category IN ('Electronics', 'Computers', 'Accessories'); -- Equivalent but vebeginner

IN Operator — Match Against a List IN tests whether a value matches any value in a list. It's cleaner than multiple OR. -- IN with literal list SELECT * FROM products WHERE category IN ('Electronics', 'Computers', 'Accessories'); -- Equivalent but ve

Example
-- IN with literal list
SELECT * FROM products
WHERE category IN ('Electronics', 'Computers', 'Accessories');

-- Equivalent but verbose OR:
WHERE category = 'Electronics'
   OR category = 'Computers'
   OR category = 'Accessories'

-- IN with numbers
SELECT * FROM orders WHERE id IN (1, 5, 8, 22, 100);

-- NOT IN
SELECT * FROM employees
WHERE department NOT IN ('Intern', 'Contractor', 'Temp');

-- IN with subquery (very powerful!)
SELECT * FROM orders
WHERE customer_id IN (
    SELECT id FROM customers WHERE country = 'US'
);

-- IN with NULL gotcha!
-- NOT IN fails if the list contains NULL:
WHERE dept NOT IN ('HR', NULL)  -- returns NO rows! NULL poisons NOT IN
Quiz

WHERE col NOT IN (1, 2, NULL) returns:

IN vs multiple OR for same column:

IN with a subquery allows:

14MySQL BETWEENBETWEEN — Range Comparison BETWEEN tests if a value falls within an inclusive range. It works with numbers, strings, and. -- Numeric range (inclusive on both ends) WHERE price BETWEEN 100 AND 500 -- Equivalent to: WHERE price >= 100 AND price 500 -- beginner

BETWEEN — Range Comparison BETWEEN tests if a value falls within an inclusive range. It works with numbers, strings, and. -- Numeric range (inclusive on both ends) WHERE price BETWEEN 100 AND 500 -- Equivalent to: WHERE price >= 100 AND price 500 --

Example
-- Numeric range (inclusive on both ends)
WHERE price BETWEEN 100 AND 500
-- Equivalent to: WHERE price >= 100 AND price <= 500

-- Date range
WHERE order_date BETWEEN '2026-01-01' AND '2026-12-31'
WHERE created_at BETWEEN '2026-04-01 00:00:00' AND '2026-04-06 23:59:59'

-- String range (alphabetical)
WHERE last_name BETWEEN 'Adams' AND 'Brown'
-- Includes Adams, Al*, Ba*, Br*, Brown but NOT "Browne"

-- NOT BETWEEN
WHERE price NOT BETWEEN 100 AND 500
-- Equivalent to: WHERE price < 100 OR price > 500

-- Age range
SELECT * FROM users WHERE age BETWEEN 18 AND 65;

-- BETWEEN with NULL
-- BETWEEN never matches NULL values
Quiz

BETWEEN 10 AND 20 includes the boundary values?

NOT BETWEEN 100 AND 500 is equivalent to:

BETWEEN with DATETIME '2026-12-31' as upper bound:

15MySQL AliasesAliases — Renaming Columns and Tables Aliases are temporary names. Column aliases rename output columns; table aliases shorten table references in queries. -- AS keyword (optional but recommended for clarity) SELECT first_name AS name, last_name AS sbeginner

Aliases — Renaming Columns and Tables Aliases are temporary names. Column aliases rename output columns; table aliases shorten table references in queries. -- AS keyword (optional but recommended for clarity) SELECT first_name AS name, last_name AS s

Example
-- AS keyword (optional but recommended for clarity)
SELECT
    first_name AS name,
    last_name AS surname,
    price * 1.2 AS price_with_tax,
    CONCAT(first_name, ' ', last_name) AS full_name,
    COUNT(*) AS total_orders,
    ROUND(AVG(salary), 2) AS average_salary
FROM employees;

-- Alias with spaces: use backticks or quotes
SELECT price * 1.2 AS `Price With Tax`
FROM products;

-- Can you use alias in WHERE? NO — WHERE runs before SELECT
-- This fails:
WHERE price_with_tax > 100   -- ❌ alias not yet defined

-- Use HAVING for aliases (runs after SELECT):
SELECT dept, AVG(salary) AS avg_sal
FROM employees
GROUP BY dept
HAVING avg_sal > 75000;  -- ✓ alias works in HAVING
Quiz

Can you use a column alias in the WHERE clause?

AS keyword for aliases is:

Table aliases are useful for:

Track 03beginner

Module 3: MySQL Joins

Work through the complete MYSQL stack: querying, joins, indexes, schema design, constraints, views, stored.

01MySQL JoinsWhat are JOINs? JOINs combine rows from two or more tables based on a related column. They are the. INNER JOIN — only rows with matching values in. LEFT JOIN — all rows from left + matching. SELECT t1.col, t2.col FROM table1 t1 JOIN table2 t2 ON t1.ibeginner

What are JOINs? JOINs combine rows from two or more tables based on a related column. They are the. INNER JOIN — only rows with matching values in. LEFT JOIN — all rows from left + matching. SELECT t1.col, t2.col FROM table1 t1 JOIN table2 t2 ON t1.i

Example
SELECT t1.col, t2.col
FROM table1 t1
JOIN table2 t2 ON t1.id = t2.foreign_key;

-- ANSI style (preferred, works everywhere):
FROM orders o
INNER JOIN customers c ON o.customer_id = c.id

-- Old comma syntax (avoid):
FROM orders o, customers c
WHERE o.customer_id = c.id
Quiz

INNER JOIN returns:

The ON clause in a JOIN specifies:

A customer with no orders will appear in:

02MySQL INNER JOININNER JOIN — The Most Common Join Returns only rows where the join condition matches in BOTH tables. Non-matching rows from either table. -- Basic INNER JOIN SELECT customers.name, orders.total, orders.created_at FROM customers INNER JOIN orders ON cbeginner

INNER JOIN — The Most Common Join Returns only rows where the join condition matches in BOTH tables. Non-matching rows from either table. -- Basic INNER JOIN SELECT customers.name, orders.total, orders.created_at FROM customers INNER JOIN orders ON c

Example
-- Basic INNER JOIN
SELECT customers.name, orders.total, orders.created_at
FROM customers
INNER JOIN orders ON customers.id = orders.customer_id;

-- With table aliases (cleaner)
SELECT c.name, c.city, o.total, o.status
FROM customers c
INNER JOIN orders o ON c.id = o.customer_id;

-- Multiple JOINs (chaining)
SELECT c.name, p.name AS product, oi.quantity, oi.price
FROM customers c
INNER JOIN orders o ON c.id = o.customer_id
INNER JOIN order_items oi ON o.id = oi.order_id
INNER JOIN products p ON oi.product_id = p.id
WHERE o.status = 'completed'
ORDER BY c.name, o.id;

-- INNER JOIN with aggregation
SELECT c.name, COUNT(o.id) AS order_count, SUM(o.total) AS total_spent
FROM customers c
INNER JOIN orders o ON c.id = o.customer_id
GROUP BY c.id, c.name
HAVING total_spent > 1000
ORDER BY total_spent DESC;
Quiz

How many rows does an INNER JOIN produce if table A has 5 rows and table B has 3 rows with 2 matches?

INNER JOIN with multiple tables chains:

INNER JOIN vs comma syntax (FROM t1, t2 WHERE ...):

03MySQL LEFT JOINLEFT JOIN — Keep All Left Rows LEFT JOIN returns ALL rows from the left (first) table, and matching rows from the right. -- All customers, even those without orders SELECT c.name, o.id AS order_id, o.total FROM customers c LEFT JOIN orders o ON c.id beginner

LEFT JOIN — Keep All Left Rows LEFT JOIN returns ALL rows from the left (first) table, and matching rows from the right. -- All customers, even those without orders SELECT c.name, o.id AS order_id, o.total FROM customers c LEFT JOIN orders o ON c.id

Example
-- All customers, even those without orders
SELECT c.name, o.id AS order_id, o.total
FROM customers c
LEFT JOIN orders o ON c.id = o.customer_id;

-- Find customers with NO orders (anti-join pattern)
SELECT c.name
FROM customers c
LEFT JOIN orders o ON c.id = o.customer_id
WHERE o.id IS NULL;    -- NULL means no matching order

-- Find products never ordered
SELECT p.name, p.price
FROM products p
LEFT JOIN order_items oi ON p.id = oi.product_id
WHERE oi.id IS NULL;

-- Multiple LEFT JOINs
SELECT c.name, COUNT(o.id) AS orders, COUNT(r.id) AS reviews
FROM customers c
LEFT JOIN orders o ON c.id = o.customer_id
LEFT JOIN reviews r ON c.id = r.customer_id
GROUP BY c.id, c.name;
Quiz

LEFT JOIN guarantees all rows from:

LEFT JOIN WHERE right.id IS NULL finds:

A LEFT JOIN with no matching right rows shows:

04MySQL RIGHT JOINRIGHT JOIN — Keep All Right Rows RIGHT JOIN is the mirror image of LEFT JOIN — it returns ALL rows from the. -- All orders, even if the customer was deleted SELECT c.name, o.id, o.total FROM customers c RIGHT JOIN orders o ON c.id = o.customer_id; --intermediate

RIGHT JOIN — Keep All Right Rows RIGHT JOIN is the mirror image of LEFT JOIN — it returns ALL rows from the. -- All orders, even if the customer was deleted SELECT c.name, o.id, o.total FROM customers c RIGHT JOIN orders o ON c.id = o.customer_id; --

Example
-- All orders, even if the customer was deleted
SELECT c.name, o.id, o.total
FROM customers c
RIGHT JOIN orders o ON c.id = o.customer_id;

-- Find orphan orders (orders with deleted customers)
SELECT o.id, o.total, c.name
FROM customers c
RIGHT JOIN orders o ON c.id = o.customer_id
WHERE c.id IS NULL;   -- customer was deleted!
Quiz

RIGHT JOIN returns all rows from:

RIGHT JOIN is most easily replaced by:

When would you use RIGHT JOIN over LEFT JOIN?

05MySQL CROSS JOINCROSS JOIN — Cartesian Product CROSS JOIN returns every possible combination of rows from two tables. If table A has M. Generate all product variants (size × color × material) Create a date series or calendar -- CROSS JOIN syntax SELECT colors.name, intermediate

CROSS JOIN — Cartesian Product CROSS JOIN returns every possible combination of rows from two tables. If table A has M. Generate all product variants (size × color × material) Create a date series or calendar -- CROSS JOIN syntax SELECT colors.name,

Example
-- CROSS JOIN syntax
SELECT colors.name, sizes.label
FROM colors
CROSS JOIN sizes;

-- If colors has 3 rows (Red, Blue, Green)
-- and sizes has 4 rows (XS, S, M, L)
-- Result: 12 rows (all combinations!)

-- Equivalent (implicit):
SELECT colors.name, sizes.label
FROM colors, sizes;

-- Practical use: generate all possible pairings
SELECT a.team AS home, b.team AS away
FROM teams a
CROSS JOIN teams b
WHERE a.team != b.team;   -- all possible matchups
Quiz

CROSS JOIN of tables with 100 and 200 rows produces:

CROSS JOIN is useful for:

CROSS JOIN with 1000 rows × 1000 rows results in:

06MySQL Self JoinSelf JOIN — A Table Joined to Itself A Self JOIN joins a table with itself. This is useful for hierarchical data (employees/managers), comparing. -- Classic example: employees and their managers -- (manager_id is a foreign key to the same table's id)intermediate

Self JOIN — A Table Joined to Itself A Self JOIN joins a table with itself. This is useful for hierarchical data (employees/managers), comparing. -- Classic example: employees and their managers -- (manager_id is a foreign key to the same table's id)

Example
-- Classic example: employees and their managers
-- (manager_id is a foreign key to the same table's id)
SELECT
    e.name AS employee,
    m.name AS manager
FROM employees e
LEFT JOIN employees m ON e.manager_id = m.id;

-- Find employees earning more than their manager
SELECT e.name, e.salary AS emp_salary, m.salary AS mgr_salary
FROM employees e
JOIN employees m ON e.manager_id = m.id
WHERE e.salary > m.salary;

-- Find all direct reports for a manager
SELECT e.name AS report, e.dept
FROM employees e
JOIN employees m ON e.manager_id = m.id
WHERE m.name = 'Alice';
Quiz

Self JOIN is used when:

In a self JOIN, you MUST use:

Self JOIN to find employees with no manager uses:

Track 04intermediate

Module 4: UNION, Grouping & Advanced Queries

Work through the complete MYSQL stack: querying, joins, indexes, schema design, constraints, views, stored.

01MySQL UNIONUNION — Combine Results from Multiple Queries UNION stacks result sets vertically. Both queries must have the same number of columns and compatible. Merge data from similar but separate tables (monthly archives. Combine two different types of entitieintermediate

UNION — Combine Results from Multiple Queries UNION stacks result sets vertically. Both queries must have the same number of columns and compatible. Merge data from similar but separate tables (monthly archives. Combine two different types of entitie

Example
-- UNION: combine two queries, remove duplicates
SELECT name, email FROM current_customers
UNION
SELECT name, email FROM archive_customers;

-- Column names come from the FIRST query
SELECT first_name AS name, email FROM customers_us
UNION
SELECT nombre, correo FROM customers_mx;  -- uses first query's names

-- UNION with ORDER BY (applies to the whole result)
SELECT name FROM employees
UNION
SELECT name FROM contractors
ORDER BY name ASC;

-- UNION with LIMIT
SELECT name FROM vip_customers
UNION
SELECT name FROM regular_customers
ORDER BY name
LIMIT 100;
Quiz

UNION vs UNION ALL:

Column names in a UNION result come from:

Both queries in a UNION must have:

02MySQL UNION ALLUNION ALL — Keep All Rows Including Duplicates UNION ALL combines results without removing duplicates. It is faster than UNION because it skips the. -- UNION ALL: keep every row SELECT product_id, quantity FROM warehouse_north UNION ALL SELECT producintermediate

UNION ALL — Keep All Rows Including Duplicates UNION ALL combines results without removing duplicates. It is faster than UNION because it skips the. -- UNION ALL: keep every row SELECT product_id, quantity FROM warehouse_north UNION ALL SELECT produc

Example
-- UNION ALL: keep every row
SELECT product_id, quantity FROM warehouse_north
UNION ALL
SELECT product_id, quantity FROM warehouse_south;
-- Shows all records including same product from both warehouses

-- Use UNION ALL when duplicates are meaningful (e.g., transaction logs)
SELECT 'Income' AS type, amount, date FROM income_table
UNION ALL
SELECT 'Expense' AS type, amount, date FROM expense_table
ORDER BY date;

-- UNION ALL is faster than UNION:
-- UNION must sort+compare all rows to find duplicates
-- UNION ALL just concatenates — no overhead
Quiz

UNION ALL is faster than UNION because:

Use UNION ALL (not UNION) when:

UNION ALL + GROUP BY in a subquery is used to:

03MySQL GROUP BYGROUP BY — Aggregate by Category GROUP BY collapses rows into groups and applies aggregate functions (COUNT, SUM, AVG, etc.) to each. -- Count orders per customer SELECT customer_id, COUNT(*) AS order_count FROM orders GROUP BY customer_id; -- Revenuintermediate

GROUP BY — Aggregate by Category GROUP BY collapses rows into groups and applies aggregate functions (COUNT, SUM, AVG, etc.) to each. -- Count orders per customer SELECT customer_id, COUNT(*) AS order_count FROM orders GROUP BY customer_id; -- Revenu

Example
-- Count orders per customer
SELECT customer_id, COUNT(*) AS order_count
FROM orders
GROUP BY customer_id;

-- Revenue by department
SELECT department,
       COUNT(*) AS headcount,
       ROUND(AVG(salary), 0) AS avg_salary,
       SUM(salary) AS total_payroll
FROM employees
GROUP BY department
ORDER BY total_payroll DESC;

-- Group by multiple columns
SELECT YEAR(order_date) AS year, MONTH(order_date) AS month,
       SUM(total) AS monthly_revenue
FROM orders
GROUP BY YEAR(order_date), MONTH(order_date)
ORDER BY year, month;

-- GROUP BY with WHERE (filter before grouping)
SELECT dept, AVG(salary) AS avg_sal
FROM employees
WHERE active = 1                   -- filter rows first
GROUP BY dept;                     -- then group
Quiz

Every column in SELECT with GROUP BY must be:

WHERE vs HAVING with GROUP BY:

GROUP BY YEAR(date), MONTH(date) groups:

04MySQL HAVINGHAVING — Filter Groups After GROUP BY HAVING filters groups produced by GROUP BY, similar to how WHERE filters rows. You can use. -- Departments with average salary > 75000 SELECT department, AVG(salary) AS avg_sal FROM employees GROUP BY department intermediate

HAVING — Filter Groups After GROUP BY HAVING filters groups produced by GROUP BY, similar to how WHERE filters rows. You can use. -- Departments with average salary > 75000 SELECT department, AVG(salary) AS avg_sal FROM employees GROUP BY department

Example
-- Departments with average salary > 75000
SELECT department, AVG(salary) AS avg_sal
FROM employees
GROUP BY department
HAVING AVG(salary) > 75000;

-- Customers who have placed more than 3 orders
SELECT customer_id, COUNT(*) AS order_count
FROM orders
GROUP BY customer_id
HAVING COUNT(*) > 3;

-- Products that generated > $10,000 in revenue
SELECT p.name, SUM(oi.quantity * oi.price) AS revenue
FROM products p
JOIN order_items oi ON p.id = oi.product_id
GROUP BY p.id, p.name
HAVING revenue > 10000
ORDER BY revenue DESC;

-- Using both WHERE and HAVING
SELECT department, ROUND(AVG(salary), 0) AS avg_sal
FROM employees
WHERE active = 1                     -- filter rows first
GROUP BY department
HAVING avg_sal > 70000               -- then filter groups
ORDER BY avg_sal DESC;
Quiz

HAVING is needed instead of WHERE when:

Execution order for WHERE and HAVING:

HAVING COUNT(*) > 5 finds:

05MySQL EXISTSEXISTS — Check for Subquery Results EXISTS tests whether a subquery returns any rows. It returns TRUE as soon as the first. -- Customers who have at least one order SELECT c.name FROM customers c WHERE EXISTS ( SELECT 1 FROM orders o WHERE o.customerintermediate

EXISTS — Check for Subquery Results EXISTS tests whether a subquery returns any rows. It returns TRUE as soon as the first. -- Customers who have at least one order SELECT c.name FROM customers c WHERE EXISTS ( SELECT 1 FROM orders o WHERE o.customer

Example
-- Customers who have at least one order
SELECT c.name
FROM customers c
WHERE EXISTS (
    SELECT 1 FROM orders o
    WHERE o.customer_id = c.id
);

-- The SELECT 1 (or SELECT *) inside doesn't matter — only presence matters

-- Customers who NEVER ordered (NOT EXISTS)
SELECT c.name
FROM customers c
WHERE NOT EXISTS (
    SELECT 1 FROM orders o
    WHERE o.customer_id = c.id
);

-- Products with stock in at least one warehouse
SELECT p.name
FROM products p
WHERE EXISTS (
    SELECT 1 FROM inventory i
    WHERE i.product_id = p.id
    AND i.quantity > 0
);

-- EXISTS with complex conditions
SELECT c.name, c.email
FROM customers c
WHERE EXISTS (
    SELECT 1 FROM orders o
    JOIN order_items oi ON o.id = oi.order_id
    WHERE o.customer_id = c.id
    AND oi.product_id IN (1, 5, 10)
    AND o.status = 'completed'
    AND o.total > 500
);
Quiz

EXISTS returns TRUE when:

SELECT 1 inside an EXISTS subquery:

NOT EXISTS finds:

06MySQL ANY, ALLANY — Compare Against Any Value in Subquery -- ANY: true if the condition is true for AT LEAST ONE value -- Find products more expensive than SOME order total SELECT name, price FROM products WHERE price > ANY (SELECT total FROM orders WHERE status =intermediate

ANY — Compare Against Any Value in Subquery -- ANY: true if the condition is true for AT LEAST ONE value -- Find products more expensive than SOME order total SELECT name, price FROM products WHERE price > ANY (SELECT total FROM orders WHERE status =

Example
-- ANY: true if the condition is true for AT LEAST ONE value
-- Find products more expensive than SOME order total
SELECT name, price
FROM products
WHERE price > ANY (SELECT total FROM orders WHERE status = 'completed');

-- = ANY is the same as IN:
WHERE dept = ANY (SELECT dept FROM managers)
-- same as:
WHERE dept IN (SELECT dept FROM managers)

-- > ANY: greater than the minimum value
WHERE salary > ANY (SELECT salary FROM employees WHERE dept = 'Junior')
-- Returns employees earning more than at least one junior
Quiz

salary > ANY (subquery) means salary is greater than:

= ANY is equivalent to:

salary > ALL (subquery) means salary is greater than:

07MySQL INSERT SELECTINSERT INTO ... SELECT — Copy Data Between Tables Inserts rows into a table using the results of a SELECT query. Extremely useful for archiving. -- Archive old orders to another table INSERT INTO archive_orders (id, customer_id, total, created_at) SEintermediate

INSERT INTO ... SELECT — Copy Data Between Tables Inserts rows into a table using the results of a SELECT query. Extremely useful for archiving. -- Archive old orders to another table INSERT INTO archive_orders (id, customer_id, total, created_at) SE

Example
-- Archive old orders to another table
INSERT INTO archive_orders (id, customer_id, total, created_at)
SELECT id, customer_id, total, created_at
FROM orders
WHERE created_at < '2024-01-01';

-- Copy a table structure and data
CREATE TABLE products_backup LIKE products;
INSERT INTO products_backup SELECT * FROM products;

-- Insert with transformation
INSERT INTO customers_report (name, total_orders, total_spent)
SELECT
    c.name,
    COUNT(o.id),
    SUM(o.total)
FROM customers c
JOIN orders o ON c.id = o.customer_id
GROUP BY c.id, c.name;

-- Insert from multiple tables
INSERT INTO audit_log (action, user, entity_id, timestamp)
SELECT 'order_created', c.email, o.id, NOW()
FROM orders o
JOIN customers c ON o.customer_id = c.id
WHERE DATE(o.created_at) = CURDATE();
Quiz

INSERT INTO t1 SELECT * FROM t2:

CREATE TABLE t AS SELECT is useful for:

Can INSERT ... SELECT include JOINs?

08MySQL CASECASE — Conditional Logic in SQL CASE is SQL's if-then-else construct. Use it in SELECT, WHERE, ORDER BY, and HAVING clauses. -- Simple CASE: compare one value against multiple conditions SELECT name, CASE status WHEN 'A' THEN 'Active' WHEN 'I' THEN 'intermediate

CASE — Conditional Logic in SQL CASE is SQL's if-then-else construct. Use it in SELECT, WHERE, ORDER BY, and HAVING clauses. -- Simple CASE: compare one value against multiple conditions SELECT name, CASE status WHEN 'A' THEN 'Active' WHEN 'I' THEN '

Example
-- Simple CASE: compare one value against multiple conditions
SELECT name,
       CASE status
           WHEN 'A' THEN 'Active'
           WHEN 'I' THEN 'Inactive'
           WHEN 'D' THEN 'Deleted'
           ELSE 'Unknown'
       END AS status_label
FROM customers;
Quiz

CASE evaluates conditions in:

CASE with no matching WHEN and no ELSE returns:

SUM(CASE WHEN gender='M' THEN 1 ELSE 0 END) computes:

09MySQL Null FunctionsMySQL NULL Handling Functions -- Return alt if expr is NULL, otherwise return expr SELECT name, IFNULL(phone, 'N/A') AS contact FROM customers; SELECT IFNULL(SUM(total), 0) AS revenue FROM orders WHERE date = TODAY();intermediate

MySQL NULL Handling Functions -- Return alt if expr is NULL, otherwise return expr SELECT name, IFNULL(phone, 'N/A') AS contact FROM customers; SELECT IFNULL(SUM(total), 0) AS revenue FROM orders WHERE date = TODAY();

Example
-- Return alt if expr is NULL, otherwise return expr
SELECT name, IFNULL(phone, 'N/A') AS contact FROM customers;
SELECT IFNULL(SUM(total), 0) AS revenue FROM orders WHERE date = TODAY();
Quiz

IFNULL(col, 0) is equivalent to:

NULLIF(a, b) returns NULL when:

COALESCE(a, b, c) returns:

10MySQL Stored ProceduresStored Procedures — Reusable SQL Code Stored procedures are precompiled SQL programs stored in the database. They can accept parameters, contain logic. -- Create a stored procedure DELIMITER $$ CREATE PROCEDURE GetCustomerOrders(IN customer_id INT) Bintermediate

Stored Procedures — Reusable SQL Code Stored procedures are precompiled SQL programs stored in the database. They can accept parameters, contain logic. -- Create a stored procedure DELIMITER $$ CREATE PROCEDURE GetCustomerOrders(IN customer_id INT) B

Example
-- Create a stored procedure
DELIMITER $$

CREATE PROCEDURE GetCustomerOrders(IN customer_id INT)
BEGIN
    SELECT o.id, o.total, o.status, o.created_at
    FROM orders o
    WHERE o.customer_id = customer_id
    ORDER BY o.created_at DESC;
END $$

DELIMITER ;

-- Call the procedure
CALL GetCustomerOrders(42);
Quiz

Stored procedures are:

OUT parameters in stored procedures:

DELIMITER $$ before a procedure is needed because:

11MySQL CommentsMySQL Comments — Document Your SQL SQL comments explain code intent and are ignored by MySQL during execution. Document complex JOINs and subqueries Explain the why, not just the what -- This is a single-line comment (ANSI standard) SELECT * FROM useintermediate

MySQL Comments — Document Your SQL SQL comments explain code intent and are ignored by MySQL during execution. Document complex JOINs and subqueries Explain the why, not just the what -- This is a single-line comment (ANSI standard) SELECT * FROM use

Example
-- This is a single-line comment (ANSI standard)
SELECT * FROM users; -- inline comment after statement

# This is also a single-line comment (MySQL-specific)
SELECT name # comment after column
FROM users;

-- Best practice: use -- (works in all SQL databases)
Quiz

MySQL supports how many comment styles?

/* ... */ comments can span:

Which comment style is standard SQL (works in all databases)?

12MySQL OperatorsMySQL Operators — Complete Reference + addition SELECT 5 + 3 -- 8 - subtraction SELECT 10 - 4 -- 6 * multiplication SELECT 3 * 4 -- 12 / division SELECT 10 / 4 -- 2.5000 DIV integer division SELECT 10 DIV 4 -- 2 % modulo SELECT 10 % 3 -- 1 (MOD is syintermediate

MySQL Operators — Complete Reference + addition SELECT 5 + 3 -- 8 - subtraction SELECT 10 - 4 -- 6 * multiplication SELECT 3 * 4 -- 12 / division SELECT 10 / 4 -- 2.5000 DIV integer division SELECT 10 DIV 4 -- 2 % modulo SELECT 10 % 3 -- 1 (MOD is sy

Example
+   addition          SELECT 5 + 3       -- 8
-   subtraction       SELECT 10 - 4      -- 6
*   multiplication    SELECT 3 * 4       -- 12
/   division          SELECT 10 / 4      -- 2.5000
DIV integer division  SELECT 10 DIV 4   -- 2
%   modulo            SELECT 10 % 3     -- 1 (MOD is synonym)
MOD modulo synonym    SELECT 10 MOD 3   -- 1
Quiz

10 DIV 3 in MySQL returns:

<=> operator is the NULL-safe version of:

XOR in MySQL returns TRUE when:

Track 05intermediate

Module 5: Database Management

Work through the complete MYSQL stack: querying, joins, indexes, schema design, constraints, views, stored.

01MySQL Create DBCreating and Selecting Databases -- Show all databases SHOW DATABASES; -- Create a database CREATE DATABASE myapp; CREATE DATABASE myapp CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; -- Create only if it doesn't exist (safe) CREATE DATABASE IF NOintermediate

Creating and Selecting Databases -- Show all databases SHOW DATABASES; -- Create a database CREATE DATABASE myapp; CREATE DATABASE myapp CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; -- Create only if it doesn't exist (safe) CREATE DATABASE IF NO

Example
-- Show all databases
SHOW DATABASES;

-- Create a database
CREATE DATABASE myapp;
CREATE DATABASE myapp CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;

-- Create only if it doesn't exist (safe)
CREATE DATABASE IF NOT EXISTS myapp;

-- Select a database to use
USE myapp;

-- Show current database
SELECT DATABASE();

-- Show database info
SHOW CREATE DATABASE myapp;
Quiz

Why use utf8mb4 instead of utf8 in MySQL?

USE database_name:

CREATE DATABASE IF NOT EXISTS:

02MySQL Drop DBDropping Databases -- Drop a database (PERMANENT — no undo!) DROP DATABASE myapp; -- Safe version (no error if doesn't exist) DROP DATABASE IF EXISTS myapp; -- Shows what you're about to drop: SHOW CREATE DATABASE myapp; -- Example workflow: -- 1. Dointermediate

Dropping Databases -- Drop a database (PERMANENT — no undo!) DROP DATABASE myapp; -- Safe version (no error if doesn't exist) DROP DATABASE IF EXISTS myapp; -- Shows what you're about to drop: SHOW CREATE DATABASE myapp; -- Example workflow: -- 1. Do

Example
-- Drop a database (PERMANENT — no undo!)
DROP DATABASE myapp;

-- Safe version (no error if doesn't exist)
DROP DATABASE IF EXISTS myapp;

-- Shows what you're about to drop:
SHOW CREATE DATABASE myapp;

-- Example workflow:
-- 1. Double-check which database is active
SELECT DATABASE();

-- 2. List tables inside it
SHOW TABLES;

-- 3. Take a backup first!
-- mysqldump -u root -p myapp > backup.sql

-- 4. Then drop
DROP DATABASE myapp;
Quiz

DROP DATABASE deletes:

DROP DATABASE IF EXISTS:

Before dropping a production database, you should:

03MySQL Create TableCREATE TABLE — Defining the Schema CREATE TABLE users ( id INT AUTO_INCREMENT PRIMARY KEY, username VARCHAR(50) NOT NULL UNIQUE, email VARCHAR(255) NOT NULL UNIQUE, password VARCHAR(255) NOT NULL, role ENUM('admin','user','guest') DEFAULT 'user', actintermediate

CREATE TABLE — Defining the Schema CREATE TABLE users ( id INT AUTO_INCREMENT PRIMARY KEY, username VARCHAR(50) NOT NULL UNIQUE, email VARCHAR(255) NOT NULL UNIQUE, password VARCHAR(255) NOT NULL, role ENUM('admin','user','guest') DEFAULT 'user', act

Example
CREATE TABLE users (
    id          INT AUTO_INCREMENT PRIMARY KEY,
    username    VARCHAR(50) NOT NULL UNIQUE,
    email       VARCHAR(255) NOT NULL UNIQUE,
    password    VARCHAR(255) NOT NULL,
    role        ENUM('admin','user','guest') DEFAULT 'user',
    active      BOOLEAN DEFAULT TRUE,
    created_at  TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    updated_at  TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
);

CREATE TABLE products (
    id          INT AUTO_INCREMENT PRIMARY KEY,
    name        VARCHAR(200) NOT NULL,
    description TEXT,
    price       DECIMAL(10, 2) NOT NULL CHECK (price >= 0),
    stock       INT DEFAULT 0 CHECK (stock >= 0),
    category_id INT,
    created_at  TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    FOREIGN KEY (category_id) REFERENCES categories(id) ON DELETE SET NULL
);
Quiz

DECIMAL(10,2) stores:

VARCHAR vs CHAR:

TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP:

04MySQL Drop TableDROP TABLE — Removing Tables -- Drop a table (permanent — all data lost!) DROP TABLE products; -- Safe version DROP TABLE IF EXISTS products; -- Drop multiple tables at once DROP TABLE IF EXISTS temp_data, import_stage, old_logs; -- Rename a table (dintermediate

DROP TABLE — Removing Tables -- Drop a table (permanent — all data lost!) DROP TABLE products; -- Safe version DROP TABLE IF EXISTS products; -- Drop multiple tables at once DROP TABLE IF EXISTS temp_data, import_stage, old_logs; -- Rename a table (d

Example
-- Drop a table (permanent — all data lost!)
DROP TABLE products;

-- Safe version
DROP TABLE IF EXISTS products;

-- Drop multiple tables at once
DROP TABLE IF EXISTS temp_data, import_stage, old_logs;

-- Rename a table (does not drop — safer!)
RENAME TABLE old_name TO new_name;
ALTER TABLE old_name RENAME TO new_name;

-- Drop the data but keep structure (TRUNCATE)
TRUNCATE TABLE session_tokens;  -- faster than DELETE, resets AUTO_INCREMENT
Quiz

TRUNCATE TABLE removes:

DROP TABLE is different from TRUNCATE because:

DROP TABLE IF EXISTS prevents:

05MySQL Alter TableALTER TABLE — Modifying Table Structure ALTER TABLE modifies an existing table's structure without dropping it. -- Add a column ALTER TABLE users ADD COLUMN phone VARCHAR(20); ALTER TABLE users ADD COLUMN updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMadvanced

ALTER TABLE — Modifying Table Structure ALTER TABLE modifies an existing table's structure without dropping it. -- Add a column ALTER TABLE users ADD COLUMN phone VARCHAR(20); ALTER TABLE users ADD COLUMN updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAM

Example
-- Add a column
ALTER TABLE users ADD COLUMN phone VARCHAR(20);
ALTER TABLE users ADD COLUMN updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP AFTER email;

-- Drop a column
ALTER TABLE users DROP COLUMN phone;

-- Rename a column (MySQL 8.0+)
ALTER TABLE users RENAME COLUMN username TO login_name;

-- Change column definition (type, size, constraints)
ALTER TABLE users MODIFY COLUMN email VARCHAR(320) NOT NULL;

-- Change column name AND definition
ALTER TABLE users CHANGE COLUMN old_name new_name VARCHAR(100) NOT NULL;

-- Add/remove constraints
ALTER TABLE orders ADD CONSTRAINT fk_customer FOREIGN KEY (customer_id) REFERENCES customers(id);
ALTER TABLE orders DROP FOREIGN KEY fk_customer;
ALTER TABLE products ADD CONSTRAINT chk_price CHECK (price >= 0);

-- Add/remove index
ALTER TABLE users ADD INDEX idx_email (email);
ALTER TABLE users DROP INDEX idx_email;

-- Rename the table
ALTER TABLE users RENAME TO customers;

-- Combine multiple changes
ALTER TABLE products
    ADD COLUMN sku VARCHAR(50) UNIQUE,
    ADD COLUMN tags JSON,
    MODIFY COLUMN description TEXT NOT NULL,
    ADD INDEX idx_category (category_id);
Quiz

ALTER TABLE ... MODIFY COLUMN changes:

ALTER TABLE ... CHANGE COLUMN allows:

Multiple changes in one ALTER TABLE statement:

Track 06intermediate

Module 6: Constraints & Indexes

Work through the complete MYSQL stack: querying, joins, indexes, schema design, constraints, views, stored.

01MySQL ConstraintsConstraints — Enforcing Data Integrity Constraints are rules applied to columns that enforce data quality at the database level — regardless. NOT NULL -- value required UNIQUE -- no duplicates (NULL allowed unless NOT NULL) PRIMARY KEY -- NOT NULL + advanced

Constraints — Enforcing Data Integrity Constraints are rules applied to columns that enforce data quality at the database level — regardless. NOT NULL -- value required UNIQUE -- no duplicates (NULL allowed unless NOT NULL) PRIMARY KEY -- NOT NULL +

Example
NOT NULL     -- value required
UNIQUE       -- no duplicates (NULL allowed unless NOT NULL)
PRIMARY KEY  -- NOT NULL + UNIQUE + row identifier
FOREIGN KEY  -- references another table
CHECK        -- custom expression (MySQL 8.0.16+)
DEFAULT      -- fallback value when none provided
Quiz

CHECK constraint was fully enforced (not just parsed) in MySQL starting from:

A composite primary key spans:

Named constraints are better because:

02MySQL Not NullNOT NULL Constraint NOT NULL ensures a column always has a value. Attempting to insert or update with NULL. -- Define NOT NULL at column level CREATE TABLE users ( id INT NOT NULL AUTO_INCREMENT PRIMARY KEY, name VARCHAR(100) NOT NULL, email VARCHAR(advanced

NOT NULL Constraint NOT NULL ensures a column always has a value. Attempting to insert or update with NULL. -- Define NOT NULL at column level CREATE TABLE users ( id INT NOT NULL AUTO_INCREMENT PRIMARY KEY, name VARCHAR(100) NOT NULL, email VARCHAR(

Example
-- Define NOT NULL at column level
CREATE TABLE users (
    id       INT NOT NULL AUTO_INCREMENT PRIMARY KEY,
    name     VARCHAR(100) NOT NULL,
    email    VARCHAR(255) NOT NULL,
    phone    VARCHAR(20),         -- NULL allowed (optional)
    bio      TEXT                  -- NULL allowed (optional)
);

-- Add NOT NULL to an existing column (requires all existing rows to have values)
ALTER TABLE products MODIFY COLUMN price DECIMAL(10,2) NOT NULL;

-- Remove NOT NULL (allow NULL)
ALTER TABLE products MODIFY COLUMN price DECIMAL(10,2) NULL;

-- Check for NOT NULL columns in schema
SELECT column_name, is_nullable, data_type
FROM information_schema.columns
WHERE table_name = 'users'
  AND table_schema = 'myapp';
Quiz

NOT NULL constraint means:

Adding NOT NULL to an existing column with NULLs:

NOT NULL with DEFAULT:

03MySQL UniqueUNIQUE Constraint UNIQUE ensures no two rows have the same value in the column(s). Unlike PRIMARY KEY, UNIQUE. -- Column-level UNIQUE CREATE TABLE users ( id INT PRIMARY KEY, email VARCHAR(255) UNIQUE, username VARCHAR(50) UNIQUE ); -- Table-level UNadvanced

UNIQUE Constraint UNIQUE ensures no two rows have the same value in the column(s). Unlike PRIMARY KEY, UNIQUE. -- Column-level UNIQUE CREATE TABLE users ( id INT PRIMARY KEY, email VARCHAR(255) UNIQUE, username VARCHAR(50) UNIQUE ); -- Table-level UN

Example
-- Column-level UNIQUE
CREATE TABLE users (
    id    INT PRIMARY KEY,
    email VARCHAR(255) UNIQUE,
    username VARCHAR(50) UNIQUE
);

-- Table-level UNIQUE (can span multiple columns)
CREATE TABLE product_reviews (
    user_id    INT,
    product_id INT,
    rating     TINYINT,
    UNIQUE KEY uq_user_product (user_id, product_id)  -- one review per user per product
);

-- Add UNIQUE constraint to existing table
ALTER TABLE users ADD UNIQUE (email);
ALTER TABLE users ADD CONSTRAINT uq_username UNIQUE (username);

-- Remove UNIQUE constraint
ALTER TABLE users DROP INDEX uq_username;

-- UNIQUE index is automatically created by UNIQUE constraint
SHOW INDEX FROM users;
Quiz

How many NULL values can a UNIQUE column have in MySQL?

Composite UNIQUE (user_id, product_id) means:

UNIQUE constraint automatically creates:

04MySQL Primary KeyPRIMARY KEY — Row Identifier Every table should have a primary key. It uniquely identifies each row and is automatically indexed. -- Single column PK CREATE TABLE users ( id INT AUTO_INCREMENT PRIMARY KEY, name VARCHAR(100) NOT NULL ); -- Named PK (aadvanced

PRIMARY KEY — Row Identifier Every table should have a primary key. It uniquely identifies each row and is automatically indexed. -- Single column PK CREATE TABLE users ( id INT AUTO_INCREMENT PRIMARY KEY, name VARCHAR(100) NOT NULL ); -- Named PK (a

Example
-- Single column PK
CREATE TABLE users (
    id INT AUTO_INCREMENT PRIMARY KEY,
    name VARCHAR(100) NOT NULL
);

-- Named PK (at table level)
CREATE TABLE users (
    id INT AUTO_INCREMENT,
    name VARCHAR(100) NOT NULL,
    CONSTRAINT pk_users PRIMARY KEY (id)
);

-- Composite primary key (no AUTO_INCREMENT with composite)
CREATE TABLE order_items (
    order_id   INT,
    product_id INT,
    quantity   INT NOT NULL,
    PRIMARY KEY (order_id, product_id)
);

-- Natural key (meaningful data as PK)
CREATE TABLE countries (
    code  CHAR(2) PRIMARY KEY,  -- 'US', 'GB', 'DE'
    name  VARCHAR(100) NOT NULL
);

-- Add PK to existing table
ALTER TABLE users ADD PRIMARY KEY (id);

-- Change PK (drop old, add new)
ALTER TABLE users DROP PRIMARY KEY;
ALTER TABLE users ADD PRIMARY KEY (new_id);
Quiz

PRIMARY KEY is equivalent to:

A table can have how many primary keys?

Surrogate key vs Natural key:

05MySQL Foreign KeyForeign Key — Referential Integrity A foreign key enforces that a value in one table exists as a primary key in. CREATE TABLE orders ( id INT AUTO_INCREMENT PRIMARY KEY, customer_id INT NOT NULL, total DECIMAL(10,2), CONSTRAINT fk_orders_customer FORadvanced

Foreign Key — Referential Integrity A foreign key enforces that a value in one table exists as a primary key in. CREATE TABLE orders ( id INT AUTO_INCREMENT PRIMARY KEY, customer_id INT NOT NULL, total DECIMAL(10,2), CONSTRAINT fk_orders_customer FOR

Example
CREATE TABLE orders (
    id          INT AUTO_INCREMENT PRIMARY KEY,
    customer_id INT NOT NULL,
    total       DECIMAL(10,2),
    CONSTRAINT fk_orders_customer
        FOREIGN KEY (customer_id)
        REFERENCES customers(id)
        ON DELETE CASCADE     -- delete orders when customer deleted
        ON UPDATE CASCADE     -- update FK when customer ID changes
);

-- ON DELETE / ON UPDATE actions:
ON DELETE CASCADE    -- delete child rows automatically
ON DELETE SET NULL   -- set FK to NULL (column must be NULLable)
ON DELETE RESTRICT   -- refuse to delete parent if children exist
ON DELETE NO ACTION  -- same as RESTRICT (default)
ON DELETE SET DEFAULT -- set to default value (rarely used)

-- Add FK to existing table
ALTER TABLE orders
ADD CONSTRAINT fk_orders_customer
FOREIGN KEY (customer_id) REFERENCES customers(id)
ON DELETE CASCADE;

-- Remove FK
ALTER TABLE orders DROP FOREIGN KEY fk_orders_customer;

-- Show FKs
SELECT * FROM information_schema.KEY_COLUMN_USAGE
WHERE REFERENCED_TABLE_NAME = 'customers';
Quiz

ON DELETE CASCADE means:

ON DELETE RESTRICT (default) means:

Foreign keys in MySQL require:

06MySQL CheckCHECK Constraint (MySQL 8.0.16+) CHECK defines a boolean expression that must be true for any value inserted or updated. Prior. CREATE TABLE employees ( id INT PRIMARY KEY, name VARCHAR(100) NOT NULL, age INT CHECK (age >= 18 AND age 0), email VARCHAadvanced

CHECK Constraint (MySQL 8.0.16+) CHECK defines a boolean expression that must be true for any value inserted or updated. Prior. CREATE TABLE employees ( id INT PRIMARY KEY, name VARCHAR(100) NOT NULL, age INT CHECK (age >= 18 AND age 0), email VARCHA

Example
CREATE TABLE employees (
    id      INT PRIMARY KEY,
    name    VARCHAR(100) NOT NULL,
    age     INT CHECK (age >= 18 AND age <= 65),
    salary  DECIMAL(10,2) CHECK (salary > 0),
    email   VARCHAR(255) CHECK (email LIKE '%@%.%'),
    gender  CHAR(1) CHECK (gender IN ('M','F','N'))
);

-- Named CHECK constraint (recommended)
CREATE TABLE products (
    id       INT PRIMARY KEY,
    price    DECIMAL(10,2),
    discount DECIMAL(5,2),
    CONSTRAINT chk_price CHECK (price > 0),
    CONSTRAINT chk_discount CHECK (discount >= 0 AND discount <= price),
    CONSTRAINT chk_price_positive CHECK (price - discount >= 0)
);

-- Cross-column CHECK
CREATE TABLE events (
    id         INT PRIMARY KEY,
    start_date DATE NOT NULL,
    end_date   DATE NOT NULL,
    CONSTRAINT chk_dates CHECK (end_date >= start_date)
);

-- Add CHECK to existing table
ALTER TABLE users ADD CONSTRAINT chk_age CHECK (age >= 0);

-- Drop CHECK
ALTER TABLE users DROP CHECK chk_age;
Quiz

CHECK constraint in MySQL was silently ignored before:

Cross-column CHECK (end_date >= start_date) checks:

A CHECK constraint violation:

07MySQL DEFAULTDEFAULT — Fallback Column Values DEFAULT specifies a value to use when a column is omitted from an INSERT or when. CREATE TABLE users ( id INT AUTO_INCREMENT PRIMARY KEY, name VARCHAR(100) NOT NULL, status VARCHAR(20) DEFAULT 'active', -- literal stradvanced

DEFAULT — Fallback Column Values DEFAULT specifies a value to use when a column is omitted from an INSERT or when. CREATE TABLE users ( id INT AUTO_INCREMENT PRIMARY KEY, name VARCHAR(100) NOT NULL, status VARCHAR(20) DEFAULT 'active', -- literal str

Example
CREATE TABLE users (
    id         INT AUTO_INCREMENT PRIMARY KEY,
    name       VARCHAR(100) NOT NULL,
    status     VARCHAR(20) DEFAULT 'active',          -- literal string
    score      INT DEFAULT 0,                          -- literal number
    role       ENUM('admin','user') DEFAULT 'user',
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,    -- function
    updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
    metadata   JSON DEFAULT (JSON_OBJECT()),           -- expression (MySQL 8.0+)
    uuid       VARCHAR(36) DEFAULT (UUID())            -- generated UUID
);

-- Omitting the column uses the default
INSERT INTO users (name) VALUES ('Alice');
-- status='active', score=0, role='user', created_at=now, etc.

-- Explicitly use DEFAULT keyword
INSERT INTO users (name, status) VALUES ('Bob', DEFAULT);

-- Alter existing column to add default
ALTER TABLE products MODIFY COLUMN stock INT NOT NULL DEFAULT 0;

-- Remove a default
ALTER TABLE products MODIFY COLUMN stock INT NOT NULL;
Quiz

DEFAULT CURRENT_TIMESTAMP sets the timestamp:

ON UPDATE CURRENT_TIMESTAMP:

Inserting without specifying a DEFAULT column:

08MySQL Create IndexIndexes — Speeding Up Queries Indexes are data structures (B-tree by default) that let MySQL find rows without scanning the entire. -- Create index CREATE INDEX idx_email ON users(email); CREATE INDEX idx_last_name ON customers(last_name); -- Unique advanced

Indexes — Speeding Up Queries Indexes are data structures (B-tree by default) that let MySQL find rows without scanning the entire. -- Create index CREATE INDEX idx_email ON users(email); CREATE INDEX idx_last_name ON customers(last_name); -- Unique

Example
-- Create index
CREATE INDEX idx_email ON users(email);
CREATE INDEX idx_last_name ON customers(last_name);

-- Unique index (enforces uniqueness + speed)
CREATE UNIQUE INDEX idx_username ON users(username);

-- Composite index (multi-column)
CREATE INDEX idx_dept_salary ON employees(department, salary);
-- Useful for: WHERE dept='...' OR WHERE dept='...' AND salary > ...
-- NOT useful for: WHERE salary > ... alone (left-prefix rule!)

-- Full-text index (for MATCH ... AGAINST queries)
CREATE FULLTEXT INDEX idx_content ON articles(title, body);

-- Show indexes on a table
SHOW INDEX FROM users;
SHOW CREATE TABLE users;  -- shows all indexes

-- Drop index
DROP INDEX idx_email ON users;
ALTER TABLE users DROP INDEX idx_email;  -- alternative

-- Force/ignore an index in a query
SELECT * FROM orders USE INDEX (idx_customer_date)
WHERE customer_id = 1 ORDER BY created_at;
Quiz

The left-prefix rule for composite indexes means:

Too many indexes cause:

FULLTEXT index is used with:

09MySQL Auto IncrementAUTO_INCREMENT — Automatic Primary Keys AUTO_INCREMENT automatically assigns a unique incrementing integer to new rows. It is typically used for primary. CREATE TABLE users ( id INT AUTO_INCREMENT PRIMARY KEY, name VARCHAR(100) NOT NULL ); -- Insertsadvanced

AUTO_INCREMENT — Automatic Primary Keys AUTO_INCREMENT automatically assigns a unique incrementing integer to new rows. It is typically used for primary. CREATE TABLE users ( id INT AUTO_INCREMENT PRIMARY KEY, name VARCHAR(100) NOT NULL ); -- Inserts

Example
CREATE TABLE users (
    id   INT AUTO_INCREMENT PRIMARY KEY,
    name VARCHAR(100) NOT NULL
);

-- Inserts without specifying id
INSERT INTO users (name) VALUES ('Alice');  -- id = 1
INSERT INTO users (name) VALUES ('Bob');    -- id = 2

-- Get last inserted ID
SELECT LAST_INSERT_ID();  -- returns id of last INSERT in this session

-- Check current auto_increment value
SHOW TABLE STATUS LIKE 'users';
SELECT AUTO_INCREMENT FROM information_schema.tables
WHERE table_name = 'users';

-- Set custom starting value
ALTER TABLE users AUTO_INCREMENT = 1000;

-- Reset after data load
INSERT INTO users (name) VALUES ('Carol'); -- id = 1000
Quiz

AUTO_INCREMENT gaps (like 1, 3, 5) occur because:

LAST_INSERT_ID() returns:

TRUNCATE resets AUTO_INCREMENT to:

10MySQL DatesDate and Time in MySQL DATE '2026-04-06' -- date only TIME '14:30:00' -- time only DATETIME '2026-04-06 14:30:00' -- no timezone TIMESTAMP '2026-04-06 14:30:00' -- auto timezone conversion YEAR 2026 -- year onlyadvanced

Date and Time in MySQL DATE '2026-04-06' -- date only TIME '14:30:00' -- time only DATETIME '2026-04-06 14:30:00' -- no timezone TIMESTAMP '2026-04-06 14:30:00' -- auto timezone conversion YEAR 2026 -- year only

Example
DATE        '2026-04-06'                  -- date only
TIME        '14:30:00'                    -- time only
DATETIME    '2026-04-06 14:30:00'         -- no timezone
TIMESTAMP   '2026-04-06 14:30:00'         -- auto timezone conversion
YEAR        2026                           -- year only
Quiz

DATETIME vs TIMESTAMP in MySQL:

DATEDIFF('2026-04-06', '2026-01-01') returns:

DATE_FORMAT(date, '%Y-%m-%d') formats as:

11MySQL ViewsViews — Virtual Tables A view is a saved SELECT query that acts as a virtual table. It does not. -- Create a view CREATE VIEW active_users AS SELECT id, name, email, created_at FROM users WHERE status = 'active'; -- Use like a table SELECT * FROM actadvanced

Views — Virtual Tables A view is a saved SELECT query that acts as a virtual table. It does not. -- Create a view CREATE VIEW active_users AS SELECT id, name, email, created_at FROM users WHERE status = 'active'; -- Use like a table SELECT * FROM act

Example
-- Create a view
CREATE VIEW active_users AS
SELECT id, name, email, created_at
FROM users
WHERE status = 'active';

-- Use like a table
SELECT * FROM active_users WHERE name LIKE 'A%';

-- Create or replace existing view
CREATE OR REPLACE VIEW high_value_orders AS
SELECT o.id, c.name AS customer, o.total
FROM orders o
JOIN customers c ON o.customer_id = c.id
WHERE o.total > 1000
ORDER BY o.total DESC;

-- View with aggregation
CREATE VIEW customer_stats AS
SELECT
    c.id, c.name,
    COUNT(o.id) AS order_count,
    COALESCE(SUM(o.total), 0) AS total_spent,
    MAX(o.created_at) AS last_order
FROM customers c
LEFT JOIN orders o ON c.id = o.customer_id
GROUP BY c.id, c.name;

-- Drop a view
DROP VIEW active_users;
DROP VIEW IF EXISTS active_users;

-- Show all views
SHOW FULL TABLES WHERE Table_type = 'VIEW';
Quiz

A MySQL view stores:

CREATE OR REPLACE VIEW:

Views are useful for:

Track 07advanced

Module 7: Complete MYSQL Path

Work through the complete MYSQL stack: querying, joins, indexes, schema design, constraints, views, stored.

01MYSQL Documentation & Production ChecklistOfficial MYSQL Documentation For accurate behavior, syntax, and edge cases, always verify against the official manual. Document your target MYSQL major/minor version in every project because. MySQL 8.4 Reference Manual: https://dev.mysql.com/doc/refmadvanced

Official MYSQL Documentation For accurate behavior, syntax, and edge cases, always verify against the official manual. Document your target MYSQL major/minor version in every project because. MySQL 8.4 Reference Manual: https://dev.mysql.com/doc/refm

Example
// MYSQL production readiness checklist (simulation)
const checklist = [
  { item: "Strict SQL mode enabled", ok: true },
  { item: "utf8mb4 charset/collation configured", ok: true },
  { item: "Backups tested with restore", ok: false },
  { item: "Slow query log reviewed weekly", ok: true },
  { item: "High-impact indexes validated via EXPLAIN", ok: false },
  { item: "Connection pooling configured", ok: true },
];

console.log("MYSQL production checklist:");
checklist.forEach((c, i) => console.log((i + 1) + '. ' + (c.ok ? 'PASS' : 'FAIL') + ' - ' + c.item));

const pass = checklist.filter(c => c.ok).length;
const pct = Math.round((pass / checklist.length) * 100);
console.log('\nReadiness: ' + pct + '%');
if (pct < 100) console.log("Action: close FAIL items before launch.");
Quiz

Best source for exact MYSQL behavior:

Recommended text charset for modern MYSQL apps:

A critical production practice is:

02MYSQL Capstone: Build and Optimize an Ecommerce DBCapstone Objective Design, populate, query, and optimize a production-style MYSQL schema for e-commerce analytics. Add or adjust composite indexes based on execution plan, then. Create schema: users, products, orders, order_items, payments Add constradvanced

Capstone Objective Design, populate, query, and optimize a production-style MYSQL schema for e-commerce analytics. Add or adjust composite indexes based on execution plan, then. Create schema: users, products, orders, order_items, payments Add constr

Example
EXPLAIN ANALYZE
SELECT o.customer_id, SUM(o.total) AS revenue
FROM orders o
WHERE o.status = 'paid'
  AND o.created_at >= '2026-01-01'
GROUP BY o.customer_id
ORDER BY revenue DESC
LIMIT 10;
Quiz

First step in MYSQL optimization should be:

A capstone-ready schema should include:

Primary goal of the capstone is: