انتقل إلى المحتوى
أمين أبوزيد
منشورBEG-049-L03

المفاتيح الأجنبية والعلاقات

  • الدرس 3 من 6
  • 12 دقيقة قراءة
  • مبتدئ
في هذا الدرس 11 موضوعات

هذا الدرس متاح حاليًا باللغة الإنجليزية فقط، ولم تُنشر نسخته العربية بعد.

Lesson Overview

At the end of the last lesson our loans table could say when a loan started, but not who borrowed or what. Copying the member's name and the book's title into every loan row would bring back the repeated, inconsistent data we avoided by splitting tables. The solution is for a loan to point at a member row and a book row, the way a loan slip in a card catalog says "see card 1042".

That pointer is a foreign key. In this lesson you learn what a foreign key is, where it goes in a one-to-many relationship, and what the database does to protect it (referential integrity). You also learn to read a simple relationship sketch.

You need the previous two lessons: tables with typed columns, and primary keys.

Learning Objectives

By the end of this lesson you will be able to:

  • Define a foreign key and say which table's key it refers to.
  • Explain how a foreign key avoids copying data between tables.
  • Place the foreign key correctly in a one-to-many relationship.
  • Read and write the notation author_id FK -> authors.id.
  • Describe what referential integrity means and what the database does when a foreign key points at nothing.
  • Explain, at a high level, the choices for what happens when the referenced row is deleted.
  • Recognize a one-to-one relationship.

Written Explanation

The idea: store the id, not the details

A foreign key (FK) is a column in one table whose values are primary-key values of another table. Instead of repeating a member's details in each loan, the loan stores the member's id.

text
members(id PK, name, email, ...)
loans(id PK, member_id FK -> members.id, book_id FK -> books.id, loaned_on, due_on, returned_on)

Read member_id FK -> members.id as "the column member_id holds values taken from members.id". Sample data:

members.idname
1Layla Hassan
2Omar Said
loans.idmember_idbook_idloaned_on
10152025-03-01
11162025-03-08
12252025-03-09

Layla's details are stored once, in members. Loans 10 and 11 both say member_id = 1. If her email changes, you edit one row and every loan that points at her is correct automatically. This is the central benefit: each fact is stored in exactly one place.

Naming convention: a foreign key is named after the table it points to, in singular, plus _id: member_id, book_id, author_id. The name tells readers where it leads.

Referential integrity

A pointer is only useful if it points somewhere. The database can guarantee that. PostgreSQL's manual describes a foreign key as a rule that the values in a column must match values appearing in some row of another table, and calls maintaining this referential integrity. In practice:

  • If you try to create a loan with member_id = 999 and no such member exists, the database refuses.
  • You cannot silently end up with "orphan" rows pointing at nothing.

Without the rule, every program that writes data must remember to check. With it, the database enforces consistency. You will declare foreign keys in the SQL module.

Two details worth knowing:

  • A foreign key can point only at a column that is unique, normally the other table's primary key. That is why the previous lesson came first.
  • The FK column can contain NULL if "no value" makes sense. The PostgreSQL manual notes that a row whose foreign key column is NULL passes the check. For a loan, member_id should always be present, so you would also mark it required. For an optional relationship (a book with no known publisher), NULL is acceptable.

One-to-many

The most common relationship is one-to-many: one row on one side relates to many rows on the other side. One member has many loans; one author has written many books; one customer places many orders.

Rule: the foreign key lives on the "many" side, and points at the "one" side.

Why? A single cell can hold only one value. A loan has exactly one member, so loans can hold one member_id. A member has many loans, so a members row would need a list of loan ids in one cell, which a column cannot hold (and lists in cells break the rules we meet in lesson L05).

To find where the FK goes, ask two questions about each pair of entities:

  1. Can one A relate to many B?
  2. Can one B relate to many A?

If A to many B is yes and B to many A is no, the FK goes in B's table. "One author, many books" (in the simplified case where each book has a single author): one author has many books, but each book has one author, so books gets author_id.

text
authors(id PK, name)
books(id PK, title, isbn, published_year, author_id FK -> authors.id)

If both answers are yes, you have a many-to-many relationship; the next lesson handles it. Note that real books often have several authors, which is exactly why lesson L04 replaces this simple version.

Reading relationship diagrams

