| | 1 | = Normalization = |
| | 2 | |
| | 3 | == De-normalized Database Form == |
| | 4 | |
| | 5 | The normalization process starts from a single universal relation '''U''' that contains all attributes from the ER model, as if all data resided in one table. Attribute names are made globally unique by prefixing where necessary. |
| | 6 | |
| | 7 | {{{ |
| | 8 | U ( reservation_id, start_time, end_time, status, purpose, created_at, recurrence_group_id, |
| | 9 | user_id, first_name, last_name, email, password, user_type_id, user_type_name, user_type_description, |
| | 10 | approved_by, |
| | 11 | resource_id, resource_name, resource_description, available_from, available_to, available_weekends, |
| | 12 | resource_type_id, resource_type_name, is_physical, |
| | 13 | location_id, building, room ) |
| | 14 | }}} |
| | 15 | |
| | 16 | The relation has 27 attributes total, representing reservation data, user data, resource data, and their associated types and locations. |
| | 17 | |
| | 18 | == Functional Dependencies == |
| | 19 | |
| | 20 | === Initial Set of Functional Dependencies === |
| | 21 | |
| | 22 | The following functional dependencies are derived from the semantics of the domain: |
| | 23 | |
| | 24 | ||'''ID'''||'''Functional Dependency'''||'''Source'''|| |
| | 25 | ||FD1||user_type_id -> user_type_name, user_type_description||!UserType entity; type_id uniquely identifies each user type|| |
| | 26 | ||FD2||user_type_name -> user_type_id, user_type_description||!UserType entity; type_name is also unique (UNIQUE constraint)|| |
| | 27 | ||FD3||user_id -> first_name, last_name, email, password, user_type_id||User entity; user_id uniquely identifies each user|| |
| | 28 | ||FD4||email -> user_id, first_name, last_name, password, user_type_id||User entity; email is also unique (UNIQUE constraint)|| |
| | 29 | ||FD5||resource_type_id -> resource_type_name, is_physical||!ResourceType entity; type_id uniquely identifies each resource type|| |
| | 30 | ||FD6||resource_type_name -> resource_type_id, is_physical||!ResourceType entity; type_name is also unique (UNIQUE constraint)|| |
| | 31 | ||FD7||location_id -> building, room||Location entity; location_id uniquely identifies each location|| |
| | 32 | ||FD8||{building, room} -> location_id||Location entity; building+room combination is unique (composite UNIQUE constraint)|| |
| | 33 | ||FD9||resource_id -> resource_name, resource_description, available_from, available_to, available_weekends, resource_type_id, location_id||Resource entity; resource_id uniquely identifies each resource|| |
| | 34 | ||FD10||reservation_id -> start_time, end_time, status, purpose, created_at, recurrence_group_id, user_id, approved_by, resource_id||Reservation entity; reservation_id uniquely identifies each reservation|| |
| | 35 | |
| | 36 | === Canonical Cover === |
| | 37 | |
| | 38 | To obtain the canonical cover, we apply the following reductions: |
| | 39 | |
| | 40 | 1. '''Reduce FD2:''' Since user_type_name -> user_type_id (FD2) and user_type_id -> user_type_description (FD1), the attribute user_type_description is derivable by transitivity. Reduced to: '''user_type_name -> user_type_id''' |
| | 41 | |
| | 42 | 2. '''Reduce FD4:''' Since email -> user_id (FD4) and user_id -> first_name, last_name, password, user_type_id (FD3), all attributes except user_id are derivable by transitivity. Reduced to: '''email -> user_id''' |
| | 43 | |
| | 44 | 3. '''Reduce FD6:''' Since resource_type_name -> resource_type_id (FD6) and resource_type_id -> is_physical (FD5), the attribute is_physical is derivable by transitivity. Reduced to: '''resource_type_name -> resource_type_id''' |
| | 45 | |
| | 46 | No further reductions are possible (no extraneous left-hand-side attributes, no redundant FDs). |
| | 47 | |
| | 48 | '''Canonical Cover F,,c,,:''' |
| | 49 | |
| | 50 | ||'''ID'''||'''Functional Dependency'''|| |
| | 51 | ||FD1||user_type_id -> user_type_name, user_type_description|| |
| | 52 | ||FD2'||user_type_name -> user_type_id|| |
| | 53 | ||FD3||user_id -> first_name, last_name, email, password, user_type_id|| |
| | 54 | ||FD4'||email -> user_id|| |
| | 55 | ||FD5||resource_type_id -> resource_type_name, is_physical|| |
| | 56 | ||FD6'||resource_type_name -> resource_type_id|| |
| | 57 | ||FD7||location_id -> building, room|| |
| | 58 | ||FD8||{building, room} -> location_id|| |
| | 59 | ||FD9||resource_id -> resource_name, resource_description, available_from, available_to, available_weekends, resource_type_id, location_id|| |
| | 60 | ||FD10||reservation_id -> start_time, end_time, status, purpose, created_at, recurrence_group_id, user_id, approved_by, resource_id|| |
| | 61 | |
| | 62 | == Candidate Keys and Primary Key == |
| | 63 | |
| | 64 | To find candidate keys of U, we compute attribute closures. |
| | 65 | |
| | 66 | === Attribute Classification (Left / Right Side) === |
| | 67 | |
| | 68 | ||'''Attribute'''||'''Left Side'''||'''Right Side'''||'''Classification'''|| |
| | 69 | ||reservation_id||Yes (FD10)||No||'''Left only'''|| |
| | 70 | ||start_time||No||Yes (FD10)||Right only|| |
| | 71 | ||end_time||No||Yes (FD10)||Right only|| |
| | 72 | ||status||No||Yes (FD10)||Right only|| |
| | 73 | ||purpose||No||Yes (FD10)||Right only|| |
| | 74 | ||created_at||No||Yes (FD10)||Right only|| |
| | 75 | ||recurrence_group_id||No||Yes (FD10)||Right only|| |
| | 76 | ||user_id||Yes (FD3)||Yes (FD4', FD10)||Both|| |
| | 77 | ||first_name||No||Yes (FD3)||Right only|| |
| | 78 | ||last_name||No||Yes (FD3)||Right only|| |
| | 79 | ||email||Yes (FD4')||Yes (FD3)||Both|| |
| | 80 | ||password||No||Yes (FD3)||Right only|| |
| | 81 | ||user_type_id||Yes (FD1)||Yes (FD2', FD3)||Both|| |
| | 82 | ||user_type_name||Yes (FD2')||Yes (FD1)||Both|| |
| | 83 | ||user_type_description||No||Yes (FD1)||Right only|| |
| | 84 | ||approved_by||No||Yes (FD10)||Right only|| |
| | 85 | ||resource_id||Yes (FD9)||Yes (FD10)||Both|| |
| | 86 | ||resource_name||No||Yes (FD9)||Right only|| |
| | 87 | ||resource_description||No||Yes (FD9)||Right only|| |
| | 88 | ||available_from||No||Yes (FD9)||Right only|| |
| | 89 | ||available_to||No||Yes (FD9)||Right only|| |
| | 90 | ||available_weekends||No||Yes (FD9)||Right only|| |
| | 91 | ||resource_type_id||Yes (FD5)||Yes (FD6', FD9)||Both|| |
| | 92 | ||resource_type_name||Yes (FD6')||Yes (FD5)||Both|| |
| | 93 | ||is_physical||No||Yes (FD5)||Right only|| |
| | 94 | ||location_id||Yes (FD7)||Yes (FD8, FD9)||Both|| |
| | 95 | ||building||Yes (FD8)||Yes (FD7)||Both|| |
| | 96 | ||room||Yes (FD8)||Yes (FD7)||Both|| |
| | 97 | |
| | 98 | '''Attributes appearing ONLY on the left side:''' reservation_id |
| | 99 | |
| | 100 | This attribute must be part of every candidate key. |
| | 101 | |
| | 102 | === Closure Computation === |
| | 103 | |
| | 104 | {{{ |
| | 105 | {reservation_id}+ |
| | 106 | = {reservation_id} |
| | 107 | apply FD10: + {start_time, end_time, status, purpose, created_at, recurrence_group_id, user_id, approved_by, resource_id} |
| | 108 | apply FD3: + {first_name, last_name, email, password, user_type_id} |
| | 109 | apply FD1: + {user_type_name, user_type_description} |
| | 110 | apply FD9: + {resource_name, resource_description, available_from, available_to, available_weekends, resource_type_id, location_id} |
| | 111 | apply FD5: + {resource_type_name, is_physical} |
| | 112 | apply FD7: + {building, room} |
| | 113 | = U (all 27 attributes) |
| | 114 | }}} |
| | 115 | |
| | 116 | Since {reservation_id}^+^ = U, '''reservation_id''' is a superkey. It is a single attribute, so it is minimal, therefore it is a '''candidate key'''. |
| | 117 | |
| | 118 | Since reservation_id is the only attribute appearing exclusively on the left side of all FDs, and its closure already covers U, no other minimal set of attributes can form a candidate key. Therefore, '''{reservation_id} is the only candidate key'''. |
| | 119 | |
| | 120 | '''Primary key selected:''' reservation_id |
| | 121 | |
| | 122 | '''Prime attribute:''' reservation_id [[BR]] |
| | 123 | '''Non-prime attributes:''' all remaining 26 attributes |
| | 124 | |
| | 125 | == 1NF Decomposition == |
| | 126 | |
| | 127 | For a relation to be in 1NF, all attribute values must be atomic (no repeating groups, no multi-valued attributes). |
| | 128 | |
| | 129 | '''Check:''' All 27 attributes of U have atomic domains: |
| | 130 | * VARCHAR for text attributes (first_name, last_name, email, etc.) |
| | 131 | * INTEGER for identifiers (user_id, resource_id, etc.) |
| | 132 | * BOOLEAN for flags (is_physical, available_weekends) |
| | 133 | * TIMESTAMP for date/time values (start_time, end_time, created_at) |
| | 134 | * TIME for daily time values (available_from, available_to) |
| | 135 | * UUID for group identifiers (recurrence_group_id) |
| | 136 | |
| | 137 | There are no multi-valued or composite attributes. '''U is already in 1NF.''' No decomposition is needed at this stage. |
| | 138 | |
| | 139 | == 2NF Decomposition == |
| | 140 | |
| | 141 | For a relation to be in 2NF, it must be in 1NF and have no partial dependencies (no non-prime attribute may depend on a proper subset of a candidate key). |
| | 142 | |
| | 143 | '''Check:''' The only candidate key of U is {reservation_id}, which is a single attribute. A single-attribute key has no proper subsets, therefore partial dependencies are impossible by definition. |
| | 144 | |
| | 145 | '''U is already in 2NF.''' No decomposition is needed at this stage. |
| | 146 | |
| | 147 | == 3NF Decomposition == |
| | 148 | |
| | 149 | For a relation to be in 3NF, it must be in 2NF and for every non-trivial FD X -> A: either X is a superkey, or A is a prime attribute. |
| | 150 | |
| | 151 | '''Check:''' The following FDs violate 3NF because their left-hand sides are not superkeys and their right-hand sides contain only non-prime attributes: |
| | 152 | |
| | 153 | ||'''FD'''||'''Violation'''||'''Transitive Chain'''|| |
| | 154 | ||FD1: user_type_id -> user_type_name, user_type_description||user_type_id is not a superkey||reservation_id -> user_id -> user_type_id -> {user_type_name, user_type_description}|| |
| | 155 | ||FD3: user_id -> first_name, last_name, email, password, user_type_id||user_id is not a superkey||reservation_id -> user_id -> {first_name, last_name, email, password, user_type_id}|| |
| | 156 | ||FD5: resource_type_id -> resource_type_name, is_physical||resource_type_id is not a superkey||reservation_id -> resource_id -> resource_type_id -> {resource_type_name, is_physical}|| |
| | 157 | ||FD7: location_id -> building, room||location_id is not a superkey||reservation_id -> resource_id -> location_id -> {building, room}|| |
| | 158 | ||FD9: resource_id -> resource_name, ...||resource_id is not a superkey||reservation_id -> resource_id -> {resource_name, ...}|| |
| | 159 | |
| | 160 | '''U is NOT in 3NF.''' We proceed with step-by-step decomposition, starting from the deepest transitive dependency chains. |
| | 161 | |
| | 162 | === Step 1: Decompose !UserTypes from U === |
| | 163 | |
| | 164 | '''Relation analyzed:''' U (27 attributes, key: {reservation_id}) |
| | 165 | |
| | 166 | '''FDs that apply:''' FD1, FD2', FD3, FD4', FD5, FD6', FD7, FD8, FD9, FD10 |
| | 167 | |
| | 168 | '''Violating FD selected:''' FD1: user_type_id -> user_type_name, user_type_description [[BR]] |
| | 169 | user_type_id is not a superkey of U, and user_type_name, user_type_description are non-prime. This is a transitive dependency: reservation_id -> user_id -> user_type_id -> {user_type_name, user_type_description}. |
| | 170 | |
| | 171 | '''Decomposition:''' |
| | 172 | |
| | 173 | * '''R1(user_type_id, user_type_name, user_type_description)''' |
| | 174 | * FDs in R1: user_type_id -> user_type_name, user_type_description; user_type_name -> user_type_id |
| | 175 | * Candidate keys: {user_type_id}, {user_type_name} |
| | 176 | * Primary key: user_type_id |
| | 177 | * Normal form: BCNF (all FD determinants are superkeys) |
| | 178 | |
| | 179 | * '''U1''' = U - {user_type_name, user_type_description}, keeping user_type_id |
| | 180 | {{{ |
| | 181 | U1 ( reservation_id, start_time, end_time, status, purpose, created_at, recurrence_group_id, |
| | 182 | user_id, first_name, last_name, email, password, user_type_id, |
| | 183 | approved_by, |
| | 184 | resource_id, resource_name, resource_description, available_from, available_to, available_weekends, |
| | 185 | resource_type_id, resource_type_name, is_physical, |
| | 186 | location_id, building, room ) |
| | 187 | }}} |
| | 188 | * Key of U1: {reservation_id} |
| | 189 | |
| | 190 | '''Lossless join check:''' R1 ∩ U1 = {user_type_id}, which is a candidate key of R1. Lossless join preserved. ✓ [[BR]] |
| | 191 | '''Dependency preservation:''' FD1 and FD2' are preserved in R1; all other FDs remain in U1. ✓ |
| | 192 | |
| | 193 | === Step 2: Decompose Users from U1 === |
| | 194 | |
| | 195 | '''Relation analyzed:''' U1 (25 attributes, key: {reservation_id}) |
| | 196 | |
| | 197 | '''FDs that apply:''' FD3, FD4', FD5, FD6', FD7, FD8, FD9, FD10 |
| | 198 | |
| | 199 | '''Violating FD selected:''' FD3: user_id -> first_name, last_name, email, password, user_type_id [[BR]] |
| | 200 | user_id is not a superkey of U1, and all RHS attributes are non-prime. This is a transitive dependency: reservation_id -> user_id -> {first_name, last_name, email, password, user_type_id}. |
| | 201 | |
| | 202 | '''Decomposition:''' |
| | 203 | |
| | 204 | * '''R2(user_id, first_name, last_name, email, password, user_type_id)''' |
| | 205 | * FDs in R2: user_id -> first_name, last_name, email, password, user_type_id; email -> user_id |
| | 206 | * Candidate keys: {user_id}, {email} (since {email}^+^ = {email, user_id, first_name, last_name, password, user_type_id} = R2) |
| | 207 | * Primary key: user_id |
| | 208 | * Normal form: BCNF (both user_id and email are superkeys) |
| | 209 | |
| | 210 | * '''U2''' = U1 - {first_name, last_name, email, password, user_type_id}, keeping user_id |
| | 211 | {{{ |
| | 212 | U2 ( reservation_id, start_time, end_time, status, purpose, created_at, recurrence_group_id, |
| | 213 | user_id, approved_by, |
| | 214 | resource_id, resource_name, resource_description, available_from, available_to, available_weekends, |
| | 215 | resource_type_id, resource_type_name, is_physical, |
| | 216 | location_id, building, room ) |
| | 217 | }}} |
| | 218 | * Key of U2: {reservation_id} |
| | 219 | |
| | 220 | '''Lossless join check:''' R2 ∩ U2 = {user_id}, which is a candidate key of R2. Lossless join preserved. ✓ [[BR]] |
| | 221 | '''Dependency preservation:''' FD3 and FD4' are preserved in R2; all other FDs remain in U2. ✓ |
| | 222 | |
| | 223 | === Step 3: Decompose !ResourceTypes from U2 === |
| | 224 | |
| | 225 | '''Relation analyzed:''' U2 (20 attributes, key: {reservation_id}) |
| | 226 | |
| | 227 | '''FDs that apply:''' FD5, FD6', FD7, FD8, FD9, FD10 |
| | 228 | |
| | 229 | '''Violating FD selected:''' FD5: resource_type_id -> resource_type_name, is_physical [[BR]] |
| | 230 | resource_type_id is not a superkey of U2. This is a transitive dependency: reservation_id -> resource_id -> resource_type_id -> {resource_type_name, is_physical}. |
| | 231 | |
| | 232 | '''Decomposition:''' |
| | 233 | |
| | 234 | * '''R3(resource_type_id, resource_type_name, is_physical)''' |
| | 235 | * FDs in R3: resource_type_id -> resource_type_name, is_physical; resource_type_name -> resource_type_id |
| | 236 | * Candidate keys: {resource_type_id}, {resource_type_name} |
| | 237 | * Primary key: resource_type_id |
| | 238 | * Normal form: BCNF (all FD determinants are superkeys) |
| | 239 | |
| | 240 | * '''U3''' = U2 - {resource_type_name, is_physical}, keeping resource_type_id |
| | 241 | {{{ |
| | 242 | U3 ( reservation_id, start_time, end_time, status, purpose, created_at, recurrence_group_id, |
| | 243 | user_id, approved_by, |
| | 244 | resource_id, resource_name, resource_description, available_from, available_to, available_weekends, |
| | 245 | resource_type_id, |
| | 246 | location_id, building, room ) |
| | 247 | }}} |
| | 248 | * Key of U3: {reservation_id} |
| | 249 | |
| | 250 | '''Lossless join check:''' R3 ∩ U3 = {resource_type_id}, which is a candidate key of R3. Lossless join preserved. ✓ [[BR]] |
| | 251 | '''Dependency preservation:''' FD5 and FD6' are preserved in R3; all other FDs remain in U3. ✓ |
| | 252 | |
| | 253 | === Step 4: Decompose Locations from U3 === |
| | 254 | |
| | 255 | '''Relation analyzed:''' U3 (18 attributes, key: {reservation_id}) |
| | 256 | |
| | 257 | '''FDs that apply:''' FD7, FD8, FD9, FD10 |
| | 258 | |
| | 259 | '''Violating FD selected:''' FD7: location_id -> building, room [[BR]] |
| | 260 | location_id is not a superkey of U3. This is a transitive dependency: reservation_id -> resource_id -> location_id -> {building, room}. |
| | 261 | |
| | 262 | '''Decomposition:''' |
| | 263 | |
| | 264 | * '''R4(location_id, building, room)''' |
| | 265 | * FDs in R4: location_id -> building, room; {building, room} -> location_id |
| | 266 | * Candidate keys: {location_id}, {building, room} |
| | 267 | * Primary key: location_id |
| | 268 | * Normal form: BCNF (all FD determinants are superkeys) |
| | 269 | |
| | 270 | * '''U4''' = U3 - {building, room}, keeping location_id |
| | 271 | {{{ |
| | 272 | U4 ( reservation_id, start_time, end_time, status, purpose, created_at, recurrence_group_id, |
| | 273 | user_id, approved_by, |
| | 274 | resource_id, resource_name, resource_description, available_from, available_to, available_weekends, |
| | 275 | resource_type_id, location_id ) |
| | 276 | }}} |
| | 277 | * Key of U4: {reservation_id} |
| | 278 | |
| | 279 | '''Lossless join check:''' R4 ∩ U4 = {location_id}, which is a candidate key of R4. Lossless join preserved. ✓ [[BR]] |
| | 280 | '''Dependency preservation:''' FD7 and FD8 are preserved in R4; all other FDs remain in U4. ✓ |
| | 281 | |
| | 282 | === Step 5: Decompose Resources from U4 === |
| | 283 | |
| | 284 | '''Relation analyzed:''' U4 (16 attributes, key: {reservation_id}) |
| | 285 | |
| | 286 | '''FDs that apply:''' FD9, FD10 |
| | 287 | |
| | 288 | '''Violating FD selected:''' FD9: resource_id -> resource_name, resource_description, available_from, available_to, available_weekends, resource_type_id, location_id [[BR]] |
| | 289 | resource_id is not a superkey of U4. This is a transitive dependency: reservation_id -> resource_id -> {resource_name, resource_description, ...}. |
| | 290 | |
| | 291 | '''Decomposition:''' |
| | 292 | |
| | 293 | * '''R5(resource_id, resource_name, resource_description, available_from, available_to, available_weekends, resource_type_id, location_id)''' |
| | 294 | * FDs in R5: resource_id -> resource_name, resource_description, available_from, available_to, available_weekends, resource_type_id, location_id |
| | 295 | * Candidate key: {resource_id} |
| | 296 | * Primary key: resource_id |
| | 297 | * Normal form: BCNF (the only FD has superkey determinant) |
| | 298 | |
| | 299 | * '''U5''' = U4 - {resource_name, resource_description, available_from, available_to, available_weekends, resource_type_id, location_id}, keeping resource_id |
| | 300 | {{{ |
| | 301 | U5 ( reservation_id, start_time, end_time, status, purpose, created_at, recurrence_group_id, |
| | 302 | user_id, approved_by, resource_id ) |
| | 303 | }}} |
| | 304 | * Key of U5: {reservation_id} |
| | 305 | |
| | 306 | '''Lossless join check:''' R5 ∩ U5 = {resource_id}, which is a candidate key of R5. Lossless join preserved. ✓ [[BR]] |
| | 307 | '''Dependency preservation:''' FD9 is preserved in R5; FD10 remains in U5. ✓ |
| | 308 | |
| | 309 | === Step 6: Verify U5 (Reservations) === |
| | 310 | |
| | 311 | '''Relation analyzed:''' U5 (10 attributes) |
| | 312 | |
| | 313 | '''FDs in U5:''' FD10: reservation_id -> start_time, end_time, status, purpose, created_at, recurrence_group_id, user_id, approved_by, resource_id |
| | 314 | |
| | 315 | '''Candidate key:''' {reservation_id} [[BR]] |
| | 316 | '''Primary key:''' reservation_id |
| | 317 | |
| | 318 | Every FD in U5 has a superkey (reservation_id) on the left-hand side. There are no transitive dependencies. '''U5 is in 3NF.''' ✓ |
| | 319 | |
| | 320 | === 3NF Result === |
| | 321 | |
| | 322 | After decomposition, we have six relations, all in 3NF: |
| | 323 | |
| | 324 | ||'''Relation'''||'''Attributes'''||'''Candidate Keys'''||'''Primary Key'''|| |
| | 325 | ||R1||user_type_id, user_type_name, user_type_description||{user_type_id}, {user_type_name}||user_type_id|| |
| | 326 | ||R2||user_id, first_name, last_name, email, password, user_type_id||{user_id}, {email}||user_id|| |
| | 327 | ||R3||resource_type_id, resource_type_name, is_physical||{resource_type_id}, {resource_type_name}||resource_type_id|| |
| | 328 | ||R4||location_id, building, room||{location_id}, {building, room}||location_id|| |
| | 329 | ||R5||resource_id, resource_name, resource_description, available_from, available_to, available_weekends, resource_type_id, location_id||{resource_id}||resource_id|| |
| | 330 | ||R6||reservation_id, start_time, end_time, status, purpose, created_at, recurrence_group_id, user_id, approved_by, resource_id||{reservation_id}||reservation_id|| |
| | 331 | |
| | 332 | '''All functional dependencies from F,,c,, are preserved:''' |
| | 333 | ||'''FD'''||'''Preserved in'''|| |
| | 334 | ||FD1: user_type_id -> user_type_name, user_type_description||R1|| |
| | 335 | ||FD2': user_type_name -> user_type_id||R1|| |
| | 336 | ||FD3: user_id -> first_name, last_name, email, password, user_type_id||R2|| |
| | 337 | ||FD4': email -> user_id||R2|| |
| | 338 | ||FD5: resource_type_id -> resource_type_name, is_physical||R3|| |
| | 339 | ||FD6': resource_type_name -> resource_type_id||R3|| |
| | 340 | ||FD7: location_id -> building, room||R4|| |
| | 341 | ||FD8: {building, room} -> location_id||R4|| |
| | 342 | ||FD9: resource_id -> resource_name, ...||R5|| |
| | 343 | ||FD10: reservation_id -> start_time, ...||R6|| |
| | 344 | |
| | 345 | == BCNF Verification == |
| | 346 | |
| | 347 | A relation is in BCNF if for every non-trivial FD X -> A, X is a superkey. We verify each relation from the 3NF result. |
| | 348 | |
| | 349 | === R1 (!UserTypes) === |
| | 350 | |
| | 351 | '''FDs:''' user_type_id -> user_type_name, user_type_description; user_type_name -> user_type_id [[BR]] |
| | 352 | '''Candidate keys:''' {user_type_id}, {user_type_name} |
| | 353 | |
| | 354 | ||'''FD'''||'''Determinant'''||'''Superkey?'''|| |
| | 355 | ||user_type_id -> user_type_name, user_type_description||user_type_id||Yes|| |
| | 356 | ||user_type_name -> user_type_id||user_type_name||Yes|| |
| | 357 | |
| | 358 | '''R1 is in BCNF.''' ✓ |
| | 359 | |
| | 360 | === R2 (Users) === |
| | 361 | |
| | 362 | '''FDs:''' user_id -> first_name, last_name, email, password, user_type_id; email -> user_id [[BR]] |
| | 363 | '''Candidate keys:''' {user_id}, {email} (verified: {email}^+^ = {email, user_id, first_name, last_name, password, user_type_id} = R2) |
| | 364 | |
| | 365 | ||'''FD'''||'''Determinant'''||'''Superkey?'''|| |
| | 366 | ||user_id -> first_name, last_name, email, password, user_type_id||user_id||Yes|| |
| | 367 | ||email -> user_id||email||Yes|| |
| | 368 | |
| | 369 | '''R2 is in BCNF.''' ✓ |
| | 370 | |
| | 371 | === R3 (!ResourceTypes) === |
| | 372 | |
| | 373 | '''FDs:''' resource_type_id -> resource_type_name, is_physical; resource_type_name -> resource_type_id [[BR]] |
| | 374 | '''Candidate keys:''' {resource_type_id}, {resource_type_name} |
| | 375 | |
| | 376 | ||'''FD'''||'''Determinant'''||'''Superkey?'''|| |
| | 377 | ||resource_type_id -> resource_type_name, is_physical||resource_type_id||Yes|| |
| | 378 | ||resource_type_name -> resource_type_id||resource_type_name||Yes|| |
| | 379 | |
| | 380 | '''R3 is in BCNF.''' ✓ |
| | 381 | |
| | 382 | === R4 (Locations) === |
| | 383 | |
| | 384 | '''FDs:''' location_id -> building, room; {building, room} -> location_id [[BR]] |
| | 385 | '''Candidate keys:''' {location_id}, {building, room} |
| | 386 | |
| | 387 | ||'''FD'''||'''Determinant'''||'''Superkey?'''|| |
| | 388 | ||location_id -> building, room||location_id||Yes|| |
| | 389 | ||{building, room} -> location_id||{building, room}||Yes|| |
| | 390 | |
| | 391 | '''R4 is in BCNF.''' ✓ |
| | 392 | |
| | 393 | === R5 (Resources) === |
| | 394 | |
| | 395 | '''FDs:''' resource_id -> resource_name, resource_description, available_from, available_to, available_weekends, resource_type_id, location_id [[BR]] |
| | 396 | '''Candidate keys:''' {resource_id} |
| | 397 | |
| | 398 | ||'''FD'''||'''Determinant'''||'''Superkey?'''|| |
| | 399 | ||resource_id -> resource_name, resource_description, ...||resource_id||Yes|| |
| | 400 | |
| | 401 | '''R5 is in BCNF.''' ✓ |
| | 402 | |
| | 403 | === R6 (Reservations) === |
| | 404 | |
| | 405 | '''FDs:''' reservation_id -> start_time, end_time, status, purpose, created_at, recurrence_group_id, user_id, approved_by, resource_id [[BR]] |
| | 406 | '''Candidate keys:''' {reservation_id} |
| | 407 | |
| | 408 | ||'''FD'''||'''Determinant'''||'''Superkey?'''|| |
| | 409 | ||reservation_id -> start_time, end_time, ...||reservation_id||Yes|| |
| | 410 | |
| | 411 | '''R6 is in BCNF.''' ✓ |
| | 412 | |
| | 413 | '''Conclusion: All six relations are in BCNF.''' No further decomposition is needed. The decomposition preserves all functional dependencies and maintains the lossless join property. |
| | 414 | |
| | 415 | == Final Result and Discussion == |
| | 416 | |
| | 417 | === Normalized Relational Model === |
| | 418 | |
| | 419 | The normalization process yields the following six relations, all in BCNF: |
| | 420 | |
| | 421 | 1. '''!UserTypes''' (user_type_id PK, user_type_name UNIQUE NOT NULL, user_type_description NOT NULL) |
| | 422 | 2. '''Users''' (user_id PK, first_name NOT NULL, last_name NOT NULL, email UNIQUE NOT NULL, password NOT NULL, user_type_id NOT NULL FK->!UserTypes) |
| | 423 | 3. '''!ResourceTypes''' (resource_type_id PK, resource_type_name UNIQUE NOT NULL, is_physical NOT NULL) |
| | 424 | 4. '''Locations''' (location_id PK, building NOT NULL, room NOT NULL, UNIQUE(building, room)) |
| | 425 | 5. '''Resources''' (resource_id PK, resource_name NOT NULL, resource_description NOT NULL, available_from NOT NULL, available_to NOT NULL, available_weekends NOT NULL, resource_type_id NOT NULL FK->!ResourceTypes, location_id FK->Locations) |
| | 426 | 6. '''Reservations''' (reservation_id PK, start_time NOT NULL, end_time NOT NULL, status NOT NULL, purpose NOT NULL, created_at NOT NULL, recurrence_group_id, user_id NOT NULL FK->Users, approved_by FK->Users, resource_id NOT NULL FK->Resources) |
| | 427 | |
| | 428 | === Discussion === |
| | 429 | |
| | 430 | The normalization process, starting from the universal de-normalized relation and proceeding through formal decomposition, produces '''exactly the same six relations''' as the Phase 2 relational design. This confirms that the original ER model (Phase 1) and its relational mapping (Phase 2) were already well-normalized. |
| | 431 | |
| | 432 | The key reasons for this outcome: |
| | 433 | |
| | 434 | * The ER model correctly identified separate entities (!UserTypes, Users, !ResourceTypes, Locations, Resources, Reservations) corresponding to distinct real-world concepts, each with its own independent set of attributes. |
| | 435 | * Each entity was mapped to its own relation with a surrogate primary key, and relationships were represented through foreign keys. |
| | 436 | * No entity combined attributes from multiple independent concepts, so no transitive dependencies existed within any individual table. |
| | 437 | |
| | 438 | Since the normalization result matches the existing design, '''no changes to the database schema are required'''. The Phase 2 relational design and DDL scripts remain valid and will continue to be used for all subsequent project phases. |