Changes between Initial Version and Version 1 of Normalization


Ignore:
Timestamp:
08/28/26 23:46:10 (13 days ago)
Author:
236021
Comment:

--

Legend:

Unmodified
Added
Removed
Modified
  • Normalization

    v1 v1  
     1= Normalization =
     2
     3
     4== De-normalized database form ==
     5
     6=== Global set of attributes ===
     7
     8{{{
     9R = {
     10  doctor_id, d_first_name, d_last_name, d_email,
     11  level_id, level_name,
     12  specialization_id, specialization_name,
     13  department_id, department_name,
     14
     15  patient_id, p_first_name, p_last_name, p_email, date_of_birth, blood_type, gender, phone_number, embg,
     16
     17  admin_id, admin_username, admin_name, admin_lastname, admin_email,
     18  technician_id, tech_username, tech_name, tech_lastname, tech_email,
     19  user_id, users_username, users_password, users_role,
     20  users_first_name, users_last_name, is_active,
     21
     22  appointment_id, appointment_date, appointment_time, appointment_status,
     23  appt_patient_id, appt_doctor_id,
     24
     25  diagnosis_id, diagnosis_name, diagnosis_description, diag_patient_id, diag_doctor_id,
     26
     27  procedure_id, procedure_type, procedure_sched_date, procedure_description,
     28  procedure_cost, proc_doctor_id, proc_diagnosis_id,
     29  proc_result_id, proc_result_description, proc_result_date,
     30  performed_id, perf_proc_id, perf_doctor_id, perf_patient_id, perf_diagnosis_id, perf_date, perf_notes,
     31
     32  test_id, test_name, test_description, test_cost,
     33  lab_result_id, lab_result_value, lab_result_date,
     34  performed_test_id, pt_test_id, pt_patient_id, pt_doctor_id, pt_technician_id, pt_test_date, pt_notes,
     35
     36  prescription_id, medication_name,
     37  restriction_id, restriction_description,
     38  dosage, frequency, duration, prescription_notes,
     39
     40  allergy_id, allergy_name, allergy_severity,
     41  record_allergy_reaction, record_allergy_severity,
     42
     43  symptom_id, symptom_name, symptom_description, record_symptom_severity,
     44
     45  record_id,
     46
     47  referral_id, referral_reason, referral_date, ref_from_doctor_id, ref_to_doctor_id,
     48  report_id, report_description, report_date, report_doctor_id,
     49  bill_id, total_cost, payment_status, payment_date, billing_admin_id
     50}
     51}}}
     52
     53=== Functional dependencies ===
     54
     55`Doctor`
     56
     57{{{
     58F1  level_id          -> level_name
     59F2  specialization_id -> specialization_name
     60F3  department_id     -> department_name
     61F4  doctor_id         -> d_first_name, d_last_name, d_email, level_id, specialization_id, department_id
     62}}}
     63
     64`Patient`
     65
     66{{{
     67F5  patient_id -> p_first_name, p_last_name, p_email, date_of_birth, blood_type, gender, phone_number, embg
     68}}}
     69
     70`Admin`
     71
     72{{{
     73F6  admin_id -> admin_username, admin_name, admin_lastname, admin_email
     74}}}
     75
     76`Lab Technician`
     77
     78{{{
     79F7  technician_id -> tech_username, tech_name, tech_lastname, tech_email
     80}}}
     81
     82`User`
     83
     84{{{
     85F8  user_id -> users_username, users_password, users_role,
     86               users_first_name, users_last_name, is_active
     87}}}
     88
     89**user_id** also has four nullable foreign keys: patient_id, doctor_id, admin_id,
     90and technician_id. Only one of these is ever filled in for a given user, depending
     91on their role, and the rest stay null. Because of that, `user_id` is not treated as
     92determining any of the four below, since a login row only optionally points to one
     93profile row rather than always pointing to the same one. This is the same subtype
     94idea used for `Admin`, `Clients`, and `Owners` elsewhere, except in our database it keeps all
     95four subtypes together in one `users` table instead of giving each one its own table.
     96
     97`Appointment`
     98
     99{{{
     100F9  appointment_id -> appointment_date, appointment_time, appointment_status,
     101                       appt_patient_id, appt_doctor_id
     102}}}
     103
     104`Diagnosis`
     105
     106{{{
     107F10 diagnosis_id -> diagnosis_name, diagnosis_description, diag_patient_id, diag_doctor_id
     108}}}
     109
     110`Procedure`
     111
     112{{{
     113F11 procedure_id   -> procedure_type, procedure_sched_date, procedure_description,
     114                       procedure_cost, proc_doctor_id, proc_diagnosis_id
     115F12 proc_result_id -> proc_result_description, proc_result_date, procedure_id
     116F13 performed_id   -> perf_proc_id, perf_doctor_id, perf_patient_id, perf_diagnosis_id,
     117                       perf_date, perf_notes
     118}}}
     119
     120`Prescription`
     121
     122{{{
     123F14 prescription_id -> medication_name
     124F15 restriction_id  -> restriction_description, prescription_id
     125}}}
     126
     127`Lab test`
     128
     129{{{
     130F16 test_id           -> test_name, test_description, test_cost
     131F17 lab_result_id     -> lab_result_value, lab_result_date, test_id
     132F18 performed_test_id -> pt_test_id, pt_patient_id, pt_doctor_id, pt_technician_id,
     133                          pt_test_date, pt_notes
     134}}}
     135
     136`Medical record`
     137
     138{{{
     139F19 record_id -> patient_id
     140}}}
     141
     142`Allergy`
     143
     144{{{
     145F20 allergy_id -> allergy_name, allergy_severity
     146}}}
     147
     148`Symptom`
     149
     150{{{
     151F21 symptom_id -> symptom_name, symptom_description
     152}}}
     153
     154`Referral`
     155
     156{{{
     157F22 referral_id -> referral_reason, referral_date, record_id, ref_from_doctor_id, ref_to_doctor_id
     158}}}
     159
     160`Medical report`
     161
     162{{{
     163F23 report_id -> report_description, report_date, record_id, report_doctor_id
     164}}}
     165
     166`Billing`
     167
     168{{{
     169F24 bill_id -> total_cost, payment_status, payment_date, record_id, billing_admin_id
     170}}}
     171
     172`Medical record associations (composite key attributes)`
     173
     174{{{
     175F25 {record_id, symptom_id}      -> record_symptom_severity
     176F26 {record_id, allergy_id}      -> record_allergy_reaction, record_allergy_severity
     177F27 {prescription_id, record_id} -> dosage, frequency, duration, prescription_notes
     178}}}
     179
     180`Many-to-many association tables (no dependent attribute  trivial so FDs only on the full pair)`
     181
     182{{{
     183{diagnosis_id, record_id}          {doctor_id, record_id}            {record_id, procedure_id}
     184{record_id, lab_result_id}         {patient_id, allergy_id}          {patient_id, symptom_id}
     185{diagnosis_id, symptom_id}         {diagnosis_id, procedure_id}      {specialization_id, procedure_id}
     186{department_id, procedure_id}      {bill_id, procedure_id}           {bill_id, test_id}
     187{report_id, lab_result_id}         {allergy_id, restriction_id}
     188}}}
     189
     190----
     191=== Candidate keys and primary key ===
     192
     193A candidate key of the de-normalized relation is found by picking a minimal set of
     194identifiers whose closure reaches every attribute of R. Several identifiers already
     195determine others through chains inside the FD set — for instance:
     196
     197proc_result_id -> procedure_id
     198lab_result_id -> test_id
     199restriction_id -> prescription_id
     200referral_id -> record_id, and in turn record_id -> patient_id
     201doctor_id -> level_id, specialization_id, department_id
     202
     203These chains matter because whatever gets reached this way (procedure_id, test_id,
     204prescription_id, record_id, patient_id, level_id, specialization_id, department_id)
     205does not need to be included in the key separately — it is already recoverable once
     206its determinant is present.
     207
     208'''Left-hand side only''' (identifiers that never appear as a dependent on the right
     209side of any FD, and therefore cannot be reached this way):
     210`doctor_id, admin_id, technician_id, user_id, appointment_id, diagnosis_id, proc_result_id,
     211performed_id, restriction_id, lab_result_id, performed_test_id, allergy_id, symptom_id,
     212referral_id, report_id, bill_id`
     213
     214Since none of these 16 identifiers can be derived from anything else in R, every candidate
     215key of R is required to contain all of them:
     216
     217{{{
     218K = { doctor_id, admin_id, technician_id, user_id, appointment_id, diagnosis_id,
     219      proc_result_id, performed_id, restriction_id, lab_result_id, performed_test_id,
     220      allergy_id, symptom_id, referral_id, report_id, bill_id }
     221}}}
     222=== Closure proof for K ===
     223
     224{{{
     225Start:
     226K+ = {doctor_id, admin_id, technician_id, user_id, appointment_id, diagnosis_id,
     227      proc_result_id, performed_id, restriction_id, lab_result_id, performed_test_id,
     228      allergy_id, symptom_id, referral_id, report_id, bill_id}
     229
     230From F4:
     231doctor_id -> d_first_name, d_last_name, d_email, level_id, specialization_id, department_id
     232
     233From F1 using level_id:
     234level_id -> level_name
     235
     236From F2 using specialization_id:
     237specialization_id -> specialization_name
     238
     239From F3 using department_id:
     240department_id -> department_name
     241
     242From F6:
     243admin_id -> admin_username, admin_name, admin_lastname, admin_email
     244
     245From F7:
     246technician_id -> tech_username, tech_name, tech_lastname, tech_email
     247
     248From F8:
     249user_id -> users_username, users_password, users_role, users_first_name, users_last_name, is_active
     250
     251From F9:
     252appointment_id -> appointment_date, appointment_time, appointment_status, appt_patient_id, appt_doctor_id
     253
     254From F10:
     255diagnosis_id -> diagnosis_name, diagnosis_description, diag_patient_id, diag_doctor_id
     256
     257From F12:
     258proc_result_id -> proc_result_description, proc_result_date, procedure_id
     259
     260From F11 using procedure_id:
     261procedure_id -> procedure_type, procedure_sched_date, procedure_description,
     262                procedure_cost, proc_doctor_id, proc_diagnosis_id
     263
     264From F13:
     265performed_id -> perf_proc_id, perf_doctor_id, perf_patient_id, perf_diagnosis_id, perf_date, perf_notes
     266
     267From F15:
     268restriction_id -> restriction_description, prescription_id
     269
     270From F14 using prescription_id:
     271prescription_id -> medication_name
     272
     273From F17:
     274lab_result_id -> lab_result_value, lab_result_date, test_id
     275
     276From F16 using test_id:
     277test_id -> test_name, test_description, test_cost
     278
     279From F18:
     280performed_test_id -> pt_test_id, pt_patient_id, pt_doctor_id, pt_technician_id, pt_test_date, pt_notes
     281
     282From F20:
     283allergy_id -> allergy_name, allergy_severity
     284
     285From F21:
     286symptom_id -> symptom_name, symptom_description
     287
     288From F22:
     289referral_id -> referral_reason, referral_date, record_id, ref_from_doctor_id, ref_to_doctor_id
     290
     291From F19 using record_id:
     292record_id -> patient_id
     293
     294From F5 using patient_id:
     295patient_id -> p_first_name, p_last_name, p_email, date_of_birth, blood_type, gender, phone_number, embg
     296
     297From F23:
     298report_id -> report_description, report_date, record_id, report_doctor_id
     299
     300From F24:
     301bill_id -> total_cost, payment_status, payment_date, record_id, billing_admin_id
     302
     303From F25 using record_id and symptom_id:
     304{record_id, symptom_id} -> record_symptom_severity
     305
     306From F26 using record_id and allergy_id:
     307{record_id, allergy_id} -> record_allergy_reaction, record_allergy_severity
     308
     309From F27 using prescription_id and record_id:
     310{prescription_id, record_id} -> dosage, frequency, duration, prescription_notes
     311}}}
     312
     313Therefore, K+ determines all attributes of R, so K is a `superkey`.
     314
     315=== Minimality / uniqueness ===
     316
     317To promote K from only "a candidate key" to the chosen primary key, we also need to show
     318that no other candidate key exists.
     319
     320None of the 16 identifiers in K is functionally determined by another determinant in the
     321given FD set:
     322
     323
     324
     325 **doctor_id** does not appear on the right-hand side of any FD, so it cannot be derived.
     326
     327 **admin_id** does not appear on the right-hand side of any FD, so it cannot be derived.
     328
     329 **technician_id** does not appear on the right-hand side of any FD, so it cannot be derived.
     330
     331 **user_id** does not appear on the right-hand side of any FD, so it cannot be derived.
     332
     333 **appointment_id** does not appear on the right-hand side of any FD, so it cannot be derived.
     334
     335 **diagnosis_id** does not appear on the right-hand side of any FD, so it cannot be derived.
     336
     337 **proc_result_id** does not appear on the right-hand side of any FD, so it cannot be derived.
     338
     339 **performed_id** does not appear on the right-hand side of any FD, so it cannot be derived.
     340
     341 **restriction_id** does not appear on the right-hand side of any FD, so it cannot be derived.
     342
     343 **lab_result_id** does not appear on the right-hand side of any FD, so it cannot be derived.
     344
     345 **performed_test_id** does not appear on the right-hand side of any FD, so it cannot be derived.
     346
     347 **allergy_id** does not appear on the right-hand side of any FD, so it cannot be derived.
     348
     349 **symptom_id** does not appear on the right-hand side of any FD, so it cannot be derived.
     350
     351 **referral_id** does not appear on the right-hand side of any FD, so it cannot be derived.
     352
     353 **report_id** does not appear on the right-hand side of any FD, so it cannot be derived.
     354
     355 **bill_id** does not appear on the right-hand side of any FD, so it cannot be derived.
     356
     357Every superkey of R must contain all attributes in:
     358
     359{{{
     360E = { doctor_id, admin_id, technician_id, user_id, appointment_id, diagnosis_id,
     361      proc_result_id, performed_id, restriction_id, lab_result_id, performed_test_id,
     362      allergy_id, symptom_id, referral_id, report_id, bill_id }
     363}}}
     364
     365Because K and E are the same set, and we already showed K+ = R, K qualifies as a
     366superkey. And because every superkey is required to contain E, so nothing smaller than K
     367could ever work as a key. There's also no way to swap out one of these 16 attributes for
     368a different one and still get a valid candidate key, since none of the attributes in E
     369can be reached from any other determinant in the functional dependency set so there's simply no substitute
     370available for any of them.
     371
     372This means K stands as the sole candidate key of the de-normalized relation, given the
     373functional dependencies defined above.
     374
     375'''Primary key of the de-normalized relation:'''
     376
     377{{{
     378PK = { doctor_id, admin_id, technician_id, user_id, appointment_id, diagnosis_id,
     379       proc_result_id, performed_id, restriction_id, lab_result_id, performed_test_id,
     380       allergy_id, symptom_id, referral_id, report_id, bill_id }
     381}}}
     382----
     383
     384== 1NF decomposition ==
     385
     386Every attribute in `R` is single valued and atomic, and the composite `PK` above uniquely identifies each row, so '''`R` satisfies 1NF'''.
     387
     388----
     389
     390== 2NF decomposition ==
     391
     392`R` has a composite primary key of 16 attributes, and none of its non-key attributes depends on the whole 16 attribute keys as each one is reachable from just one or two members of K, either directly (F4, F5, F6, ... etc) or through a short chain (e.g. `proc_result_id -> procedure_id -> procedure_type`, `referral_id -> record_id -> patient_id`). Either way, that is a '''partial dependency''' with respect to K, so '''`R` violates 2NF'''. Examples:
     393
     394{{{
     395doctor_id      -> d_first_name, d_last_name, d_email, level_id, specialization_id, department_id
     396
     397diagnosis_id   -> diagnosis_name, diagnosis_description, diag_patient_id, diag_doctor_id
     398
     399proc_result_id -> proc_result_description, proc_result_date, procedure_id
     400
     401referral_id    -> referral_reason, referral_date, record_id, ref_from_doctor_id, ref_to_doctor_id
     402}}}
     403
     404=== Grouping by determinants ===
     405
     406{{{
     407doctor_id        -> d_first_name, d_last_name, d_email, level_id, specialization_id, department_id
     408
     409level_id         -> level_name
     410
     411specialization_id-> specialization_name
     412
     413department_id    -> department_name
     414
     415patient_id        -> p_first_name, p_last_name, p_email, date_of_birth, blood_type, gender, phone_number, embg
     416
     417admin_id         -> admin_username, admin_name, admin_lastname, admin_email
     418
     419technician_id    -> tech_username, tech_name, tech_lastname, tech_email
     420
     421user_id          -> users_username, users_password, users_role, users_first_name, users_last_name, is_active
     422
     423appointment_id   -> appt_patient_id, appt_doctor_id, appointment_date, appointment_time, appointment_status
     424
     425diagnosis_id     -> diagnosis_name, diagnosis_description, diag_patient_id, diag_doctor_id
     426
     427procedure_id     -> procedure_type, procedure_sched_date, procedure_description, procedure_cost,
     428                     proc_doctor_id, proc_diagnosis_id
     429
     430proc_result_id   -> proc_result_description, proc_result_date, procedure_id
     431
     432performed_id     -> perf_proc_id, perf_doctor_id, perf_patient_id, perf_diagnosis_id, perf_date, perf_notes
     433
     434prescription_id  -> medication_name
     435
     436restriction_id   -> restriction_description, prescription_id
     437
     438test_id          -> test_name, test_description, test_cost
     439
     440lab_result_id    -> lab_result_value, lab_result_date, test_id
     441
     442performed_test_id-> pt_test_id, pt_patient_id, pt_doctor_id, pt_technician_id, pt_test_date, pt_notes
     443
     444record_id        -> patient_id
     445
     446allergy_id       -> allergy_name, allergy_severity
     447
     448symptom_id       -> symptom_name, symptom_description
     449
     450referral_id      -> referral_reason, referral_date, record_id, ref_from_doctor_id, ref_to_doctor_id
     451
     452report_id        -> report_description, report_date, record_id, report_doctor_id
     453
     454bill_id          -> total_cost, payment_status, payment_date, record_id, billing_admin_id
     455
     456{record_id, symptom_id}      -> record_symptom_severity
     457
     458{record_id, allergy_id}      -> record_allergy_reaction, record_allergy_severity
     459
     460{prescription_id, record_id} -> dosage, frequency, duration, prescription_notes
     461}}}
     462
     463`MedicalRecordSymptoms`, `MedicalRecordAllergies` and `PrescriptionRecords` already depend on their whole composite key so these three are already 2NF compliant  and are not split further.
     464
     465=== 2NF relations ===
     466
     467* {{{Doctors(doctor_id, d_first_name, d_last_name, d_email, level_id, specialization_id, department_id)}}}
     468* {{{DoctorLevels(level_id, level_name)}}}
     469* {{{Specializations(specialization_id, specialization_name)}}}
     470* {{{Departments(department_id, department_name)}}}
     471* {{{Patients(patient_id, p_first_name, p_last_name, p_email, date_of_birth, blood_type, gender, phone_number, embg)}}}
     472* {{{Admin(admin_id, admin_username, admin_name, admin_lastname, admin_email)}}}
     473* {{{LabTechnician(technician_id, tech_username, tech_name, tech_lastname, tech_email)}}}
     474* {{{Users(user_id, users_username, users_password, users_role, users_first_name, users_last_name, is_active)}}}
     475* {{{Appointments(appointment_id, appt_patient_id, appt_doctor_id, appointment_date, appointment_time, appointment_status)}}}
     476* {{{Diagnoses(diagnosis_id, diagnosis_name, diagnosis_description, diag_patient_id, diag_doctor_id)}}}
     477* {{{Procedures(procedure_id, procedure_type, procedure_sched_date, procedure_description, procedure_cost, proc_doctor_id, proc_diagnosis_id)}}}
     478* {{{ProcedureResults(proc_result_id, proc_result_description, proc_result_date, procedure_id)}}}
     479* {{{PerformedProcedures(performed_id, perf_proc_id, perf_doctor_id, perf_patient_id, perf_diagnosis_id, perf_date, perf_notes)}}}
     480* {{{Prescriptions(prescription_id, medication_name)}}}
     481* {{{PrescriptionRestrictions(restriction_id, restriction_description, prescription_id)}}}
     482* {{{LabTests(test_id, test_name, test_description, test_cost)}}}
     483* {{{LabResults(lab_result_id, lab_result_value, lab_result_date, test_id)}}}
     484* {{{PerformedLabTests(performed_test_id, pt_test_id, pt_patient_id, pt_doctor_id, pt_technician_id, pt_test_date, pt_notes)}}}
     485* {{{MedicalRecords(record_id, patient_id)}}}
     486* {{{Allergies(allergy_id, allergy_name, allergy_severity)}}}
     487* {{{Symptoms(symptom_id, symptom_name, symptom_description)}}}
     488* {{{Referrals(referral_id, referral_reason, referral_date, record_id, ref_from_doctor_id, ref_to_doctor_id)}}}
     489* {{{MedicalReports(report_id, report_description, report_date, record_id, report_doctor_id)}}}
     490* {{{Billing(bill_id, total_cost, payment_status, payment_date, record_id, billing_admin_id)}}}
     491* {{{MedicalRecordSymptoms(record_id, symptom_id, record_symptom_severity)}}}
     492* {{{MedicalRecordAllergies(record_id, allergy_id, record_allergy_reaction, record_allergy_severity)}}}
     493* {{{PrescriptionRecords(prescription_id, record_id, dosage, frequency, duration, prescription_notes)}}}
     494
     495=== Lossless join ===
     496
     497A split of relation R into R1 and R2 keeps every row reconstructable  as long as the columns R1 and R2 have in common are by themselves
     498sufficient to pin down one of the two sides completely:
     499
     500{{{
     501(R1 ∩ R2) -> R1     or     (R1 ∩ R2) -> R2
     502}}}
     503
     504Each relation below comes from one FD: its determinant becomes the new table's key, and
     505that same determinant stays behind as a foreign key connecting it to the rest. Because
     506the FD already guarantees the determinant decides everything in the new table, every
     507split passes the lossless-join test automatically.
     508
     509||= # =||= Relation produced =||= Shared column =||= FD used =||
     510|| 1  || `Doctors` || `doctor_id` || F4 ||
     511|| 2  || `DoctorLevels` || `level_id` || F1 ||
     512|| 3  || `Specializations` || `specialization_id` || F2 ||
     513|| 4  || `Departments` || `department_id` || F3 ||
     514|| 5  || `Patients` || `patient_id` || F5 ||
     515|| 6  || `Admin` || `admin_id` || F6 ||
     516|| 7  || `LabTechnician` || `technician_id` || F7 ||
     517|| 8  || `Users` || `user_id` || F8 ||
     518|| 9  || `Appointments` || `appointment_id` || F9 ||
     519|| 10 || `Diagnoses` || `diagnosis_id` || F10 ||
     520|| 11 || `Procedures` || `procedure_id` || F11 ||
     521|| 12 || `ProcedureResults` || `proc_result_id` || F12 ||
     522|| 13 || `PerformedProcedures` || `performed_id` || F13 ||
     523|| 14 || `Prescriptions` || `prescription_id` || F14 ||
     524|| 15 || `PrescriptionRestrictions` || `restriction_id` || F15 ||
     525|| 16 || `LabTests` || `test_id` || F16 ||
     526|| 17 || `LabResults` || `lab_result_id` || F17 ||
     527|| 18 || `PerformedLabTests` || `performed_test_id` || F18 ||
     528|| 19 || `MedicalRecords` || `record_id` || F19 ||
     529|| 20 || `Allergies` || `allergy_id` || F20 ||
     530|| 21 || `Symptoms` || `symptom_id` || F21 ||
     531|| 22 || `Referrals` || `referral_id` || F22 ||
     532|| 23 || `MedicalReports` || `report_id` || F23 ||
     533|| 24 || `Billing` || `bill_id` || F24 ||
     534|| 25 || `MedicalRecordSymptoms` || `{record_id, symptom_id}` || F25 ||
     535|| 26 || `MedicalRecordAllergies` || `{record_id, allergy_id}` || F26 ||
     536|| 27 || `PrescriptionRecords` || `{prescription_id, record_id}` || F27 ||
     537
     538Rows 25–27 work the same way, just with two columns acting as the determinant instead of
     539one. F25, F26, and F27 each show that the pair alone determines the whole relation and
     540that pair is the new table's primary key, so the same test still applies.
     541
     542Since each of the 27 splits is lossless on its own, the whole chain is too, therefore joining the
     543pieces back together, one at a time in reverse order, which rebuilds R exactly.
     544
     545=== Dependency preservation ===
     546
     547Every FD from F1 through F27 landed fully inside one relation, its determinant and all
     548of its dependents together, never split across two tables and never left out entirely.
     549So the FDs that hold across `Doctors`, `DoctorLevels`, and every relation through
     550`PrescriptionRecords` are the same as the FDs we started with, which is what makes the
     551decomposition dependency preserving.
     552
     553----
     554== 3NF check ==
     555
     5562NF removed **partial** dependencies. We now check whether any **transitive**
     557dependency remains so a non-key attribute reachable only through another non-key
     558attribute. Because the 2NF grouping step split on every determinant found in F1–F27,
     559no relation ended up keeping a borrowed descriptive attribute alongside the foreign
     560key that points to where it actually lives — so no transitive dependency survived
     561into the 2NF output. A few examples confirm this:
     562
     563`Doctors` keeps level_id, specialization_id, department_id as foreign keys, but
     564level_name, specialization_name, department_name stay in `DoctorLevels`,
     565`Specializations`, `Departments` are never duplicated back into `Doctors`.
     566
     567`ProcedureResults` keeps only the foreign key procedure_id, the procedure's own
     568procedure_type, procedure_cost, etc. stay in `Procedures`. `Procedures`
     569keeps only proc_diagnosis_id, the diagnosis's diagnosis_name and diagnosis_description
     570stay in `Diagnoses`.
     571
     572`LabResults` keeps only the foreign key test_id. test_name, test_description and
     573test_cost stay in `LabTests`.
     574
     575`Referrals`, `MedicalReports`, `Billing`, `Appointments`, `PerformedProcedures`,
     576`PerformedLabTests`, `PrescriptionRestrictions` each carry only foreign keys back to
     577record_id / doctor_id / patient_id / admin_id / technician_id / procedure_id
     578/ test_id / prescription_id and never the descriptive attributes belonging to the
     579entity on the other end of that key.
     580`MedicalRecordSymptoms`, `MedicalRecordAllergies`,
     581`PrescriptionRecords` have composite keys with no non-key attribute that could point
     582through another non-key attribute, so they aren't affected either way.
     583
     584In conclusion, every non-key attribute in every relation depends only and
     585fully on that relation's own key. The schema is in '''3NF''', and since this is a
     586by-product of how the 2NF split was done rather than a separate fix meaning that no further
     587decomposition is needed at this stage.
     588
     589== BCNF check ==
     590
     591BCNF requires every non-trivial determinant to be a superkey. This is stronger than
     5923NF, which still allows a non-superkey determinant as long as the dependent attribute
     593is itself part of some candidate key. Below is a check of each 3NF relation. The primary key is bolded.
     594
     595||= Relation =||= Key =||= BCNF? =||
     596|| `DoctorLevels` || '''level_id''', alt. key `level_name` || Yes ||
     597|| `Specializations` || '''specialization_id''', alt. key `specialization_name` || Yes ||
     598|| `Departments` || '''department_id''', alt. key `department_name` || Yes ||
     599|| `Doctors` || '''doctor_id''', alt. key `d_email` || Yes ||
     600|| `Patients` || '''patient_id''', alt. key `embg` || Yes ||
     601|| `Admin` || '''admin_id''', alt. keys `admin_username`, `admin_email` || Yes ||
     602|| `LabTechnician` || '''technician_id''', alt. keys `tech_username`, `tech_email` || Yes ||
     603|| `Users` || '''user_id''', alt. key `users_username` || Yes ||
     604|| `Appointments` || '''appointment_id''' || Yes ||
     605|| `Diagnoses` || '''diagnosis_id''' || Yes ||
     606|| `Procedures` || '''procedure_id''' || Yes ||
     607|| `ProcedureResults` || '''proc_result_id''' || Yes ||
     608|| `PerformedProcedures` || '''performed_id''' || Yes ||
     609|| `Prescriptions` || '''prescription_id''' || Yes ||
     610|| `PrescriptionRestrictions` || '''restriction_id''' || Yes ||
     611|| `LabTests` || '''test_id''' || Yes ||
     612|| `LabResults` || '''lab_result_id''' || Yes ||
     613|| `PerformedLabTests` || '''performed_test_id''' || Yes ||
     614|| `MedicalRecords` || '''record_id''' || Yes ||
     615|| `Allergies` || '''allergy_id''', alt. key `allergy_name` || Yes ||
     616|| `Symptoms` || '''symptom_id''', alt. key `symptom_name` || Yes ||
     617|| `Referrals` || '''referral_id''' || Yes ||
     618|| `MedicalReports` || '''report_id''' || Yes ||
     619|| `Billing` || '''bill_id''' || Yes ||
     620|| `MedicalRecordSymptoms` || '''{record_id, symptom_id}''' || Yes ||
     621|| `MedicalRecordAllergies` || '''{record_id, allergy_id}''' || Yes ||
     622|| `PrescriptionRecords` || '''{prescription_id, record_id}''' || Yes ||
     623
     624`p_email` (`patients.email_address`) is left off as an alternate key as it carries a `UNIQUE`
     625constraint but is nullable, so it doesn't function as a true candidate key under the
     626relational model.
     627
     628No relation has a non-key attribute determining part of a candidate key without itself being a superkey so the schema **satisfies BCNF**.
     629
     630----
     631
     632== 4NF check ==
     633
     634BCNF only removes anomalies caused by functional dependencies. This domain still has
     635the fourteen pure many-to-many pairs listed at the end of the FD section, and none of
     636them was involved in any FD above, so BCNF does not evaluate them at all.
     637
     638Four entities in this schema each sit at the center of more than one independent
     639many-to-many relationship at once. That's exactly the situation that produces a
     640genuine multivalued dependency. If you tried to store two of those relationships in
     641the same table, you'd be forced to repeat every combination of the two, even though
     642the two facts have nothing to do with each other.
     643
     644- A **medical record** can be linked to several diagnoses, several doctors, several
     645procedures, and several lab results, and each of those four lists grows and shrinks
     646on its own. Adding another diagnosis to a record says nothing about how many doctors,
     647procedures, or lab results are attached to that same record.
     648
     649- A **diagnosis** can be linked to several typical symptoms and several procedures used to
     650investigate or treat it. Neither list has any bearing on the other.
     651
     652- A **procedure** can be linked to several specializations qualified to perform it, several
     653departments that perform it, and several bills it appears on. Its link to a diagnosis
     654was already covered above and isn't repeated here.
     655
     656- A **patient** can be linked to several allergies and several symptoms, tracked completely
     657separately from one another.
     658
     659This gives eleven multivalued dependencies:
     660
     661{{{
     662record_id     ->> diagnosis_id
     663record_id     ->> doctor_id
     664record_id     ->> procedure_id
     665record_id     ->> lab_result_id
     666
     667diagnosis_id  ->> symptom_id
     668diagnosis_id  ->> procedure_id
     669
     670procedure_id  ->> specialization_id
     671procedure_id  ->> department_id
     672procedure_id  ->> bill_id
     673
     674patient_id    ->> allergy_id
     675patient_id    ->> symptom_id
     676}}}
     677
     678The remaining three pairs, `{test_id, bill_id}`, `{report_id, lab_result_id}`, and
     679`{allergy_id, restriction_id}`, are different. Each one is the only many-to-many link
     680at its anchor, so there's no competing relationship to collide with. They still need
     681their own table, since a plain many-to-many link can never be expressed as a
     682functional dependency, but they aren't 4NF violations the way the four groups above
     683are.
     684
     685None of the MVDs above are trivial. In each case, the right side isn't already part
     686of the left side, and the left side isn't a key of any table that currently holds
     687both sides together. So '''4NF is violated''' until all fourteen pairs, the eleven
     688grouped above plus these three standalone ones, each get their own table.
     689
     690
     691----
     692
     693== MVD check ==
     694
     695Nothing is actually being split here. These fourteen pairs were already separate,
     696attribute only tables back in the FD section, since none of them ever had an extra
     697column that would tie them to something else during 2NF. This section just confirms
     698that keeping them separate was correct. If any two had been merged that would create redundant, repeated rows. Keeping each pair on its own avoids that.
     699
     700* {{{DiagnosisMedicalRecords(diagnosis_id, record_id)}}}
     701* {{{DoctorMedicalRecords(doctor_id, record_id)}}}
     702* {{{MedicalRecordProcedures(record_id, procedure_id)}}}
     703* {{{MedicalRecordLabResults(record_id, lab_result_id)}}}
     704* {{{PatientAllergies(patient_id, allergy_id)}}}
     705* {{{PatientSymptoms(patient_id, symptom_id)}}}
     706* {{{DiagnosisSymptoms(diagnosis_id, symptom_id)}}}
     707* {{{DiagnosisProcedures(diagnosis_id, procedure_id)}}}
     708* {{{SpecializationProcedures(specialization_id, procedure_id)}}}
     709* {{{DepartmentProcedures(department_id, procedure_id)}}}
     710* {{{BillingProcedures(bill_id, procedure_id)}}}
     711* {{{BillingLabTests(bill_id, test_id)}}}
     712* {{{MedicalReportLabResults(report_id, lab_result_id)}}}
     713* {{{AllergyPrescriptionRestrictions(allergy_id, restriction_id)}}}
     714
     715'''Lossless join'''
     716The general MVD theorem guarantees that for any relation
     717containing `X`, `Y`, and other attributes `Z` where `X ->> Y` holds, splitting into
     718`(X, Y)` and `(X, Z)` always reconstructs the original without loss. Since none of
     719these fourteen pairs was ever combined with a competing multivalued group in any
     720table we produced, that theorem is satisfied trivially rather than needing to be
     721invoked as an active repair.
     722
     723'''Dependency preservation'''
     724None of these fourteen relations carries a non-trivial
     725FD of its own. Both attributes are needed together just to identify a membership row,
     726so there's nothing beyond the membership fact itself that needs preserving.
     727
     728The schema satisfies '''4NF'''.
     729
     730* {{{DoctorLevels(level_id, level_name)}}}
     731* {{{Specializations(specialization_id, specialization_name)}}}
     732* {{{Departments(department_id, department_name)}}}
     733* {{{Doctors(doctor_id, d_first_name, d_last_name, d_email, level_id, specialization_id, department_id)}}}
     734* {{{Patients(patient_id, p_first_name, p_last_name, p_email, date_of_birth, blood_type, gender, phone_number, embg)}}}
     735* {{{Admin(admin_id, admin_username, admin_name, admin_lastname, admin_email)}}}
     736* {{{LabTechnician(technician_id, tech_username, tech_name, tech_lastname, tech_email)}}}
     737* {{{Users(user_id, users_username, users_password, users_role, users_first_name, users_last_name, is_active)}}}
     738* {{{Appointments(appointment_id, appt_patient_id, appt_doctor_id, appointment_date, appointment_time, appointment_status)}}}
     739* {{{Diagnoses(diagnosis_id, diagnosis_name, diagnosis_description, diag_patient_id, diag_doctor_id)}}}
     740* {{{Procedures(procedure_id, procedure_type, procedure_sched_date, procedure_description, procedure_cost, proc_doctor_id, proc_diagnosis_id)}}}
     741* {{{ProcedureResults(proc_result_id, proc_result_description, proc_result_date, procedure_id)}}}
     742* {{{PerformedProcedures(performed_id, perf_proc_id, perf_doctor_id, perf_patient_id, perf_diagnosis_id, perf_date, perf_notes)}}}
     743* {{{LabTests(test_id, test_name, test_description, test_cost)}}}
     744* {{{LabResults(lab_result_id, lab_result_value, lab_result_date, test_id)}}}
     745* {{{PerformedLabTests(performed_test_id, pt_test_id, pt_patient_id, pt_doctor_id, pt_technician_id, pt_test_date, pt_notes)}}}
     746* {{{MedicalRecords(record_id, patient_id)}}}
     747* {{{Referrals(referral_id, referral_reason, referral_date, record_id, ref_from_doctor_id, ref_to_doctor_id)}}}
     748* {{{MedicalReports(report_id, report_description, report_date, record_id, report_doctor_id)}}}
     749* {{{Billing(bill_id, total_cost, payment_status, payment_date, record_id, billing_admin_id)}}}
     750* {{{Prescriptions(prescription_id, medication_name)}}}
     751* {{{PrescriptionRestrictions(restriction_id, restriction_description, prescription_id)}}}
     752* {{{Allergies(allergy_id, allergy_name, allergy_severity)}}}
     753* {{{Symptoms(symptom_id, symptom_name, symptom_description)}}}
     754* {{{MedicalRecordSymptoms(record_id, symptom_id, record_symptom_severity)}}}
     755* {{{MedicalRecordAllergies(record_id, allergy_id, record_allergy_reaction, record_allergy_severity)}}}
     756* {{{PrescriptionRecords(prescription_id, record_id, dosage, frequency, duration, prescription_notes)}}}
     757* {{{DiagnosisMedicalRecords(diagnosis_id, record_id)}}}
     758* {{{DoctorMedicalRecords(doctor_id, record_id)}}}
     759* {{{MedicalRecordProcedures(record_id, procedure_id)}}}
     760* {{{MedicalRecordLabResults(record_id, lab_result_id)}}}
     761* {{{PatientAllergies(patient_id, allergy_id)}}}
     762* {{{PatientSymptoms(patient_id, symptom_id)}}}
     763* {{{DiagnosisSymptoms(diagnosis_id, symptom_id)}}}
     764* {{{DiagnosisProcedures(diagnosis_id, procedure_id)}}}
     765* {{{SpecializationProcedures(specialization_id, procedure_id)}}}
     766* {{{DepartmentProcedures(department_id, procedure_id)}}}
     767* {{{BillingProcedures(bill_id, procedure_id)}}}
     768* {{{BillingLabTests(bill_id, test_id)}}}
     769* {{{MedicalReportLabResults(report_id, lab_result_id)}}}
     770* {{{AllergyPrescriptionRestrictions(allergy_id, restriction_id)}}}
     771
     772----
     773
     774== Conclusion ==
     775
     776Decomposing the de-normalized relation from scratch, following only the formal rules through 1NF, 2NF, 3NF, BCNF, and 4NF, produced the same 41 relations already present in the Phase 2 design — same keys, same columns, same many-to-many tables. The normalization process confirms the same structural design obtained from the ER model in Phase 2.