Designers draw entities as boxes and relationships as lines; a common notation (crow's foot) draws a fork on the "many" end. In text we can write:

text
members 1 ---- * loans * ---- 1 books

Read it as "one member has many loans; one book appears in many loans". The 1 and * mark the ends. Notice that the table in the middle holds both foreign keys. Whichever notation you use, include table names, the keys, and the cardinality (the 1 or *) on each end. The ER diagram in the module lab uses such boxes and lines.

What happens when you delete the referenced row?

Suppose member 1 is deleted, but loans point to her. Options exist, and the database lets you choose per foreign key. The PostgreSQL manual lists these actions, and other systems offer similar ones:

ActionEffect when the referenced row is deleted
Refuse (restrict / no action)The delete fails while rows still point to it
CascadeThe rows that point to it are deleted too
Set to NULLThe foreign key column in those rows becomes NULL

Which is right depends on meaning. The manual's guidance: choose cascade when the referencing rows are a component that cannot exist on its own; choose refuse when the two tables describe independent things. A loan record is history the library probably wants to keep, so refusing to delete a member who has loans (or marking the member inactive instead, as our is_active column allows) is a sensible choice. You will set these actions in the SQL module; for now just notice that the decision exists.

One-to-one, briefly

In a one-to-one relationship each row in A relates to at most one row in B and vice versa: for example members and a separate member_photos table. It is rare, because the two tables could usually be one. When needed, the FK goes in one table and is also marked unique.

Examples

Example 1: authors and books (simple)

One author, many books, assuming each book has one author for now.

text
authors(id PK, name)
books(id PK, title, author_id FK -> authors.id)
authors.idname
1Jane Austen
books.idtitleauthor_id
1Emma1
2Persuasion1

The name "Jane Austen" is stored once; both books point at 1. Correcting a spelling touches one row. Attempting to add a book with author_id = 42 is refused unless an author 42 exists.

Example 2: linking loans to members and books (practical)

The loans table is the "many" side of two one-to-many relationships.

text
members(id PK, name, email, joined_on, is_active)
books(id PK, title, isbn)
loans(id PK, member_id FK -> members.id, book_id FK -> books.id, loaned_on, due_on, returned_on)

Now answer a question using the tables by hand. "Which books does Omar have on loan?" Look up Omar's id in members (2). Collect the rows of loans where member_id = 2 and returned_on has no value. For each, read book_id and look up the title in books. Following keys from one table to another is exactly what a SQL join will automate, in a later module.

Because the ids carry the relationship, no name or title is copied into loans. A book can be renamed in one place and every loan reflects it.

Example 3: deciding which side holds the key (realistic)

A school wants to track classes. Entities: teachers, classes, rooms. Rules: a teacher can teach many classes, but each class has one teacher; each class is held in one room, and a room hosts many classes over the week. For each pair, ask the two questions.

PairOne A to many B?One B to many A?Result
teacher / classyesnoFK on classes: teacher_id
room / classyesnoFK on classes: room_id
text
teachers(id PK, name, email)
rooms(id PK, name, capacity)
classes(id PK, title, starts_at, teacher_id FK -> teachers.id, room_id FK -> rooms.id)

classes is the many side of both relationships, so it carries both keys, just as loans did.

Backend Insight

When your Java code later reads a Loan, it will usually hold the member_id rather than a copy of the member. To show a member's name next to a loan, the code (or a SQL join) follows the key. This is also how REST-style addresses are often designed: /members/2/loans means "the loans whose member_id is 2".

Foreign keys also protect you from your own bugs. If a service forgets to check that a member exists before creating a loan, the database refuses the invalid row instead of storing garbage. Errors found at insert time are far cheaper than discovering orphan rows months later.

A performance note: the PostgreSQL manual says it does not index the referencing column automatically and suggests adding an index. You do not need to act on this yet.

Common Mistakes

Mistake 1: Putting the foreign key on the wrong side

  • Incorrect: authors(id PK, name, book_id FK -> books.id) for "one author, many books".
  • Correct: books(..., author_id FK -> authors.id).

Ask "which side has many?"; the key goes there.

Mistake 2: Copying details instead of pointing

  • Incorrect: loans(id, member_name, book_title, ...).
  • Correct: loans(id, member_id, book_id, ...).

Copies drift apart; pointers do not.

Mistake 3: Storing a list of ids in one column

  • Incorrect: members(id, name, loan_ids) with loan_ids holding 10,11.
  • Correct: put member_id on each loan row.

Mistake 4: A foreign key pointing at something that is not unique

  • Incorrect: loans.member_name -> members.name, when two members can share a name.
  • Correct: always point at a primary key.

Exercises

Level A: Foundation

A1. Define foreign key in one sentence and say which kind of column it must point at.

أظهر الحل A1

A1. A foreign key is a column whose values are primary-key values of another table (or another unique column), so a row can point to a row elsewhere. It must point at a column that is unique, normally the other table's primary key.

A2. In "one customer places many orders", which table holds the foreign key, and what is the column called?

أظهر الحل A2

A2. orders (the many side) holds customer_id, written customer_id FK -> customers.id.

A3. What does referential integrity prevent?

أظهر الحل A3

A3. It prevents rows that point at something that does not exist, such as a loan whose member does not exist.

Level B: Application

B1. A music app has artists, albums, and songs. An artist releases many albums; an album contains many songs; each album has one artist, and each song belongs to one album. Write the three tables with primary keys and foreign keys in our notation (at least two ordinary columns each).

أظهر الحل B1

B1.

text
artists(id PK, name, country)
albums(id PK, title, released_year, artist_id FK -> artists.id)
songs(id PK, title, duration_seconds, album_id FK -> albums.id)

B2. The table loans(id, member_id, book_id) currently contains (20, 7, 3). Members 1 and 2 exist; no member 7 exists. What should the database do, and what would go wrong in a system without the foreign key rule?

أظهر الحل B2

B2. The database should refuse the insert, because no member 7 exists. Without the rule, the row would be stored, and the loan would belong to nobody: reports would show a blank member, or code would fail when it follows the key.

B3. A library decides to retire member 4, who has 12 old loans. Compare the three delete actions for the foreign key loans.member_id. Which would you choose, and what alternative to deleting does the members table already allow?

أظهر الحل B3

B3. Refuse: the delete fails while loans exist, protecting history. Cascade: the 12 loans disappear, losing history, rarely wanted here. Set to NULL: loans stay but lose their member; that conflicts with the idea that every loan has a member. Choose refuse, and use is_active (set to false) to retire the member instead of deleting.

Level C: Challenge

C1. A bike-sharing service has stations, bikes, and rides. A bike is based at one station; a station holds many bikes; each ride is one bike used by one rider, and a bike has many rides. Also there is a riders table. Sketch all four tables with keys, and draw the relationship line for each pair with 1 and *.

أظهر الحل C1

C1.

text
stations(id PK, name, capacity)
bikes(id PK, serial, station_id FK -> stations.id)
riders(id PK, name, email)
rides(id PK, bike_id FK -> bikes.id, rider_id FK -> riders.id, started_at, ended_at)
text
stations 1 ---- * bikes 1 ---- * rides * ---- 1 riders

ended_at can be empty while the ride is in progress.

C2. A teammate designed books(id, title, author_id) and authors(id, name, book_ids), with book_ids holding text like 1,2,5, and says "this way I can go both directions". Explain three concrete problems and give the corrected design.

أظهر الحل C2

C2. Problems: book_ids is a list in one cell, so the database cannot check that the ids exist or search it reliably; the list and books.author_id can disagree (the same fact is stored twice, so an update can fix one and miss the other); a cell with many values cannot be indexed or joined properly. Correct design: remove book_ids and keep only books.author_id FK -> authors.id. To find an author's books, look up the rows of books whose author_id equals that author's id. That already works in both directions.

Mini Challenge

Using your own small system from earlier lessons (or the library), add a fourth table so that you have at least two one-to-many relationships. Write each table in our notation with PK and FK marks. Draw each relationship as a line with 1 and *. For each foreign key, state in a sentence what delete behavior you would choose and why.

Check your work: every FK is on the many side; every FK points at a primary key; no column holds a list; no details are copied from another table.

Summary & Next Lesson

Key takeaways

  • A foreign key is a column holding another table's primary-key value, so rows point at rows instead of copying data.
  • In one-to-many, the foreign key goes on the "many" side.
  • The database enforces referential integrity: a foreign key value must match an existing row (or be NULL where allowed).
  • Delete behavior (refuse, cascade, set to NULL) is a design choice that depends on the meaning of the data.
  • Lists of ids in a cell are never a substitute for a foreign key.

Next lesson

Some relationships have many on both sides: a book with several authors, who also each wrote several books. A single foreign key cannot model that. The next lesson, "Many-to-many", introduces the junction table.

Self-check

  1. Which side of a one-to-many relationship holds the foreign key?
  2. What happens when you insert a loan with a member_id that does not exist?
  3. Why not copy the member's name into every loan?
  4. Name the three common choices for what happens to referencing rows when the referenced row is deleted.
  5. In books(id PK, title, author_id FK -> authors.id), what is the relationship between authors and books?
أظهر الإجابات
  1. The "many" side.
  2. The database refuses it, because of referential integrity.
  3. Copies must be kept in sync and drift apart; storing the id keeps each fact in one place.
  4. Refuse the delete, cascade it to the referencing rows, or set the foreign key to NULL.
  5. One-to-many: one author can have many books, and each book has one author.

References

  • PostgreSQL documentation, Constraints: foreign keys. Supports the definition of a foreign key and referential integrity, the NULL behavior, that referenced columns must be unique, the delete actions and the guidance on choosing between them, and the note about indexing referencing columns. Notation such as crow's foot and the 1/* sketch are common diagramming conventions; I did not verify a specific standard for them.

التطبيق

طبّق هذا الدرس بمختبر الوحدة وتمارينها واختبارها.

المختبر والتمارين الاختبار

تعليم الدرس كمكتمل اختياري ويمكنك التراجع عنه في أي وقت. يبقى تقدمك محفوظًا في هذا المتصفح.

اكتب للبحث في المستويات والمراحل والوحدات والدروس.