Constraints & Picking Data Types
Make the database enforce your rules — NOT NULL, UNIQUE, DEFAULT, CHECK — and choose the right column type so bad data can never get in.
What you will learn
- Add constraints that guarantee data integrity
- Choose the right data type for each column
- Change an existing table’s structure with ALTER TABLE
Why constraints matter
A constraint is a rule attached to a column that the database enforces for you. Instead of hoping your application always sends good data, you tell MySQL “this can never be empty” or “this must be unique”, and MySQL refuses any row that breaks the rule. This is the single best defence against messy, inconsistent data — the rule lives with the data, so every program that touches the table obeys it.
Here are the constraints you will use constantly:
| Constraint | Guarantees |
|---|---|
NOT NULL | The column must always have a value |
UNIQUE | No two rows can share this value (e.g. email) |
DEFAULT x | If no value is given, use x |
CHECK (cond) | The value must satisfy a condition |
PRIMARY KEY | Unique + not null — the row’s identity |
AUTO_INCREMENT | MySQL fills in the next number automatically |
FOREIGN KEY | The value must match a row in another table |
A fully-constrained table
Let us build an accounts table where the rules are baked into the schema. Read the columns, then we walk through each constraint:
CREATE TABLE accounts (
id INT AUTO_INCREMENT PRIMARY KEY,
username VARCHAR(50) NOT NULL UNIQUE,
email VARCHAR(150) NOT NULL UNIQUE,
age INT CHECK (age >= 18),
role VARCHAR(20) DEFAULT 'member',
balance DECIMAL(10,2) DEFAULT 0.00
);Line by line: id INT AUTO_INCREMENT PRIMARY KEY is the auto-numbered unique identity. username VARCHAR(50) NOT NULL UNIQUE must be present and different from every other username. email has the same two rules. age INT CHECK (age >= 18) rejects any row with an age under 18. role VARCHAR(20) DEFAULT 'member' fills in “member” when you do not supply a role. balance DECIMAL(10,2) DEFAULT 0.00 starts every account at zero with two decimal places.
Note: Output: Query OK, 0 rows affected. The table is created with all rules attached. From now on MySQL polices them automatically.
Watching constraints reject bad data
Now the rules do their job. Each of these inserts breaks a rule, and MySQL stops it:
-- 1) duplicate username (UNIQUE)
INSERT INTO accounts (username, email) VALUES ('asha', 'a@x.com');
INSERT INTO accounts (username, email) VALUES ('asha', 'b@x.com');
-- 2) under-age (CHECK)
INSERT INTO accounts (username, email, age) VALUES ('ravi', 'r@x.com', 15);
-- 3) missing required email (NOT NULL)
INSERT INTO accounts (username) VALUES ('meera');The first insert succeeds. The second fails because asha already exists and username is UNIQUE. The third fails because 15 breaks CHECK (age >= 18). The fourth fails because email is NOT NULL but none was given. In every case the bad row is rejected — your table stays clean.
Note: Output: Query OK, 1 row affected. -- first insert ERROR 1062: Duplicate entry 'asha' for key 'username' ERROR 3819: Check constraint 'accountschk1' is violated. ERROR 1364: Field 'email' doesn't have a default value Only the first, valid row was stored. Each violation produced a clear error and changed nothing — exactly what you want.
Choosing the right data type
Picking the column type is the other half of a good schema. The wrong type wastes space or, worse, corrupts your data (storing money as FLOAT causes rounding errors). A quick guide:
| Use | For |
|---|---|
INT | Whole numbers: ids, counts, ages |
DECIMAL(10,2) | Money and exact decimals (never FLOAT for money) |
VARCHAR(n) | Short text with a sensible max length (names, emails) |
TEXT | Long free text (articles, comments) — cannot have a default |
DATE / DATETIME | A date, or a date-and-time |
BOOLEAN | true / false flags (stored as 0 / 1) |
ENUM(...) | A fixed short list of allowed values (e.g. status) |
A worked example of ENUM, which restricts a column to a small set of allowed values — perfect for a status field:
CREATE TABLE tickets (
id INT AUTO_INCREMENT PRIMARY KEY,
subject VARCHAR(120) NOT NULL,
status ENUM('open', 'pending', 'closed') NOT NULL DEFAULT 'open'
);status ENUM('open', 'pending', 'closed') means only those three words are ever allowed in the column — trying to insert 'banana' would be rejected. DEFAULT 'open' means new tickets start as “open”. This is cleaner and safer than a plain VARCHAR where any typo would slip through.
Note: Output:
Query OK, 0 rows affected.
The status column now accepts only the three listed values. DECIMAL(10,2) for money and ENUM for fixed choices are two type decisions that mark a thoughtfully designed schema.
ALTER TABLE — changing a table that already exists
You will rarely get a schema perfect on the first try. ALTER TABLE lets you change a table after it has data — add a column, change a type, add a constraint, or drop something — without rebuilding it. The common forms:
ALTER TABLE accounts ADD phone VARCHAR(20); -- add a column
ALTER TABLE accounts MODIFY age INT NOT NULL; -- change a column
ALTER TABLE accounts ADD UNIQUE (phone); -- add a constraint
ALTER TABLE accounts DROP COLUMN balance; -- remove a columnReading each line: ADD phone VARCHAR(20) tacks a new column onto the end. MODIFY age INT NOT NULL changes the age column’s definition. ADD UNIQUE (phone) attaches a UNIQUE constraint to an existing column. DROP COLUMN balance removes a column (and its data) for good. To delete an entire table you would use DROP TABLE accounts; — also permanent, so handle with care.
Note: Output: Query OK, 0 rows affected. Records: 0 Duplicates: 0 Warnings: 0 Each ALTER changes the table’s structure in place. MySQL reports success and how many existing rows were affected — here 0, because we only changed the shape, not the data.
Watch out: ALTER TABLE ... DROP COLUMN and DROP TABLE permanently delete data — there is no undo. On a real database, always take a backup (next units cover mysqldump) before structural changes.
Tip: A reliable rule: put as many rules as you can into the schema with constraints and correct types. Code can be skipped or buggy; a database constraint can never be bypassed, so your data stays trustworthy no matter what touches it.
Q. Which constraint guarantees that no two rows can have the same email address?
✍️ Practice
- Create a table with at least one
UNIQUE, oneNOT NULL, oneDEFAULTand oneCHECKconstraint, then try inserts that break each rule. - Use
ALTER TABLEto add a new column to an existing table and give it a UNIQUE constraint.
🏠 Homework
- Take your library or shop schema and add sensible constraints (UNIQUE emails, NOT NULL titles, a CHECK on price > 0) and an ENUM status column.