← Back to Lessons
BEGINNER 🔒 CONSTRAINTS ⏱ 40 minutes

Foreign Keys: Enforcing Relationships and Referential Integrity

Primary keys identify rows uniquely, but foreign keys connect tables together. They're the glue that makes relational databases work, ensuring that relationships between tables remain valid and consistent. A foreign key in one table references a primary key in another table, creating an enforceable link. In this lesson, you'll learn how foreign keys maintain data integrity, prevent orphaned records, and make your database relationships bulletproof.


📚 What You'll Learn

1. What is a Foreign Key?

A foreign key is a column (or set of columns) in one table that references the primary key of another table. It creates a link between the two tables and ensures that the relationship remains valid.

Visual Example: Students and Enrollments

students Table (Parent)

🔑 student_id name email
100234 Alice Johnson alice@uni.edu
100567 Bob Smith bob@uni.edu

Foreign key references primary key

enrollments Table (Child)

enrollment_id 🔗 student_id (FK) course_code grade
1001 100234 CS101 A
1002 100234 CS201 B+
1003 100567 BIO101 A-

The student_id column in enrollments is a foreign key that references the student_id primary key in the students table. This creates a relationship: each enrollment belongs to exactly one student.

2. What is Referential Integrity?

Referential integrity means that relationships between tables remain valid. If a foreign key says "this enrollment belongs to student 100234," then student 100234 must exist in the students table. The database enforces this automatically.

Foreign Keys Prevent Invalid Data

❌ Without Foreign Keys (Disaster Waiting to Happen)

-- No foreign key constraint
INSERT INTO enrollments (student_id, course_code, grade)
VALUES (999999, 'CS101', 'A');

-- ✓ Insert succeeds... but student 999999 doesn't exist! 😱
-- This is an "orphaned record" - an enrollment with no student!

Problem: Now you have an enrollment record for a student that doesn't exist. Who got the grade? Where do you send the transcript? The data is meaningless!

✅ With Foreign Keys (Protected)

-- With foreign key constraint
INSERT INTO enrollments (student_id, course_code, grade)
VALUES (999999, 'CS101', 'A');

-- ✗ ERROR: Cannot add or update a child row: 
-- a foreign key constraint fails
-- Student 999999 doesn't exist, so insert is REJECTED ✓

✅ This is good! The database protects you from creating invalid relationships. You cannot create enrollments for non-existent students.

3. Parent Tables vs. Child Tables

Parent Table

The table being referenced (contains the primary key). Example: students table.

  • Contains primary key (🔑)
  • Referenced by other tables
  • Must exist first

Child Table

The table doing the referencing (contains the foreign key). Example: enrollments table.

  • Contains foreign key (🔗)
  • Depends on parent table
  • References parent records

The Relationship

students (Parent - has PK)

enrollments (Child - has FK)

One parent can have many children (one-to-many relationship)

4. How Foreign Keys Protect Your Data

Protection 1: Cannot Insert Invalid References

-- ❌ This will fail
INSERT INTO enrollments (student_id, course_code)
VALUES (888888, 'CS101');

-- Error: student 888888 doesn't exist in students table

Protection 2: Cannot Delete Referenced Parents

-- ❌ This will fail if student 100234 has enrollments
DELETE FROM students 
WHERE student_id = 100234;

-- Error: Cannot delete - student has enrollments referencing them
-- This prevents orphaned enrollments!

⚠️ Important: This is a good thing! Foreign keys prevent you from accidentally deleting a student who still has enrollment records. You must handle the enrollments first (delete them or reassign them).

Protection 3: Cannot Update Primary Keys That Are Referenced

-- ❌ This will fail if student 100234 is referenced elsewhere
UPDATE students 
SET student_id = 200000 
WHERE student_id = 100234;

-- Error: Cannot update - enrollments still reference 100234
-- This is why primary keys should NEVER change!

5. Creating Foreign Keys in SQL

Method 1: Define at Table Creation (Column Level)

CREATE TABLE enrollments (
    enrollment_id INT PRIMARY KEY AUTO_INCREMENT,
    student_id INT NOT NULL,
    course_code VARCHAR(20) NOT NULL,
    grade VARCHAR(2),
    FOREIGN KEY (student_id) REFERENCES students(student_id)
);

Method 2: Define at Table Level (Named Constraint)

CREATE TABLE enrollments (
    enrollment_id INT PRIMARY KEY AUTO_INCREMENT,
    student_id INT NOT NULL,
    course_code VARCHAR(20) NOT NULL,
    grade VARCHAR(2),
    CONSTRAINT fk_enrollment_student 
        FOREIGN KEY (student_id) 
        REFERENCES students(student_id)
);

