MySQL Constraints

Learn how to enforce data integrity rules in MySQL

🔒 What are Constraints?

Constraints are rules applied to table columns to enforce data integrity and accuracy. They prevent invalid data from being entered into the database, ensuring your data remains consistent, reliable, and follows business rules automatically.


-- Create table with constraints
CREATE TABLE users (
    id INT PRIMARY KEY,
    email VARCHAR(100) UNIQUE NOT NULL,
    age INT CHECK (age >= 18)
);
                                    

Output:

Query OK, 0 rows affected (0.06 sec)

✓ Table created with data integrity rules

Types of Constraints

🔑

PRIMARY KEY

Unique identifier for each row

id INT PRIMARY KEY
🔗

FOREIGN KEY

Links tables together

FOREIGN KEY (user_id) 
REFERENCES users(id)

UNIQUE

Ensures all values are different

email VARCHAR(100) UNIQUE

NOT NULL

Requires a value (no empty fields)

name VARCHAR(50) NOT NULL

CHECK

Validates data against conditions

age INT CHECK (age >= 0)
📌

DEFAULT

Sets automatic default values

status VARCHAR(20) 
DEFAULT 'active'

🔹 PRIMARY KEY Constraint

A PRIMARY KEY uniquely identifies each record in a table. Each table can have only one primary key, which must contain unique values and cannot be NULL. Primary keys are automatically indexed for fast lookups and are commonly used with AUTO_INCREMENT for automatic ID generation.

-- Primary key in column definition
CREATE TABLE students (
    student_id INT PRIMARY KEY AUTO_INCREMENT,
    name VARCHAR(100),
    email VARCHAR(100)
);

-- Primary key as table constraint
CREATE TABLE courses (
    course_id INT AUTO_INCREMENT,
    course_name VARCHAR(100),
    PRIMARY KEY (course_id)
);

-- Composite primary key (multiple columns)
CREATE TABLE enrollments (
    student_id INT,
    course_id INT,
    enrollment_date DATE,
    PRIMARY KEY (student_id, course_id)
);

Output:

✓ Primary key ensures unique identification

✓ Automatically creates index for fast lookups

✓ Cannot contain NULL or duplicate values

🔹 FOREIGN KEY Constraint

A FOREIGN KEY creates a link between two tables by referencing the primary key of another table. This maintains referential integrity, ensuring that relationships between tables remain consistent. Foreign keys prevent orphaned records and can automatically cascade updates or deletions to related records.

-- Create parent table first
CREATE TABLE customers (
    customer_id INT PRIMARY KEY AUTO_INCREMENT,
    name VARCHAR(100)
);

-- Create child table with foreign key
CREATE TABLE orders (
    order_id INT PRIMARY KEY AUTO_INCREMENT,
    order_date DATE,
    customer_id INT,
    FOREIGN KEY (customer_id) REFERENCES customers(customer_id)
);

-- Foreign key with cascade options
CREATE TABLE order_items (
    item_id INT PRIMARY KEY AUTO_INCREMENT,
    order_id INT,
    product_name VARCHAR(100),
    FOREIGN KEY (order_id) 
        REFERENCES orders(order_id)
        ON DELETE CASCADE
        ON UPDATE CASCADE
);

Output:

✓ Foreign key relationship established

✓ Prevents invalid customer_id values

✓ CASCADE: Deleting parent deletes children

🔹 UNIQUE Constraint

The UNIQUE constraint ensures all values in a column are different from each other. Unlike PRIMARY KEY, a table can have multiple UNIQUE constraints, and UNIQUE columns can contain NULL values (though only one NULL per column in most cases). This is perfect for fields like email addresses or usernames.

-- Unique constraint in column definition
CREATE TABLE users (
    user_id INT PRIMARY KEY AUTO_INCREMENT,
    username VARCHAR(50) UNIQUE,
    email VARCHAR(100) UNIQUE,
    phone VARCHAR(20)
);

-- Unique constraint as table constraint
CREATE TABLE products (
    product_id INT PRIMARY KEY,
    product_code VARCHAR(50),
    product_name VARCHAR(100),
    UNIQUE (product_code)
);

