CMD Guide
HomeDatabasesNormalization

Designing a Hotel Management System

Designing a Hotel Management System

This case study walks through modelling a Hotel Management System (HMS) end to end: first as an Entity-Relationship (ER) design, then mapped to a normalised relational schema. The goal is to show why each table looks the way it does, not just the final answer.

The system must support six core capabilities:

Step 1 — Identify the entities

Entities are the nouns the system stores data about. For the HMS we have eight:

  1. Guest — a person who books and stays.
  2. Room — a physical room.
  3. Room_Type — the category of a room (Single, Double, Suite) and its rate.
  4. Reservation — a booking made by a guest.
  5. Payment — money received against a reservation.
  6. Service — an extra a guest can buy.
  7. Staff — an employee.
  8. Department — an organisational unit that owns rooms and staff.

Step 2 — Attach attributes

Each entity gets attributes, with primary keys (PK) and foreign keys (FK) called out.

EntityKey attributes
GuestGuest_ID (PK), First_Name, Last_Name, Date_of_Birth, Gender, Address, Email, Identification_Type, Identification_Number
RoomRoom_ID (PK), Room_Number, Floor, Status, Room_Type_ID (FK), Department_ID (FK)
Room_TypeRoom_Type_ID (PK), Type_Name, Description, Price_Per_Night, Max_Occupancy
ReservationReservation_ID (PK), Check_In_Date, Check_Out_Date, Number_of_Guests, Reservation_Status, Guest_ID (FK)
PaymentPayment_ID (PK), Payment_Date, Amount, Payment_Method, Reservation_ID (FK)
StaffStaff_ID (PK), First_Name, Last_Name, Role, Email, Department_ID (FK)
DepartmentDepartment_ID (PK), Department_Name, Description
ServiceService_ID (PK), Service_Name, Description, Price

Note that Phone_Number is multi-valued for both Guest and Staff, so it will become its own table when we map to a schema rather than living as a single column.

Step 3 — Define the relationships

Seven relationships connect the eight entities. The cardinality on each end drives how the schema is built.

RelationshipCardinalityReading
Guest – Reservation1 : NOne guest makes many reservations; each reservation has one guest.
Reservation – RoomM : NA reservation can span many rooms; a room is reused across many reservations over time.
Room – Room_TypeN : 1Many rooms share one type; a room has exactly one type.
Guest – ServiceM : NA guest uses many services; a service is used by many guests.
Reservation – Payment1 : NA reservation can have several payments; each payment is for one reservation.
Room – DepartmentN : 1A department maintains many rooms; a room is maintained by one department.
Staff – DepartmentN : 1A department has many staff; a staff member belongs to one department.

The two M : N relationships — Reservation–Room and Guest–Service — cannot be drawn as a single foreign key. Each needs a junction entity that holds the pair of keys plus any data that belongs to the pairing itself (the room rate applied, the quantity of a service).

diagram
diagram

The complete ER diagram

Putting the entities, junctions, and relationships together gives the full picture below. Crow's-foot markers (legend above) show each end's cardinality; the amber boxes are the junction entities that resolve the two many-to-many relationships.

diagram
diagram

Mapping to a relational schema — with a normalisation trace

Rather than jump straight to the final tables, let us watch one wide, un-normalised booking record march through 1NF, 2NF, and 3NF. This is where the design earns its keep.

The starting point (un-normalised)

Imagine the booking desk recorded everything about a stay in one flat row, including every room on the reservation and the guest's phone numbers:

Reservation(
  Resv_ID, Guest_ID, Guest_Phones[],
  Room_No, Room_Type, Type_Price, Rate_Applied,
  Check_In, Check_Out
)

Two problems are visible immediately: Guest_Phones is a repeating group, and a single reservation row tries to hold several rooms at once.

1NF — remove repeating groups, make every cell atomic

We give each (reservation, room) its own row and lift the multi-valued phone numbers into a separate table. The reservation–room pairing now needs a composite key (Resv_ID, Room_No) because one reservation can list several rooms:

ResvRoom(
  Resv_ID, Room_No,            -- composite PK
  Guest_ID,
  Room_Type, Type_Price,
  Rate_Applied,
  Check_In, Check_Out
)
Guest_Phone( Guest_ID, Phone_Number )   -- repeating group removed

2NF — remove partial dependencies on the composite key

2NF asks: does every non-key attribute depend on the whole composite key (Resv_ID, Room_No), or only on part of it? Walking the columns:

Splitting out each partial dependency to the part of the key it actually depends on:

Reservation( Resv_ID PK, Guest_ID, Check_In, Check_Out )
Room( Room_No PK, Room_Type, Type_Price )
ResvRoom( Resv_ID, Room_No, Rate_Applied,  PK(Resv_ID, Room_No) )

Note that Room_Type and Type_Price moved to a Room table at this step, because pulling them out is a 2NF action (a partial dependency on Room_No), not a 3NF one. Folding them into the later 3NF step would blur the boundary.

3NF — remove transitive dependencies

Now each table has a single-attribute key, so partial dependencies are gone. 3NF asks whether any non-key attribute depends on another non-key attribute. Look at Room:

Room_No → Room_Type → Type_Price

Type_Price does not depend on the room directly; it depends on the room's type. That is a transitive dependency: a non-key attribute (Type_Price) hanging off another non-key attribute (Room_Type). We break it by promoting type to its own table:

Room( Room_No PK, Room_Type_ID FK )
Room_Type( Room_Type_ID PK, Type_Name, Type_Price )

Now the room number determines only which type a room is; the price lives once, with the type. Change a suite's nightly rate in one place and every suite reflects it.

