Skip to content
Part IA Michaelmas Term

Implementing Relationships in Tables

The clean approach

Given a relationship RR between entity tables S(Z,W)S(\underline{Z}, W) and T(X,Y)T(\underline{X}, Y), the default approach is to create a separate table:

R(X,Z,U)R(X, Z, U)

where UU are any relationship-specific attributes (e.g., a salary paid to an actor for a particular film). Both XX and ZZ serve as foreign keys to TT and SS respectively.

Many-to-many (M:N)

For a many-to-many relationship, the separate-table approach is mandatory. The key of RR is (X,Z)(X, Z) — both foreign keys together form the primary key. If the relationship has attributes UU, they are included as non-key columns.

Example: Directed(movie_id, person_id) with key (movie_id, person_id).

One-to-many (1:M)

For a one-to-many relationship, we have two options.

Option 1: Separate table. Create R(X,Z,U)R(X, Z, U) as above. The key is XX (the “many” side), since each entity on the many side participates in at most one relationship. Requires a JOIN to navigate.

Option 2: Expand the “many” side table. Expand TT to T(X,Y,Z,U)T(\underline{X}, Y, Z, U), adding the foreign key ZZ and relationship attributes UU directly. Rows that do not participate in the relationship store NULL in ZZ and UU.

Separate tableExpanded table
JOINs neededYes, for every navigationNo
NULL valuesNoneFor rows not in the relationship
NormalisationCleanViolates normal forms (see below)
Update complexityInsert/delete into the relationship tableUpdate a single column

The expansion approach saves a join but introduces NULL columns and violates normalisation principles: ZZ (the foreign key) functionally depends on XX (the key of the expanded table), but ZZ is not itself a key. A later lecture covers normal forms in detail.

One-to-one (1:1)

For a one-to-one relationship, either side can absorb the foreign key. Choose the side where NULL values are less common.

Relationship attributes

When a relationship carries its own attributes, the clean approach uses a separate relationship table:

E/R diagram: Actor(id, name) — PerformsIn(character, salary) — Movie(id, title)

Relational schema:

Actors(actor_id, name)
Movies(movie_id, title)
PerformsIn(actor_id, movie_id, character, salary)

The character and salary columns belong to the relationship, not to either entity. Placing them in Actors or Movies would be incorrect: an actor can play different characters in different films for different salaries.

Merging multiple relationships

Sometimes multiple relationships between the same entity sets are merged into a single table with a type discriminator. The course example is has_position:

has_position(movie_id, person_id, role, character_name, position_in_credits)

This single table combines what could have been five separate tables: Directed, ActedIn, Produced, Composed, Wrote. The role column is a type discriminator (e.g., 'director', 'actor', 'producer').

Advantages:

  • One table instead of five — simpler conceptual model.
  • Queries across all roles require only one table.
  • Adding a new role type requires only a new value in the role column, not a new table.

Disadvantages:

  • character_name is NULL for non-actor roles; position_in_credits might be NULL for some roles.
  • Redundancy: if we know role = 'director', we can deduce that character_name is NULL. This is a violation of normalisation principles — the value of one non-key column (role) determines whether another column (character_name) must be NULL.
  • Foreign key constraints cannot easily express “an actor must have a character name” at the schema level. This logic moves to application code or triggers.

The type-tag redundancy problem

Consider two separate relationship tables:

R(X, Z, U)     -- e.g., directed roles
Q(X, Z, V)     -- e.g., acted roles

Now squash them into one:

RQ(X, Z, type, U, V)

where type indicates whether this row belongs to RR or QQ. This introduces a redundancy: the type value determines which of UU or VV is meaningful. If type = 'R', then VV must be NULL. If type = 'Q', then UU must be NULL. The fact that UU is NULL is not independent data — it is computable from type. This violates the principle that data should not be derivable from other data.

Summary table

Relationship typeDefault implementationKey of relationship tableAlternative
M:NSeparate table(X,Z)(X, Z) (composite)None required
1:MSeparate table or expand the “many” sideXX (the many side)Expand TT: T(X,Y,Z,U)T(\underline{X}, Y, Z, U)
1:1Foreign key on either sideEither XX or ZZExpand either table
With attributesSeparate tableAs aboveAttributes placed in the table that makes semantic sense

Summary

  • The clean approach creates a separate table for each relationship.
  • M:N relationships must use a separate table with a composite key.
  • 1:M relationships can use a separate table or expand the “many” side, trading JOIN cost against normalisation.
  • Merging multiple relationships into one table with a type tag reduces table count but introduces redundancy and weakens constraint enforcement.
  • Relationship attributes belong in the relationship table, not in entity tables.