PRIMARY KEY and FOREIGN KEY
The two constraints that give a database its structure: one that identifies a row, and one that guarantees a reference points at something real.
- SQL Basics
- DDL
- DML
- SELECT
- WHERE
- Functions
- NULL and Logic
- Aggregate Functions
- GROUP BY
- JOIN
- Subqueries
- Set Operations
- CTEs
- Constraints
- Keys
- Relationships
- Database Design
- Normalisation
- Views
- Window Functions
- Advanced SQL
- Procedures and Functions
- Triggers
- Temporary Tables
- Transactions
- Isolation and Locking
- Indexes
- Query Performance
- SQL Security
- SQL Dialects
- Practical SQL
Concept
A primary key identifies each row uniquely within its table. A foreign key says that a column's values must exist as a primary key in another table. Together they turn a set of tables into a connected model.
Syntax
-- Primary key: single column
CONSTRAINT pk_departments PRIMARY KEY (id)
-- Primary key: composite
CONSTRAINT pk_assignments PRIMARY KEY (employee_id, project_id)
-- Foreign key
CONSTRAINT fk_employees_dept FOREIGN KEY (dept_id)
REFERENCES departments(id)
ON DELETE RESTRICT
ON UPDATE CASCADEExample
CREATE TABLE departments (
id INT PRIMARY KEY,
name VARCHAR(60) NOT NULL
);
CREATE TABLE employees (
id INT PRIMARY KEY,
name VARCHAR(60) NOT NULL,
dept_id INT NULL,
CONSTRAINT fk_emp_dept FOREIGN KEY (dept_id) REFERENCES departments(id)
);
-- Rejected: department 99 does not exist
INSERT INTO employees VALUES (1, 'Asha', 99);
-- Accepted: NULL means "no department yet", which is not a broken reference
INSERT INTO employees VALUES (2, 'Nikhil', NULL);Explanation
The second insert is the detail people miss: a foreign key constrains non NULL values only. NULL means "no reference", and a missing reference cannot be dangling. If every employee must have a department, the column needs NOT NULL as well as the foreign key.
Primary key properties
- Unique across the table.
- Implicitly
NOT NULL- you never declare it. - One per table, though it may span several columns.
- Backed by an index, which is what makes lookups by key fast.
- In InnoDB it is the clustered key: the table rows are physically stored in primary key order, and every secondary index stores the primary key. A wide primary key therefore makes every index bigger.
Composite primary keys
CREATE TABLE assignments (
employee_id INT,
project_id INT,
hours DECIMAL(6,1) NOT NULL DEFAULT 0,
PRIMARY KEY (employee_id, project_id),
CONSTRAINT fk_asg_emp FOREIGN KEY (employee_id) REFERENCES employees(id),
CONSTRAINT fk_asg_proj FOREIGN KEY (project_id) REFERENCES projects(id)
);The composite key does real work here: it enforces that one employee can be assigned to one project only once, which is exactly the business rule for a junction table.
Important rules
- A foreign key must reference a primary key or a unique key - not an arbitrary column.
- The referenced and referencing columns must have compatible types. An
INTreferencing aBIGINTwill be rejected or silently prevent index use. - Parent rows must exist before child rows. Load and drop tables in dependency order.
- MySQL and MariaDB create an index on the foreign key column automatically. PostgreSQL, SQL Server and Oracle do not - and an unindexed foreign key makes joins and parent deletes slow.
- Foreign keys are only enforced by InnoDB in MySQL. MyISAM parses and ignores them. SQLite needs
PRAGMA foreign_keys = ONper connection.
Common mistakes
- Assuming a foreign key stops
NULL. It does not; addNOT NULL. - Forgetting to index the foreign key column outside MySQL.
- Mismatched types between parent and child columns.
- Using a mutable business value - an email, a phone number - as the primary key, and then having to update it everywhere.
- Dropping foreign keys "for performance" and discovering orphan rows months later.
Best practices
- Give every table a primary key. A table without one cannot be reliably updated, replicated or deduplicated.
- Prefer a short, stable surrogate key - an auto increment or identity integer - over a wide natural key.
- Index every foreign key column.
- Name keys consistently:
pk_table,fk_child_parent. - Declare foreign keys even in systems that "handle it in code" - they cost little and catch what code misses.
Practice
- Add a foreign key from
orders.customer_idtocustomers.idand test it with a bad insert. - Explain why
assignmentsuses a composite primary key rather than its own id column. - What breaks if
employees.dept_idisINTanddepartments.idisBIGINT?