Changes between Version 1 and Version 2 of Normalization


Ignore:
Timestamp:
08/31/26 18:24:39 (7 days ago)
Author:
223091
Comment:

--

Legend:

Unmodified
Added
Removed
Modified
  • Normalization

    v1 v2  
    33This page documents Phase P5 of the Room Reservation System project. The goal of this phase is to check the database model using formal normalization methods and to improve the model if typical problems of un-normalized designs are found.
    44
    5 The normalization process starts from one theoretical de-normalized relation that contains all attributes from the model. From that relation, functional dependencies, candidate keys, and normal forms are analyzed. The relation is then decomposed step by step to the highest possible normal form while preserving functional dependencies and loss-less join properties.
    6 
    7 == De-normalized database form ==
     5The normalization process starts from one theoretical de-normalized relation that contains the important attributes from the project model. From that relation, functional dependencies, candidate keys, primary keys, normal forms and lossless-join properties are analyzed. The relation is then decomposed step by step into a normalized relational schema.
     6
     7The final result is consistent with the relational schema from Phase P2 and with the implemented project database.
     8
     9== De-normalized Database Form ==
    810
    911=== Initial de-normalized relation ===
     
    1113The process starts from a single unified de-normalized relation. This relation is not implemented in the database. It is used only as a formal starting point for the normalization process.
    1214
    13 The relation contains data about reservations, rooms, buildings, users, equipment, requested equipment, equipment assigned to rooms, and approvals. Attribute names are made globally unique in order to avoid duplicate attribute names.
     15The relation contains data about reservations, rooms, buildings, users, equipment, requested equipment, equipment assigned to rooms and approvals. Attribute names are made globally unique in order to avoid ambiguity when the same entity type appears in different roles.
    1416
    1517{{{
    1618R0(
    17 reservation_id,
    18 reservation_date,
    19 start_time,
    20 end_time,
    21 status,
    22 
    23 ```
    24 room_id,
    25 room_code,
    26 room_capacity,
    27 room_type,
    28 
    29 building_id,
    30 building_name,
    31 building_address,
    32 
    33 requester_user_id,
    34 requester_username,
    35 requester_email,
    36 requester_full_name,
    37 requester_role,
    38 
    39 requested_equipment_id,
    40 requested_equipment_name,
    41 requested_equipment_stock_quantity,
    42 requested_quantity,
    43 
    44 room_equipment_id,
    45 room_equipment_name,
    46 room_equipment_stock_quantity,
    47 room_equipment_quantity,
    48 
    49 approval_id,
    50 approver_user_id,
    51 approver_username,
    52 approver_email,
    53 approver_full_name,
    54 approver_role,
    55 
    56 decision,
    57 decision_time,
    58 note
    59 ```
    60 
    61 )
    62 }}}
    63 
    64 In this theoretical relation, the attributes ''requested_equipment_id'' and ''room_equipment_id'' both refer to equipment items, but they are named differently in the de-normalized form because the same entity type appears in two different roles. During normalization, these two groups are merged into one final ''equipment'' relation.
     19    reservation_id,
     20    reservation_date,
     21    start_time,
     22    end_time,
     23    status,
     24
     25    room_id,
     26    room_code,
     27    room_capacity,
     28    room_type,
     29
     30    building_id,
     31    building_name,
     32    building_address,
     33
     34    requester_user_id,
     35    requester_username,
     36    requester_email,
     37    requester_full_name,
     38    requester_role,
     39
     40    requested_equipment_id,
     41    requested_equipment_name,
     42    requested_equipment_stock_quantity,
     43    requested_quantity,
     44
     45    room_equipment_id,
     46    room_equipment_name,
     47    room_equipment_stock_quantity,
     48    room_equipment_quantity,
     49
     50    approval_id,
     51    approver_user_id,
     52    approver_username,
     53    approver_email,
     54    approver_full_name,
     55    approver_role,
     56
     57    decision,
     58    decision_time,
     59    note
     60)
     61}}}
     62
     63In this theoretical relation, `requested_equipment_id` and `room_equipment_id` both refer to equipment items. They are named differently in the de-normalized form because the same entity type appears in two different roles.
     64
     65During normalization, these two equipment groups are merged into one final `equipment` relation.
     66
     67Similarly, requester and approver attributes both describe users. During normalization, these groups are merged into one final `users` relation.
    6568
    6669=== Functional dependencies ===
     
    7275
    7376room_id -> room_code, room_capacity, room_type, building_id
    74 room_code -> room_id
     77room_code -> room_id, room_capacity, room_type, building_id
    7578
    7679building_id -> building_name, building_address
    77 building_name, building_address -> building_id
    7880
    7981requester_user_id -> requester_username, requester_email, requester_full_name, requester_role
     
    8688
    8789requested_equipment_id -> requested_equipment_name, requested_equipment_stock_quantity
    88 requested_equipment_name -> requested_equipment_id, requested_equipment_stock_quantity
    8990
    9091room_equipment_id -> room_equipment_name, room_equipment_stock_quantity
    91 room_equipment_name -> room_equipment_id, room_equipment_stock_quantity
    92 
     92
     93reservation_id, requested_equipment_id -> requested_quantity
     94
     95room_id, room_equipment_id -> room_equipment_quantity
     96
     97approval_id -> reservation_id, approver_user_id, decision, decision_time, note
     98reservation_id -> approval_id, approver_user_id, decision, decision_time, note
     99}}}
     100
     101The dependency:
     102
     103{{{
     104reservation_id -> approval_id, approver_user_id, decision, decision_time, note
     105}}}
     106
     107is valid for the final approval decision of a reservation. A reservation can be pending, so approval-related values may be missing until an approval decision is made. The business rule in this design is that a reservation can have at most one final approval decision.
     108
     109=== Candidate key of the de-normalized relation ===
     110
     111The de-normalized relation contains information about one reservation, one requested equipment item and one room-equipment item.
     112
     113A complete row in the de-normalized relation is therefore identified by:
     114
     115{{{
     116K = (reservation_id, requested_equipment_id, room_equipment_id)
     117}}}
     118
     119Closure of the proposed key:
     120
     121{{{
     122{reservation_id, requested_equipment_id, room_equipment_id}+
     123}}}
     124
     125Using the functional dependencies:
     126
     127{{{
     128reservation_id -> reservation_date, start_time, end_time, status, room_id, requester_user_id
     129room_id -> room_code, room_capacity, room_type, building_id
     130building_id -> building_name, building_address
     131requester_user_id -> requester_username, requester_email, requester_full_name, requester_role
     132requested_equipment_id -> requested_equipment_name, requested_equipment_stock_quantity
     133room_equipment_id -> room_equipment_name, room_equipment_stock_quantity
    93134reservation_id, requested_equipment_id -> requested_quantity
    94135room_id, room_equipment_id -> room_equipment_quantity
    95 
    96 approval_id -> reservation_id, approver_user_id, decision, decision_time, note
    97136reservation_id -> approval_id, approver_user_id, decision, decision_time, note
    98 }}}
    99 
    100 The dependency ''reservation_id -> approval_id, approver_user_id, decision, decision_time, note'' is valid for reservations that already have an approval decision. A reservation may still be pending, so the approval-related values may be missing until an approval is created. The business rule is that one reservation can have at most one approval record.
    101 
    102 === Candidate keys and primary key ===
    103 
    104 The de-normalized relation contains information about one reservation, the equipment requested in that reservation, and the equipment assigned to the selected room. Therefore, a complete row in the de-normalized relation is identified by a combination of the reservation and the two equipment-role identifiers.
    105 
    106 Candidate keys for the de-normalized relation include:
    107 
    108 {{{
    109 K1 = (reservation_id, requested_equipment_id, room_equipment_id)
    110 K2 = (reservation_id, requested_equipment_name, room_equipment_id)
    111 K3 = (reservation_id, requested_equipment_id, room_equipment_name)
    112 K4 = (reservation_id, requested_equipment_name, room_equipment_name)
    113 }}}
    114 
    115 The selected primary key for the de-normalized relation is:
     137approver_user_id -> approver_username, approver_email, approver_full_name, approver_role
     138}}}
     139
     140we get:
     141
     142{{{
     143{reservation_id, requested_equipment_id, room_equipment_id}+ =
     144{
     145    reservation_id,
     146    reservation_date,
     147    start_time,
     148    end_time,
     149    status,
     150    room_id,
     151    room_code,
     152    room_capacity,
     153    room_type,
     154    building_id,
     155    building_name,
     156    building_address,
     157    requester_user_id,
     158    requester_username,
     159    requester_email,
     160    requester_full_name,
     161    requester_role,
     162    requested_equipment_id,
     163    requested_equipment_name,
     164    requested_equipment_stock_quantity,
     165    requested_quantity,
     166    room_equipment_id,
     167    room_equipment_name,
     168    room_equipment_stock_quantity,
     169    room_equipment_quantity,
     170    approval_id,
     171    approver_user_id,
     172    approver_username,
     173    approver_email,
     174    approver_full_name,
     175    approver_role,
     176    decision,
     177    decision_time,
     178    note
     179}
     180}}}
     181
     182Therefore, the proposed key determines all attributes of R0.
     183
     184The key is minimal because:
     185
     186 * `reservation_id` alone does not determine all requested equipment rows.
     187 * `requested_equipment_id` alone does not determine the reservation or room-equipment data.
     188 * `room_equipment_id` alone does not determine the reservation or requested-equipment data.
     189 * `(reservation_id, requested_equipment_id)` determines requested quantity, but does not determine room-equipment quantity.
     190 * `(reservation_id, room_equipment_id)` does not determine the requested equipment item.
     191 * `(requested_equipment_id, room_equipment_id)` does not determine the reservation.
     192
     193Therefore, the candidate key used for the de-normalized relation is:
    116194
    117195{{{
     
    119197}}}
    120198
    121 This key is selected because it uses stable numeric identifiers instead of descriptive names.
     199The selected primary key for the de-normalized relation is the same candidate key:
     200
     201{{{
     202(reservation_id, requested_equipment_id, room_equipment_id)
     203}}}
     204
     205This is only a theoretical primary key used for the normalization analysis. The de-normalized relation is not implemented in the database.
    122206
    123207=== Normal form of the de-normalized relation ===
    124208
    125 The de-normalized relation is in 1NF because all attributes contain atomic values. There are no repeating groups inside one attribute, and equipment occurrences are represented as separate rows.
    126 
    127 However, the relation is not in 2NF because there are partial dependencies on parts of the composite key. Examples:
     209The de-normalized relation is in 1NF because all attributes contain atomic values.
     210
     211However, the relation is not in 2NF because there are partial dependencies on parts of the composite key.
     212
     213Examples:
    128214
    129215{{{
     
    137223These dependencies show that some non-key attributes depend only on part of the composite key, not on the whole key. Because of this, the relation is not in 2NF. Consequently, it is also not in 3NF or BCNF.
    138224
    139 == 1NF decomposition ==
    140 
    141 The first normal form requires that all attribute values are atomic and that repeating groups are removed.
     225== 1NF Decomposition ==
     226
     227First normal form requires atomic attribute values and no repeating groups inside a single attribute.
    142228
    143229The relation R0 is already represented with atomic values. Multiple requested equipment items and multiple equipment items assigned to a room are represented as separate rows instead of being stored as lists in one attribute.
     
    149235{{{
    150236R0(
    151 reservation_id,
    152 reservation_date,
    153 start_time,
    154 end_time,
    155 status,
    156 room_id,
    157 room_code,
    158 room_capacity,
    159 room_type,
    160 building_id,
    161 building_name,
    162 building_address,
    163 requester_user_id,
    164 requester_username,
    165 requester_email,
    166 requester_full_name,
    167 requester_role,
    168 requested_equipment_id,
    169 requested_equipment_name,
    170 requested_equipment_stock_quantity,
    171 requested_quantity,
    172 room_equipment_id,
    173 room_equipment_name,
    174 room_equipment_stock_quantity,
    175 room_equipment_quantity,
    176 approval_id,
    177 approver_user_id,
    178 approver_username,
    179 approver_email,
    180 approver_full_name,
    181 approver_role,
    182 decision,
    183 decision_time,
    184 note
     237    reservation_id,
     238    reservation_date,
     239    start_time,
     240    end_time,
     241    status,
     242    room_id,
     243    room_code,
     244    room_capacity,
     245    room_type,
     246    building_id,
     247    building_name,
     248    building_address,
     249    requester_user_id,
     250    requester_username,
     251    requester_email,
     252    requester_full_name,
     253    requester_role,
     254    requested_equipment_id,
     255    requested_equipment_name,
     256    requested_equipment_stock_quantity,
     257    requested_quantity,
     258    room_equipment_id,
     259    room_equipment_name,
     260    room_equipment_stock_quantity,
     261    room_equipment_quantity,
     262    approval_id,
     263    approver_user_id,
     264    approver_username,
     265    approver_email,
     266    approver_full_name,
     267    approver_role,
     268    decision,
     269    decision_time,
     270    note
    185271)
    186272}}}
     
    188274The relation is in 1NF, but it is not in 2NF because of partial dependencies.
    189275
    190 == 2NF decomposition ==
     276== 2NF Decomposition ==
    191277
    192278A relation is in 2NF if it is in 1NF and every non-prime attribute is fully functionally dependent on the whole candidate key.
    193279
    194 The relation R0 violates 2NF because many attributes depend only on part of the composite key. For example, reservation data depends only on ''reservation_id'', requested equipment data depends only on ''requested_equipment_id'', and room equipment data depends only on ''room_equipment_id''.
     280The relation R0 violates 2NF because many attributes depend only on part of the composite key.
     281
     282Examples:
     283
     284{{{
     285reservation_id -> reservation_date, start_time, end_time, status, room_id, requester_user_id
     286
     287requested_equipment_id -> requested_equipment_name, requested_equipment_stock_quantity
     288
     289room_equipment_id -> room_equipment_name, room_equipment_stock_quantity
     290}}}
    195291
    196292The first decomposition removes these partial dependencies.
    197293
    198 === Decomposition step ===
    199 
    200 The following relations are obtained:
     294=== Reservation data ===
    201295
    202296{{{
    203297R1_reservations(
    204 reservation_id,
    205 reservation_date,
    206 start_time,
    207 end_time,
    208 status,
    209 room_id,
    210 requester_user_id
    211 )
    212 }}}
    213 
    214 Key:
     298    reservation_id,
     299    reservation_date,
     300    start_time,
     301    end_time,
     302    status,
     303    room_id,
     304    requester_user_id
     305)
     306}}}
     307
     308Functional dependency:
     309
     310{{{
     311reservation_id -> reservation_date, start_time, end_time, status, room_id, requester_user_id
     312}}}
     313
     314Candidate key and selected primary key:
    215315
    216316{{{
     
    218318}}}
    219319
    220 Functional dependencies preserved:
    221 
    222 {{{
    223 reservation_id -> reservation_date, start_time, end_time, status, room_id, requester_user_id
    224 }}}
     320=== Room data ===
    225321
    226322{{{
    227323R1_rooms(
    228 room_id,
    229 room_code,
    230 room_capacity,
    231 room_type,
    232 building_id,
    233 building_name,
    234 building_address
    235 )
    236 }}}
    237 
    238 Keys:
     324    room_id,
     325    room_code,
     326    room_capacity,
     327    room_type,
     328    building_id,
     329    building_name,
     330    building_address
     331)
     332}}}
     333
     334Functional dependencies:
     335
     336{{{
     337room_id -> room_code, room_capacity, room_type, building_id, building_name, building_address
     338room_code -> room_id, room_capacity, room_type, building_id, building_name, building_address
     339}}}
     340
     341Candidate keys:
    239342
    240343{{{
     
    243346}}}
    244347
    245 Functional dependencies preserved:
    246 
    247 {{{
    248 room_id -> room_code, room_capacity, room_type, building_id, building_name, building_address
    249 room_code -> room_id
    250 }}}
     348Selected primary key:
     349
     350{{{
     351room_id
     352}}}
     353
     354=== User data in requester role ===
    251355
    252356{{{
    253357R1_requester_users(
    254 requester_user_id,
    255 requester_username,
    256 requester_email,
    257 requester_full_name,
    258 requester_role
    259 )
    260 }}}
    261 
    262 Keys:
     358    requester_user_id,
     359    requester_username,
     360    requester_email,
     361    requester_full_name,
     362    requester_role
     363)
     364}}}
     365
     366Functional dependencies:
     367
     368{{{
     369requester_user_id -> requester_username, requester_email, requester_full_name, requester_role
     370requester_username -> requester_user_id, requester_email, requester_full_name, requester_role
     371requester_email -> requester_user_id, requester_username, requester_full_name, requester_role
     372}}}
     373
     374Candidate keys:
    263375
    264376{{{
     
    268380}}}
    269381
    270 Functional dependencies preserved:
    271 
    272 {{{
    273 requester_user_id -> requester_username, requester_email, requester_full_name, requester_role
    274 requester_username -> requester_user_id, requester_email, requester_full_name, requester_role
    275 requester_email -> requester_user_id, requester_username, requester_full_name, requester_role
    276 }}}
     382Selected primary key:
     383
     384{{{
     385requester_user_id
     386}}}
     387
     388=== User data in approver role ===
    277389
    278390{{{
    279391R1_approver_users(
    280 approver_user_id,
    281 approver_username,
    282 approver_email,
    283 approver_full_name,
    284 approver_role
    285 )
    286 }}}
    287 
    288 Keys:
     392    approver_user_id,
     393    approver_username,
     394    approver_email,
     395    approver_full_name,
     396    approver_role
     397)
     398}}}
     399
     400Functional dependencies:
     401
     402{{{
     403approver_user_id -> approver_username, approver_email, approver_full_name, approver_role
     404approver_username -> approver_user_id, approver_email, approver_full_name, approver_role
     405approver_email -> approver_user_id, approver_username, approver_full_name, approver_role
     406}}}
     407
     408Candidate keys:
    289409
    290410{{{
     
    294414}}}
    295415
    296 Functional dependencies preserved:
    297 
    298 {{{
    299 approver_user_id -> approver_username, approver_email, approver_full_name, approver_role
    300 approver_username -> approver_user_id, approver_email, approver_full_name, approver_role
    301 approver_email -> approver_user_id, approver_username, approver_full_name, approver_role
    302 }}}
     416Selected primary key:
     417
     418{{{
     419approver_user_id
     420}}}
     421
     422=== Requested equipment data ===
    303423
    304424{{{
    305425R1_requested_equipment(
    306 requested_equipment_id,
    307 requested_equipment_name,
    308 requested_equipment_stock_quantity
    309 )
    310 }}}
    311 
    312 Keys:
     426    requested_equipment_id,
     427    requested_equipment_name,
     428    requested_equipment_stock_quantity
     429)
     430}}}
     431
     432Functional dependency:
     433
     434{{{
     435requested_equipment_id -> requested_equipment_name, requested_equipment_stock_quantity
     436}}}
     437
     438Candidate key and selected primary key:
    313439
    314440{{{
    315441requested_equipment_id
    316 requested_equipment_name
    317 }}}
    318 
    319 Functional dependencies preserved:
    320 
    321 {{{
    322 requested_equipment_id -> requested_equipment_name, requested_equipment_stock_quantity
    323 requested_equipment_name -> requested_equipment_id, requested_equipment_stock_quantity
    324 }}}
     442}}}
     443
     444=== Room equipment item data ===
    325445
    326446{{{
    327447R1_room_equipment_item(
    328 room_equipment_id,
    329 room_equipment_name,
    330 room_equipment_stock_quantity
    331 )
    332 }}}
    333 
    334 Keys:
     448    room_equipment_id,
     449    room_equipment_name,
     450    room_equipment_stock_quantity
     451)
     452}}}
     453
     454Functional dependency:
     455
     456{{{
     457room_equipment_id -> room_equipment_name, room_equipment_stock_quantity
     458}}}
     459
     460Candidate key and selected primary key:
    335461
    336462{{{
    337463room_equipment_id
    338 room_equipment_name
    339 }}}
    340 
    341 Functional dependencies preserved:
    342 
    343 {{{
    344 room_equipment_id -> room_equipment_name, room_equipment_stock_quantity
    345 room_equipment_name -> room_equipment_id, room_equipment_stock_quantity
    346 }}}
     464}}}
     465
     466=== Reservation-equipment quantity ===
    347467
    348468{{{
    349469R1_reservation_equipment(
    350 reservation_id,
    351 requested_equipment_id,
    352 requested_quantity
    353 )
    354 }}}
    355 
    356 Key:
     470    reservation_id,
     471    requested_equipment_id,
     472    requested_quantity
     473)
     474}}}
     475
     476Functional dependency:
     477
     478{{{
     479reservation_id, requested_equipment_id -> requested_quantity
     480}}}
     481
     482Candidate key and selected primary key:
    357483
    358484{{{
     
    360486}}}
    361487
    362 Functional dependency preserved:
    363 
    364 {{{
    365 reservation_id, requested_equipment_id -> requested_quantity
    366 }}}
     488=== Room-equipment quantity ===
    367489
    368490{{{
    369491R1_room_equipment(
    370 room_id,
    371 room_equipment_id,
    372 room_equipment_quantity
    373 )
    374 }}}
    375 
    376 Key:
     492    room_id,
     493    room_equipment_id,
     494    room_equipment_quantity
     495)
     496}}}
     497
     498Functional dependency:
     499
     500{{{
     501room_id, room_equipment_id -> room_equipment_quantity
     502}}}
     503
     504Candidate key and selected primary key:
    377505
    378506{{{
     
    380508}}}
    381509
    382 Functional dependency preserved:
    383 
    384 {{{
    385 room_id, room_equipment_id -> room_equipment_quantity
    386 }}}
     510=== Approval data ===
    387511
    388512{{{
    389513R1_approvals(
    390 approval_id,
    391 reservation_id,
    392 approver_user_id,
    393 decision,
    394 decision_time,
    395 note
    396 )
    397 }}}
    398 
    399 Keys:
     514    approval_id,
     515    reservation_id,
     516    approver_user_id,
     517    decision,
     518    decision_time,
     519    note
     520)
     521}}}
     522
     523Functional dependencies:
     524
     525{{{
     526approval_id -> reservation_id, approver_user_id, decision, decision_time, note
     527reservation_id -> approval_id, approver_user_id, decision, decision_time, note
     528}}}
     529
     530Candidate keys:
    400531
    401532{{{
     
    404535}}}
    405536
    406 Functional dependencies preserved:
    407 
    408 {{{
    409 approval_id -> reservation_id, approver_user_id, decision, decision_time, note
    410 reservation_id -> approval_id, approver_user_id, decision, decision_time, note
    411 }}}
    412 
    413 === Loss-less join and dependency preservation ===
    414 
    415 The decomposition to 2NF is loss-less because every decomposition step uses a determinant that is a key in one of the resulting relations. For example, reservation details are separated using ''reservation_id'', room details are separated using ''room_id'', and equipment details are separated using equipment identifiers.
    416 
    417 The functional dependencies are preserved because every dependency from the original relation is represented in one of the decomposed relations.
    418 
    419 After this step, the relations are in 2NF. However, some relations are still not in 3NF because transitive dependencies still exist.
    420 
    421 == 3NF decomposition ==
     537Selected primary key:
     538
     539{{{
     540approval_id
     541}}}
     542
     543=== 2NF conclusion ===
     544
     545After this decomposition, the partial dependencies from the original composite key are removed.
     546
     547The resulting relations are in 2NF. However, some transitive dependencies still exist, especially between rooms and buildings. Therefore, the schema must be checked for 3NF.
     548
     549== 3NF Decomposition ==
    422550
    423551A relation is in 3NF if it is in 2NF and no non-prime attribute depends transitively on a candidate key.
    424552
    425 The relation ''R1_rooms'' still contains a transitive dependency:
     553The relation `R1_rooms` still contains a transitive dependency:
    426554
    427555{{{
     
    436564}}}
    437565
    438 This means that building data depends on ''room_id'' only through ''building_id''. To remove this transitive dependency, building data is decomposed into a separate relation.
     566This means that building data depends on `room_id` only through `building_id`. To remove this transitive dependency, building data is decomposed into a separate relation.
    439567
    440568=== Decomposition of room and building data ===
     
    444572{{{
    445573R1_rooms(
    446 room_id,
    447 room_code,
    448 room_capacity,
    449 room_type,
    450 building_id,
    451 building_name,
    452 building_address
     574    room_id,
     575    room_code,
     576    room_capacity,
     577    room_type,
     578    building_id,
     579    building_name,
     580    building_address
    453581)
    454582}}}
     
    458586{{{
    459587buildings(
    460 building_id,
    461 name,
    462 address
    463 )
    464 }}}
    465 
    466 Keys:
     588    building_id,
     589    name,
     590    address
     591)
     592}}}
     593
     594Functional dependency:
     595
     596{{{
     597building_id -> name, address
     598}}}
     599
     600Candidate key and selected primary key:
    467601
    468602{{{
    469603building_id
    470 (name, address)
     604}}}
     605
     606and:
     607
     608{{{
     609rooms(
     610    room_id,
     611    building_id,
     612    room_code,
     613    capacity,
     614    type
     615)
    471616}}}
    472617
     
    474619
    475620{{{
    476 building_id -> name, address
    477 name, address -> building_id
    478 }}}
    479 
    480 {{{
    481 rooms(
    482 room_id,
    483 building_id,
    484 room_code,
    485 capacity,
    486 type
    487 )
    488 }}}
    489 
    490 Keys:
     621room_id -> building_id, room_code, capacity, type
     622room_code -> room_id, building_id, capacity, type
     623}}}
     624
     625Candidate keys:
    491626
    492627{{{
     
    495630}}}
    496631
     632Selected primary key:
     633
     634{{{
     635room_id
     636}}}
     637
     638This removes the transitive dependency between rooms and building attributes.
     639
     640=== Merging user roles ===
     641
     642The 2NF decomposition produced separate requester and approver user relations because the same entity type appeared in different roles in the de-normalized relation.
     643
     644However, both groups represent the same real entity set: users of the system. Therefore, they are merged into one relation:
     645
     646{{{
     647users(
     648    user_id,
     649    username,
     650    email,
     651    full_name,
     652    role
     653)
     654}}}
     655
    497656Functional dependencies:
    498657
    499658{{{
    500 room_id -> building_id, room_code, capacity, type
    501 room_code -> room_id
    502 }}}
    503 
    504 This removes the transitive dependency between rooms and building attributes.
    505 
    506 === Merging user roles ===
    507 
    508 The 2NF decomposition produced separate requester and approver user relations because the same entity type appeared in different roles in the de-normalized relation.
    509 
    510 However, both groups represent the same real entity set: users of the system. Therefore, they are merged into one relation:
    511 
    512 {{{
    513 users(
    514 user_id,
    515 username,
    516 email,
    517 full_name,
    518 role
    519 )
    520 }}}
    521 
    522 Keys:
     659user_id -> username, email, full_name, role
     660username -> user_id, email, full_name, role
     661email -> user_id, username, full_name, role
     662}}}
     663
     664Candidate keys:
    523665
    524666{{{
     
    528670}}}
    529671
     672Selected primary key:
     673
     674{{{
     675user_id
     676}}}
     677
     678In the final schema, `reservations.user_id` references the requester, while `approvals.approver_id` references the user who made the approval decision.
     679
     680=== Merging equipment roles ===
     681
     682The 2NF decomposition produced separate requested-equipment and room-equipment item relations because equipment appeared in two different roles in the de-normalized relation.
     683
     684However, both groups represent the same entity set: equipment types. Therefore, they are merged into one relation:
     685
     686{{{
     687equipment(
     688    equipment_id,
     689    name,
     690    stock_quantity
     691)
     692}}}
     693
     694Functional dependency:
     695
     696{{{
     697equipment_id -> name, stock_quantity
     698}}}
     699
     700Candidate key and selected primary key:
     701
     702{{{
     703equipment_id
     704}}}
     705
     706The relation `room_equipment` represents equipment assigned to rooms, while `reservation_equipment` represents equipment requested as part of reservations.
     707
     708=== Final relations after 3NF decomposition ===
     709
     710After removing partial and transitive dependencies, the resulting relations are:
     711
     712{{{
     713buildings(
     714    building_id,
     715    name,
     716    address
     717)
     718}}}
     719
     720{{{
     721rooms(
     722    room_id,
     723    building_id,
     724    room_code,
     725    capacity,
     726    type
     727)
     728}}}
     729
     730{{{
     731equipment(
     732    equipment_id,
     733    name,
     734    stock_quantity
     735)
     736}}}
     737
     738{{{
     739room_equipment(
     740    room_id,
     741    equipment_id,
     742    quantity
     743)
     744}}}
     745
     746{{{
     747users(
     748    user_id,
     749    username,
     750    email,
     751    full_name,
     752    role
     753)
     754}}}
     755
     756{{{
     757reservations(
     758    reservation_id,
     759    room_id,
     760    user_id,
     761    reservation_date,
     762    start_time,
     763    end_time,
     764    status
     765)
     766}}}
     767
     768{{{
     769reservation_equipment(
     770    reservation_id,
     771    equipment_id,
     772    requested_quantity
     773)
     774}}}
     775
     776{{{
     777approvals(
     778    approval_id,
     779    reservation_id,
     780    approver_id,
     781    decision,
     782    decision_time,
     783    note
     784)
     785}}}
     786
     787{{{
     788user_credentials(
     789    user_id,
     790    password_hash,
     791    created_at,
     792    updated_at
     793)
     794}}}
     795
     796The `user_credentials` relation is an application authentication extension used in the final implementation. It is also normalized because the password hash and credential timestamps depend on `user_id`.
     797
     798== Formal Candidate Key and Primary Key Proof ==
     799
     800This section formally documents the candidate keys and selected primary keys for the final normalized relations.
     801
     802The proof is based on attribute closure. For each relation, the selected key determines all attributes in the relation and is minimal.
     803
     804=== Buildings ===
     805
     806Relation:
     807
     808{{{
     809Buildings(building_id, name, address)
     810}}}
     811
     812Functional dependency:
     813
     814{{{
     815building_id -> name, address
     816}}}
     817
     818Closure:
     819
     820{{{
     821{building_id}+ = {building_id, name, address}
     822}}}
     823
     824Therefore, `building_id` determines all attributes in the relation.
     825
     826Candidate key:
     827
     828{{{
     829building_id
     830}}}
     831
     832Selected primary key:
     833
     834{{{
     835building_id
     836}}}
     837
     838No other candidate key is used because the documented functional dependencies do not show that `name` or `address` can uniquely determine a building.
     839
     840=== Rooms ===
     841
     842Relation:
     843
     844{{{
     845Rooms(room_id, building_id, room_code, capacity, type)
     846}}}
     847
     848Functional dependencies:
     849
     850{{{
     851room_id -> building_id, room_code, capacity, type
     852room_code -> room_id, building_id, capacity, type
     853}}}
     854
     855Closure of `room_id`:
     856
     857{{{
     858{room_id}+ = {room_id, building_id, room_code, capacity, type}
     859}}}
     860
     861Closure of `room_code`:
     862
     863{{{
     864{room_code}+ = {room_code, room_id, building_id, capacity, type}
     865}}}
     866
     867Candidate keys:
     868
     869{{{
     870room_id
     871room_code
     872}}}
     873
     874Selected primary key:
     875
     876{{{
     877room_id
     878}}}
     879
     880`room_code` is an alternate candidate key because room codes are unique in the system. The surrogate key `room_id` is selected as the primary key because it is stable and convenient for foreign-key references from reservations and room-equipment assignments.
     881
     882No other candidate keys exist because `building_id`, `capacity` and `type` do not uniquely identify a room.
     883
     884=== Equipment ===
     885
     886Relation:
     887
     888{{{
     889Equipment(equipment_id, name, stock_quantity)
     890}}}
     891
     892Functional dependency:
     893
     894{{{
     895equipment_id -> name, stock_quantity
     896}}}
     897
     898Closure:
     899
     900{{{
     901{equipment_id}+ = {equipment_id, name, stock_quantity}
     902}}}
     903
     904Candidate key:
     905
     906{{{
     907equipment_id
     908}}}
     909
     910Selected primary key:
     911
     912{{{
     913equipment_id
     914}}}
     915
     916No other candidate key is used because the documented dependencies do not require equipment name to uniquely determine the full equipment record.
     917
     918=== RoomEquipment ===
     919
     920Relation:
     921
     922{{{
     923RoomEquipment(room_id, equipment_id, quantity)
     924}}}
     925
     926Functional dependency:
     927
     928{{{
     929room_id, equipment_id -> quantity
     930}}}
     931
     932Closure:
     933
     934{{{
     935{room_id, equipment_id}+ = {room_id, equipment_id, quantity}
     936}}}
     937
     938Candidate key:
     939
     940{{{
     941(room_id, equipment_id)
     942}}}
     943
     944Selected primary key:
     945
     946{{{
     947(room_id, equipment_id)
     948}}}
     949
     950The key is minimal because:
     951
     952 * `room_id` alone does not determine the quantity, since a room can contain multiple equipment types.
     953 * `equipment_id` alone does not determine the quantity, since the same equipment type can exist in multiple rooms.
     954
     955Therefore, both attributes are required.
     956
     957=== Users ===
     958
     959Relation:
     960
     961{{{
     962Users(user_id, username, email, full_name, role)
     963}}}
     964
    530965Functional dependencies:
    531966
     
    536971}}}
    537972
    538 In the final schema, ''reservations.user_id'' references the requester, while ''approvals.approver_id'' references the user who made the approval decision.
    539 
    540 === Merging equipment roles ===
    541 
    542 The 2NF decomposition produced separate requested-equipment and room-equipment item relations because equipment appeared in two different roles in the de-normalized relation.
    543 
    544 However, both groups represent the same entity set: equipment types. Therefore, they are merged into one relation:
    545 
    546 {{{
    547 equipment(
    548 equipment_id,
    549 name,
    550 stock_quantity
    551 )
    552 }}}
    553 
    554 Keys:
    555 
    556 {{{
    557 equipment_id
    558 name
     973Closure of `user_id`:
     974
     975{{{
     976{user_id}+ = {user_id, username, email, full_name, role}
     977}}}
     978
     979Closure of `username`:
     980
     981{{{
     982{username}+ = {username, user_id, email, full_name, role}
     983}}}
     984
     985Closure of `email`:
     986
     987{{{
     988{email}+ = {email, user_id, username, full_name, role}
     989}}}
     990
     991Candidate keys:
     992
     993{{{
     994user_id
     995username
     996email
     997}}}
     998
     999Selected primary key:
     1000
     1001{{{
     1002user_id
     1003}}}
     1004
     1005`username` and `email` are alternate candidate keys because they are unique. The primary key is `user_id` because it is stable and used as a foreign key in reservations, approvals and user credentials.
     1006
     1007No other candidate key exists because `full_name` and `role` do not uniquely identify a user.
     1008
     1009=== Reservations ===
     1010
     1011Relation:
     1012
     1013{{{
     1014Reservations(reservation_id, room_id, user_id, reservation_date, start_time, end_time, status)
     1015}}}
     1016
     1017Functional dependency:
     1018
     1019{{{
     1020reservation_id -> room_id, user_id, reservation_date, start_time, end_time, status
     1021}}}
     1022
     1023Closure:
     1024
     1025{{{
     1026{reservation_id}+ = {reservation_id, room_id, user_id, reservation_date, start_time, end_time, status}
     1027}}}
     1028
     1029Candidate key:
     1030
     1031{{{
     1032reservation_id
     1033}}}
     1034
     1035Selected primary key:
     1036
     1037{{{
     1038reservation_id
     1039}}}
     1040
     1041No other candidate key is used. Although combinations such as room, date, start time and end time are important for overlap detection, they are not selected as candidate keys because the system also supports pending, rejected, cancelled and equipment-related reservation scenarios. Business rules about overlap are enforced separately through constraints and triggers, not by replacing the reservation primary key.
     1042
     1043=== ReservationEquipment ===
     1044
     1045Relation:
     1046
     1047{{{
     1048ReservationEquipment(reservation_id, equipment_id, requested_quantity)
     1049}}}
     1050
     1051Functional dependency:
     1052
     1053{{{
     1054reservation_id, equipment_id -> requested_quantity
     1055}}}
     1056
     1057Closure:
     1058
     1059{{{
     1060{reservation_id, equipment_id}+ = {reservation_id, equipment_id, requested_quantity}
     1061}}}
     1062
     1063Candidate key:
     1064
     1065{{{
     1066(reservation_id, equipment_id)
     1067}}}
     1068
     1069Selected primary key:
     1070
     1071{{{
     1072(reservation_id, equipment_id)
     1073}}}
     1074
     1075The key is minimal because:
     1076
     1077 * `reservation_id` alone does not determine the requested quantity for one equipment item, since a reservation can request multiple equipment types.
     1078 * `equipment_id` alone does not determine the requested quantity, since the same equipment type can be requested in many reservations.
     1079
     1080Therefore, both attributes are required.
     1081
     1082=== Approvals ===
     1083
     1084Relation:
     1085
     1086{{{
     1087Approvals(approval_id, reservation_id, approver_id, decision, decision_time, note)
    5591088}}}
    5601089
     
    5621091
    5631092{{{
     1093approval_id -> reservation_id, approver_id, decision, decision_time, note
     1094reservation_id -> approval_id, approver_id, decision, decision_time, note
     1095}}}
     1096
     1097Closure of `approval_id`:
     1098
     1099{{{
     1100{approval_id}+ = {approval_id, reservation_id, approver_id, decision, decision_time, note}
     1101}}}
     1102
     1103Closure of `reservation_id`:
     1104
     1105{{{
     1106{reservation_id}+ = {reservation_id, approval_id, approver_id, decision, decision_time, note}
     1107}}}
     1108
     1109Candidate keys:
     1110
     1111{{{
     1112approval_id
     1113reservation_id
     1114}}}
     1115
     1116Selected primary key:
     1117
     1118{{{
     1119approval_id
     1120}}}
     1121
     1122`reservation_id` is an alternate candidate key because the design stores at most one final approval decision for each reservation.
     1123
     1124=== UserCredentials ===
     1125
     1126Relation:
     1127
     1128{{{
     1129UserCredentials(user_id, password_hash, created_at, updated_at)
     1130}}}
     1131
     1132Functional dependency:
     1133
     1134{{{
     1135user_id -> password_hash, created_at, updated_at
     1136}}}
     1137
     1138Closure:
     1139
     1140{{{
     1141{user_id}+ = {user_id, password_hash, created_at, updated_at}
     1142}}}
     1143
     1144Candidate key:
     1145
     1146{{{
     1147user_id
     1148}}}
     1149
     1150Selected primary key:
     1151
     1152{{{
     1153user_id
     1154}}}
     1155
     1156This relation uses `user_id` as both a primary key and a foreign key to `Users`. This creates a one-to-one relationship between a user and the user's authentication credentials.
     1157
     1158== Formal Lossless-Join Proof ==
     1159
     1160This section proves that the decompositions used in the normalized schema are lossless.
     1161
     1162For a binary decomposition of a relation R into R1 and R2, the decomposition is lossless if:
     1163
     1164{{{
     1165R1 ∩ R2 -> R1
     1166}}}
     1167
     1168or:
     1169
     1170{{{
     1171R1 ∩ R2 -> R2
     1172}}}
     1173
     1174In other words, the common attributes between the two decomposed relations must functionally determine all attributes of at least one of the decomposed relations.
     1175
     1176=== Buildings and Rooms ===
     1177
     1178Decomposition:
     1179
     1180{{{
     1181Buildings(building_id, name, address)
     1182Rooms(room_id, building_id, room_code, capacity, type)
     1183}}}
     1184
     1185Common attribute:
     1186
     1187{{{
     1188Buildings ∩ Rooms = {building_id}
     1189}}}
     1190
     1191Functional dependency:
     1192
     1193{{{
     1194building_id -> name, address
     1195}}}
     1196
     1197Since `building_id` determines all attributes of `Buildings`, the common attribute determines the `Buildings` relation.
     1198
     1199Therefore:
     1200
     1201{{{
     1202{building_id} -> Buildings
     1203}}}
     1204
     1205The decomposition between `Buildings` and `Rooms` is lossless.
     1206
     1207=== Rooms and Reservations ===
     1208
     1209Decomposition:
     1210
     1211{{{
     1212Rooms(room_id, building_id, room_code, capacity, type)
     1213Reservations(reservation_id, room_id, user_id, reservation_date, start_time, end_time, status)
     1214}}}
     1215
     1216Common attribute:
     1217
     1218{{{
     1219Rooms ∩ Reservations = {room_id}
     1220}}}
     1221
     1222Functional dependency:
     1223
     1224{{{
     1225room_id -> building_id, room_code, capacity, type
     1226}}}
     1227
     1228Since `room_id` determines all attributes of `Rooms`, the common attribute determines the `Rooms` relation.
     1229
     1230Therefore:
     1231
     1232{{{
     1233{room_id} -> Rooms
     1234}}}
     1235
     1236The decomposition between `Rooms` and `Reservations` is lossless for room-based reservations.
     1237
     1238The design also supports equipment-only reservations where `room_id` may be null. Equipment-only reservation resources are represented separately through `ReservationEquipment`.
     1239
     1240=== Users and Reservations ===
     1241
     1242Decomposition:
     1243
     1244{{{
     1245Users(user_id, username, email, full_name, role)
     1246Reservations(reservation_id, room_id, user_id, reservation_date, start_time, end_time, status)
     1247}}}
     1248
     1249Common attribute:
     1250
     1251{{{
     1252Users ∩ Reservations = {user_id}
     1253}}}
     1254
     1255Functional dependency:
     1256
     1257{{{
     1258user_id -> username, email, full_name, role
     1259}}}
     1260
     1261Since `user_id` determines all attributes of `Users`, the common attribute determines the `Users` relation.
     1262
     1263Therefore:
     1264
     1265{{{
     1266{user_id} -> Users
     1267}}}
     1268
     1269The decomposition between `Users` and `Reservations` is lossless.
     1270
     1271=== Rooms and RoomEquipment ===
     1272
     1273Decomposition:
     1274
     1275{{{
     1276Rooms(room_id, building_id, room_code, capacity, type)
     1277RoomEquipment(room_id, equipment_id, quantity)
     1278}}}
     1279
     1280Common attribute:
     1281
     1282{{{
     1283Rooms ∩ RoomEquipment = {room_id}
     1284}}}
     1285
     1286Functional dependency:
     1287
     1288{{{
     1289room_id -> building_id, room_code, capacity, type
     1290}}}
     1291
     1292Since `room_id` determines all attributes of `Rooms`, the common attribute determines the `Rooms` relation.
     1293
     1294Therefore:
     1295
     1296{{{
     1297{room_id} -> Rooms
     1298}}}
     1299
     1300The decomposition between `Rooms` and `RoomEquipment` is lossless.
     1301
     1302=== Equipment and RoomEquipment ===
     1303
     1304Decomposition:
     1305
     1306{{{
     1307Equipment(equipment_id, name, stock_quantity)
     1308RoomEquipment(room_id, equipment_id, quantity)
     1309}}}
     1310
     1311Common attribute:
     1312
     1313{{{
     1314Equipment ∩ RoomEquipment = {equipment_id}
     1315}}}
     1316
     1317Functional dependency:
     1318
     1319{{{
    5641320equipment_id -> name, stock_quantity
    565 name -> equipment_id, stock_quantity
    566 }}}
    567 
    568 The relation ''room_equipment'' represents equipment assigned to rooms, while ''reservation_equipment'' represents equipment requested as part of reservations.
    569 
    570 === Final relations after 3NF decomposition ===
    571 
    572 After removing partial and transitive dependencies, the resulting relations are:
    573 
    574 {{{
    575 buildings(
    576 building_id,
    577 name,
    578 address
    579 )
    580 }}}
    581 
    582 {{{
    583 rooms(
    584 room_id,
    585 building_id,
    586 room_code,
    587 capacity,
    588 type
    589 )
    590 }}}
    591 
    592 {{{
    593 equipment(
    594 equipment_id,
    595 name,
    596 stock_quantity
    597 )
    598 }}}
    599 
    600 {{{
    601 room_equipment(
    602 room_id,
    603 equipment_id,
    604 quantity
    605 )
    606 }}}
    607 
    608 {{{
    609 users(
    610 user_id,
    611 username,
    612 email,
    613 full_name,
    614 role
    615 )
    616 }}}
    617 
    618 {{{
    619 reservations(
    620 reservation_id,
    621 room_id,
    622 user_id,
    623 reservation_date,
    624 start_time,
    625 end_time,
    626 status
    627 )
    628 }}}
    629 
    630 {{{
    631 reservation_equipment(
    632 reservation_id,
    633 equipment_id,
    634 requested_quantity
    635 )
    636 }}}
    637 
    638 {{{
    639 approvals(
    640 approval_id,
    641 reservation_id,
    642 approver_id,
    643 decision,
    644 decision_time,
    645 note
    646 )
    647 }}}
    648 
    649 === Loss-less join and dependency preservation ===
    650 
    651 The 3NF decomposition is loss-less because each decomposition separates attributes based on a functional dependency where the determinant becomes a key in one of the resulting relations.
    652 
    653 Examples:
     1321}}}
     1322
     1323Since `equipment_id` determines all attributes of `Equipment`, the common attribute determines the `Equipment` relation.
     1324
     1325Therefore:
     1326
     1327{{{
     1328{equipment_id} -> Equipment
     1329}}}
     1330
     1331The decomposition between `Equipment` and `RoomEquipment` is lossless.
     1332
     1333=== Reservations and ReservationEquipment ===
     1334
     1335Decomposition:
     1336
     1337{{{
     1338Reservations(reservation_id, room_id, user_id, reservation_date, start_time, end_time, status)
     1339ReservationEquipment(reservation_id, equipment_id, requested_quantity)
     1340}}}
     1341
     1342Common attribute:
     1343
     1344{{{
     1345Reservations ∩ ReservationEquipment = {reservation_id}
     1346}}}
     1347
     1348Functional dependency:
     1349
     1350{{{
     1351reservation_id -> room_id, user_id, reservation_date, start_time, end_time, status
     1352}}}
     1353
     1354Since `reservation_id` determines all attributes of `Reservations`, the common attribute determines the `Reservations` relation.
     1355
     1356Therefore:
     1357
     1358{{{
     1359{reservation_id} -> Reservations
     1360}}}
     1361
     1362The decomposition between `Reservations` and `ReservationEquipment` is lossless.
     1363
     1364=== Equipment and ReservationEquipment ===
     1365
     1366Decomposition:
     1367
     1368{{{
     1369Equipment(equipment_id, name, stock_quantity)
     1370ReservationEquipment(reservation_id, equipment_id, requested_quantity)
     1371}}}
     1372
     1373Common attribute:
     1374
     1375{{{
     1376Equipment ∩ ReservationEquipment = {equipment_id}
     1377}}}
     1378
     1379Functional dependency:
     1380
     1381{{{
     1382equipment_id -> name, stock_quantity
     1383}}}
     1384
     1385Since `equipment_id` determines all attributes of `Equipment`, the common attribute determines the `Equipment` relation.
     1386
     1387Therefore:
     1388
     1389{{{
     1390{equipment_id} -> Equipment
     1391}}}
     1392
     1393The decomposition between `Equipment` and `ReservationEquipment` is lossless.
     1394
     1395=== Reservations and Approvals ===
     1396
     1397Decomposition:
     1398
     1399{{{
     1400Reservations(reservation_id, room_id, user_id, reservation_date, start_time, end_time, status)
     1401Approvals(approval_id, reservation_id, approver_id, decision, decision_time, note)
     1402}}}
     1403
     1404Common attribute:
     1405
     1406{{{
     1407Reservations ∩ Approvals = {reservation_id}
     1408}}}
     1409
     1410Functional dependency:
     1411
     1412{{{
     1413reservation_id -> room_id, user_id, reservation_date, start_time, end_time, status
     1414}}}
     1415
     1416Since `reservation_id` determines all attributes of `Reservations`, the common attribute determines the `Reservations` relation.
     1417
     1418Therefore:
     1419
     1420{{{
     1421{reservation_id} -> Reservations
     1422}}}
     1423
     1424The decomposition between `Reservations` and `Approvals` is lossless.
     1425
     1426=== Users and Approvals ===
     1427
     1428The approval relation stores the approver using `approver_id`, which references `Users(user_id)`.
     1429
     1430Decomposition:
     1431
     1432{{{
     1433Users(user_id, username, email, full_name, role)
     1434Approvals(approval_id, reservation_id, approver_id, decision, decision_time, note)
     1435}}}
     1436
     1437Relationship attribute:
     1438
     1439{{{
     1440approver_id references user_id
     1441}}}
     1442
     1443Functional dependency in `Users`:
     1444
     1445{{{
     1446user_id -> username, email, full_name, role
     1447}}}
     1448
     1449After matching `approver_id` with `user_id`, the referenced user identifier determines all attributes of the corresponding user.
     1450
     1451Therefore, the decomposition between approver data and approval data is lossless.
     1452
     1453=== Users and UserCredentials ===
     1454
     1455Decomposition:
     1456
     1457{{{
     1458Users(user_id, username, email, full_name, role)
     1459UserCredentials(user_id, password_hash, created_at, updated_at)
     1460}}}
     1461
     1462Common attribute:
     1463
     1464{{{
     1465Users ∩ UserCredentials = {user_id}
     1466}}}
     1467
     1468Functional dependencies:
     1469
     1470{{{
     1471user_id -> username, email, full_name, role
     1472user_id -> password_hash, created_at, updated_at
     1473}}}
     1474
     1475Since `user_id` determines all attributes in both `Users` and `UserCredentials`, the decomposition is lossless.
     1476
     1477This also preserves the intended one-to-one relationship between a user profile and user credentials.
     1478
     1479== BCNF Check ==
     1480
     1481A relation is in BCNF if for every non-trivial functional dependency X -> Y, X is a superkey.
     1482
     1483The final relations are checked for BCNF as follows.
     1484
     1485=== buildings ===
     1486
     1487Relation:
     1488
     1489{{{
     1490buildings(building_id, name, address)
     1491}}}
     1492
     1493Functional dependency:
    6541494
    6551495{{{
     
    6571497}}}
    6581498
    659 is preserved in:
    660 
    661 {{{
    662 buildings(building_id, name, address)
    663 }}}
    664 
    665 and the relation can be joined back with:
     1499Candidate key:
     1500
     1501{{{
     1502building_id
     1503}}}
     1504
     1505The determinant is a candidate key, so the relation is in BCNF.
     1506
     1507=== rooms ===
     1508
     1509Relation:
    6661510
    6671511{{{
     
    6691513}}}
    6701514
    671 using the foreign key ''building_id''.
    672 
    673 Similarly, user data and equipment data are separated into their own relations and referenced by identifiers from reservations, approvals, room equipment, and reservation equipment.
    674 
    675 The decomposition preserves the functional dependencies because each important dependency is represented inside one of the final relations.
    676 
    677 == BCNF if possible ==
    678 
    679 A relation is in BCNF if for every non-trivial functional dependency X -> Y, X is a superkey.
    680 
    681 The final relations are checked for BCNF as follows.
    682 
    683 === buildings ===
    684 
    685 Relation:
    686 
    687 {{{
    688 buildings(building_id, name, address)
    689 }}}
    690 
    6911515Functional dependencies:
    6921516
    6931517{{{
    694 building_id -> name, address
    695 name, address -> building_id
    696 }}}
    697 
    698 Candidate keys:
    699 
    700 {{{
    701 building_id
    702 (name, address)
    703 }}}
    704 
    705 All determinants are candidate keys, so the relation is in BCNF.
    706 
    707 === rooms ===
    708 
    709 Relation:
    710 
    711 {{{
    712 rooms(room_id, building_id, room_code, capacity, type)
    713 }}}
    714 
    715 Functional dependencies:
    716 
    717 {{{
    7181518room_id -> building_id, room_code, capacity, type
    719 room_code -> room_id
     1519room_code -> room_id, building_id, capacity, type
    7201520}}}
    7211521
     
    7371537}}}
    7381538
    739 Functional dependencies:
     1539Functional dependency:
    7401540
    7411541{{{
    7421542equipment_id -> name, stock_quantity
    743 name -> equipment_id, stock_quantity
    744 }}}
    745 
    746 Candidate keys:
     1543}}}
     1544
     1545Candidate key:
    7471546
    7481547{{{
    7491548equipment_id
    750 name
    751 }}}
    752 
    753 All determinants are candidate keys, so the relation is in BCNF.
     1549}}}
     1550
     1551The determinant is a candidate key, so the relation is in BCNF.
    7541552
    7551553=== room_equipment ===
     
    8091607}}}
    8101608
    811 Functional dependencies:
     1609Functional dependency:
    8121610
    8131611{{{
    8141612reservation_id -> room_id, user_id, reservation_date, start_time, end_time, status
    815 room_id, reservation_date, start_time, end_time -> reservation_id
    816 }}}
    817 
    818 Candidate keys:
     1613}}}
     1614
     1615Candidate key:
    8191616
    8201617{{{
    8211618reservation_id
    822 (room_id, reservation_date, start_time, end_time) for room-based reservations
    823 }}}
    824 
    825 The determinant ''reservation_id'' is the primary key. The determinant ''(room_id, reservation_date, start_time, end_time)'' is an alternative key for room-based reservations. Therefore, the relation is in BCNF.
    826 
    827 The attribute ''room_id'' is optional because equipment-only reservations are allowed.
     1619}}}
     1620
     1621The determinant is the candidate key, so the relation is in BCNF.
     1622
     1623Business rules about overlapping reservations are enforced through constraints and triggers. They are not treated as functional dependencies for the purpose of selecting the primary key.
    8281624
    8291625=== reservation_equipment ===
     
    8731669All determinants are candidate keys, so the relation is in BCNF.
    8741670
    875 === BCNF conclusion ===
    876 
    877 All final relations are in BCNF. The decomposition also preserves the important functional dependencies and has loss-less join properties. Therefore, no further decomposition is required.
    878 
    879 == Final result and discussion ==
     1671=== user_credentials ===
     1672
     1673Relation:
     1674
     1675{{{
     1676user_credentials(user_id, password_hash, created_at, updated_at)
     1677}}}
     1678
     1679Functional dependency:
     1680
     1681{{{
     1682user_id -> password_hash, created_at, updated_at
     1683}}}
     1684
     1685Candidate key:
     1686
     1687{{{
     1688user_id
     1689}}}
     1690
     1691The determinant is the candidate key, so the relation is in BCNF.
     1692
     1693== Final Result and Discussion ==
    8801694
    8811695=== Normalized relational model ===
     
    8851699{{{
    8861700buildings(
    887 building_id PK,
    888 name,
    889 address
     1701    building_id PK,
     1702    name,
     1703    address
    8901704)
    8911705}}}
     
    8931707{{{
    8941708rooms(
    895 room_id PK,
    896 building_id FK,
    897 room_code AK,
    898 capacity,
    899 type
     1709    room_id PK,
     1710    building_id FK,
     1711    room_code AK,
     1712    capacity,
     1713    type
    9001714)
    9011715}}}
     
    9031717{{{
    9041718equipment(
    905 equipment_id PK,
    906 name AK,
    907 stock_quantity
     1719    equipment_id PK,
     1720    name,
     1721    stock_quantity
    9081722)
    9091723}}}
     
    9111725{{{
    9121726room_equipment(
    913 room_id PK, FK,
    914 equipment_id PK, FK,
    915 quantity
     1727    room_id PK, FK,
     1728    equipment_id PK, FK,
     1729    quantity
    9161730)
    9171731}}}
     
    9191733{{{
    9201734users(
    921 user_id PK,
    922 username AK,
    923 email AK,
    924 full_name,
    925 role
     1735    user_id PK,
     1736    username AK,
     1737    email AK,
     1738    full_name,
     1739    role
    9261740)
    9271741}}}
     
    9291743{{{
    9301744reservations(
    931 reservation_id PK,
    932 room_id FK,
    933 user_id FK,
    934 reservation_date,
    935 start_time,
    936 end_time,
    937 status
     1745    reservation_id PK,
     1746    room_id FK,
     1747    user_id FK,
     1748    reservation_date,
     1749    start_time,
     1750    end_time,
     1751    status
    9381752)
    9391753}}}
     
    9411755{{{
    9421756reservation_equipment(
    943 reservation_id PK, FK,
    944 equipment_id PK, FK,
    945 requested_quantity
     1757    reservation_id PK, FK,
     1758    equipment_id PK, FK,
     1759    requested_quantity
    9461760)
    9471761}}}
     
    9491763{{{
    9501764approvals(
    951 approval_id PK,
    952 reservation_id FK, AK,
    953 approver_id FK,
    954 decision,
    955 decision_time,
    956 note
     1765    approval_id PK,
     1766    reservation_id AK, FK,
     1767    approver_id FK,
     1768    decision,
     1769    decision_time,
     1770    note
     1771)
     1772}}}
     1773
     1774{{{
     1775user_credentials(
     1776    user_id PK, FK,
     1777    password_hash,
     1778    created_at,
     1779    updated_at
    9571780)
    9581781}}}
     
    9601783=== Discussion ===
    9611784
    962 The normalization process started from a single de-normalized relation containing all attributes from the model. That relation was in 1NF, but it was not in 2NF because many attributes depended only on parts of the composite key. The decomposition removed partial dependencies, then removed transitive dependencies, and finally checked the resulting relations for BCNF.
    963 
    964 The final normalized design contains separate relations for buildings, rooms, equipment, users, reservations, room equipment, reservation equipment, and approvals.
     1785The normalization process started from a single de-normalized relation containing the important attributes from the model. That relation was in 1NF, but it was not in 2NF because many attributes depended only on parts of the composite key.
     1786
     1787The decomposition removed partial dependencies, then removed transitive dependencies, and finally checked the resulting relations for BCNF.
     1788
     1789The final normalized design contains separate relations for:
     1790
     1791 * buildings
     1792 * rooms
     1793 * equipment
     1794 * room_equipment
     1795 * users
     1796 * reservations
     1797 * reservation_equipment
     1798 * approvals
     1799 * user_credentials
    9651800
    9661801The M:N relationship between rooms and equipment is represented by:
     
    9781813This prevents repeated data and allows equipment to be managed independently from both rooms and reservations.
    9791814
     1815The authentication data is separated into:
     1816
     1817{{{
     1818user_credentials(user_id, password_hash, created_at, updated_at)
     1819}}}
     1820
     1821This avoids storing password hashes directly in the `users` relation and keeps authentication details separate from general user profile data.
     1822
    9801823=== Comparison with Phase P2 ===
    9811824
    9821825The final normalized model obtained in this phase is consistent with the relational design from Phase P2. The same main relations are used:
    9831826
    984 * buildings
    985 * rooms
    986 * equipment
    987 * room_equipment
    988 * users
    989 * reservations
    990 * reservation_equipment
    991 * approvals
    992 
    993 This means that the Phase P2 relational schema was already designed in a normalized way. No structural change to the existing PostgreSQL database schema is required after the normalization check.
    994 
    995 The existing Phase P2 design will continue to be used in the following project phases.
     1827 * buildings
     1828 * rooms
     1829 * equipment
     1830 * room_equipment
     1831 * users
     1832 * reservations
     1833 * reservation_equipment
     1834 * approvals
     1835
     1836The `user_credentials` relation was added later as an application-level authentication extension in the final implementation. It is also normalized and connected to `users` through a one-to-one relationship.
     1837
     1838This means that the Phase P2 relational schema was already designed in a normalized way. No structural change to the main database model is required after the normalization check.
    9961839
    9971840== Conclusion ==
    9981841
    999 The formal normalization process shows that the final relational model is in BCNF. The decomposition preserves the functional dependencies, has loss-less join properties, and matches the already implemented relational schema from Phase P2.
    1000 
    1001 Therefore, the current database design is appropriate for the next phases of the project.
     1842The formal normalization process shows that the final relational model is in BCNF.
     1843
     1844The documentation now includes:
     1845
     1846 * formal candidate key proof using attribute closure
     1847 * selected primary keys for every final relation
     1848 * explanation why the selected keys are minimal
     1849 * formal lossless-join proof for the decompositions
     1850 * BCNF verification for every final relation
     1851
     1852The decomposition preserves the important functional dependencies, has lossless-join properties and matches the implemented relational schema.
     1853
     1854Therefore, the current database design is appropriate for the final Room Reservation System implementation.