Changes between Version 1 and Version 2 of Normalization


Ignore:
Timestamp:
09/04/26 10:56:33 (6 days ago)
Author:
236021
Comment:

--

Legend:

Unmodified
Added
Removed
Modified
  • Normalization

    v1 v2  
    321321given FD set:
    322322
    323 
    324 
    325323 **doctor_id** does not appear on the right-hand side of any FD, so it cannot be derived.
    326324
     
    387385
    388386----
    389 
    390387== 2NF decomposition ==
    391388
    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:
     389R 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:
    393390
    394391{{{
     
    461458}}}
    462459
    463 `MedicalRecordSymptoms`, `MedicalRecordAllergies` and `PrescriptionRecords` already depend on their whole composite key so these three are already 2NF compliant and are not split further.
     460MedicalRecordSymptoms, MedicalRecordAllergies and PrescriptionRecords already depend on their whole composite key so these three are already 2NF compliant and are not split further.
    464461
    465462=== 2NF relations ===
    466463
    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)}}}
     464{{{
     465Doctors(doctor_id, d_first_name, d_last_name, d_email, level_id, specialization_id, department_id)
     466DoctorLevels(level_id, level_name)
     467Specializations(specialization_id, specialization_name)
     468Departments(department_id, department_name)
     469Patients(patient_id, p_first_name, p_last_name, p_email, date_of_birth, blood_type, gender, phone_number, embg)
     470Admin(admin_id, admin_username, admin_name, admin_lastname, admin_email)
     471LabTechnician(technician_id, tech_username, tech_name, tech_lastname, tech_email)
     472Users(user_id, users_username, users_password, users_role, users_first_name, users_last_name, is_active)
     473Appointments(appointment_id, appt_patient_id, appt_doctor_id, appointment_date, appointment_time, appointment_status)
     474Diagnoses(diagnosis_id, diagnosis_name, diagnosis_description, diag_patient_id, diag_doctor_id)
     475Procedures(procedure_id, procedure_type, procedure_sched_date, procedure_description, procedure_cost, proc_doctor_id, proc_diagnosis_id)
     476ProcedureResults(proc_result_id, proc_result_description, proc_result_date, procedure_id)
     477PerformedProcedures(performed_id, perf_proc_id, perf_doctor_id, perf_patient_id, perf_diagnosis_id, perf_date, perf_notes)
     478Prescriptions(prescription_id, medication_name)
     479PrescriptionRestrictions(restriction_id, restriction_description, prescription_id)
     480LabTests(test_id, test_name, test_description, test_cost)
     481LabResults(lab_result_id, lab_result_value, lab_result_date, test_id)
     482PerformedLabTests(performed_test_id, pt_test_id, pt_patient_id, pt_doctor_id, pt_technician_id, pt_test_date, pt_notes)
     483MedicalRecords(record_id, patient_id)
     484Allergies(allergy_id, allergy_name, allergy_severity)
     485Symptoms(symptom_id, symptom_name, symptom_description)
     486Referrals(referral_id, referral_reason, referral_date, record_id, ref_from_doctor_id, ref_to_doctor_id)
     487MedicalReports(report_id, report_description, report_date, record_id, report_doctor_id)
     488Billing(bill_id, total_cost, payment_status, payment_date, record_id, billing_admin_id)
     489MedicalRecordSymptoms(record_id, symptom_id, record_symptom_severity)
     490MedicalRecordAllergies(record_id, allergy_id, record_allergy_reaction, record_allergy_severity)
     491PrescriptionRecords(prescription_id, record_id, dosage, frequency, duration, prescription_notes)
     492}}}
    494493
    495494=== Lossless join ===
    496495
    497 A 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
    498 sufficient to pin down one of the two sides completely:
     496A 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 sufficient to pin down one of the two sides completely:
    499497
    500498{{{
     
    502500}}}
    503501
    504 Each relation below comes from one FD: its determinant becomes the new table's key, and
    505 that same determinant stays behind as a foreign key connecting it to the rest. Because
    506 the FD already guarantees the determinant decides everything in the new table, every
    507 split 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 
    538 Rows 25–27 work the same way, just with two columns acting as the determinant instead of
    539 one. F25, F26, and F27 each show that the pair alone determines the whole relation and
    540 that pair is the new table's primary key, so the same test still applies.
    541 
    542 Since each of the 27 splits is lossless on its own, the whole chain is too, therefore joining the
    543 pieces back together, one at a time in reverse order, which rebuilds R exactly.
    544 
    545 === Dependency preservation ===
    546 
    547 Every FD from F1 through F27 landed fully inside one relation, its determinant and all
    548 of its dependents together, never split across two tables and never left out entirely.
    549 So 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
    551 decomposition dependency preserving.
     502Each relation below comes from one FD: its determinant becomes the new table's key, and that same determinant stays behind as a foreign key connecting it to the rest. Because the FD already guarantees the determinant decides everything in the new table, every split passes the lossless-join test automatically.
     503
     504=== Decomposition into relations ===
     505
     506'''`Doctors`(doctor_id, d_first_name, d_last_name, d_email, level_id, specialization_id, department_id)'''
     507
     508R1 = R - {d_first_name, d_last_name, d_email} (level_id, specialization_id, department_id remain in R since they are needed as determinants for steps 2-4)
     509
     510Lossless join: The shared column with R is `doctor_id`. Since `doctor_id -> Doctors` holds (F4), the relation can be reconstructed via join on `doctor_id`.
     511
     512Dependency preservation: F4 is preserved entirely within the new relation `Doctors`.
     513
     514'''`DoctorLevels`(level_id, level_name)'''
     515
     516R2 = R1 - {level_name, level_id}
     517
     518Lossless join: Shared column is `level_id`. `level_id -> DoctorLevels` holds (F1), so the relation is reconstructed via join on `level_id`.
     519
     520Dependency preservation: F1 preserved entirely in `DoctorLevels`.
     521
     522'''`Specializations`(specialization_id, specialization_name)'''
     523
     524R3 = R2 - {specialization_name, specialization_id}
     525
     526Lossless join: Shared column `specialization_id`. `specialization_id -> Specializations` holds (F2).
     527
     528Dependency preservation: F2 preserved entirely in `Specializations`.
     529
     530'''`Departments`(department_id, department_name)'''
     531
     532R4 = R3 - {department_name, department_id}
     533
     534Lossless join: Shared column `department_id`. `department_id -> Departments` holds (F3).
     535
     536Dependency preservation: F3 preserved entirely in `Departments`.
     537
     538'''`Patients`(patient_id, p_first_name, p_last_name, p_email, date_of_birth, blood_type, gender, phone_number, embg)'''
     539
     540R5 = R4 - {p_first_name, p_last_name, p_email, date_of_birth, blood_type, gender, phone_number, embg} (patient_id remains in R - needed later for MedicalRecords)
     541
     542Lossless join: Shared column `patient_id`. `patient_id -> Patients` holds (F5).
     543
     544Dependency preservation: F5 preserved entirely in `Patients`.
     545
     546'''`Admin`(admin_id, admin_username, admin_name, admin_lastname, admin_email)'''
     547
     548R6 = R5 - {admin_username, admin_name, admin_lastname, admin_email}
     549
     550Lossless join: Shared column `admin_id`. `admin_id -> Admin` holds (F6).
     551
     552Dependency preservation: F6 preserved entirely in `Admin`.
     553
     554'''`LabTechnician`(technician_id, tech_username, tech_name, tech_lastname, tech_email)'''
     555
     556R7 = R6 - {tech_username, tech_name, tech_lastname, tech_email}
     557
     558Lossless join: Shared column `technician_id`. `technician_id -> LabTechnician` holds (F7).
     559
     560Dependency preservation: F7 preserved entirely in `LabTechnician`.
     561
     562'''`Users`(user_id, users_username, users_password, users_role, users_first_name, users_last_name, is_active)'''
     563
     564R8 = R7 - {users_username, users_password, users_role, users_first_name, users_last_name, is_active}
     565
     566Lossless join: Shared column `user_id`. `user_id -> Users` holds (F8).
     567
     568Dependency preservation: F8 preserved entirely in `Users`.
     569
     570'''`Appointments`(appointment_id, appt_patient_id, appt_doctor_id, appointment_date, appointment_time, appointment_status)'''
     571
     572R9 = R8 - {appt_patient_id, appt_doctor_id, appointment_date, appointment_time, appointment_status}
     573
     574Lossless join: Shared column `appointment_id`. `appointment_id -> Appointments` holds (F9).
     575
     576Dependency preservation: F9 preserved entirely in `Appointments`.
     577
     578'''`Diagnoses`(diagnosis_id, diagnosis_name, diagnosis_description, diag_patient_id, diag_doctor_id)'''
     579
     580R10 = R9 - {diagnosis_name, diagnosis_description, diag_patient_id, diag_doctor_id}
     581
     582Lossless join: Shared column `diagnosis_id`. `diagnosis_id -> Diagnoses` holds (F10).
     583
     584Dependency preservation: F10 preserved entirely in `Diagnoses`.
     585
     586'''`Procedures`(procedure_id, procedure_type, procedure_sched_date, procedure_description, procedure_cost, proc_doctor_id, proc_diagnosis_id)'''
     587
     588R11 = R10 - {procedure_type, procedure_sched_date, procedure_description, procedure_cost, proc_doctor_id, proc_diagnosis_id} (procedure_id remains - needed for ProcedureResults)
     589
     590Lossless join: Shared column `procedure_id`. `procedure_id -> Procedures` holds (F11).
     591
     592Dependency preservation: F11 preserved entirely in `Procedures`.
     593
     594'''`ProcedureResults`(proc_result_id, proc_result_description, proc_result_date, procedure_id)'''
     595
     596R12 = R11 - {proc_result_description, proc_result_date, procedure_id}
     597
     598Lossless join: Shared column `proc_result_id`. `proc_result_id -> ProcedureResults` holds (F12).
     599
     600Dependency preservation: F12 preserved entirely in `ProcedureResults`.
     601
     602'''`PerformedProcedures`(performed_id, perf_proc_id, perf_doctor_id, perf_patient_id, perf_diagnosis_id, perf_date, perf_notes)'''
     603
     604R13 = R12 - {perf_proc_id, perf_doctor_id, perf_patient_id, perf_diagnosis_id, perf_date, perf_notes}
     605
     606Lossless join: Shared column `performed_id`. `performed_id -> PerformedProcedures` holds (F13).
     607
     608Dependency preservation: F13 preserved entirely in `PerformedProcedures`.
     609
     610'''`Prescriptions`(prescription_id, medication_name)'''
     611
     612R14 = R13 - {medication_name} (prescription_id remains - needed for PrescriptionRestrictions and PrescriptionRecords)
     613
     614Lossless join: Shared column `prescription_id`. `prescription_id -> Prescriptions` holds (F14).
     615
     616Dependency preservation: F14 preserved entirely in `Prescriptions`.
     617
     618'''`PrescriptionRestrictions`(restriction_id, restriction_description, prescription_id)'''
     619
     620R15 = R14 - {restriction_description}
     621
     622Lossless join: Shared column `restriction_id`. `restriction_id -> PrescriptionRestrictions` holds (F15).
     623
     624Dependency preservation: F15 preserved entirely in `PrescriptionRestrictions`.
     625
     626'''`LabTests`(test_id, test_name, test_description, test_cost)'''
     627
     628R16 = R15 - {test_name, test_description, test_cost} (test_id remains - needed for LabResults)
     629
     630Lossless join: Shared column `test_id`. `test_id -> LabTests` holds (F16).
     631
     632Dependency preservation: F16 preserved entirely in `LabTests`.
     633
     634'''`LabResults`(lab_result_id, lab_result_value, lab_result_date, test_id)'''
     635
     636R17 = R16 - {lab_result_value, lab_result_date, test_id}
     637
     638Lossless join: Shared column `lab_result_id`. `lab_result_id -> LabResults` holds (F17).
     639
     640Dependency preservation: F17 preserved entirely in `LabResults`.
     641
     642'''`PerformedLabTests`(performed_test_id, pt_test_id, pt_patient_id, pt_doctor_id, pt_technician_id, pt_test_date, pt_notes)'''
     643
     644R18 = R17 - {pt_test_id, pt_patient_id, pt_doctor_id, pt_technician_id, pt_test_date, pt_notes}
     645
     646Lossless join: Shared column `performed_test_id`. `performed_test_id -> PerformedLabTests` holds (F18).
     647
     648Dependency preservation: F18 preserved entirely in `PerformedLabTests`.
     649
     650'''`MedicalRecords`(record_id, patient_id)'''
     651
     652R19 = R18 - {patient_id} (record_id remains - needed for Referrals, MedicalReports, Billing, and the three composite-key relations)
     653
     654Lossless join: Shared column `record_id`. `record_id -> MedicalRecords` holds (F19).
     655
     656Dependency preservation: F19 preserved entirely in `MedicalRecords`.
     657
     658'''`Allergies`(allergy_id, allergy_name, allergy_severity)'''
     659
     660R20 = R19 - {allergy_name, allergy_severity}
     661
     662Lossless join: Shared column `allergy_id`. `allergy_id -> Allergies` holds (F20).
     663
     664Dependency preservation: F20 preserved entirely in `Allergies`.
     665
     666'''`Symptoms`(symptom_id, symptom_name, symptom_description)'''
     667
     668R21 = R20 - {symptom_name, symptom_description}
     669
     670Lossless join: Shared column `symptom_id`. `symptom_id -> Symptoms` holds (F21).
     671
     672Dependency preservation: F21 preserved entirely in `Symptoms`.
     673
     674'''`Referrals`(referral_id, referral_reason, referral_date, record_id, ref_from_doctor_id, ref_to_doctor_id)'''
     675
     676R22 = R21 - {referral_reason, referral_date, ref_from_doctor_id, ref_to_doctor_id}
     677
     678Lossless join: Shared column `referral_id`. `referral_id -> Referrals` holds (F22).
     679
     680Dependency preservation: F22 preserved entirely in `Referrals`.
     681
     682'''`MedicalReports`(report_id, report_description, report_date, record_id, report_doctor_id)'''
     683
     684R23 = R22 - {report_description, report_date, report_doctor_id}
     685
     686Lossless join: Shared column `report_id`. `report_id -> MedicalReports` holds (F23).
     687
     688Dependency preservation: F23 preserved entirely in `MedicalReports`.
     689
     690'''`Billing`(bill_id, total_cost, payment_status, payment_date, record_id, billing_admin_id)'''
     691
     692R24 = R23 - {total_cost, payment_status, payment_date, billing_admin_id}
     693
     694Lossless join: Shared column `bill_id`. `bill_id -> Billing` holds (F24).
     695
     696Dependency preservation: F24 preserved entirely in `Billing`.
     697
     698'''`MedicalRecordSymptoms`(record_id, symptom_id, record_symptom_severity)'''
     699
     700R25 = R24 - {record_symptom_severity}
     701
     702Lossless join: Shared column {`record_id`, `symptom_id`}. `{record_id,symptom_id} -> MedicalRecordSymptoms` holds (F25) - the same principle, just with a composite determinant.
     703
     704Dependency preservation: F25 preserved entirely; the relation already depends on the whole composite key, so it is not split further.
     705
     706'''`MedicalRecordAllergies`(record_id, allergy_id, record_allergy_reaction, record_allergy_severity)'''
     707
     708R26 = R25 - {record_allergy_reaction, record_allergy_severity}
     709
     710Lossless join: Shared column {`record_id`, `allergy_id`}. `{record_id,allergy_id} -> MedicalRecordAllergies` holds (F26).
     711
     712Dependency preservation: F26 preserved entirely; already depends on the whole composite key.
     713
     714'''`PrescriptionRecords`(prescription_id, record_id, dosage, frequency, duration, prescription_notes)'''
     715
     716R27 = R26 - {dosage, frequency, duration, prescription_notes, prescription_id, record_id}
     717
     718Lossless join: Shared column {`prescription_id`, `record_id`}. `{prescription_id,record_id} -> PrescriptionRecords` holds (F27).
     719
     720Dependency preservation: F27 preserved entirely; already depends on the whole composite key.
     721
     722----
     723
     724'''Final result:''' after step 27, R27 = { doctor_id, admin_id, technician_id, user_id, appointment_id, diagnosis_id, proc_result_id, performed_id, restriction_id, lab_result_id, performed_test_id, allergy_id, symptom_id, referral_id, report_id, bill_id } - exactly the 16 PK attributes, nothing more. This confirms the decomposition is complete: everything partially dependent has been removed, leaving only the composite key that "links" the rest through foreign keys in all 27 new relations.
     725
     726Since each of the 27 splits is lossless on its own, the whole chain of decompositions is lossless too (joining all the pieces back together, one at a time, reconstructs R exactly). Likewise, every FD from F1 through F27 is fully contained within a single relation, none split across tables, which means the decomposition is also dependency preserving.
    552727
    553728----
    554729== 3NF check ==
    555730
    556 2NF removed **partial** dependencies. We now check whether any **transitive**
    557 dependency remains so a non-key attribute reachable only through another non-key
    558 attribute. Because the 2NF grouping step split on every determinant found in F1–F27,
    559 no relation ended up keeping a borrowed descriptive attribute alongside the foreign
    560 key that points to where it actually lives — so no transitive dependency survived
    561 into the 2NF output. A few examples confirm this:
    562 
    563 `Doctors` keeps level_id, specialization_id, department_id as foreign keys, but
    564 level_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
    568 procedure_type, procedure_cost, etc. stay in `Procedures`. `Procedures`
    569 keeps only proc_diagnosis_id, the diagnosis's diagnosis_name and diagnosis_description
    570 stay in `Diagnoses`.
    571 
    572 `LabResults` keeps only the foreign key test_id. test_name, test_description and
    573 test_cost stay in `LabTests`.
    574 
    575 `Referrals`, `MedicalReports`, `Billing`, `Appointments`, `PerformedProcedures`,
    576 `PerformedLabTests`, `PrescriptionRestrictions` each carry only foreign keys back to
    577 record_id / doctor_id / patient_id / admin_id / technician_id / procedure_id
    578 / test_id / prescription_id and never the descriptive attributes belonging to the
    579 entity on the other end of that key.
    580 `MedicalRecordSymptoms`, `MedicalRecordAllergies`,
    581 `PrescriptionRecords` have composite keys with no non-key attribute that could point
    582 through another non-key attribute, so they aren't affected either way.
    583 
    584 In conclusion, every non-key attribute in every relation depends only and
    585 fully on that relation's own key. The schema is in '''3NF''', and since this is a
    586 by-product of how the 2NF split was done rather than a separate fix meaning that no further
    587 decomposition is needed at this stage.
     7312NF removed partial dependencies. We now check whether any transitive dependency remains — i.e. a non-key attribute reachable only through another non-key attribute. Because the 2NF grouping step split on every determinant found in F1–F27, no relation ended up keeping a borrowed descriptive attribute alongside the foreign key that points to where it actually lives — so no transitive dependency survived into the 2NF output. A few examples confirm this:
     732
     733* `Doctors` keeps level_id, specialization_id, department_id as foreign keys, but level_name, specialization_name, and department_name stay in `DoctorLevels`, `Specializations`, and `Departments` — they are never duplicated back into `Doctors`.
     734* `ProcedureResults` keeps only the foreign key procedure_id, the procedure's own procedure_type, procedure_cost, etc. stay in `Procedures`. `Procedures` keeps only proc_diagnosis_id, the diagnosis's diagnosis_name and diagnosis_description stay in `Diagnoses`.
     735* `LabResults` keeps only the foreign key test_id. test_name, test_description and test_cost stay in `LabTests`.
     736* `Referrals`, `MedicalReports`, `Billing`, `Appointments`, `PerformedProcedures`, `PerformedLabTests`, `PrescriptionRestrictions` each carry only foreign keys back to record_id / doctor_id / patient_id / admin_id / technician_id / procedure_id / test_id / prescription_id and never the descriptive attributes belonging to the entity on the other end of that key. `MedicalRecordSymptoms`, `MedicalRecordAllergies`, `PrescriptionRecords` have composite keys with no non-key attribute that could point through another non-key attribute, so they aren't affected either way.
     737
     738In conclusion, every non-key attribute in every relation depends only and fully on that relation's own key. The schema is in 3NF. This is a by-product of how the 2NF split was done, rather than something that required a separate fix — so no further decomposition is needed at this stage.
    588739
    589740== BCNF check ==
    590741
    591 BCNF requires every non-trivial determinant to be a superkey. This is stronger than
    592 3NF, which still allows a non-superkey determinant as long as the dependent attribute
    593 is itself part of some candidate key. Below is a check of each 3NF relation. The primary key is bolded.
     742BCNF requires every non-trivial determinant to be a superkey. This is stronger than 3NF, which still allows a non-superkey determinant as long as the dependent attribute is itself part of some candidate key. Below is a check of each 3NF relation. The primary key is bolded.
    594743
    595744||= Relation =||= Key =||= BCNF? =||
     
    622771|| `PrescriptionRecords` || '''{prescription_id, record_id}''' || Yes ||
    623772
    624 `p_email` (`patients.email_address`) is left off as an alternate key as it carries a `UNIQUE`
    625 constraint but is nullable, so it doesn't function as a true candidate key under the
    626 relational model.
    627 
    628 No relation has a non-key attribute determining part of a candidate key without itself being a superkey so the schema **satisfies BCNF**.
     773`p_email` is left off as an alternate key because it carries a `UNIQUE` constraint but is nullable, so it doesn't function as a true candidate key under the relational model.
     774
     775No relation has a non-key attribute determining part of a candidate key without itself being a superkey, so the schema **satisfies BCNF**.
    629776
    630777----
     
    632779== 4NF check ==
    633780
    634 BCNF only removes anomalies caused by functional dependencies. This domain still has
    635 the fourteen pure many-to-many pairs listed at the end of the FD section, and none of
    636 them was involved in any FD above, so BCNF does not evaluate them at all.
    637 
    638 Four entities in this schema each sit at the center of more than one independent
    639 many-to-many relationship at once. That's exactly the situation that produces a
    640 genuine multivalued dependency. If you tried to store two of those relationships in
    641 the same table, you'd be forced to repeat every combination of the two, even though
    642 the two facts have nothing to do with each other.
    643 
    644 - A **medical record** can be linked to several diagnoses, several doctors, several
    645 procedures, and several lab results, and each of those four lists grows and shrinks
    646 on its own. Adding another diagnosis to a record says nothing about how many doctors,
    647 procedures, 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
    650 investigate 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
    653 departments that perform it, and several bills it appears on. Its link to a diagnosis
    654 was already covered above and isn't repeated here.
    655 
    656 - A **patient** can be linked to several allergies and several symptoms, tracked completely
    657 separately from one another.
     781BCNF only removes anomalies caused by functional dependencies. This domain still has the fourteen pure many-to-many pairs listed at the end of the FD section, and none of them was involved in any FD above, so BCNF does not evaluate them at all.
     782
     783Four entities in this schema each sit at the center of more than one independent many-to-many relationship at once. That's exactly the situation that produces a genuine multivalued dependency. If you tried to store two of those relationships in the same table, you'd be forced to repeat every combination of the two, even though the two facts have nothing to do with each other.
     784
     785* A '''medical record''' can be linked to several diagnoses, several doctors, several procedures, and several lab results, and each of those four lists grows and shrinks on its own. Adding another diagnosis to a record says nothing about how many doctors, procedures, or lab results are attached to that same record.
     786
     787* A '''diagnosis''' can be linked to several typical symptoms and several procedures used to investigate or treat it. Neither list has any bearing on the other.
     788
     789* A '''procedure''' can be linked to several specializations qualified to perform it, several departments that perform it, and several bills it appears on. Its link to a diagnosis was already covered above and isn't repeated here.
     790
     791* A '''patient''' can be linked to several allergies and several symptoms, tracked completely separately from one another.
    658792
    659793This gives eleven multivalued dependencies:
     
    676810}}}
    677811
    678 The 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
    680 at its anchor, so there's no competing relationship to collide with. They still need
    681 their own table, since a plain many-to-many link can never be expressed as a
    682 functional dependency, but they aren't 4NF violations the way the four groups above
    683 are.
    684 
    685 None of the MVDs above are trivial. In each case, the right side isn't already part
    686 of the left side, and the left side isn't a key of any table that currently holds
    687 both sides together. So '''4NF is violated''' until all fourteen pairs, the eleven
    688 grouped above plus these three standalone ones, each get their own table.
    689 
     812The remaining three pairs, `{test_id, bill_id}`, `{report_id, lab_result_id}`, and `{allergy_id, restriction_id}`, are different. Each one is the only many-to-many link at its anchor, so there's no competing relationship to collide with. They still need their own table, since a plain many-to-many link can never be expressed as a functional dependency, but they aren't 4NF violations the way the four groups above are.
     813
     814None of the MVDs above is trivial. In each case, the right side isn't already part of the left side, and the left side isn't a key of any table that currently holds both sides together. So '''4NF is violated''' until all fourteen pairs — the eleven grouped above plus these three standalone ones — each gets their own table.
    690815
    691816----
     
    693818== MVD check ==
    694819
    695 Nothing is actually being split here. These fourteen pairs were already separate,
    696 attribute only tables back in the FD section, since none of them ever had an extra
    697 column that would tie them to something else during 2NF. This section just confirms
    698 that 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'''
    716 The general MVD theorem guarantees that for any relation
    717 containing `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
    719 these fourteen pairs was ever combined with a competing multivalued group in any
    720 table we produced, that theorem is satisfied trivially rather than needing to be
    721 invoked as an active repair.
    722 
    723 '''Dependency preservation'''
    724 None of these fourteen relations carries a non-trivial
    725 FD of its own. Both attributes are needed together just to identify a membership row,
    726 so there's nothing beyond the membership fact itself that needs preserving.
     820Nothing is actually being split here. These fourteen pairs were already separate, attribute-only tables back in the FD section, since none of them ever had an extra column that would tie them to something else during 2NF. This section just confirms that 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.
     821
     822=== Lossless join & dependency preservation per relation ===
     823
     824'''`DiagnosisMedicalRecords`(diagnosis_id, record_id)'''
     825
     826MVD: `record_id ->> diagnosis_id`
     827
     828Lossless join: The general MVD theorem states that for a relation containing `X`, `Y`, and other attributes `Z` where `X ->> Y` holds, splitting into `(X, Y)` and `(X, Z)` reconstructs the original without loss. Here `X = record_id`, `Y = diagnosis_id`, so `(record_id, diagnosis_id)` and the rest of `MedicalRecords`'s attributes split losslessly.
     829
     830Dependency preservation: This relation carries no non-trivial FD of its own. Both attributes are needed together simply to identify a membership row.
     831
     832'''`DoctorMedicalRecords`(doctor_id, record_id)'''
     833
     834MVD: `record_id ->> doctor_id`
     835
     836Lossless join: `X = record_id`, `Y = doctor_id`. `record_id ->> doctor_id` holds, so `(record_id, doctor_id)` splits losslessly from the rest.
     837
     838Dependency preservation: This relation carries no non-trivial FD of its own. Both attributes are needed together simply to identify a membership row.
     839
     840'''`MedicalRecordProcedures`(record_id, procedure_id)'''
     841
     842MVD: `record_id ->> procedure_id`
     843
     844Lossless join: `X = record_id`, `Y = procedure_id`. `record_id ->> procedure_id` holds, so `(record_id, procedure_id)` splits losslessly from the rest.
     845
     846Dependency preservation: This relation carries no non-trivial FD of its own. Both attributes are needed together simply to identify a membership row.
     847
     848'''`MedicalRecordLabResults`(record_id, lab_result_id)'''
     849
     850MVD: `record_id ->> lab_result_id`
     851
     852Lossless join: `X = record_id`, `Y = lab_result_id`. `record_id ->> lab_result_id` holds, so `(record_id, lab_result_id)` splits losslessly from the rest.
     853
     854Dependency preservation: This relation carries no non-trivial FD of its own. Both attributes are needed together simply to identify a membership row.
     855
     856'''`DiagnosisSymptoms`(diagnosis_id, symptom_id)'''
     857
     858MVD: `diagnosis_id ->> symptom_id`
     859
     860Lossless join: `X = diagnosis_id`, `Y = symptom_id`. `diagnosis_id ->> symptom_id` holds, so `(diagnosis_id, symptom_id)` splits losslessly from the rest.
     861
     862Dependency preservation: This relation carries no non-trivial FD of its own. Both attributes are needed together simply to identify a membership row.
     863
     864'''`DiagnosisProcedures`(diagnosis_id, procedure_id)'''
     865
     866MVD: `diagnosis_id ->> procedure_id`
     867
     868Lossless join: `X = diagnosis_id`, `Y = procedure_id`. `diagnosis_id ->> procedure_id` holds, so `(diagnosis_id, procedure_id)` splits losslessly from the rest.
     869
     870Dependency preservation: This relation carries no non-trivial FD of its own. Both attributes are needed together simply to identify a membership row.
     871
     872'''`SpecializationProcedures`(specialization_id, procedure_id)'''
     873
     874MVD: `procedure_id ->> specialization_id`
     875
     876Lossless join: `X = procedure_id`, `Y = specialization_id`. `procedure_id ->> specialization_id` holds, so `(procedure_id, specialization_id)` splits losslessly from the rest.
     877
     878Dependency preservation: This relation carries no non-trivial FD of its own. Both attributes are needed together simply to identify a membership row.
     879
     880'''`DepartmentProcedures`(department_id, procedure_id)'''
     881
     882MVD: `procedure_id ->> department_id`
     883
     884Lossless join: `X = procedure_id`, `Y = department_id`. `procedure_id ->> department_id` holds, so `(procedure_id, department_id)` splits losslessly from the rest.
     885
     886Dependency preservation: This relation carries no non-trivial FD of its own. Both attributes are needed together simply to identify a membership row.
     887
     888'''`BillingProcedures`(bill_id, procedure_id)'''
     889
     890MVD: `procedure_id ->> bill_id`
     891
     892Lossless join: `X = procedure_id`, `Y = bill_id`. `procedure_id ->> bill_id` holds, so `(procedure_id, bill_id)` splits losslessly from the rest.
     893
     894Dependency preservation: This relation carries no non-trivial FD of its own. Both attributes are needed together simply to identify a membership row.
     895
     896'''`PatientAllergies`(patient_id, allergy_id)'''
     897
     898MVD: `patient_id ->> allergy_id`
     899
     900Lossless join: `X = patient_id`, `Y = allergy_id`. `patient_id ->> allergy_id` holds, so `(patient_id, allergy_id)` splits losslessly from the rest.
     901
     902Dependency preservation: This relation carries no non-trivial FD of its own. Both attributes are needed together simply to identify a membership row.
     903
     904'''`PatientSymptoms`(patient_id, symptom_id)'''
     905
     906MVD: `patient_id ->> symptom_id`
     907
     908Lossless join: `X = patient_id`, `Y = symptom_id`. `patient_id ->> symptom_id` holds, so `(patient_id, symptom_id)` splits losslessly from the rest.
     909
     910Dependency preservation: This relation carries no non-trivial FD of its own. Both attributes are needed together simply to identify a membership row.
     911
     912----
     913
     914The remaining three pairs are standalone many-to-many links rather than part of a competing MVD group, so they were never at risk of collision, but they still need their own table since a plain many-to-many relationship can't be expressed as a functional dependency:
     915
     916'''`BillingLabTests`(bill_id, test_id)'''
     917
     918Lossless join: `{bill_id, test_id}` is the only many-to-many link at either anchor, so this pair was never combined with a competing multivalued group in any table produced. The pairing is kept in its own relation, which trivially reconstructs via join on `{bill_id, test_id}`.
     919
     920Dependency preservation: This relation carries no non-trivial FD of its own. Both attributes are needed together simply to identify a membership row.
     921
     922'''`MedicalReportLabResults`(report_id, lab_result_id)'''
     923
     924Lossless join: `{report_id, lab_result_id}` is the only many-to-many link at either anchor, kept in its own relation, trivially reconstructed via join on `{report_id, lab_result_id}`.
     925
     926Dependency preservation: This relation carries no non-trivial FD of its own. Both attributes are needed together simply to identify a membership row.
     927
     928'''`AllergyPrescriptionRestrictions`(allergy_id, restriction_id)'''
     929
     930Lossless join: `{allergy_id, restriction_id}` is the only many-to-many link at either anchor, kept in its own relation, trivially reconstructed via join on `{allergy_id, restriction_id}`.
     931
     932Dependency preservation: This relation carries no non-trivial FD of its own. Both attributes are needed together simply to identify a membership row.
     933----
    727934
    728935The schema satisfies '''4NF'''.
     
    769976* {{{MedicalReportLabResults(report_id, lab_result_id)}}}
    770977* {{{AllergyPrescriptionRestrictions(allergy_id, restriction_id)}}}
    771 
    772978----
    773979
    774980== Conclusion ==
    775981
    776 Decomposing 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.
     982Decomposing 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 the 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.