The 2NF/3NF distinction in one line: 2NF removed Room_Type because it depended on part of the composite key (Room_No); 3NF then removed Type_Price because it depended on another non-key column (Room_Type). Two different defects, fixed at two different steps.

The final schema

Applying the same reasoning across every entity and resolving both M:N relationships with junction tables yields these tables. Primary keys are bold; foreign keys are marked (FK).

TableColumns
GuestGuest_ID, First_Name, Last_Name, Date_of_Birth, Gender, Address, Email, Identification_Type, Identification_Number
Guest_Phone_NumberGuest_ID+Phone_Number, Guest_ID (FK)
Room_TypeRoom_Type_ID, Type_Name, Description, Price_Per_Night, Max_Occupancy
RoomRoom_ID, Room_Number, Floor, Status, Room_Type_ID (FK), Department_ID (FK)
ReservationReservation_ID, Check_In_Date, Check_Out_Date, Number_of_Guests, Reservation_Status, Guest_ID (FK)
Reservation_RoomReservation_ID+Room_ID, Rate_Applied, Notes, both (FK)
PaymentPayment_ID, Payment_Date, Amount, Payment_Method, Reservation_ID (FK)
StaffStaff_ID, First_Name, Last_Name, Role, Email, Department_ID (FK)
Staff_Phone_NumberStaff_ID+Phone_Number, Staff_ID (FK)
DepartmentDepartment_ID, Department_Name, Description
ServiceService_ID, Service_Name, Description, Price
Guest_ServiceGuest_ID+Service_ID+Reservation_ID, Quantity, Total_Cost, all three (FK)

The two amber junction tables from the ER diagram — Reservation_Room and Guest_Service — are exactly the tables that carry the M:N pairings, each with a composite primary key built from the keys it joins.

SQL for the load-bearing tables

The junction tables are where the design is easiest to get wrong, so here is the DDL for both, plus the two tables whose split was driven by the normalisation trace above.

CREATE TABLE Room_Type (
    Room_Type_ID    INT PRIMARY KEY,
    Type_Name       VARCHAR(20),
    Description     TEXT,
    Price_Per_Night DECIMAL(10,2),
    Max_Occupancy   INT
);

CREATE TABLE Room (
    Room_ID       INT PRIMARY KEY,
    Room_Number   VARCHAR(10),
    Floor         INT,
    Status        VARCHAR(20),
    Room_Type_ID  INT,
    Department_ID INT,
    FOREIGN KEY (Room_Type_ID)  REFERENCES Room_Type(Room_Type_ID),
    FOREIGN KEY (Department_ID) REFERENCES Department(Department_ID)
);

CREATE TABLE Reservation_Room (
    Reservation_ID INT,
    Room_ID        INT,
    Rate_Applied   DECIMAL(10,2),
    Notes          TEXT,
    PRIMARY KEY (Reservation_ID, Room_ID),
    FOREIGN KEY (Reservation_ID) REFERENCES Reservation(Reservation_ID),
    FOREIGN KEY (Room_ID)        REFERENCES Room(Room_ID)
);

CREATE TABLE Guest_Service (
    Guest_ID       INT,
    Service_ID     INT,
    Reservation_ID INT,
    Quantity       INT,
    Total_Cost     DECIMAL(10,2),
    PRIMARY KEY (Guest_ID, Service_ID, Reservation_ID),
    FOREIGN KEY (Guest_ID)       REFERENCES Guest(Guest_ID),
    FOREIGN KEY (Service_ID)     REFERENCES Service(Service_ID),
    FOREIGN KEY (Reservation_ID) REFERENCES Reservation(Reservation_ID)
);

Source

Adapted from the “Designing a Hotel Management System” normalisation case study, with the ER diagram redrawn in crow's-foot notation and an explicit 1NF→2NF→3NF trace added to separate the partial-dependency (2NF) and transitive-dependency (3NF) steps on the room attributes.

🤖 Don't fully get this? Learn it with Claude

Stuck on Designing a Hotel Management System? Open Claude, copy a block below, and it'll teach you this exact concept — visually and interactively.

🎨 Explain it visually

Build the mental picture, not memorization.

I just read a lesson on **Designing a Hotel Management System** (Databases) and want to truly understand it. Explain Designing a Hotel Management System from first principles using ONE vivid real-world analogy and a visual mental model — draw it as ASCII art or a clear step-by-step diagram — with a concrete example using real numbers. Then ask me one question to check I got the mental picture, and wait for my reply. If you're unsure or a claim isn't standard, say so and reason from first principles instead of guessing.
🤔 Walk me through it (interactive)

Socratic — adapts to where you're stuck.

Teach me **Designing a Hotel Management System** interactively. Ask me ONE guiding question at a time, wait for my answer, and adapt to my confusion — build the idea with me step by step instead of explaining it all at once. If you're unsure or a claim isn't standard, say so and reason from first principles instead of guessing.
🧪 Quiz me & fix my gaps

Active recall exposes what you missed.

Quiz me on **Designing a Hotel Management System** with 5 questions, easy to tricky, ONE at a time. Tell me if each answer is right; at the end, explain clearly what I got wrong and why. If you're unsure or a claim isn't standard, say so and reason from first principles instead of guessing.
🧠 Make it stick

Intuition + hook + flashcards for long-term memory.

Help me remember **Designing a Hotel Management System** for the long term: give the one-sentence intuition, a memorable hook/mnemonic, a tiny worked example, and 3 active-recall flashcards (Q -> A). If you're unsure or a claim isn't standard, say so and reason from first principles instead of guessing.

📝 My notes