NOT NULL & UNIQUE Constraints: Ensuring Data Quality
While primary and foreign keys enforce relationships, NOT NULL and UNIQUE constraints ensure data quality at the column level. NOT NULL guarantees that required data is always present, while UNIQUE prevents duplicate values. Together, they form a crucial layer of data validation that catches errors before they corrupt your database. In this lesson, you'll learn when to use each constraint, how they affect inserts and updates, and how they improve overall data integrity.
📚 What You'll Learn
- What NOT NULL constraint does and why it matters
- What UNIQUE constraint does and when to use it
- The difference between NOT NULL and UNIQUE
- How these constraints affect INSERT operations
- How these constraints affect UPDATE operations
- How to combine NOT NULL and UNIQUE
- When to use UNIQUE vs. making something a primary key
- How to add and remove constraints
- Best practices for data validation at the database level
1. NOT NULL: Ensuring Required Data
NOT NULL means a column must have a value. You cannot insert or update a row without providing data for that column. It prevents NULL (missing/unknown) values.
What is NULL?
NULL is a special marker in SQL that means "no value" or "unknown." It's different from an empty string ('') or zero (0).
| Value | Meaning |
|---|---|
NULL |
No value / Unknown / Not applicable |
'' (empty string) |
A value that is empty (but still a value!) |
0 (zero) |
A numeric value of zero |
Example: With and Without NOT NULL
❌ Without NOT NULL (Allows Missing Data)
CREATE TABLE students (
student_id INT PRIMARY KEY AUTO_INCREMENT,
name VARCHAR(255), -- No NOT NULL
email VARCHAR(255) -- No NOT NULL
);
-- This succeeds, but creates incomplete data! 😱
INSERT INTO students (student_id) VALUES (100234);
-- Result: student with no name or email
SELECT * FROM students;
-- +------------+------+-------+
-- | student_id | name | email |
-- +------------+------+-------+
-- | 100234 | NULL | NULL |
-- +------------+------+-------+
Problem: You now have a student with no name or email. How do you contact them? How do you identify them? The data is useless!
✅ With NOT NULL (Requires Data)
CREATE TABLE students (
student_id INT PRIMARY KEY AUTO_INCREMENT,
name VARCHAR(255) NOT NULL, -- Required!
email VARCHAR(255) NOT NULL -- Required!
);
-- This fails - database protects you ✓
INSERT INTO students (student_id) VALUES (100234);
-- ERROR: Column 'name' cannot be null
-- This succeeds
INSERT INTO students (name, email)
VALUES ('Alice Johnson', 'alice@uni.edu');
Result: Database forces you to provide required data. You cannot create incomplete student records!
When to Use NOT NULL
| ✅ Use NOT NULL When... |
|---|
| The data is absolutely required for the record to make sense |
| Every single record will always have this value |
| Missing this data would make the record incomplete or invalid |
| Examples: product name, customer email, order date, student name |
| ⚪ Allow NULL When... |
|---|
| The data is optional or not always applicable |
| The record can exist without this information |
| Examples: middle name, phone number (if email required), apartment number |
2. UNIQUE: Preventing Duplicates
UNIQUE means no two rows can have the same value in that column. The database automatically rejects any insert or update that would create a duplicate.
Example: Preventing Duplicate Emails
❌ Without UNIQUE (Allows Duplicates)
CREATE TABLE users (
user_id INT PRIMARY KEY AUTO_INCREMENT,
username VARCHAR(50) NOT NULL,
email VARCHAR(255) NOT NULL -- No UNIQUE!
);
-- Both succeed, creating duplicate emails 😱
INSERT INTO users (username, email)
VALUES ('alice', 'alice@example.com');
INSERT INTO users (username, email)
VALUES ('alice2', 'alice@example.com'); -- Same email!
SELECT * FROM users;
-- +---------+----------+--------------------+
-- | user_id | username | email |
-- +---------+----------+--------------------+
-- | 1 | alice | alice@example.com |
-- | 2 | alice2 | alice@example.com | ← Duplicate!
-- +---------+----------+--------------------+
Problem: Two different users share the same email! Which one gets password reset emails? Which one should log in? Data integrity is broken!
✅ With UNIQUE (Prevents Duplicates)
CREATE TABLE users (
user_id INT PRIMARY KEY AUTO_INCREMENT,
username VARCHAR(50) NOT NULL UNIQUE, -- UNIQUE!
email VARCHAR(255) NOT NULL UNIQUE -- UNIQUE!
);
-- First insert succeeds
INSERT INTO users (username, email)
VALUES ('alice', 'alice@example.com');
-- Second insert FAILS - database protects you ✓
INSERT INTO users (username, email)
VALUES ('alice2', 'alice@example.com');
-- ERROR: Duplicate entry 'alice@example.com' for key 'email'
Result: Database prevents duplicate emails and usernames. Each user has unique identifiers!
UNIQUE vs. Primary Key
| Feature | Primary Key | UNIQUE |
|---|---|---|
| Uniqueness | ✓ Guaranteed unique | ✓ Guaranteed unique |
| NULL allowed? | ❌ Never NULL | ✓ Can be NULL |
| How many per table? | One only | Multiple allowed |
| Referenced by FKs? | ✓ Yes (most common) | ✓ Possible but rare |
| Auto-indexed? | ✓ Always | ✓ Always |
| Purpose | Identify the row | Prevent duplicate values |
3. Combining NOT NULL and UNIQUE
You can (and often should) use both constraints together:
CREATE TABLE users (
user_id INT PRIMARY KEY AUTO_INCREMENT,
username VARCHAR(50) NOT NULL UNIQUE, -- Required AND unique
email VARCHAR(255) NOT NULL UNIQUE, -- Required AND unique
bio TEXT -- Optional, can be NULL
);
This ensures that:
- ✓ Every user must have a username (NOT NULL)
- ✓ No two users can have the same username (UNIQUE)
- ✓ Every user must have an email (NOT NULL)
- ✓ No two users can have the same email (UNIQUE)
- ✓ bio is optional and can be different or NULL
Testing the Constraints
-- ✓ Success: all required fields, all unique
INSERT INTO users (username, email)
VALUES ('alice', 'alice@example.com');
-- ❌ Fails: missing required field (email)
INSERT INTO users (username) VALUES ('bob');
-- ERROR: Column 'email' cannot be null
-- ❌ Fails: duplicate username
INSERT INTO users (username, email)
VALUES ('alice', 'different@example.com');
-- ERROR: Duplicate entry 'alice' for key 'username'
-- ❌ Fails: duplicate email
INSERT INTO users (username, email)
VALUES ('bob', 'alice@example.com');
-- ERROR: Duplicate entry 'alice@example.com' for key 'email'
-- ✓ Success: all fields valid and unique
INSERT INTO users (username, email, bio)
VALUES ('bob', 'bob@example.com', 'Software developer');
4. How Constraints Affect INSERT and UPDATE
INSERT Operations
| Constraint | What Happens on INSERT |
|---|---|
| NOT NULL | ❌ INSERT fails if you don't provide a value (or provide NULL) |
| UNIQUE | ❌ INSERT fails if value already exists in another row |
| NOT NULL + UNIQUE | ❌ INSERT fails if missing OR if duplicate |
UPDATE Operations
-- Assume users table with username UNIQUE NOT NULL
-- ❌ Fails: can't set to NULL
UPDATE users
SET username = NULL
WHERE user_id = 1;
-- ERROR: Column 'username' cannot be null
-- ❌ Fails: can't set to existing value
UPDATE users
SET username = 'bob' -- bob already exists
WHERE user_id = 1;
-- ERROR: Duplicate entry 'bob' for key 'username'
-- ✓ Success: new unique value
UPDATE users
SET username = 'alice_new'
WHERE user_id = 1;
5. Multiple UNIQUE Columns
A table can have multiple UNIQUE constraints:
CREATE TABLE employees (
employee_id INT PRIMARY KEY AUTO_INCREMENT,
email VARCHAR(255) NOT NULL UNIQUE, -- Must be unique
ssn VARCHAR(11) UNIQUE, -- Optional but unique if provided
badge_number VARCHAR(20) NOT NULL UNIQUE, -- Must be unique
phone VARCHAR(20) -- Can be duplicate
);
-- Each UNIQUE column is enforced independently
-- email, ssn, and badge_number must all be unique
-- But phone can be shared (no UNIQUE constraint)
Composite UNIQUE Constraint
You can also make a combination of columns unique:
CREATE TABLE course_sections (
section_id INT PRIMARY KEY AUTO_INCREMENT,
course_code VARCHAR(20) NOT NULL,
semester VARCHAR(20) NOT NULL,
section_number INT NOT NULL,
instructor VARCHAR(255),
-- Combination must be unique (but individual values can repeat)
UNIQUE (course_code, semester, section_number)
);
-- ✓ Allowed: CS101 can have multiple sections in different semesters
INSERT INTO course_sections (course_code, semester, section_number)
VALUES ('CS101', 'Fall2024', 1);
INSERT INTO course_sections (course_code, semester, section_number)
VALUES ('CS101', 'Spring2025', 1);
-- ❌ Fails: CS101 Fall2024 section 1 already exists
INSERT INTO course_sections (course_code, semester, section_number)
VALUES ('CS101', 'Fall2024', 1);
-- ERROR: Duplicate entry 'CS101-Fall2024-1' for key
6. Adding and Removing Constraints
Adding NOT NULL
-- Add NOT NULL to existing column
ALTER TABLE students
MODIFY email VARCHAR(255) NOT NULL;
-- WARNING: This fails if any existing rows have NULL email!
Adding UNIQUE
-- Add UNIQUE to existing column
ALTER TABLE students
ADD UNIQUE (email);
-- Or with named constraint
ALTER TABLE students
ADD CONSTRAINT uq_student_email UNIQUE (email);
-- WARNING: This fails if any duplicate emails exist!
Removing Constraints
-- Remove UNIQUE constraint (by name)
ALTER TABLE students
DROP INDEX uq_student_email;
-- Remove NOT NULL (make column nullable)
ALTER TABLE students
MODIFY email VARCHAR(255) NULL;
7. 🎯 Practice Exercises
Exercise 1: Design a Products Table
Create a products table with appropriate NOT NULL and UNIQUE constraints:
- product_id (primary key)
- name (required)
- sku (required, must be unique - Stock Keeping Unit)
- price (required)
- description (optional)
Show Solution
CREATE TABLE products (
product_id INT PRIMARY KEY AUTO_INCREMENT,
name VARCHAR(255) NOT NULL,
sku VARCHAR(50) NOT NULL UNIQUE,
price DECIMAL(10, 2) NOT NULL,
description TEXT
);
-- Test inserts
-- ✓ Success
INSERT INTO products (name, sku, price)
VALUES ('Laptop', 'LAP-001', 999.99);
-- ❌ Fails: missing required field (sku)
INSERT INTO products (name, price)
VALUES ('Mouse', 29.99);
-- ERROR: Column 'sku' cannot be null
-- ❌ Fails: duplicate SKU
INSERT INTO products (name, sku, price)
VALUES ('Keyboard', 'LAP-001', 79.99);
-- ERROR: Duplicate entry 'LAP-001' for key 'sku'
-- ✓ Success: description is optional
INSERT INTO products (name, sku, price, description)
VALUES ('Monitor', 'MON-001', 299.99, '27-inch 4K display');
Key Points:
- ✅ sku is both NOT NULL and UNIQUE (every product needs one, no duplicates)
- ✅ name and price are required (NOT NULL)
- ✅ description is optional (can be NULL)
- ✅ Database prevents incomplete or duplicate products
Exercise 2: Fix a Poorly Designed Table
This table has several design problems. Identify them and create the corrected version:
-- ❌ Problematic design
CREATE TABLE customers (
customer_id INT PRIMARY KEY AUTO_INCREMENT,
email VARCHAR(255),
phone VARCHAR(20),
first_name VARCHAR(100),
last_name VARCHAR(100)
);
Think about: What fields are required? Should any fields be unique?
Show Solution
Problems with Original:
- ❌ Email is not required (customers need a way to contact them!)
- ❌ Email is not unique (multiple customers could share email)
- ❌ Names are not required (who is this customer?)
- ❌ Could create customer with all NULL values!
Fixed Version:
CREATE TABLE customers (
customer_id INT PRIMARY KEY AUTO_INCREMENT,
email VARCHAR(255) NOT NULL UNIQUE, -- Required and must be unique
phone VARCHAR(20), -- Optional (not everyone has phone)
first_name VARCHAR(100) NOT NULL, -- Required
last_name VARCHAR(100) NOT NULL -- Required
);
-- Now database enforces data quality:
-- - Every customer MUST have email (and it's unique)
-- - Every customer MUST have first and last name
-- - Phone is optional
Exercise 3: Composite UNIQUE Constraint
Create a student_courses table that prevents a student from enrolling in the same course twice, but allows multiple students to take the same course.
Requirements:
- enrollment_id (primary key)
- student_id (required, foreign key)
- course_id (required, foreign key)
- enrollment_date (required)
- The combination of (student_id, course_id) must be unique
Show Solution
CREATE TABLE student_courses (
enrollment_id INT PRIMARY KEY AUTO_INCREMENT,
student_id INT NOT NULL,
course_id INT NOT NULL,
enrollment_date DATE NOT NULL DEFAULT CURRENT_DATE,
-- Foreign keys
FOREIGN KEY (student_id) REFERENCES students(student_id),
FOREIGN KEY (course_id) REFERENCES courses(course_id),
-- Composite UNIQUE: prevent duplicate enrollments
UNIQUE (student_id, course_id)
);
-- ✓ Student 100 enrolls in Course 1
INSERT INTO student_courses (student_id, course_id)
VALUES (100, 1);
-- ✓ Student 200 enrolls in Course 1 (same course, different student - OK)
INSERT INTO student_courses (student_id, course_id)
VALUES (200, 1);
-- ✓ Student 100 enrolls in Course 2 (same student, different course - OK)
INSERT INTO student_courses (student_id, course_id)
VALUES (100, 2);
-- ❌ Student 100 tries to enroll in Course 1 again (duplicate!)
INSERT INTO student_courses (student_id, course_id)
VALUES (100, 1);
-- ERROR: Duplicate entry '100-1' for key
How It Works:
- ✅ student_id can appear multiple times (enroll in many courses)
- ✅ course_id can appear multiple times (many students in course)
- ✅ But (student_id, course_id) combination must be unique
- ✅ Prevents accidental duplicate enrollments
🔥 Challenge: Real-World Scenario
Design a social media users table with these requirements:
- Every user must have a unique username (required)
- Every user must have a unique email (required)
- Phone number is optional but must be unique if provided
- Display name is optional and doesn't need to be unique
- Bio is optional
Think carefully about which fields need NOT NULL, UNIQUE, or both!
Show Solution
CREATE TABLE social_users (
user_id INT PRIMARY KEY AUTO_INCREMENT,
username VARCHAR(50) NOT NULL UNIQUE, -- Required and unique
email VARCHAR(255) NOT NULL UNIQUE, -- Required and unique
phone VARCHAR(20) UNIQUE, -- Optional but unique if provided
display_name VARCHAR(100), -- Optional, can be duplicate
bio TEXT, -- Optional
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
-- Test scenarios
-- ✓ Success: All required fields provided
INSERT INTO social_users (username, email, display_name)
VALUES ('alice123', 'alice@example.com', 'Alice Johnson');
-- ✓ Success: Phone provided and unique
INSERT INTO social_users (username, email, phone)
VALUES ('bob456', 'bob@example.com', '555-1234');
-- ✓ Success: Same display name is allowed
INSERT INTO social_users (username, email, display_name)
VALUES ('carol789', 'carol@example.com', 'Alice Johnson');
-- ❌ Fails: Duplicate username
INSERT INTO social_users (username, email)
VALUES ('alice123', 'different@example.com');
-- ERROR: Duplicate entry 'alice123' for key 'username'
-- ❌ Fails: Duplicate phone
INSERT INTO social_users (username, email, phone)
VALUES ('dave999', 'dave@example.com', '555-1234');
-- ERROR: Duplicate entry '555-1234' for key 'phone'
-- ✓ Success: Multiple users with NULL phone (NULL != NULL in UNIQUE)
INSERT INTO social_users (username, email)
VALUES ('eve111', 'eve@example.com');
INSERT INTO social_users (username, email)
VALUES ('frank222', 'frank@example.com');
Key Design Decisions:
- ✅ username: NOT NULL UNIQUE (identity required, no duplicates)
- ✅ email: NOT NULL UNIQUE (contact required, no duplicates)
- ✅ phone: UNIQUE only (optional, but no duplicates if provided)
- ✅ display_name: No constraints (cosmetic, can be anything)
- ✅ Multiple users can have NULL phone (UNIQUE allows multiple NULLs)
💡 Important: UNIQUE allows multiple NULL values! NULL is not considered equal to another NULL, so multiple rows can have NULL in a UNIQUE column.
📝 Key Takeaways
- NOT NULL ensures required data is always present
- UNIQUE prevents duplicate values in a column
- Use NOT NULL when data is absolutely required
- Use UNIQUE when values must be distinct (emails, usernames, SKUs)
- Can combine NOT NULL and UNIQUE for required, unique fields
- Primary keys are automatically NOT NULL and UNIQUE
- UNIQUE columns can be NULL (and multiple NULLs are allowed)
- Tables can have multiple UNIQUE columns
- Composite UNIQUE constraints enforce uniqueness on combinations
- Constraints are enforced on both INSERT and UPDATE operations
- Adding constraints to existing tables fails if data violates constraints
- Database-level validation is more reliable than application-level validation