✍ 05: Database Design & Normalization — Exercises¶
Tip
Practice — try each question first, then expand the answer to check your reasoning.
Work through each question, then click Show answer to check yourself. Review the notes if you get stuck.
🔹 Q1. A PROJECT entity has a 1:N relationship with ASSIGNMENT conceptually, but PROJECT and EMPLOYEE are really related N:M through hours worked. How should this be represented in the relational model?¶
Show answer
Create an **intersection table** (e.g., `ASSIGNMENT`) whose primary key is the composite of both parent keys: `(project_id, employee_number)`. Store `hours_worked` — an attribute of the relationship itself — in that intersection table, not in either `PROJECT` or `EMPLOYEE`.❓ Q2. A DEPARTMENT has many EMPLOYEEs. Which table gets the foreign key, and why can't it go the other way?¶
Show answer
The foreign key (`department_id`) goes in `EMPLOYEE` — the "many" side. It can't go in `DEPARTMENT` because a single column in one department row can only hold one value, but a department needs to reference *many* employees; only the "many" side can hold a single reference back to its "one" parent.🔹 Q3. What makes an entity a "weak, ID-dependent" entity, and how does that change its primary key?¶
Show answer
A weak, ID-dependent entity cannot be uniquely identified without its parent — it has no independent existence. Its primary key must include the parent's primary key as a foreign key, combined with a discriminator that's only unique *within* that parent (e.g., `LOG(charter_id, entry_number)` — `entry_number` restarts at 1 for every charter).🎯 Q4. Write the CREATE TABLE statement for a 1:1 relationship between CUSTOMER and CONTACT, where every customer has at most one contact record. What constraint (beyond the foreign key itself) is required to enforce "at most one"?¶
Show answer
The `UNIQUE` constraint on `customer_id` in `contact` is required — without it, a plain foreign key alone permits many `contact` rows per customer, which would silently make the relationship 1:N instead of 1:1.❓ Q5. Look at this table. What normal-form violation does it show, and how would you fix it?¶
| order_id | customer_name | item1 | item2 | item3 |
|---|---|---|---|---|
| 5001 | Alvarez, Rosa | Anchor | Life jacket | |
| 5002 | Bloom, Sam | Rope |
Show answer
This violates **1NF** — `item1`/`item2`/`item3` are a repeating group crammed into extra columns instead of separate rows. Fix: create an `ORDER_ITEM` table with one row per item, keyed by `(order_id, item_number)` or a surrogate key plus a foreign key back to `order_id`. This also removes the wasted empty cells and the hard cap on how many items an order can have.🔹 Q6. A table SHIPMENT(shipment_id, boat_reg_number, boat_name, boat_daily_rate, ship_date) uses a composite key (shipment_id, boat_reg_number). boat_name and boat_daily_rate depend only on boat_reg_number. What normalization problem is this, and which normal form fixes it?¶
Show answer
This is a **partial dependency** — a **2NF violation**. `boat_name` and `boat_daily_rate` depend on only part of the composite key (`boat_reg_number`), not the whole key. Fix: move `boat_name` and `boat_daily_rate` into their own `BOAT` table keyed by `boat_reg_number`, leaving only `boat_reg_number` as a foreign key in `SHIPMENT`.❓ Q7. A table CHARTER(charter_id, customer_id, customer_name, customer_phone, departure_date) has customer_name and customer_phone depending on customer_id, not on charter_id (the primary key). What's this called, and how do you fix it?¶
Show answer
This is a **transitive dependency** — a **3NF violation**: a non-key column (`customer_name`) depends on another non-key column (`customer_id`) rather than directly on the primary key. Fix: move `customer_name` and `customer_phone` into their own `CUSTOMER` table keyed by `customer_id`, leaving `customer_id` as a foreign key in `CHARTER`.🔹 Q8. Give one legitimate business reason to denormalize a table on purpose, and explain the trade-off being made.¶
Show answer
Example: a read-heavy dashboard duplicates `customer_name` directly onto the `charter` table to avoid a `JOIN` to `customer` on every page load. Trade-off: faster reads, at the cost of `customer_name` now living in two places — an `UPDATE` must touch both, and if one is missed the data disagrees with itself (the exact anomaly normalization exists to prevent). This is only acceptable when the denormalized copy is not the sole system of record, or is rebuilt/refreshed automatically.🎯 Q9. Write CREATE TABLE statements for EMPLOYEE with a recursive 1:N "supervises" relationship (an employee has at most one supervisor, who is also an employee).¶
Show answer
`supervisor_id` references the table's own primary key — this is what makes it recursive/unary rather than a relationship between two different entities.❓ Q10. Write a self-join query that lists every employee's first/last name next to their supervisor's first/last name. Why must you use a LEFT JOIN instead of an inner join?¶
Show answer
A `LEFT JOIN` is required because the top-level employee (e.g., the president) has `supervisor_id IS NULL`. An inner join would have nothing to match on the right side and would silently drop that employee from the results entirely.🔹 Q11. What does WITH RECURSIVE let you do that a fixed chain of self-joins can't?¶
Show answer
A self-join walks exactly one level of the hierarchy per join, so you'd need to know in advance how many levels deep to go (and write that many joins). `WITH RECURSIVE` repeatedly applies the same join step until no new rows are produced (e.g., until it reaches an employee with `supervisor_id IS NULL`), so it correctly walks a chain of *any* depth without the query needing to know the depth ahead of time.All Exercises | Next: 06: Database Administration — Exercises