💡 Naming Convention: Use fk_childtable_parenttable for constraint names. Example: fk_enrollment_student makes it clear that enrollments references students.

Method 3: Add to Existing Table

-- Add foreign key constraint after table creation
ALTER TABLE enrollments
ADD CONSTRAINT fk_enrollment_student
FOREIGN KEY (student_id) REFERENCES students(student_id);

Multiple Foreign Keys in One Table

CREATE TABLE enrollments (
    enrollment_id INT PRIMARY KEY AUTO_INCREMENT,
    student_id INT NOT NULL,
    course_id INT NOT NULL,
    enrollment_date DATE NOT NULL,
    grade VARCHAR(2),
    -- Two foreign keys!
    FOREIGN KEY (student_id) REFERENCES students(student_id),
    FOREIGN KEY (course_id) REFERENCES courses(course_id)
);

6. Introduction to ON DELETE and ON UPDATE

You can control what happens when a parent record is deleted or updated. Here are the basic options:

Option What Happens Use Case
RESTRICT ❌ Prevent deletion/update (default) Most common - force explicit handling
CASCADE 🔄 Delete/update children automatically When children can't exist without parent
SET NULL ⚪ Set child FK to NULL When relationship is optional
NO ACTION ❌ Same as RESTRICT Compatibility with other databases

Example: CASCADE Delete

CREATE TABLE enrollments (
    enrollment_id INT PRIMARY KEY AUTO_INCREMENT,
    student_id INT NOT NULL,
    course_code VARCHAR(20) NOT NULL,
    grade VARCHAR(2),
    FOREIGN KEY (student_id) 
        REFERENCES students(student_id)
        ON DELETE CASCADE  -- If student deleted, delete their enrollments too
);

⚠️ Be Careful with CASCADE: Deleting one student could automatically delete dozens of enrollment records! Use CASCADE only when you're absolutely sure child records should be deleted with the parent.

Example: SET NULL

CREATE TABLE orders (
    order_id INT PRIMARY KEY AUTO_INCREMENT,
    customer_id INT,  -- Can be NULL
    order_date DATE NOT NULL,
    total DECIMAL(10, 2),
    FOREIGN KEY (customer_id) 
        REFERENCES customers(customer_id)
        ON DELETE SET NULL  -- If customer deleted, keep order but set customer to NULL
);

7. Common Foreign Key Mistakes

❌ Mistake ✅ Fix
Referencing a column that isn't a primary key Foreign keys must reference primary keys (or unique keys)
Mismatched data types (INT → VARCHAR) FK and PK must have identical data types
Creating child table before parent table Always create parent tables first
Forgetting NOT NULL on required foreign keys Add NOT NULL if relationship is required
Using CASCADE without understanding consequences Default to RESTRICT; use CASCADE carefully

8. 🎯 Practice Exercises

Exercise 1: Create Related Tables

Create two tables: authors and books. Each book has exactly one author, but each author can write multiple books. Include appropriate foreign keys.

Requirements:

  • authors: author_id (PK), name, country
  • books: book_id (PK), title, author_id (FK), publication_year
  • Every book must have an author (FK cannot be NULL)
Show Solution
-- Create parent table first
CREATE TABLE authors (
    author_id INT PRIMARY KEY AUTO_INCREMENT,
    name VARCHAR(255) NOT NULL,
    country VARCHAR(100)
);

-- Create child table with foreign key
CREATE TABLE books (
    book_id INT PRIMARY KEY AUTO_INCREMENT,
    title VARCHAR(255) NOT NULL,
    author_id INT NOT NULL,  -- Required relationship
    publication_year INT,
    CONSTRAINT fk_book_author
        FOREIGN KEY (author_id) 
        REFERENCES authors(author_id)
);

-- Test: Insert author first, then book
INSERT INTO authors (name, country) 
VALUES ('J.K. Rowling', 'United Kingdom');

INSERT INTO books (title, author_id, publication_year)
VALUES ('Harry Potter', 1, 1997);

