| 1 | | = Normalization |
| 2 | | |
| 3 | | == De-normalized database form == |
| 4 | | |
| 5 | | '''Unified Relation:''' |
| 6 | | {{{ |
| 7 | | PROPERTY_MANAGMENT_SYSTEM( |
| 8 | | admin_id, admin_name, admin_email, admin_password, |
| 9 | | building_id, building_name, building_address, building_description, |
| 10 | | architect_id, architect_full_name, architect_contact_info, |
| 11 | | floor_id, floor_number, floor_layout_image, |
| 12 | | unit_id, unit_number, unit_room_number, unit_floor_area, unit_status, |
| 13 | | unit_price, unit_image, unit_floorplan, unit_vector_image, |
| 14 | | agent_id, agent_name, agent_email, agent_phone, |
| 15 | | timeslot_id, timeslot_date, timeslot_time_start, timeslot_time_end, timeslot_status, |
| 16 | | appointment_id, appointment_status, |
| 17 | | client_id, client_name, client_email, client_phone |
| 18 | | ) |
| 19 | | }}} |
| 20 | | |
| 21 | | Note: some attributes were renamed due to having multiple attributes with the same name, e.g. email... |
| 22 | | |
| 23 | | '''Functional dependencies:''' |
| 24 | | |
| 25 | | |
| 26 | | FD1: admin_id -> admin_name, admin_email, admin_password |
| 27 | | |
| 28 | | FD2: building_id -> building_name, building_address, building_description, admin_id |
| 29 | | |
| 30 | | FD3: architect_id -> architect_full_name, architect_contact_info |
| 31 | | |
| 32 | | FD4: floor_id -> floor_number, floor_layout_image, building_id |
| 33 | | |
| 34 | | FD5: unit_id -> unit_number, room_number, floor_area, status, price, image, floorplan, vector_image, floor_id |
| 35 | | |
| 36 | | FD6: agent_id -> agent_name, agent_email, agent_phone |
| 37 | | |
| 38 | | FD7: timeslot_id -> date, time_start, time_end, status, agent_id |
| 39 | | |
| 40 | | FD8: client_id -> client_name, client_email, client_phone |
| 41 | | |
| 42 | | FD9: client_email -> client_id |
| 43 | | |
| 44 | | FD10: appointment_id -> appointment_status, client_id, unit_id, timeslot_id |
| 45 | | |
| 46 | | |
| 47 | | == 1NF decomposition |
| 48 | | |
| 49 | | '''Relation:''' PROPERTY_MANAGEMENT_SYSTEM |
| 50 | | |
| 51 | | '''Functional dependencies in this relation:''' FD1-FD10 |
| 52 | | |
| 53 | | '''Candidate Keys:''' |
| 54 | | * {appointment_id, architect_id} |
| 55 | | |
| 56 | | '''Primary Key:''' {appointment_id, architect_id} |
| 57 | | |
| 58 | | '''Normal Form Status:''' Relation is in 1NF, all attributes are atomic, there are no repeating groups and we have a primary key |
| 59 | | |
| 60 | | '''Issues:''' Due to the existance of partial dependencies this relation does not conform to 2NF. In FD1 the admin_id is not part of key but determines attributes, in FD2 the building_id is not part of key, in FD3 the architect_id is part of key but determines attributes independently, in FD4-FD10 all have determinants that are not the full composite key. |
| 61 | | |
| 62 | | '''Decomposition strategy:''' Each entity is extracted into its own relation based on defined functional dependencies, starting with entities that have single-attribute determinants. |
| 63 | | |
| 64 | | '''Decomposition:''' |
| 65 | | |
| 66 | | {{{ |
| 67 | | R1_ADMIN(admin_id, admin_name, admin_email, admin_password) |
| 68 | | PK: admin_id, FD: FD1 |
| 69 | | |
| 70 | | R2_BUILDING(building_id, building_name, building_address, building_description, admin_id) |
| 71 | | PK: building_id, FK: admin_id, FD: FD2 |
| 72 | | |
| 73 | | R3_ARCHITECT(architect_id, architect_full_name, architect_contact_info) |
| 74 | | PK: architect_id, FD: FD3 |
| 75 | | |
| 76 | | R4_DESIGNS(building_id, architect_id) |
| 77 | | PK: {building_id, architect_id}, FK: building_id, architect_id |
| 78 | | |
| 79 | | R5_FLOOR(floor_id, floor_number, floor_layout_image, building_id) |
| 80 | | PK: floor_id, FK: building_id, FD: FD4 |
| 81 | | |
| 82 | | R6_UNIT(unit_id, unit_number, unit_room_number, unit_floor_area, unit_status, |
| 83 | | unit_price, unit_image, unit_floorplan, unit_vector_image, floor_id) |
| 84 | | PK: unit_id, FK: floor_id, FD: FD5 |
| 85 | | |
| 86 | | R7_AGENT(agent_id, agent_name, agent_email, agent_phone) |
| 87 | | PK: agent_id, FD: FD6 |
| 88 | | |
| 89 | | R8_TIMESLOT(timeslot_id, timeslot_date, timeslot_time_start, timeslot_time_end, |
| 90 | | timeslot_status, agent_id) |
| 91 | | PK: timeslot_id, FK: agent_id, FD: FD7 |
| 92 | | |
| 93 | | R9_CLIENT(client_id, client_name, client_email, client_phone) |
| 94 | | PK: client_id, Candidate Key: client_email, FD: FD8, FD9 |
| 95 | | |
| 96 | | R10_APPOINTMENT(appointment_id, appointment_status, client_id, unit_id, timeslot_id) |
| 97 | | PK: appointment_id, FK: client_id, unit_id, timeslot_id, FD: FD10 |
| 98 | | }}} |
| 99 | | |
| 100 | | '''Preservation of functional dependencies:''' All FD1-FD10 preserved. |
| 101 | | |
| 102 | | '''Lossless join:''' Foreign keys enable the reconstruction through natural joins. |
| 103 | | |
| 104 | | == 2NF decomposition |
| 105 | | |
| 106 | | '''Relations to analyze:''' R1_ADMIN - R10_APPOINTMENT (All 1NF relations) |
| 107 | | |
| 108 | | '''Analysis:''' |
| 109 | | |
| 110 | | All relations from the 1NF decomposition have single attribute primary keys, only exception is R4_DESIGNS which contains only key attributes. |
| 111 | | |
| 112 | | '''Relation R1_ADMIN:''' |
| 113 | | * Primary Key: admin_id |
| 114 | | * Functional Dependencies: admin_id -> admin_name, admin_email, admin_password |
| 115 | | * Candidate Keys: {admin_id} |
| 116 | | * Normal Form Status: In 2NF |
| 117 | | |
| 118 | | '''Relation R2_BUILDING:''' |
| 119 | | * Primary Key: building_id |
| 120 | | * Functional Dependencies: building_id -> building_name, building_address, building_description, admin_id |
| 121 | | * Candidate Keys: {building_id} |
| 122 | | * Normal Form Status: In 2NF |
| 123 | | |
| 124 | | '''Relation R3_ARCHITECT:''' |
| 125 | | * Primary Key: architect_id |
| 126 | | * Functional Dependencies: architect_id -> architect_full_name, architect_contact_info |
| 127 | | * Candidate Keys: {architect_id} |
| 128 | | * Normal Form Status: In 2NF |
| 129 | | |
| 130 | | '''Relation R4_DESIGNS:''' |
| 131 | | * Primary Key: {building_id, architect_id} |
| 132 | | * Functional Dependencies: None |
| 133 | | * Normal Form Status: In 2NF |
| 134 | | |
| 135 | | '''Relation R5_FLOOR:''' |
| 136 | | * Primary Key: floor_id |
| 137 | | * Functional Dependencies: floor_id -> floor_number, floor_layout_image, building_id |
| 138 | | * Candidate Keys: {floor_id} |
| 139 | | * Normal Form Status: In 2NF |
| 140 | | |
| 141 | | '''Relation R6_UNIT:''' |
| 142 | | * Primary Key: unit_id |
| 143 | | * Functional Dependencies: unit_id -> unit_number, unit_room_number, unit_floor_area, unit_status, unit_price, unit_image, unit_floorplan, unit_vector_image, floor_id |
| 144 | | * Candidate Keys: {unit_id} |
| 145 | | * Normal Form Status: In 2NF |
| 146 | | |
| 147 | | '''Relation R7_AGENT:''' |
| 148 | | * Primary Key: agent_id |
| 149 | | * Functional Dependencies: agent_id -> agent_name, agent_email, agent_phone |
| 150 | | * Candidate Keys: {agent_id} |
| 151 | | * Normal Form Status: In 2NF |
| 152 | | |
| 153 | | '''Relation R8_TIMESLOT:''' |
| 154 | | * Primary Key: timeslot_id |
| 155 | | * Functional Dependencies: timeslot_id -> timeslot_date, timeslot_time_start, timeslot_time_end, timeslot_status, agent_id |
| 156 | | * Candidate Keys: {timeslot_id} |
| 157 | | * Normal Form Status: In 2NF |
| 158 | | |
| 159 | | '''Relation R9_CLIENT:''' |
| 160 | | * Primary Key: client_id |
| 161 | | * Functional Dependencies: client_id -> client_name, client_email, client_phone; client_email -> client_id |
| 162 | | * Candidate Keys: {client_id}, {client_email} |
| 163 | | * Normal Form Status: In 2NF |
| 164 | | |
| 165 | | '''Relation R10_APPOINTMENT:''' |
| 166 | | * Primary Key: appointment_id |
| 167 | | * Functional Dependencies: appointment_id -> appointment_status, client_id, unit_id, timeslot_id |
| 168 | | * Candidate Keys: {appointment_id} |
| 169 | | * Normal Form Status: In 2NF |
| 170 | | |
| 171 | | '''Issues:''' There are no issues, all relations conform to 2NF. |
| 172 | | |
| 173 | | '''Decomposition:''' There is no need for decomposition. |
| 174 | | |
| 175 | | '''Preservation of functional dependencies:''' All FD1-FD10 preserved. |
| 176 | | |
| 177 | | '''Lossless join:''' Foreign keys enable the reconstruction through natural joins. |
| 178 | | |
| | 1 | = Phase P5: Normalization = |
| | 2 | |
| | 3 | == Initial de-normalized relation and functional dependencies == |
| | 4 | |
| | 5 | **Universal Relation R** |
| | 6 | |
| | 7 | All attributes from the ER model are merged into one flat relation. |
| | 8 | Attribute names are changed where the same concept appears in multiple entities. |
| | 9 | {{{ |
| | 10 | R( |
| | 11 | admin_id, admin_name, admin_email, admin_password, |
| | 12 | architect_id, architect_name, |
| | 13 | building_id, building_name, building_address, building_description, |
| | 14 | floor_id, floor_number, layout_image, |
| | 15 | unit_id, unit_number, room_number, floor_area, unit_status, |
| | 16 | unit_price, unit_image, unit_floorplan, vector_image, |
| | 17 | agent_id, agent_name, agent_email, agent_password, |
| | 18 | client_id, client_name, client_email, client_phone, |
| | 19 | timeslot_id, ts_date, ts_time_start, ts_time_end, ts_status, |
| | 20 | inquiry_id, inquiry_message, inquiry_status, inquiry_created_at, |
| | 21 | appointment_id, appt_status |
| | 22 | ) |
| | 23 | }}} |
| | 24 | |
| | 25 | === Canonical Cover === |
| | 26 | |
| | 27 | {{{ |
| | 28 | FD01: admin_id → admin_name, admin_email, admin_password |
| | 29 | FD02: admin_email → admin_id |
| | 30 | FD03: architect_id → architect_name |
| | 31 | FD04: building_id → building_name, building_address, building_description, admin_id |
| | 32 | FD05: floor_id → floor_number, layout_image, building_id |
| | 33 | FD06: (building_id, floor_number) → floor_id |
| | 34 | FD07: unit_id → unit_number, room_number, floor_area, unit_status, unit_price, unit_image, unit_floorplan, vector_image, floor_id |
| | 35 | FD08: (floor_id, unit_number) → unit_id |
| | 36 | FD09: agent_id → agent_name, agent_email, agent_password |
| | 37 | FD10: agent_email → agent_id |
| | 38 | FD11: client_id → client_name, client_email, client_phone |
| | 39 | FD12: client_email → client_id |
| | 40 | FD13: timeslot_id → ts_date, ts_time_start, ts_time_end, ts_status, agent_id |
| | 41 | FD14: (agent_id, ts_date, ts_time_start) → timeslot_id |
| | 42 | FD15: inquiry_id → inquiry_message, inquiry_status, inquiry_created_at, unit_id, client_id, agent_id |
| | 43 | FD16: appointment_id → appt_status, client_id, unit_id, timeslot_id |
| | 44 | FD17: timeslot_id → appointment_id |
| | 45 | }}} |
| | 46 | |
| | 47 | ---- |
| | 48 | |
| | 49 | == Candidate Keys & Primary Key of R == |
| | 50 | |
| | 51 | === Identifying the Candidate Key === |
| | 52 | |
| | 53 | A candidate key must functionally determine all attributes in R and be minimal. |
| | 54 | |
| | 55 | **Candidate Keys:** |
| | 56 | * `{architect_id, inquiry_id, appointment_id}` |
| | 57 | * `{architect_id, inquiry_id, timeslot_id}` |
| | 58 | |
| | 59 | **Primary Key:** `{architect_id, inquiry_id, appointment_id}` |
| | 60 | |
| | 61 | === Normal Form of R Before Decomposition === |
| | 62 | |
| | 63 | R is in 1NF but not in 2NF. |
| | 64 | |
| | 65 | * **1NF**: All attributes hold atomic scalar values, and there are no repeating groups. |
| | 66 | Every row is uniquely identified by the PK. '''R is in 1NF.''' |
| | 67 | * **2NF**: A relation is in 2NF if it is in 1NF and every non‑key attribute is |
| | 68 | fully functionally dependent on the entire primary key. '''R does not conform to 2NF.''' |
| | 69 | |
| | 70 | **Example of a 2NF violation:** |
| | 71 | `architect_name` is determined by `architect_id` (FD03), which is only a |
| | 72 | proper subset of the primary key `{architect_id, inquiry_id, appointment_id}`. |
| | 73 | If an architect is associated with multiple appointments, |
| | 74 | their name is repeated unnecessarily which is a clear partial dependency. |
| | 75 | |
| | 76 | ---- |
| | 77 | |
| | 78 | == Step-by-step Decomposition == |
| | 79 | |
| | 80 | === Decompose from 1NF to 2NF === |
| | 81 | |
| | 82 | ==== 1.1 Extract `architect_id → architect_name` (FD03) ==== |
| | 83 | |
| | 84 | * **FD causing issue:** FD03, where `architect_id` ⊂ PK. |
| | 85 | * **New relation:** |
| | 86 | {{{ |
| | 87 | R_Architect( architect_id PK, architect_name ) |
| | 88 | FDs: FD03 (architect_id → architect_name) |
| | 89 | Candidate key / PK: {architect_id} |
| | 90 | }}} |
| | 91 | * **Remaining relation R1:** R minus `architect_name`, with PK unchanged |
| | 92 | `{architect_id, inquiry_id, appointment_id}` and FK `architect_id → R_Architect`. |
| | 93 | * **Lossless:** Yes – `architect_id` is the key of `R_Architect`, natural join |
| | 94 | on `architect_id` reconstructs R. |
| | 95 | * **Dependency preservation:** FD03 is wholly inside `R_Architect`; all other |
| | 96 | FDs remain in R1. |
| | 97 | |
| | 98 | ==== 1.2 Extract `inquiry_id → inquiry_message, inquiry_status, inquiry_created_at, unit_id, client_id, agent_id` (FD15) ==== |
| | 99 | |
| | 100 | * **FD causing issue:** FD15, `inquiry_id` ⊂ PK. |
| | 101 | * **From R1**, extract: |
| | 102 | {{{ |
| | 103 | R_Inquiry( |
| | 104 | inquiry_id PK, |
| | 105 | inquiry_message, |
| | 106 | inquiry_status, |
| | 107 | inquiry_created_at, |
| | 108 | unit_id, |
| | 109 | client_id, |
| | 110 | agent_id |
| | 111 | ) |
| | 112 | FDs: FD15 (inquiry_id → inquiry_message, inquiry_status, |
| | 113 | inquiry_created_at, unit_id, client_id, agent_id) |
| | 114 | Candidate key / PK: {inquiry_id} |
| | 115 | }}} |
| | 116 | * **Remaining relation R2:** R1 minus the dependent attributes |
| | 117 | {inquiry_message, inquiry_status, inquiry_created_at, unit_id, client_id, agent_id}, |
| | 118 | PK unchanged, FK `inquiry_id → R_Inquiry`. |
| | 119 | * **Lossless:** Yes – `inquiry_id` is key of `R_Inquiry`. |
| | 120 | * **Dependency preservation:** FD15 is in `R_Inquiry`. |
| | 121 | |
| | 122 | ==== 1.3 Extract `appointment_id → appt_status, client_id, unit_id, timeslot_id` (FD16) ==== |
| | 123 | |
| | 124 | * **FD causing issue:** FD16, `appointment_id` ⊂ PK. |
| | 125 | * **From R2**, extract: |
| | 126 | {{{ |
| | 127 | R_Appointment( |
| | 128 | appointment_id PK, |
| | 129 | appt_status, |
| | 130 | client_id, |
| | 131 | unit_id, |
| | 132 | timeslot_id |
| | 133 | ) |
| | 134 | FDs: FD16 (appointment_id → appt_status, client_id, unit_id, timeslot_id) |
| | 135 | FD17 (timeslot_id → appointment_id) |
| | 136 | Candidate keys: {appointment_id}, {timeslot_id} |
| | 137 | PK: {appointment_id} |
| | 138 | }}} |
| | 139 | * **Remaining relation R3:** R2 minus the dependent attributes |
| | 140 | {appt_status, client_id, unit_id, timeslot_id}, PK unchanged, |
| | 141 | FK `appointment_id → R_Appointment`. |
| | 142 | Note: `timeslot_id` moves entirely into R_Appointment and does NOT remain in R3; |
| | 143 | it is a dependent attribute of appointment_id, not a property of the residual relation. |
| | 144 | * **Lossless:** Yes – `appointment_id` is key of `R_Appointment`. |
| | 145 | * **Dependency preservation:** FD16 and FD17 are in `R_Appointment`. |
| | 146 | |
| | 147 | ==== 1.4 Extract `timeslot_id → ts_date, ts_time_start, ts_time_end, ts_status, agent_id` (FD13) ==== |
| | 148 | |
| | 149 | `timeslot_id` is the PK of R_Appointment, and FD13 shows it determines further |
| | 150 | attributes (ts_date, ts_time_start, ts_time_end, ts_status, agent_id) that were |
| | 151 | placed in R_Appointment in step 1.3 only as part of the dependent set of FD16. |
| | 152 | Those timeslot attributes are themselves determined by timeslot_id via FD13 — |
| | 153 | a partial dependency on timeslot_id (a candidate key of R_Appointment, not the |
| | 154 | chosen PK) — so they must be extracted into their own relation. |
| | 155 | |
| | 156 | * **From R_Appointment**, extract: |
| | 157 | {{{ |
| | 158 | R_Timeslot( |
| | 159 | timeslot_id PK, |
| | 160 | ts_date, |
| | 161 | ts_time_start, |
| | 162 | ts_time_end, |
| | 163 | ts_status, |
| | 164 | agent_id |
| | 165 | ) |
| | 166 | FDs: FD13 (timeslot_id → ts_date, ts_time_start, ts_time_end, ts_status, agent_id) |
| | 167 | FD14 ((agent_id, ts_date, ts_time_start) → timeslot_id) |
| | 168 | Candidate keys: {timeslot_id}, {agent_id, ts_date, ts_time_start} |
| | 169 | PK: {timeslot_id} |
| | 170 | }}} |
| | 171 | * **R_Appointment updated:** timeslot attributes removed; timeslot_id is retained |
| | 172 | as FK → R_Timeslot. R_Appointment now holds: |
| | 173 | {{{ |
| | 174 | R_Appointment( |
| | 175 | appointment_id PK, |
| | 176 | appt_status, |
| | 177 | client_id, |
| | 178 | unit_id, |
| | 179 | timeslot_id FK → R_Timeslot |
| | 180 | ) |
| | 181 | }}} |
| | 182 | * **Lossless:** Yes – `timeslot_id` is key of `R_Timeslot`. |
| | 183 | * **Dependency preservation:** FD13 and FD14 are in `R_Timeslot`. |
| | 184 | |
| | 185 | ==== 1.5 Extract `unit_id → unit_number, room_number, floor_area, unit_status, unit_price, unit_image, unit_floorplan, vector_image, floor_id` (FD07) ==== |
| | 186 | |
| | 187 | In R3, `unit_id` is a non-key attribute (it depends on `inquiry_id` and |
| | 188 | `appointment_id`, both proper subsets of the PK). Its dependent attributes |
| | 189 | cause a 2NF violation. |
| | 190 | |
| | 191 | * **From R3**, extract: |
| | 192 | {{{ |
| | 193 | R_Unit( |
| | 194 | unit_id PK, |
| | 195 | unit_number, |
| | 196 | room_number, |
| | 197 | floor_area, |
| | 198 | unit_status, |
| | 199 | unit_price, |
| | 200 | unit_image, |
| | 201 | unit_floorplan, |
| | 202 | vector_image, |
| | 203 | floor_id |
| | 204 | ) |
| | 205 | FDs: FD07 (unit_id → unit_number, room_number, floor_area, unit_status, |
| | 206 | unit_price, unit_image, unit_floorplan, vector_image, floor_id) |
| | 207 | FD08 ((floor_id, unit_number) → unit_id) |
| | 208 | Candidate keys: {unit_id}, {floor_id, unit_number} |
| | 209 | PK: {unit_id} |
| | 210 | }}} |
| | 211 | * **Remaining relation R4:** R3 minus the dependent attributes, PK unchanged, |
| | 212 | FK `unit_id → R_Unit`. |
| | 213 | * **Lossless:** Yes – `unit_id` is key of `R_Unit`. |
| | 214 | * **Dependency preservation:** FD07 and FD08 are in `R_Unit`. |
| | 215 | |
| | 216 | ==== 1.6 Extract `floor_id → floor_number, layout_image, building_id` (FD05) ==== |
| | 217 | |
| | 218 | In R4, `floor_id` is a non-key attribute, depending on `unit_id` which is |
| | 219 | itself a partial determinant — a 2NF violation. |
| | 220 | |
| | 221 | * **From R4**, extract: |
| | 222 | {{{ |
| | 223 | R_Floor( |
| | 224 | floor_id PK, |
| | 225 | floor_number, |
| | 226 | layout_image, |
| | 227 | building_id |
| | 228 | ) |
| | 229 | FDs: FD05 (floor_id → floor_number, layout_image, building_id) |
| | 230 | FD06 ((building_id, floor_number) → floor_id) |
| | 231 | Candidate keys: {floor_id}, {building_id, floor_number} |
| | 232 | PK: {floor_id} |
| | 233 | }}} |
| | 234 | * **Remaining relation R5:** R4 minus the dependent attributes, PK unchanged, |
| | 235 | FK `floor_id → R_Floor`. |
| | 236 | * **Lossless:** Yes – `floor_id` is key of `R_Floor`. |
| | 237 | * **Dependency preservation:** FD05 and FD06 are in `R_Floor`. |
| | 238 | |
| | 239 | ==== 1.7 Extract `building_id → building_name, building_address, building_description, admin_id` (FD04) ==== |
| | 240 | |
| | 241 | In R5, `building_id` is a non-key attribute depending on `floor_id` — a 2NF violation. |
| | 242 | |
| | 243 | * **From R5**, extract: |
| | 244 | {{{ |
| | 245 | R_Building( |
| | 246 | building_id PK, |
| | 247 | building_name, |
| | 248 | building_address, |
| | 249 | building_description, |
| | 250 | admin_id |
| | 251 | ) |
| | 252 | FDs: FD04 (building_id → building_name, building_address, |
| | 253 | building_description, admin_id) |
| | 254 | Candidate key / PK: {building_id} |
| | 255 | }}} |
| | 256 | * **Remaining relation R6:** R5 minus the dependent attributes, PK unchanged, |
| | 257 | FK `building_id → R_Building`. |
| | 258 | * **Lossless:** Yes – `building_id` is key of `R_Building`. |
| | 259 | * **Dependency preservation:** FD04 is in `R_Building`. |
| | 260 | |
| | 261 | ==== 1.8 Extract `admin_id → admin_name, admin_email, admin_password` (FD01, FD02) ==== |
| | 262 | |
| | 263 | In R6, `admin_id` is a non-key attribute depending on `building_id` — a 2NF violation. |
| | 264 | |
| | 265 | * **From R6**, extract: |
| | 266 | {{{ |
| | 267 | R_Admin( |
| | 268 | admin_id PK, |
| | 269 | admin_name, |
| | 270 | admin_email UNIQUE, |
| | 271 | admin_password |
| | 272 | ) |
| | 273 | FDs: FD01 (admin_id → admin_name, admin_email, admin_password) |
| | 274 | FD02 (admin_email → admin_id) |
| | 275 | Candidate keys: {admin_id}, {admin_email} |
| | 276 | PK: {admin_id} |
| | 277 | }}} |
| | 278 | * **Remaining relation R7:** R6 minus the dependent attributes, PK unchanged, |
| | 279 | FK `admin_id → R_Admin`. |
| | 280 | * **Lossless:** Yes – `admin_id` is key of `R_Admin`. |
| | 281 | * **Dependency preservation:** FD01 and FD02 are in `R_Admin`. |
| | 282 | |
| | 283 | ==== 1.9 Extract `agent_id → agent_name, agent_email, agent_password` (FD09, FD10) ==== |
| | 284 | |
| | 285 | `agent_id` appears as a non-key attribute in both R_Inquiry (step 1.2) and |
| | 286 | R_Timeslot (step 1.4). At the point of R_Inquiry's extraction, agent_id became |
| | 287 | a non-key attribute in that relation with FD09 applying to it — a partial |
| | 288 | dependency on inquiry_id. We extract the agent entity from R_Inquiry: |
| | 289 | |
| | 290 | {{{ |
| | 291 | R_Agent( |
| | 292 | agent_id PK, |
| | 293 | agent_name, |
| | 294 | agent_email UNIQUE, |
| | 295 | agent_password |
| | 296 | ) |
| | 297 | FDs: FD09 (agent_id → agent_name, agent_email, agent_password) |
| | 298 | FD10 (agent_email → agent_id) |
| | 299 | Candidate keys: {agent_id}, {agent_email} |
| | 300 | PK: {agent_id} |
| | 301 | }}} |
| | 302 | |
| | 303 | R_Inquiry and R_Timeslot both retain `agent_id` as FK → R_Agent. |
| | 304 | * **Lossless:** Yes – `agent_id` is key of `R_Agent`. |
| | 305 | * **Dependency preservation:** FD09 and FD10 are in `R_Agent`. |
| | 306 | |
| | 307 | ==== 1.10 Extract `client_id → client_name, client_email, client_phone` (FD11, FD12) ==== |
| | 308 | |
| | 309 | `client_id` appears as a non-key attribute in both R_Inquiry (step 1.2) and |
| | 310 | R_Appointment (step 1.3). At the point of R_Inquiry's extraction, client_id |
| | 311 | became a non-key attribute with FD11 applying — a partial dependency on |
| | 312 | inquiry_id. We extract the client entity from R_Inquiry: |
| | 313 | |
| | 314 | {{{ |
| | 315 | R_Client( |
| | 316 | client_id PK, |
| | 317 | client_name, |
| | 318 | client_email UNIQUE, |
| | 319 | client_phone |
| | 320 | ) |
| | 321 | FDs: FD11 (client_id → client_name, client_email, client_phone) |
| | 322 | FD12 (client_email → client_id) |
| | 323 | Candidate keys: {client_id}, {client_email} |
| | 324 | PK: {client_id} |
| | 325 | }}} |
| | 326 | |
| | 327 | R_Inquiry and R_Appointment both retain `client_id` as FK → R_Client. |
| | 328 | * **Lossless:** Yes – `client_id` is key of `R_Client`. |
| | 329 | * **Dependency preservation:** FD11 and FD12 are in `R_Client`. |
| | 330 | |
| | 331 | ==== 1.11 Materialise the M:N relationship "Designs" ==== |
| | 332 | |
| | 333 | After all extractions, R7 still contains `architect_id` (part of the original PK) |
| | 334 | and `building_id` (now a FK via the floor→building chain). The architect-building |
| | 335 | relationship is M:N with no non-key attributes of its own. It is extracted as a |
| | 336 | pure association relation: |
| | 337 | |
| | 338 | {{{ |
| | 339 | R_Designs( |
| | 340 | architect_id PK, FK → R_Architect, |
| | 341 | building_id PK, FK → R_Building |
| | 342 | ) |
| | 343 | FDs: none beyond the composite PK |
| | 344 | Candidate key / PK: {architect_id, building_id} |
| | 345 | }}} |
| | 346 | |
| | 347 | After this extraction, the residual relation R8 contains only |
| | 348 | `{architect_id, inquiry_id, appointment_id}` — the original PK of the |
| | 349 | universal relation R. This residual has no non-key attributes of its own; |
| | 350 | it exists solely as a structural artifact of treating all entities as one |
| | 351 | flat relation. The three attributes are already individually covered as PKs |
| | 352 | or FKs in R_Architect, R_Inquiry, and R_Appointment respectively, and no |
| | 353 | domain rule requires tracking the specific combination of |
| | 354 | (architect, inquiry, appointment) as a meaningful association. |
| | 355 | R8 is therefore dissolved; all information and all constraints are captured |
| | 356 | by the 11 extracted relations. |
| | 357 | |
| | 358 | **Result after Step 1: 11 relations, all in 2NF.** |
| | 359 | Lossless join holds because every extraction used a determinant that is the key |
| | 360 | of the new relation. All functional dependencies FD01-FD17 are preserved |
| | 361 | locally within exactly one relation. |
| | 362 | |
| | 363 | |
| | 364 | === Decompose from 2NF to 3NF === |
| | 365 | |
| | 366 | Each relation from Step 1 is checked below. |
| | 367 | |
| | 368 | ==== R_Admin ==== |
| | 369 | |
| | 370 | {{{ |
| | 371 | Attributes : admin_id, admin_name, admin_email, admin_password |
| | 372 | FDs : FD01 (admin_id → admin_name, admin_email, admin_password) |
| | 373 | FD02 (admin_email → admin_id) |
| | 374 | CKs : {admin_id}, {admin_email} |
| | 375 | PK : admin_id |
| | 376 | }}} |
| | 377 | |
| | 378 | Both FD01 and FD02 have superkeys on the left-hand side. There is no |
| | 379 | non-key attribute that determines another — admin_email is a full candidate |
| | 380 | key, not a "middle step" in a transitive chain. '''3NF. No decomposition needed.''' |
| | 381 | |
| | 382 | ==== R_Architect ==== |
| | 383 | |
| | 384 | {{{ |
| | 385 | Attributes : architect_id, architect_name |
| | 386 | FDs : FD03 (architect_id → architect_name) |
| | 387 | CKs / PK : {architect_id} |
| | 388 | }}} |
| | 389 | |
| | 390 | Only one non-key attribute exists. |
| | 391 | '''3NF. No decomposition needed.''' |
| | 392 | |
| | 393 | ==== R_Building ==== |
| | 394 | |
| | 395 | {{{ |
| | 396 | Attributes : building_id, building_name, building_address, |
| | 397 | building_description, admin_id |
| | 398 | FDs : FD04 (building_id → building_name, building_address, |
| | 399 | building_description, admin_id) |
| | 400 | CKs / PK : {building_id} |
| | 401 | }}} |
| | 402 | |
| | 403 | admin_id is a non-key attribute (FK to R_Admin). The question is whether |
| | 404 | admin_id determines any other attribute inside R_Building — it does not. |
| | 405 | admin_id → admin_name etc. is a fact about R_Admin, not about R_Building. |
| | 406 | No transitive chain exists within this relation. |
| | 407 | '''3NF. No decomposition needed.''' |
| | 408 | |
| | 409 | ==== R_Designs ==== |
| | 410 | |
| | 411 | {{{ |
| | 412 | Attributes : architect_id, building_id |
| | 413 | FDs : none beyond the composite PK |
| | 414 | CKs / PK : {architect_id, building_id} |
| | 415 | }}} |
| | 416 | |
| | 417 | No non-key attributes. '''3NF. No decomposition needed.''' |
| | 418 | |
| | 419 | ==== R_Floor ==== |
| | 420 | |
| | 421 | {{{ |
| | 422 | Attributes : floor_id, floor_number, layout_image, building_id |
| | 423 | FDs : FD05 (floor_id → floor_number, layout_image, building_id) |
| | 424 | FD06 ((building_id, floor_number) → floor_id) |
| | 425 | CKs : {floor_id}, {building_id, floor_number} |
| | 426 | PK : floor_id |
| | 427 | }}} |
| | 428 | |
| | 429 | building_id is a non-key attribute (FK to R_Building). FD06 uses (building_id, floor_number) together |
| | 430 | as a composite candidate key, which is a superkey. No transitive chain. |
| | 431 | '''3NF. No decomposition needed.''' |
| | 432 | |
| | 433 | ==== R_Unit ==== |
| | 434 | |
| | 435 | {{{ |
| | 436 | Attributes : unit_id, unit_number, room_number, floor_area, unit_status, |
| | 437 | unit_price, unit_image, unit_floorplan, vector_image, floor_id |
| | 438 | FDs : FD07 (unit_id → unit_number, room_number, floor_area, |
| | 439 | unit_status, unit_price, unit_image, |
| | 440 | unit_floorplan, vector_image, floor_id) |
| | 441 | FD08 ((floor_id, unit_number) → unit_id) |
| | 442 | CKs : {unit_id}, {floor_id, unit_number} |
| | 443 | PK : unit_id |
| | 444 | }}} |
| | 445 | |
| | 446 | floor_id is a non-key attribute (FK to R_Floor). Within R_Unit, floor_id |
| | 447 | does not determine room_number, floor_area, or any other attribute on its |
| | 448 | own — those all require unit_id. FD08 (floor_id, unit_number) is a |
| | 449 | candidate key. No transitive chain. '''3NF. No decomposition needed.''' |
| | 450 | |
| | 451 | ==== R_Agent ==== |
| | 452 | |
| | 453 | {{{ |
| | 454 | Attributes : agent_id, agent_name, agent_email, agent_password |
| | 455 | FDs : FD09 (agent_id → agent_name, agent_email, agent_password) |
| | 456 | FD10 (agent_email → agent_id) |
| | 457 | CKs : {agent_id}, {agent_email} |
| | 458 | PK : agent_id |
| | 459 | }}} |
| | 460 | |
| | 461 | Same structure as R_Admin. agent_email is a candidate key, so FD10 does |
| | 462 | not introduce a transitive dependency — both attributes are superkeys. |
| | 463 | '''3NF. No decomposition needed.''' |
| | 464 | |
| | 465 | ==== R_Client ==== |
| | 466 | |
| | 467 | {{{ |
| | 468 | Attributes : client_id, client_name, client_email, client_phone |
| | 469 | FDs : FD11 (client_id → client_name, client_email, client_phone) |
| | 470 | FD12 (client_email → client_id) |
| | 471 | CKs : {client_id}, {client_email} |
| | 472 | PK : client_id |
| | 473 | }}} |
| | 474 | |
| | 475 | client_email is a candidate key, so FD12 is not a transitive dependency. |
| | 476 | '''3NF. No decomposition needed.''' |
| | 477 | |
| | 478 | ==== R_Timeslot ==== |
| | 479 | |
| | 480 | {{{ |
| | 481 | Attributes : timeslot_id, ts_date, ts_time_start, ts_time_end, |
| | 482 | ts_status, agent_id |
| | 483 | FDs : FD13 (timeslot_id → ts_date, ts_time_start, ts_time_end, |
| | 484 | ts_status, agent_id) |
| | 485 | FD14 ((agent_id, ts_date, ts_time_start) → timeslot_id) |
| | 486 | CKs : {timeslot_id}, {agent_id, ts_date, ts_time_start} |
| | 487 | PK : timeslot_id |
| | 488 | }}} |
| | 489 | |
| | 490 | agent_id is a non-key attribute (FK to R_Agent). The composite (agent_id, ts_date, ts_time_start) |
| | 491 | is a candidate key, so FD14 has a superkey. |
| | 492 | No transitive chain. '''3NF. No decomposition needed.''' |
| | 493 | |
| | 494 | ==== R_Inquiry ==== |
| | 495 | |
| | 496 | {{{ |
| | 497 | Attributes : inquiry_id, inquiry_message, inquiry_status, |
| | 498 | inquiry_created_at, unit_id, client_id, agent_id |
| | 499 | FDs : FD15 (inquiry_id → inquiry_message, inquiry_status, |
| | 500 | inquiry_created_at, unit_id, client_id, agent_id) |
| | 501 | CK / PK : {inquiry_id} |
| | 502 | }}} |
| | 503 | |
| | 504 | unit_id, client_id, and agent_id are all non-key FK attributes. None of |
| | 505 | them determines any other attribute within R_Inquiry. All non-key attributes depend directly on inquiry_id. |
| | 506 | '''3NF. No decomposition needed.''' |
| | 507 | |
| | 508 | ==== R_Appointment ==== |
| | 509 | |
| | 510 | {{{ |
| | 511 | Attributes : appointment_id, appt_status, client_id, unit_id, timeslot_id |
| | 512 | FDs : FD16 (appointment_id → appt_status, client_id, unit_id, timeslot_id) |
| | 513 | FD17 (timeslot_id → appointment_id) |
| | 514 | CKs : {appointment_id}, {timeslot_id} |
| | 515 | PK : appointment_id |
| | 516 | }}} |
| | 517 | |
| | 518 | client_id and unit_id are FKs that determine nothing within R_Appointment. |
| | 519 | '''3NF. No decomposition needed.''' |
| | 520 | |
| | 521 | ---- |
| | 522 | |
| | 523 | '''No decomposition was required at the 3NF step.''' |
| | 524 | All 11 relations produced in Step 1 are already in 3NF. |
| | 525 | |
| | 526 | === STEP 3 — Decompose from 3NF to BCNF === |
| | 527 | |
| | 528 | Check every FD in each relation: |
| | 529 | |
| | 530 | * R_Admin: FD01 (admin_id superkey), FD02 (admin_email superkey) – BCNF. |
| | 531 | * R_Architect: FD03 (architect_id superkey) – BCNF. |
| | 532 | * R_Building: FD04 (building_id superkey) – BCNF. |
| | 533 | * R_Designs: no non‑trivial FDs – BCNF. |
| | 534 | * R_Floor: FD05 (floor_id superkey), FD06 (composite CK superkey) – BCNF. |
| | 535 | * R_Unit: FD07 (unit_id superkey), FD08 (composite CK superkey) – BCNF. |
| | 536 | * R_Agent: FD09 (agent_id superkey), FD10 (agent_email superkey) – BCNF. |
| | 537 | * R_Client: FD11 (client_id superkey), FD12 (client_email superkey) – BCNF. |
| | 538 | * R_Timeslot: FD13 (timeslot_id superkey), FD14 (composite CK superkey) – BCNF. |
| | 539 | * R_Inquiry: FD15 (inquiry_id superkey) – BCNF. |
| | 540 | * R_Appointment: FD16 (appointment_id superkey), FD17 (timeslot_id superkey) – BCNF. |
| | 541 | |
| | 542 | '''All 11 relations are in BCNF. No decomposition needed.''' |
| | 543 | |
| | 544 | === 4NF Note === |
| | 545 | |
| | 546 | No non‑trivial multi‑valued dependencies exist – every attribute is single‑valued |
| | 547 | per key. Therefore '''all relations are also in 4NF'''. |
| | 548 | |
| | 549 | BCNF / 4NF is the highest normal form achieved. |
| | 550 | |
| | 551 | ---- |
| | 552 | |
| | 553 | == Final Normalized Design & Discussion == |
| | 554 | |
| | 555 | === Final Relations === |
| | 556 | {{{ |
| | 557 | Admin( |
| | 558 | admin_id PK, |
| | 559 | admin_name, |
| | 560 | admin_email UNIQUE, |
| | 561 | admin_password |
| | 562 | ) |
| | 563 | |
| | 564 | Architect( |
| | 565 | architect_id PK, |
| | 566 | architect_name |
| | 567 | ) |
| | 568 | |
| | 569 | Building( |
| | 570 | building_id PK, |
| | 571 | building_name, |
| | 572 | building_address, |
| | 573 | building_description, |
| | 574 | admin_id FK → Admin |
| | 575 | ) |
| | 576 | |
| | 577 | Designs( |
| | 578 | architect_id PK, FK → Architect, |
| | 579 | building_id PK, FK → Building |
| | 580 | ) |
| | 581 | |
| | 582 | Floor( |
| | 583 | floor_id PK, |
| | 584 | floor_number, |
| | 585 | layout_image, |
| | 586 | building_id FK → Building, |
| | 587 | UNIQUE (building_id, floor_number) |
| | 588 | ) |
| | 589 | |
| | 590 | Unit( |
| | 591 | unit_id PK, |
| | 592 | unit_number, |
| | 593 | room_number, |
| | 594 | floor_area, |
| | 595 | unit_status, |
| | 596 | unit_price, |
| | 597 | unit_image, |
| | 598 | unit_floorplan, |
| | 599 | vector_image, |
| | 600 | floor_id FK → Floor, |
| | 601 | UNIQUE (floor_id, unit_number) |
| | 602 | ) |
| | 603 | |
| | 604 | Agent( |
| | 605 | agent_id PK, |
| | 606 | agent_name, |
| | 607 | agent_email UNIQUE, |
| | 608 | agent_password |
| | 609 | ) |
| | 610 | |
| | 611 | Client( |
| | 612 | client_id PK, |
| | 613 | client_name, |
| | 614 | client_email UNIQUE, |
| | 615 | client_phone |
| | 616 | ) |
| | 617 | |
| | 618 | Timeslot( |
| | 619 | timeslot_id PK, |
| | 620 | ts_date, |
| | 621 | ts_time_start, |
| | 622 | ts_time_end, |
| | 623 | ts_status, |
| | 624 | agent_id FK → Agent, |
| | 625 | UNIQUE (agent_id, ts_date, ts_time_start) |
| | 626 | ) |
| | 627 | |
| | 628 | Inquiry( |
| | 629 | inquiry_id PK, |
| | 630 | inquiry_message, |
| | 631 | inquiry_status, |
| | 632 | inquiry_created_at, |
| | 633 | unit_id FK → Unit, |
| | 634 | client_id FK → Client, |
| | 635 | agent_id FK → Agent |
| | 636 | ) |
| | 637 | |
| | 638 | Appointment( |
| | 639 | appointment_id PK, |
| | 640 | appt_status, |
| | 641 | client_id FK → Client, |
| | 642 | unit_id FK → Unit, |
| | 643 | timeslot_id FK → Timeslot, UNIQUE |
| | 644 | ) |
| | 645 | }}} |
| | 646 | |
| | 647 | === Differences vs. Phase 2 Design === |
| | 648 | |
| | 649 | || '''Aspect''' || '''Phase 2 Design''' || '''P5 Normalized Design''' || |
| | 650 | || `floor.admin_id` || Present as FK column || Not present as it is reachable via floor→building→admin || |
| | 651 | || `unit.admin_id` || Present as FK column || Not present as it is reachable via unit→floor→building→admin || |
| | 652 | || `appointment.agent_id` || Present as FK column || Not present as it is reachable via appointment→timeslot→agent || |
| | 653 | || Highest normal form || 2NF (three transitive dependencies) || BCNF / 4NF || |
| | 654 | || Admin reachability || Direct FK on floor and unit || Via FK chain – no data loss, no redundancy || |
| | 655 | || Agent reachability || Direct FK on appointment || Via FK chain through timeslot || |
| | 656 | || Update anomaly risk || Changing building’s admin forced UPDATE on all floor & unit rows || Single UPDATE on `Building` row only || |
| | 657 | |
| | 658 | === Changes Required for Subsequent Phases === |
| | 659 | |
| | 660 | The P5 normalized design (BCNF/4NF) will be used for all following project phases. |
| | 661 | The Phase 2 DDL must be updated by: |
| | 662 | |
| | 663 | 1. Dropping column `admin_id` and its FK constraint from `floor`. |
| | 664 | 2. Dropping column `admin_id` and its FK constraint from `unit`. |
| | 665 | 3. Dropping column `agent_id` and its FK constraint from `appointment`. |
| | 666 | |
| | 667 | All application queries that previously joined directly on those redundant columns |
| | 668 | must instead go trough the FK chain (e.g., `JOIN Floor → Building → Admin`). |