Changes between Version 2 and Version 3 of Normalization


Ignore:
Timestamp:
05/25/26 22:42:41 (27 hours ago)
Author:
213257
Comment:

--

Legend:

Unmodified
Added
Removed
Modified
  • Normalization

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