-- This will FAIL (author 999 doesn't exist):
-- INSERT INTO books (title, author_id) VALUES ('Test Book', 999);

Key Points:

  • ✅ authors created first (parent table)
  • ✅ books.author_id is NOT NULL (required)
  • ✅ Foreign key prevents inserting books with invalid author_id
  • ✅ Named constraint: fk_book_author
  • ✅ One-to-many: one author → many books

Exercise 2: Multiple Foreign Keys

Create an orders table that references both customers and employees tables. Each order is placed by a customer and processed by an employee.

Show Solution
-- Assume customers and employees tables already exist

CREATE TABLE orders (
    order_id INT PRIMARY KEY AUTO_INCREMENT,
    customer_id INT NOT NULL,
    employee_id INT NOT NULL,
    order_date DATE NOT NULL DEFAULT CURRENT_DATE,
    total_amount DECIMAL(10, 2) NOT NULL,
    
    -- Two foreign keys
    CONSTRAINT fk_order_customer
        FOREIGN KEY (customer_id) 
        REFERENCES customers(customer_id),
    
    CONSTRAINT fk_order_employee
        FOREIGN KEY (employee_id) 
        REFERENCES employees(employee_id)
);

-- Example insert (requires valid customer and employee IDs):
-- INSERT INTO orders (customer_id, employee_id, total_amount)
-- VALUES (1001, 2001, 149.99);

Analysis:

  • ✅ Two foreign keys in one table
  • ✅ Each FK has a descriptive constraint name
  • ✅ Both FKs are required (NOT NULL)
  • ✅ Cannot create order with invalid customer or employee
  • ✅ Cannot delete customer/employee if they have orders

Exercise 3: Optional Relationship with SET NULL

Create a blog_posts table where each post can optionally have a category. If a category is deleted, the post should remain but the category reference should become NULL.

Show Solution
-- Categories table
CREATE TABLE categories (
    category_id INT PRIMARY KEY AUTO_INCREMENT,
    name VARCHAR(100) NOT NULL UNIQUE
);

-- Posts table with optional category
CREATE TABLE blog_posts (
    post_id INT PRIMARY KEY AUTO_INCREMENT,
    title VARCHAR(255) NOT NULL,
    content TEXT NOT NULL,
    category_id INT,  -- NULL allowed (optional)
    published_date DATE DEFAULT CURRENT_DATE,
    
    CONSTRAINT fk_post_category
        FOREIGN KEY (category_id) 
        REFERENCES categories(category_id)
        ON DELETE SET NULL  -- Keep post, remove category reference
);

-- Example scenario:
-- INSERT INTO categories (name) VALUES ('Technology');
-- INSERT INTO blog_posts (title, content, category_id) 
-- VALUES ('AI Trends', 'Article about AI...', 1);

-- If you delete the Technology category:
-- DELETE FROM categories WHERE category_id = 1;
-- The post remains, but category_id becomes NULL

Key Design Decisions:

  • ✅ category_id can be NULL (optional relationship)
  • ✅ ON DELETE SET NULL preserves posts when category deleted
  • ✅ Posts can exist without a category
  • ✅ Useful when relationship is optional, not required

🔥 Challenge: Self-Referencing Foreign Key

Create an employees table where each employee can have a manager, who is also an employee. The manager_id should reference employee_id in the same table.

Show Solution
CREATE TABLE employees (
    employee_id INT PRIMARY KEY AUTO_INCREMENT,
    name VARCHAR(255) NOT NULL,
    email VARCHAR(255) UNIQUE NOT NULL,
    manager_id INT,  -- References another row in THIS table
    
    CONSTRAINT fk_employee_manager
        FOREIGN KEY (manager_id) 
        REFERENCES employees(employee_id)
        ON DELETE SET NULL  -- If manager deleted, employee remains
);

-- Example: Insert CEO first (no manager)
INSERT INTO employees (name, email, manager_id)
VALUES ('Alice CEO', 'alice@company.com', NULL);

-- Insert manager who reports to CEO
INSERT INTO employees (name, email, manager_id)
VALUES ('Bob Manager', 'bob@company.com', 1);

-- Insert employee who reports to Bob
INSERT INTO employees (name, email, manager_id)
VALUES ('Carol Employee', 'carol@company.com', 2);

-- Query to see organizational structure:
SELECT 
    e.name AS employee,
    m.name AS manager
FROM employees e
LEFT JOIN employees m ON e.manager_id = m.employee_id;

Advanced Concepts:

  • Self-referencing FK: Table references itself
  • ✅ manager_id points to another employee_id in same table
  • ✅ ON DELETE SET NULL preserves subordinates if manager leaves
  • ✅ CEO has manager_id = NULL (no manager)
  • ✅ Creates hierarchical/tree structure
  • ✅ Can query org chart using self-join

📝 Key Takeaways

← Previous: Primary Keys
Next: NOT NULL & UNIQUE →