A primary key uniquely identifies every row and requires non-NULL key values; a composite key identifies a row through a combination of columns.
An id column is common, but it is not the only way to identify a record. A relationship table may be identified naturally by both related ids together. The right key depends on the record’s actual identity and how stable that identity is over time.
This reference adds detail to the main course. You can read it independently: the small database below supplies every table and row used in the examples. Start with the question each query answers, then compare the returned values with the data rather than memorising a line of syntax.
Set up the practice database
Run this setup once in a new practice database, before the examples. It uses four customers and six sales. Ana has two sales, Ben has one, Cara has two, and Dev has none.
Sale 106 has no customer link; sale 104 has an unknown amount. These deliberate test cases expose missing information and unmatched relationships.
CREATE TABLE customers (
id INTEGER PRIMARY KEY,
name VARCHAR(40) NOT NULL,
city VARCHAR(40),
manager_id INTEGER
);
CREATE TABLE sales (
id INTEGER PRIMARY KEY,
customer_id INTEGER,
amount DECIMAL(10,2),
status VARCHAR(20) NOT NULL,
created_on DATE NOT NULL
);
INSERT INTO customers (id,name,city,manager_id) VALUES
(1,'Ana','Leeds',NULL),(2,'Ben','Leeds',1),
(3,'Cara','Porto',1),(4,'Dev',NULL,2);
INSERT INTO sales (id,customer_id,amount,status,created_on) VALUES
(101,1,10,'paid','2026-01-01'),
(102,1,30,'pending','2026-01-02'),
(103,2,20,'paid','2026-01-02'),
(104,3,NULL,'pending','2026-02-01'),
(105,3,20,'paid','2026-02-02'),
(106,NULL,5,'paid','2026-02-03');An amount of zero would be a known value. The NULL amount here means the value is unknown. A customer with no matching sale differs from a sale whose customer_id is NULL.
The primary keys identify individual records even when amounts or cities repeat. Dates use year-month-day format. The examples specify sorting whenever order matters; insertion order alone does not guarantee display order.
How Primary keys and composite keys works
A primary key expresses the table’s chosen row identity. Its values must be unique and present. Other tables can refer to that identity through foreign keys. A surrogate key such as a generated integer has no direct business meaning, while a natural key uses a meaningful domain identifier. A natural key can be suitable when its uniqueness and stability are genuinely guaranteed; a customer name usually has neither property.
A composite primary key combines columns. In an enrolment table, PRIMARY KEY(student_id,course_id) prevents the same student-course pair being stored twice. The student can still appear in different courses, and the course can still have many students. Neither column is individually unique. If repeated enrolments across terms are valid, term must be part of the identity or a different key policy is needed. The key must describe the real event rather than suppressing legitimate repetitions.
Column order in a composite key
Key order can matter for the supporting index and lookup patterns even when the uniqueness combination is the same. A composite index starting with customer_id can commonly support that leading-column lookup more directly than a sale_id-only lookup. Physical indexing details differ, so do not turn this into a claim that every engine always stores primary keys the same way. Foreign keys referring to a composite identity must provide the corresponding column combination in the correct mapping.
A surrogate primary key does not eliminate business uniqueness needs. A table with id as primary key may still need UNIQUE(customer_id,tag) to prevent duplicate assignments. Conversely, using every descriptive field in a primary key creates a brittle identity that changes whenever a label is corrected. Choose the primary identity, add separate business constraints, and document the relationship each foreign key references. Prefer stable keys so updates do not require changing many referencing records.
MySQL, PostgreSQL, SQL Server and SQLite
Composite PRIMARY KEY syntax is supported by all four engines. Ordinary SQLite tables have historical cases in which some primary-key forms permit NULL unless NOT NULL, STRICT or WITHOUT ROWID rules address it. The example explicitly marks both components NOT NULL for portability rather than relying on an implicit assumption.
Check the engine-specific syntax and version requirements in SQLite CREATE TABLE primary keys.
Practice task and worked solution
Create a small enrolment table where the student-course pair is unique. Insert two courses for the same student and verify both.
Try the task before reading its worked solution. The supplied rows make the expected answer checkable by hand.
CREATE TABLE enrolments (
student_id INTEGER NOT NULL,course_id INTEGER NOT NULL,
PRIMARY KEY(student_id,course_id)
);
INSERT INTO enrolments (student_id,course_id) VALUES (1,10),(1,20);
SELECT student_id,course_id FROM enrolments ORDER BY student_id,course_id;student_id | course_id
1 | 10
1 | 20The two rows share student 1 but have different course ids, so their composite identities differ. Repeating (1,10) would be rejected. The table demonstrates pair uniqueness without inventing a requirement that either component must be globally unique.
Try inserting (1,newsletter) again into customer_tags and expect a key violation. Then insert a new tag for customer 1 and an existing tag for a different customer; both are valid new pairs. For an enrolment model with repeat terms, ask whether student-course alone suppresses legitimate events and revise the identity before adding more sample rows.
SQL Primary Keys and Composite Keys: Design and Examples Syntax
Primary keys and composite keys
CREATE TABLE customer_tags (
customer_id INTEGER NOT NULL,tag VARCHAR(20) NOT NULL,
PRIMARY KEY(customer_id,tag)
);
INSERT INTO customer_tags (customer_id,tag) VALUES
(1,'newsletter'),(1,'vip'),(2,'newsletter');
SELECT customer_id,tag FROM customer_tags ORDER BY customer_id,tag;SQL Primary Keys and Composite Keys: Design and Examples Summary
- A primary key identifies each row; its values must be unique and never
NULL. - A surrogate key, such as a generated
id, has no business meaning and never needs to change. - A composite key identifies a row by a combination, such as
(customer_id, tag)in a link table. - In a composite key, only the whole combination must be unique; each column may repeat.
- Column order in a composite key affects which lookups its index supports best.
SQL Primary Keys and Composite Keys: Design and Examples Examples
Each example shows the code, the output it produces and what each part does.
Customer tags identified by both columns
The key distinguishes each complete customer-tag pair. Customer 1 can have two different tags, and newsletter can belong to two customers. A second identical pair would violate the key even though both individual component values are already allowed elsewhere.
CREATE TABLE customer_tags (
customer_id INTEGER NOT NULL,tag VARCHAR(20) NOT NULL,
PRIMARY KEY(customer_id,tag)
);
INSERT INTO customer_tags (customer_id,tag) VALUES
(1,'newsletter'),(1,'vip'),(2,'newsletter');
SELECT customer_id,tag FROM customer_tags ORDER BY customer_id,tag;customer_id | tag
1 | newsletter
1 | vip
2 | newsletterCommon SQL Primary Keys and Composite Keys: Design and Examples Mistakes
A key on one column of a link table
Declaring customer_id alone as the primary key in a customer-tags table prevents a customer from having more than one tag. Declaring tag alone prevents sharing a tag. Use the combination when the business rule concerns unique assignments, and add an additional term or occurrence field if repetitions are valid over time.
SQL Primary Keys and Composite Keys: Design and Examples in Real Projects
Preventing duplicate links
A many-to-many assignment table can use a composite key to prevent duplicate links. Read the requirements carefully: one permanent customer-tag assignment differs from a dated history of tag changes. A history table usually needs an identity that distinguishes legitimate later events.
Review a composite identity against repeat-event requirements. A customer-tag pair may be unique for current assignments, while a dated assignment history needs additional event identity. A generated surrogate key can simplify references, but a separate business unique constraint may still be required to prevent storing the same current assignment twice.
SQL Primary Keys and Composite Keys: Design and Examples FAQ
Can a table have several primary keys?
No. It has one primary-key constraint, which can contain several columns. Additional unique constraints can enforce other candidate keys, such as an email address or an external reference.
Does a composite key make each component unique?
No. The whole combination must be unique, so customer 1 can have several tags and a tag can belong to several customers; only the same customer-and-tag pair twice is rejected.
Should every table use an integer id?
It is a useful common choice, because it's compact, stable and easy to reference. Stable natural or composite keys can be appropriate when they express the actual identity, such as a link table or a table of country codes.
Can I change a table's primary key later?
Yes, but it's disruptive: drop the old constraint, add the new one, and update every foreign key that pointed to the old key. Existing rows must already satisfy the new key. Choose primary keys that won't need to change, which is a strong argument for surrogate ids.
What is a candidate key?
Any column or combination of columns that could uniquely identify rows, such as id, email or (customer_id, tag). One candidate key is chosen as the primary key; the others should still be protected with UNIQUE constraints.
SQL Primary Keys and Composite Keys: Design and Examples Exercises
Answer each one and check it instantly. If you get stuck, open the hint before the answer.
Can customer_tags contain both (1,newsletter) and (2,newsletter)?
SQL Primary Keys and Composite Keys: Design and Examples Quiz
2 questions to check what you learned.
What does PRIMARY KEY(customer_id,tag) make unique?
- Each complete customer-and-tag combination
- Each customer_id independently
- Each tag independently
Does a surrogate id remove the need for business uniqueness rules?
- Yes; different generated ids make duplicate business facts impossible
- No, separate UNIQUE constraints may still be required
- Yes; a primary key automatically recognises every business reference