-- Composite unique constraint
CREATE TABLE user_profiles (
    user_id INT,
    platform VARCHAR(50),
    profile_url VARCHAR(200),
    UNIQUE (user_id, platform)
);

Output:

✓ UNIQUE prevents duplicate values

✓ Allows NULL values (unlike PRIMARY KEY)

✓ Multiple UNIQUE constraints per table allowed

🔹 NOT NULL Constraint

The NOT NULL constraint ensures a column must always have a value and cannot be left empty. This is essential for required fields where missing data would make records incomplete or invalid. By default, columns allow NULL unless explicitly marked as NOT NULL.

-- NOT NULL in column definitions
CREATE TABLE employees (
    employee_id INT PRIMARY KEY AUTO_INCREMENT,
    first_name VARCHAR(50) NOT NULL,
    last_name VARCHAR(50) NOT NULL,
    email VARCHAR(100) NOT NULL,
    phone VARCHAR(20),  -- This allows NULL
    hire_date DATE NOT NULL
);

-- Attempting to insert NULL will fail
INSERT INTO employees (first_name, last_name, email, hire_date)
VALUES ('John', 'Doe', '[email protected]', '2024-01-15');

-- This will cause an error (email is NULL)
-- INSERT INTO employees (first_name, last_name, hire_date)
-- VALUES ('Jane', 'Smith', '2024-01-20');

Output:

✓ Record inserted successfully

✗ Error: Column 'email' cannot be null

NOT NULL ensures required data is provided

🔹 CHECK Constraint

The CHECK constraint validates data against a specified condition before allowing insertion or updates. This enforces business rules at the database level, such as ensuring ages are positive, prices are above zero, or dates fall within valid ranges. CHECK constraints help maintain data quality automatically.

-- CHECK constraint examples
CREATE TABLE products (
    product_id INT PRIMARY KEY AUTO_INCREMENT,
    product_name VARCHAR(100) NOT NULL,
    price DECIMAL(10, 2) CHECK (price > 0),
    stock_quantity INT CHECK (stock_quantity >= 0),
    discount_percent INT CHECK (discount_percent BETWEEN 0 AND 100)
);

-- Multiple conditions in CHECK
CREATE TABLE employees (
    employee_id INT PRIMARY KEY AUTO_INCREMENT,
    name VARCHAR(100) NOT NULL,
    age INT CHECK (age >= 18 AND age <= 65),
    salary DECIMAL(10, 2) CHECK (salary > 0),
    department VARCHAR(50) CHECK (department IN ('Sales', 'IT', 'HR', 'Finance'))
);

-- Named CHECK constraint
CREATE TABLE accounts (
    account_id INT PRIMARY KEY,
    balance DECIMAL(12, 2),
    CONSTRAINT chk_positive_balance CHECK (balance >= 0)
);

Output:

✓ CHECK constraint enforces business rules

✓ Prevents invalid data: negative prices, invalid ages

✓ Validates data before insertion/update

🔹 DEFAULT Constraint

The DEFAULT constraint provides automatic values for columns when no value is specified during insertion. This is useful for setting initial states, timestamps, or standard values. DEFAULT values reduce the need for explicit value specification and ensure consistency across records when fields are omitted.

-- DEFAULT constraint examples
CREATE TABLE users (
    user_id INT PRIMARY KEY AUTO_INCREMENT,
    username VARCHAR(50) NOT NULL,
    status VARCHAR(20) DEFAULT 'active',
    role VARCHAR(20) DEFAULT 'user',
    credits INT DEFAULT 0,
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
);

-- Insert without specifying default columns
INSERT INTO users (username) 
VALUES ('john_doe');

-- View the inserted record with defaults
SELECT * FROM users WHERE username = 'john_doe';

Output:

user_id username status role credits
1 john_doe active user 0

✓ Default values automatically applied

🔹 Adding Constraints to Existing Tables

You can add constraints to existing tables using ALTER TABLE statements. This is useful when modifying database schemas or adding data integrity rules to legacy tables. Be cautious when adding constraints to tables with existing data, as the operation will fail if current data violates the new constraint.

