PRIMARY KEY
A PRIMARY KEY constraint designates one column, or a combination of columns, as the unique identifier for every row in a table. No two rows may ever have the same primary key value, and the primary key column(s) can never be NULL — together this guarantees that any single row can always be located unambiguously.
A table may have at most one PRIMARY KEY, though that key can be composite — made up of more than one column when no single column is unique on its own.
Declaring a primary key
-- Single-column primary key, declared inline CREATE TABLE customers ( customer_id INT PRIMARY KEY, name VARCHAR(100) NOT NULL, email VARCHAR(255) ); -- Composite primary key, declared as a table-level constraint CREATE TABLE order_items ( order_id INT, line_number INT, product_id INT NOT NULL, quantity INT NOT NULL, PRIMARY KEY (order_id, line_number) );
In order_items, neither order_id nor line_number is unique by itself — many rows can share the same order_id (one per line item), and many rows across different orders can share the same line_number (1, 2, 3...). Only the combination of the two uniquely identifies a row.
Natural keys vs surrogate keys
A natural key is a primary key built from data that already has real-world meaning — an email address, a national ID number, a product SKU. A surrogate key is an artificial identifier with no business meaning at all, typically an auto-incrementing integer or a generated UUID, whose only job is to identify the row.
Natural key | Surrogate key | |
|---|---|---|
Example | email, national_id, sku | auto-increment id, UUID |
Meaningful to the business | Yes | No |
Can change over time | Sometimes (people change emails) | Never |
Storage size | Often larger (strings) | Small, fixed-size integer/UUID |
Join performance | Can be slower (string comparisons) | Fast (integer comparisons) |
Risk if it changes | Cascades through every foreign key referencing it | None — the key itself never needs to change |
-- Natural key: risky if the SKU scheme ever changes CREATE TABLE products_natural ( sku VARCHAR(20) PRIMARY KEY, name VARCHAR(100) NOT NULL ); -- Surrogate key: the SKU can still be tracked and even changed safely CREATE TABLE products_surrogate ( product_id INT PRIMARY KEY, sku VARCHAR(20) NOT NULL UNIQUE, name VARCHAR(100) NOT NULL );