Chapter 6
Chapter 6 — CREATE TABLE and Schema Design
Every table you've queried in this course was created with CREATE TABLE.
This chapter explains how to write that statement from scratch — choosing the right
columns, applying constraints to enforce data quality, defining relationships with
foreign keys, and modifying or removing tables as requirements evolve.
1. CREATE TABLE Anatomy
- ENGINE=InnoDB — the default and only choice for most tables. InnoDB supports transactions, foreign keys, and row-level locking. The older MyISAM engine does not.
- DEFAULT CHARSET=utf8mb4 — stores any Unicode character, including emoji. Avoid the misleadingly-named
utf8charset — MySQL'sutf8only stores 3-byte characters and will silently corrupt 4-byte ones (most emoji). - COLLATE=utf8mb4_unicode_ci — case-insensitive comparison.
ci= case-insensitive,cs= case-sensitive. Most applications wantci.
IF NOT EXISTS after CREATE TABLE
to prevent an error if the table already exists — the statement becomes a no-op instead
of crashing. Useful in setup scripts that may be run more than once:CREATE TABLE IF NOT EXISTS authors ( … );
2. Constraints
Constraints are rules MySQL enforces on every INSERT and UPDATE. They guarantee the data in a column is always valid — without them, you'd have to replicate the same validation logic in every application that talks to the database.
The column must always have a value — MySQL rejects any INSERT or UPDATE that would leave it NULL. Use for columns where "unknown" is never a valid state: title, price, email.
Provides an automatic value when a column is omitted from an INSERT. Common defaults: DEFAULT 0, DEFAULT '', DEFAULT TRUE, DEFAULT CURRENT_TIMESTAMP.
No two rows may have the same value in this column (or combination of columns). Unlike PRIMARY KEY, a UNIQUE column can contain NULL — and multiple NULLs are allowed because NULL ≠ NULL.
Combines NOT NULL + UNIQUE. Every table should have one. Uniquely identifies each row. Usually an INT AUTO_INCREMENT surrogate key. Only one PRIMARY KEY per table.
Enforces a relationship between tables — the value in this column must exist in the referenced parent table's column. Prevents orphaned child rows. Requires InnoDB.
A boolean expression that must be true for every row. Example: CHECK (price > 0), CHECK (stock >= 0). Enforced in MySQL 8.0.16+. Older versions parse but ignore CHECK constraints.
3. The Full Bookshop Schema
Here are all four bookshop tables written from scratch, with every constraint annotated. Notice the creation order — parent tables must exist before child tables that reference them via foreign keys:
4. Foreign Key Actions
When a parent row is updated or deleted, MySQL needs to know what to do with the
child rows that reference it. You control this with ON DELETE and
ON UPDATE actions:
| Action | Effect on child rows | Typical use |
|---|---|---|
| RESTRICT | Blocks the parent update/delete if child rows exist. The default — and the safest choice. | Default. Prevents accidental data loss. Forces you to deal with children explicitly. |
| NO ACTION | Same as RESTRICT in MySQL — the error is raised at statement end. Behaviourally identical. | Equivalent to RESTRICT. Mentioned in SQL standard; MySQL treats them the same. |
| CASCADE | Propagates the change: if parent is deleted, child rows are deleted too. If parent PK is updated, child FK is updated to match. | ON UPDATE CASCADE is very useful (PK changes ripple down). ON DELETE CASCADE is powerful but dangerous — one delete can silently wipe thousands of rows. |
| SET NULL | Sets the FK column in child rows to NULL when the parent is deleted or updated. The FK column must be nullable. | Soft orphaning — e.g. when an author is deleted, keep their books but clear author_id. Only valid if the FK column allows NULL. |
| SET DEFAULT | Sets the FK column to its DEFAULT value. Rarely used — InnoDB doesn't support it in most configurations. | Avoid. Poorly supported and almost never the right semantic. |
CONSTRAINT fk_table_column. MySQL generates one if you don't, but the
auto-generated name is unreadable in error messages (fk_books_ibfk_1).
A named constraint gives you a clear error: "Cannot delete parent row: constraint
fk_books_author" — you know exactly which relationship was violated.
5. Inspecting Tables
| Field | Type | Null | Key | Default | Extra |
|---|---|---|---|---|---|
| book_id | int | NO | PRI | NULL | auto_increment |
| author_id | int | NO | MUL | NULL | |
| title | varchar(255) | NO | NULL | ||
| genre | varchar(60) | YES | NULL | ||
| price | decimal(6,2) | NO | NULL | ||
| stock | int | NO | 0 | ||
| published_year | year | YES | NULL |
Key column: PRI = primary key, MUL = non-unique index (here: a foreign key). Default NULL means no default was set — the column must be supplied on INSERT if it's NOT NULL.
6. ALTER TABLE — Modifying Structure
Requirements change — columns get added, renamed, resized, or removed. ALTER TABLE modifies an existing table's structure without losing data (mostly — dropping a column deletes its data permanently).
| Operation | What it does | Notes |
|---|---|---|
| ADD COLUMN | Add a new column to the table | Existing rows get NULL or the DEFAULT value. Adding NOT NULL without a DEFAULT fails if there are existing rows. |
| DROP COLUMN | Remove a column and all its data permanently | Irreversible. Cannot drop if the column is part of an index or FK. |
| MODIFY COLUMN | Change a column's type, constraints, or default — keeping the same name | Narrowing a type (VARCHAR 255 → 50) may truncate existing data. MySQL warns but proceeds. |
| RENAME COLUMN | Rename a column without changing its type | MySQL 8.0+. Updates any indexes on the column automatically. |
| ADD CONSTRAINT | Add a UNIQUE, CHECK, or FOREIGN KEY constraint after creation | Adding FK or UNIQUE validates all existing rows — fails if violations exist. |
| DROP CONSTRAINT / DROP FOREIGN KEY | Remove a named constraint | Must use the constraint name. Use SHOW CREATE TABLE to find it. |
| RENAME TABLE | Rename the table itself | Also works as a standalone RENAME TABLE old TO new statement. |
7. DROP TABLE
DROP TABLE deletes the table entirely — structure and all data. It
cannot be rolled back. If you want to keep the structure but remove the data,
use TRUNCATE instead.
Chapter Summary
| Statement / concept | Key points |
|---|---|
| CREATE TABLE | Define columns, types, and constraints. Use IF NOT EXISTS for idempotent scripts. Always specify ENGINE=InnoDB and CHARSET=utf8mb4. |
| NOT NULL | Makes a column mandatory. Use by default; allow NULL only when absence is meaningful. |
| DEFAULT | Auto-fills a column when omitted from INSERT. Allows adding NOT NULL columns to existing tables safely. |
| UNIQUE | No duplicate values. NULLs are exempt — multiple NULLs are allowed. |
| PRIMARY KEY | NOT NULL + UNIQUE. One per table. Usually INT AUTO_INCREMENT. |
| FOREIGN KEY | Enforces parent-child relationships. Name constraints explicitly. Create parents before children; drop children before parents. |
| ON DELETE / ON UPDATE | RESTRICT (safe default), CASCADE (propagates change), SET NULL (orphans child row). Avoid SET DEFAULT. |
| CHECK | Boolean expression enforced on every write. MySQL 8.0.16+. Good for price > 0, stock >= 0. |
| ALTER TABLE | ADD / DROP / MODIFY / RENAME COLUMN; ADD / DROP CONSTRAINT. DDL auto-commits — no rollback. |
| DROP TABLE | Deletes table and all data permanently. Use IF NOT EXISTS. Drop children before parents. |