What I Do

Primary Key vs Foreign Key: How Tables Connect

8 min read

A primary key points to exactly one row in a table; a foreign key holds another table's primary key to link the two. Learn 1:1, 1:N and N:M relationships with a members and orders example.

Primary Key vs Foreign Key: How Tables Connect

A primary key is a column that pinpoints exactly one row in a table, and a foreign key is a column that holds another table's primary key values to link the two tables. Thanks to these two columns, you can keep members and orders in separate tables and still find out "whose order is this?" at any time. When I started learning SQL, the terms primary key and foreign key were the least familiar to me. In this post, let's look at what the two keys are and how tables relate to each other, using a small stationery shop example. Previous post: Relational Database Structure: Tables, Rows, Columns, Schema

SQL Basics series 2/12 · Full curriculum

Today's concept

If you've used VLOOKUP in Excel, you've already used a key. When you take the "member ID" from an orders sheet and look up the name on a members sheet, that member ID is doing the job of a key. As an analogy, a primary key is like a national ID number that belongs to only one person, and a foreign key is that number written on a shipping label. You can find the recipient from the label alone, but a label with a number that doesn't exist has nowhere to go.

Primary key (PK)Foreign key (FK)
What it doesTells the rows of its own table apartPoints to a row in another table
Value rulesNo duplicates, no empty valuesA value that exists in the referenced table, or NULL
Per tableAt most oneCan have several
In Excel termsThe member ID column on the members sheetThe member ID column you type on the orders sheet

According to the PostgreSQL constraints documentation, a primary key's values must be both unique and not null, and a table can have at most one primary key. The same page describes a foreign key as a constraint that "the values in a column must match the values appearing in some row of another table," and says this maintains referential integrity (the state where every referenced value actually exists) between the two tables.

Learning by example

Let's attach an orders table to the stationery shop from the last post (sample data).

members

member_id (PK)namecity
1Haneul KimSeoul
2Doyun LeeBusan
3Seoyeon ParkSeoul
4Jiwoo ChoiDaejeon

orders

order_id (PK)member_id (FK)ordered_on
10112026-10-01
10222026-10-03
10312026-10-07
10432026-10-12
10512026-10-20

orders has no member names, only member_id. If you want to know who placed order 105, take member_id 1 and find row 1 in members, and you get Haneul Kim. If a name changes, you only fix it in one place, members. PostgreSQL calls the pointing side, like orders, the referencing table and the pointed-to side, like members, the referenced table, while the SQLite documentation calls them the child table and the parent table.

The members primary key member_id linked by arrows to the orders foreign key member_id Concept figure: the foreign key in the orders table points to the primary key in the members table (figure labels in Korean)

Written as a schema, it looks like this. We'll cover the syntax from part 6, so for now just notice the two words PRIMARY KEY and REFERENCES.

CREATE TABLE members (
    member_id  INTEGER PRIMARY KEY,
    name       VARCHAR(50),
    city       VARCHAR(20)
);
 
CREATE TABLE orders (
    order_id    INTEGER PRIMARY KEY,
    member_id   INTEGER REFERENCES members (member_id),
    ordered_on  DATE
);

With this in place, if you try to insert an order for member 9, who isn't in members, PostgreSQL rejects it with a foreign key violation error. Trying to add another member with the existing ID 2 is blocked too, by the primary key.

How do tables relate to each other?

The MySQL glossary explains that a database encodes and enforces relationships such as one-to-one, one-to-many and many-to-one. When you're just starting out, splitting them into three types is enough.

RelationshipMeaningExampleWhere the keys go
1:1 (one-to-one)A row links to only one row in the other tableMember – member detailsThe foreign key column is also that table's primary key
1:N (one-to-many)A row links to many rows in the other tableMember – ordersForeign key on the "many" side (orders)
N:M (many-to-many)Both sides link to many rowsOrders – productsCreate a separate junction table

The example above is 1:N. Haneul Kim has three orders, Doyun Lee and Seoyeon Park have one each, and Jiwoo Choi has none yet. The foreign key goes on the "many" side because, as we saw in the last post, the number of columns is fixed, so you can't decide how many order-number columns to set aside in the members table.

So what if one order holds several products and one product appears in several orders? The PostgreSQL documentation shows an example of building this kind of many-to-many relationship with a table that has two foreign keys.

order_items (junction table)

order_id (FK)product_id (FK)quantity
10112
10121
10233
10311
10422
10531

product_id points to a products table holding pens (1), notebooks (2) and highlighters (3). Order 101 has two products, and pens appear in orders 101 and 103. This table uses the two columns together, PRIMARY KEY (order_id, product_id), as its primary key, because each column alone repeats but the same combination of the two never appears twice. The SQLite documentation calls a key made of several columns like this a composite key.

