Skip to content

📘 05-01: Transforming E-R Models into Tables

SQL & Databases

Module 05: Database Design & Normalization

Home All Notes Practice Quiz

💡 From Diagram to Design

An E-R diagram is a conceptual model — it says what the business cares about and how those things relate. Before any of that can live inside PostgreSQL, it has to be turned into a logical design: an actual set of tables, columns, primary keys, and foreign keys. That translation step has a small number of mechanical rules, and once you know them, transforming even a large diagram is mostly bookkeeping.

We'll use a running example modeled on the Wedgewood Pacific (WP) company: departments employ people, employees work on projects, and time is logged against project assignments.


📌 Rule 1 — Every Entity Becomes a Table

Each entity type in the E-R diagram becomes one table. Each instance of the entity (one specific department, one specific employee) becomes one row. The entity's identifier becomes the table's primary key.

CREATE TABLE department (
    department_id   SERIAL PRIMARY KEY,
    department_name VARCHAR(35) NOT NULL UNIQUE,
    budget_code     CHAR(5)     NOT NULL,
    office_number   CHAR(10),
    department_phone CHAR(12)
);

Note

The original WP textbook schema uses DepartmentName itself as the primary key. That works, but it means every table that references a department stores a full text string as a foreign key, and renaming a department becomes an update-everywhere problem. Adding a surrogate department_id (as done above) is the more robust modern choice — this is a deliberate, common improvement over the textbook's design, not a deviation from the theory.


📌 Rule 2 — Weak Entities Get the Parent's Key Too

A weak entity cannot be identified by its own attributes alone — it borrows (part of) its identity from a parent. If the weak entity is ID-dependent, its primary key must include the parent's primary key as a foreign key.

A LOG entry on a boat charter is a good example: an entry only makes sense in the context of one charter, and entry numbers restart at 1 for every charter.

CREATE TABLE log (
    charter_id     INTEGER NOT NULL REFERENCES charter(charter_id),
    entry_number   INTEGER NOT NULL,
    entry_date     DATE NOT NULL,
    entry_location VARCHAR(50),
    PRIMARY KEY (charter_id, entry_number)
);

If a weak entity is not ID-dependent (it just happens to require a parent to exist, without borrowing key attributes), treat it like a strong entity: give it its own surrogate key and a plain foreign key back to the parent.


📌 Rule 3 — 1:N Relationships → Foreign Key on the "Many" Side

This is the single most common transformation you'll perform. In a 1:N relationship, the "1" side is the parent, the "N" side is the child, and the parent's primary key is copied into the child table as a foreign key.

One department employs many employees:

CREATE TABLE employee (
    employee_number INTEGER PRIMARY KEY,
    first_name      VARCHAR(25) NOT NULL,
    last_name       VARCHAR(25) NOT NULL,
    department_id   INTEGER REFERENCES department(department_id),
    position        VARCHAR(25),
    supervisor_id   INTEGER,
    office_phone    CHAR(12),
    email_address   VARCHAR(100)
);

department_id lives in employee (the many side), never the other way around. Putting the foreign key on the "1" side would force a single department row to somehow hold a list of employee numbers — something a single column can't do.

Tip

When you're unsure which side is "many," ask: "can this thing have more than one of the other thing?" An employee belongs to exactly one department, but a department has many employees — so employee is the many side, and that's where department_id goes.


📌 Rule 4 — N:M Relationships → a New Intersection Table

A many-to-many relationship can't be represented with a single foreign key in either table — an employee can work on many projects, and a project has many employees. The fix is to create a brand-new intersection table (also called an associative or junction table) whose primary key is the combination of both parents' keys.

CREATE TABLE assignment (
    project_id      INTEGER NOT NULL REFERENCES project(project_id),
    employee_number INTEGER NOT NULL REFERENCES employee(employee_number),
    hours_worked    NUMERIC(6, 2) DEFAULT 0,
    PRIMARY KEY (project_id, employee_number)
);

This is exactly the WP ASSIGNMENT table: it connects PROJECT and EMPLOYEE, and its composite primary key (project_id, employee_number) guarantees you can't record the same employee against the same project twice. Any attribute that describes the relationship itself (like hours worked) lives here, not in either parent table.


📌 Rule 5 — 1:1 Relationships → Foreign Key on Either Side

For a strict one-to-one relationship, the foreign key can go in either table — it's a design choice, not a rule. Pick whichever side makes queries more natural, or merge the two tables entirely if every row on both sides always exists together.

A CUSTOMER and its single primary CONTACT record is a typical 1:1:

CREATE TABLE customer (
    customer_id   SERIAL PRIMARY KEY,
    customer_name VARCHAR(100) NOT NULL
);

CREATE TABLE contact (
    contact_id   SERIAL PRIMARY KEY,
    customer_id  INTEGER UNIQUE REFERENCES customer(customer_id),
    contact_name VARCHAR(100),
    phone        CHAR(12)
);

Note the UNIQUE constraint on customer_id in contact — without it, this would silently become a 1:N relationship, letting one customer have many contact rows.


📌 Putting It Together

E-R construct Relational transformation
Entity Table, identifier → primary key
Weak, ID-dependent entity Table whose PK includes the parent's PK
1:1 relationship FK on either side (UNIQUE constraint), or merge tables
1:N relationship FK placed on the "many" (child) side
N:M relationship New intersection table with a composite PK

Once every entity and relationship has been run through these rules, the result is your logical design — a complete set of CREATE TABLE statements ready for a real DBMS, before you've spent a moment worrying about performance tuning or physical storage.


See also: Representing Relationships in SQL, Normalization