Chapter 05 · Mapping Conceptual Models to Relational Schemas

Mapping Many-to-Many Relationships

Transform many-to-many relationships into associative relations, carry relationship attributes correctly, and choose keys that preserve the intended grain.

Beginner55–80 minutesAssociation mapping + SQL labLast reviewed: August 2026

Learning outcomes

A conceptual many-to-many relationship cannot be represented by placing one foreign key on either parent. The relational transformation introduces an associative relation whose rows represent relationship instances. Chapter 3 introduced the idea; here we focus on the exact mapping procedure, row grain, keys, and constraints.

01

Resolve M:N relationships into associative relations with two 1:N relationships.

02

Carry relationship attributes into the associative relation.

03

Choose composite or surrogate keys according to relationship grain.

04

Preserve duplicate-prevention and temporal rules after resolution.

The transformation

Conceptual model:

Logical relational model:

The associative relation contains foreign keys to both parents.

Minimum schema

sql · example
CREATE TABLE work_order_assignment (    work_order_id INTEGER NOT NULL,    technician_id INTEGER NOT NULL,    PRIMARY KEY (work_order_id, technician_id),    FOREIGN KEY (work_order_id)      REFERENCES work_order(work_order_id),    FOREIGN KEY (technician_id)      REFERENCES technician(technician_id));

This design says one technician can appear at most once on a particular work order for all time.

Relationship attributes belong here

If assignment has its own facts:

model · example
started_atended_atassignment_role

they belong in WorkOrderAssignment because they describe the pair/relationship instance rather than Technician or WorkOrder independently.

Row grain must be explicit

Ask: what does one WorkOrderAssignment row mean?

  • one technician's lifetime participation on one work order;
  • one continuous assignment interval;
  • one shift;
  • one role assignment;
  • one dispatch event.

Each answer implies different keys and duplication rules.

Grain before key

Do not choose a primary key until you can state what one row represents in one sentence.

When the parent-key pair is sufficient

Use:

sql · example
PRIMARY KEY (work_order_id, technician_id)

only if the relationship instance can occur at most once for the pair. This is common for static memberships such as ArticleTag when an article either has a tag or does not.

Repeated association events

If the same technician can be assigned, removed, and reassigned later, the pair is insufficient. One option is:

sql · example
PRIMARY KEY (work_order_id, technician_id, started_at)

Another is a surrogate:

sql · example
assignment_id INTEGER PRIMARY KEY

plus unique rules that match valid relationship instances.

Surrogate association key

sql · example
CREATE TABLE work_order_assignment (    assignment_id INTEGER PRIMARY KEY,    work_order_id INTEGER NOT NULL,    technician_id INTEGER NOT NULL,    started_at    TEXT NOT NULL,    ended_at      TEXT,    role_code     TEXT NOT NULL,    UNIQUE (work_order_id, technician_id, started_at),    FOREIGN KEY (work_order_id)      REFERENCES work_order(work_order_id),    FOREIGN KEY (technician_id)      REFERENCES technician(technician_id));

The surrogate is especially useful when other rows—time entries, notes, approvals—must reference one specific assignment.

Second example: WorkOrder and Part

Conceptually:

Resolved:

Relationship facts include quantity, charged price, and usage time.

Historical price belongs to PartUsage

sql · example
CREATE TABLE part_usage (    part_usage_id       INTEGER PRIMARY KEY,    work_order_id       INTEGER NOT NULL,    part_id             INTEGER NOT NULL,    quantity             INTEGER NOT NULL CHECK (quantity > 0),    charged_unit_price  NUMERIC NOT NULL CHECK (charged_unit_price >= 0),    used_at              TEXT NOT NULL,    FOREIGN KEY (work_order_id)      REFERENCES work_order(work_order_id),    FOREIGN KEY (part_id)      REFERENCES part(part_id));

charged_unit_price belongs to usage because catalog price can change later.

Should duplicate parts aggregate or repeat?

If Part P-18 is consumed three times, decide whether one PartUsage row represents total quantity or one usage event. Both can be valid, but they are different grains:

model · example
Aggregate grain:  one row per work_order + partEvent grain:  one row per individual issuance/use event

Reporting, auditing, inventory integration, and reversals influence the choice.

Many-to-many with reference data

User–Organization often becomes Membership:

model · example
Membership(  membership_id,  user_id,  organization_id,  role_code,  joined_at,  status)

The association can become a major domain entity with permissions, invitations, status transitions, and audit history.

Do not hide associative entities behind framework magic

ORMs often support a direct many-to-many declaration. That is fine only when the relationship truly has no independent attributes, lifecycle, or references. As soon as the association has business meaning, name it explicitly.

Associative relations and uniqueness

Common unique rules include:

model · example
UNIQUE (user_id, organization_id)UNIQUE (student_id, course_id, term_id)UNIQUE (article_id, tag_id)UNIQUE (work_order_id, technician_id, started_at)

Each expresses a different row grain.

Minimum participation after mapping

Each association row must reference both parents, so its foreign keys are normally NOT NULL. But parent participation can remain optional:

  • a WorkOrder may initially have zero assignments;
  • a Technician may be hired before receiving any assignment;
  • an Assignment cannot exist without both.

Practice: map Enrollment

Student enrollment

Students enroll in course offerings. A student may take the same course in different terms. Enrollment stores status, enrolled_at, final_grade, and completion time.

Propose the associative relation, row grain, foreign keys, and one suitable uniqueness rule.

Review one possible answer

Model CourseOffering separately for a course in a specific term. Then Enrollment can reference student_id and course_offering_id. One row represents one student's enrollment in one offering. A suitable unique constraint is (student_id, course_offering_id). A surrogate enrollment_id may still be useful for references from payments, attempts, or certificates.

Summary and next lesson

Many-to-many relationships map to associative relations that preserve relationship facts, keys, and row grain. The next lesson addresses another major mapping choice: how to represent supertypes, subtypes, and inheritance in a relational schema.

References

  • Ramez Elmasri and Shamkant B. Navathe, Fundamentals of Database Systems.
  • C. J. Date, Database Design and Relational Theory.
  • Martin Fowler, Patterns of Enterprise Application Architecture.

Keep knowledge open

Help the academy stay free and grow.

If these tutorials save you time, a small donation supports new lessons, technical review, diagrams, examples, and long-term maintenance.

ETHEthereum / ERC-20 only
0x716c4Ab160C4B66F31a28AE2448BfF68fc3a2ef0

Send only assets compatible with the Ethereum/ERC-20 network. Do not send TRC-20/TRON assets.