← Back to Lessons
BEGINNER ⏱ 30 minutes

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

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:

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

← Previous: Foreign Keys
Next: CHECK & DEFAULT →