-- Add PRIMARY KEY
ALTER TABLE users 
ADD PRIMARY KEY (user_id);

-- Add FOREIGN KEY
ALTER TABLE orders 
ADD FOREIGN KEY (customer_id) 
REFERENCES customers(customer_id);

-- Add UNIQUE constraint
ALTER TABLE users 
ADD UNIQUE (email);

-- Add CHECK constraint
ALTER TABLE products 
ADD CONSTRAINT chk_price CHECK (price > 0);

-- Add DEFAULT value
ALTER TABLE users 
ALTER COLUMN status SET DEFAULT 'active';

-- Modify column to NOT NULL
ALTER TABLE users 
MODIFY email VARCHAR(100) NOT NULL;

Output:

✓ Constraints added to existing table

⚠️ Existing data must satisfy new constraints

🔹 Dropping Constraints

Constraints can be removed when they're no longer needed or need to be redefined. Named constraints are easier to drop, which is why it's good practice to name your constraints explicitly. Dropping constraints removes data validation rules, so ensure this aligns with your data integrity requirements.

-- Drop PRIMARY KEY
ALTER TABLE users 
DROP PRIMARY KEY;

-- Drop FOREIGN KEY (need constraint name)
ALTER TABLE orders 
DROP FOREIGN KEY fk_customer_id;

-- Drop UNIQUE constraint
ALTER TABLE users 
DROP INDEX email;

-- Drop CHECK constraint
ALTER TABLE products 
DROP CHECK chk_price;

-- Remove DEFAULT value
ALTER TABLE users 
ALTER COLUMN status DROP DEFAULT;

-- View constraint names
SELECT CONSTRAINT_NAME, CONSTRAINT_TYPE
FROM information_schema.TABLE_CONSTRAINTS
WHERE TABLE_NAME = 'users';

Output:

✓ Constraint removed

⚠️ Data validation rule no longer enforced

🔹 Complete Example with Multiple Constraints

This comprehensive example demonstrates a real-world database design with multiple constraint types working together. It shows how constraints enforce business rules, maintain data integrity, and establish relationships between tables. This represents best practices for database design with proper data validation at every level.

-- Complete example: E-commerce database
CREATE TABLE customers (
    customer_id INT PRIMARY KEY AUTO_INCREMENT,
    email VARCHAR(100) UNIQUE NOT NULL,
    full_name VARCHAR(100) NOT NULL,
    phone VARCHAR(20),
    registration_date TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    status VARCHAR(20) DEFAULT 'active' CHECK (status IN ('active', 'inactive', 'suspended'))
);

CREATE TABLE products (
    product_id INT PRIMARY KEY AUTO_INCREMENT,
    product_name VARCHAR(100) NOT NULL,
    price DECIMAL(10, 2) NOT NULL CHECK (price > 0),
    stock INT DEFAULT 0 CHECK (stock >= 0),
    category VARCHAR(50) NOT NULL,
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

CREATE TABLE orders (
    order_id INT PRIMARY KEY AUTO_INCREMENT,
    customer_id INT NOT NULL,
    order_date TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    total_amount DECIMAL(10, 2) CHECK (total_amount >= 0),
    status VARCHAR(20) DEFAULT 'pending' CHECK (status IN ('pending', 'processing', 'shipped', 'delivered', 'cancelled')),
    FOREIGN KEY (customer_id) REFERENCES customers(customer_id) ON DELETE RESTRICT
);

-- Verify table structures
SHOW CREATE TABLE orders;

Output:

✓ Tables created with comprehensive constraints

✓ Data integrity enforced at database level

✓ Relationships established with foreign keys

✓ Business rules validated automatically

💡 Best Practices

  • Always define a PRIMARY KEY for each table
  • Use FOREIGN KEYs to maintain referential integrity
  • Apply NOT NULL to required fields
  • Use CHECK constraints to enforce business rules
  • Name your constraints explicitly for easier management
  • Use UNIQUE for fields that must be distinct (email, username)
  • Set DEFAULT values for status fields and timestamps
  • Test constraints with invalid data to ensure they work

🧠 Test Your Knowledge

Which constraint ensures a column cannot contain NULL values?