Four tables, members, orders, order_items and products, connected by 1:N and N:M relationships Concept figure: the many-to-many relationship between orders and products is bridged by the order_items table in the middle (figure labels in Korean)

How do you choose a primary key?

The MySQL glossary advises considering arbitrary sequence numbers (synthetic keys) rather than meaningful values taken from elsewhere (natural keys) for primary keys, and explains that if a natural key changes, there's a lot to fix.

CandidateExampleProsWatch out for
Synthetic keyMember IDs 1, 2, 3…Never needs to changeThe number alone doesn't tell you who it is
Natural keyEmail, phone numberNo need to create a separate numberPeople can change them, and a change means a lot to fix
Composite key(order ID, product ID)Fits junction tables wellThe referencing side must also carry both columns

If I were moving an Excel sheet into a table, I'd try to choose the primary key with these criteria.

  1. Does this column's value alone identify exactly one row? "Name" won't work because people can share names.
  2. Are there no empty rows? A primary key doesn't accept NULL.
  3. Will the value never change? If it might, keep a separate sequence number and put only a UNIQUE constraint on the email so it can't repeat.

The PostgreSQL documentation notes that relational database theory says every table must have a primary key, but PostgreSQL doesn't enforce this rule, though it's usually best to follow it.

Practice problems

1. (Easy) In the orders table above, which column is the primary key and which is the foreign key? Which table does the foreign key point to?

Show answer

The primary key is order_id and the foreign key is member_id. The foreign key member_id points to member_id, the primary key of the members table. The two columns don't need the same name, but giving them the same name makes the schema easier to read.

2. (Medium) Which of these rows would be blocked if you tried to insert them into orders? Choose all that apply. (a) (106, 4, 2026-10-25) (b) (107, 9, 2026-10-26) (c) (103, 2, 2026-10-27)

Show answer

(b) and (c). (b) violates the foreign key because there's no member 9 in members, and (c) violates the primary key because order ID 103 already exists. (a) is the first order for member 4, Jiwoo Choi, so it goes in.

3. (Medium) An online course site has a "students" table and a "courses" table. One student takes many courses, and one course has many students. What kind of relationship is this, and how would you design the tables?

Show answer

It's a many-to-many (N:M) relationship. Create a junction table such as "enrollments" and store the student ID and course ID as foreign keys. To stop the same person from enrolling in the same course twice, use the two columns together as the primary key.

Easy to confuse

  • A foreign key column can hold NULL: According to the PostgreSQL documentation, if a foreign key column is NULL, it doesn't violate the constraint even without a matching row. If you want to block "orders without a member," add NOT NULL to the foreign key column as well.
  • Deleting a referenced row is blocked by default: Trying to delete member 2, who still has orders, raises an error. When you create the foreign key, you can choose actions such as ON DELETE CASCADE (delete the orders too) or SET NULL (set the foreign key to NULL). The PostgreSQL documentation explains that when two tables represent independent objects, blocking the deletion is the more appropriate choice, and members and orders can be seen as that kind of case.
  • How strictly foreign keys are enforced differs by product: In SQLite, foreign key checks are off by default and must be turned on for each connection with PRAGMA foreign_keys = ON;. Don't assume a foreign key is always checked just because you wrote it; I recommend checking the documentation for the product you use.

Comparison showing an order with a nonexistent member ID being blocked while an order with a NULL foreign key goes in Concept figure: a foreign key blocks only member IDs that don't exist, not empty values (figure labels in Korean)

Wrapping up

A primary key is the column that tells rows apart, and a foreign key is the column that points to that primary key to link two tables. With these two, you express 1:1, 1:N and N:M relationships. It may help to think of it this way: the member ID you used to look up with VLOOKUP in Excel becomes a rule the database enforces. I'm studying along with this series myself, so please refer to the full curriculum. In the next post, we'll cover normalization: why tables get split into several.

FAQ

What is the difference between a primary key and a foreign key?
A primary key is a column that tells the rows of its own table apart. Its values can't repeat or be empty, and each table has at most one. A foreign key is a column that points to values in another table's primary key (or UNIQUE column). You can't insert a value that doesn't exist in the referenced table, and a table can have several foreign keys.
How do you build a many-to-many (N:M) relationship?
Create one more junction table between the two tables and store both tables' primary keys in it as foreign keys. For orders and products, the PostgreSQL documentation shows an example of an order_items table holding the order ID and product ID, with the two columns together as its primary key.
Does every table need a primary key (PK)?
Relational database theory says every table must have a primary key, but PostgreSQL doesn't enforce it. Still, it's usually best to have one, since it keeps the same row from being inserted twice and gives other tables something to point to.