Changes between Version 3 and Version 4 of Normalization


Ignore:
Timestamp:
09/22/26 23:26:54 (14 hours ago)
Author:
236021
Comment:

--

Legend:

Unmodified
Added
Removed
Modified
  • Normalization

    v3 v4  
    2626
    2727  procedure_id, procedure_type, procedure_sched_date, procedure_description,
    28   procedure_cost, proc_doctor_id, proc_diagnosis_id,
     28  procedure_cost, proc_doctor_id,
    2929  proc_result_id, proc_result_description, proc_result_date,
    3030  performed_id, perf_proc_id, perf_doctor_id, perf_patient_id, perf_diagnosis_id, perf_date, perf_notes,
     
    5959F2  specialization_id -> specialization_name
    6060F3  department_id     -> department_name
    61 F4  doctor_id         -> d_first_name, d_last_name, d_email, level_id, specialization_id, department_id
     61F4  doctor_id         -> d_first_name, d_last_name, d_email, level_id, specialization_id, department_id, user_id
    6262}}}
    6363
     
    6565
    6666{{{
    67 F5  patient_id -> p_first_name, p_last_name, p_email, date_of_birth, blood_type, gender, phone_number, embg
     67F5  patient_id -> p_first_name, p_last_name, p_email, date_of_birth, blood_type, gender, phone_number, embg, user_id
    6868}}}
    6969
     
    7171
    7272{{{
    73 F6  admin_id -> admin_username, admin_name, admin_lastname, admin_email
     73F6  admin_id -> admin_username, admin_name, admin_lastname, admin_email, user_id
    7474}}}
    7575
     
    7777
    7878{{{
    79 F7  technician_id -> tech_username, tech_name, tech_lastname, tech_email
     79F7  technician_id -> tech_username, tech_name, tech_lastname, tech_email, user_id
    8080}}}
    8181
     
    8787}}}
    8888
    89 **user_id** also has four nullable foreign keys: patient_id, doctor_id, admin_id,
    90 and technician_id. Only one of these is ever filled in for a given user, depending
    91 on their role, and the rest stay null. Because of that, `user_id` is not treated as
    92 determining any of the four below, since a login row only optionally points to one
    93 profile row rather than always pointing to the same one. This is the same subtype
    94 idea used for `Admin`, `Clients`, and `Owners` elsewhere, except in our database it keeps all
    95 four subtypes together in one `users` table instead of giving each one its own table.
     89Unlike a typical subtype design where the supertype (`Users`) would hold nullable
     90foreign keys to each subtype, this schema does it the other way around: `Doctors`,
     91`Patients`, `Admin`, and `LabTechnician` each carry their own mandatory `user_id`
     92foreign key back to `Users`, so it is the subtype row that points up to its login
     93row, not the reverse. Because of that, `doctor_id -> user_id`, `patient_id -> user_id`,
     94`admin_id -> user_id`, and `technician_id -> user_id` all hold (folded into F4, F5, F6,
     95F7 above), while `user_id` itself does not determine any of `doctor_id`, `patient_id`,
     96`admin_id`, or `technician_id`, since a `Users` row need not have any subtype row
     97pointing to it at all (e.g. a system administrator account with no linked profile).
     98This is the same subtype idea used for `Admin`, `Clients`, and `Owners` elsewhere,
     99except here the four subtypes each own the foreign key to their shared supertype
     100`users` table instead of the supertype holding a pointer to each of them.
    96101
    97102`Appointment`
     
    112117{{{
    113118F11 procedure_id   -> procedure_type, procedure_sched_date, procedure_description,
    114                        procedure_cost, proc_doctor_id, proc_diagnosis_id
     119                       procedure_cost, proc_doctor_id
    115120F12 proc_result_id -> proc_result_description, proc_result_date, procedure_id
    116121F13 performed_id   -> perf_proc_id, perf_doctor_id, perf_patient_id, perf_diagnosis_id,
     
    178183}}}
    179184
    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}
     185`Many-to-many association tables (no dependent attribute, trivial so FDs only on the full pair)`
     186
     187{{{
     188{record_id, procedure_id}          {record_id, lab_result_id}        {patient_id, allergy_id}
     189{patient_id, symptom_id}           {diagnosis_id, symptom_id}        {specialization_id, procedure_id}
    186190{department_id, procedure_id}      {bill_id, procedure_id}           {bill_id, test_id}
    187 {report_id, lab_result_id}         {allergy_id, restriction_id}
     191{report_id, lab_result_id}         {allergy_id, restriction_id}      {diagnosis_id, record_id}
    188192}}}
    189193
     
    199203restriction_id -> prescription_id
    200204referral_id -> record_id, and in turn record_id -> patient_id
    201 doctor_id -> level_id, specialization_id, department_id
     205doctor_id -> level_id, specialization_id, department_id, user_id
    202206
    203207These chains matter because whatever gets reached this way (procedure_id, test_id,
    204 prescription_id, record_id, patient_id, level_id, specialization_id, department_id)
    205 does not need to be included in the key separately as it is already recoverable once
    206 its determinant is present.
     208prescription_id, record_id, patient_id, level_id, specialization_id, department_id,
     209and — because of the corrected foreign-key direction discussed under `User` above —
     210user_id) does not need to be included in the key separately as it is already
     211recoverable once its determinant is present. In particular, `user_id` is reachable
     212via `doctor_id -> user_id` (F4) the moment `doctor_id` is in the key, so unlike an
     213earlier draft of this analysis that assumed `Users` held the foreign keys, `user_id`
     214is *not* one of the irreducible identifiers below.
    207215
    208216'''Left-hand side only''' (identifiers that never appear as a dependent on the right
    209217side 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,
     218`doctor_id, admin_id, technician_id, appointment_id, diagnosis_id, proc_result_id,
    211219performed_id, restriction_id, lab_result_id, performed_test_id, allergy_id, symptom_id,
    212220referral_id, report_id, bill_id`
    213221
    214 Since none of these 16 identifiers can be derived from anything else in R, every candidate
     222Since none of these 15 identifiers can be derived from anything else in R, every candidate
    215223key of R is required to contain all of them:
    216224
    217225{{{
    218 K = { doctor_id, admin_id, technician_id, user_id, appointment_id, diagnosis_id,
     226K = { doctor_id, admin_id, technician_id, appointment_id, diagnosis_id,
    219227      proc_result_id, performed_id, restriction_id, lab_result_id, performed_test_id,
    220228      allergy_id, symptom_id, referral_id, report_id, bill_id }
     
    224232{{{
    225233Start:
    226 K+ = {doctor_id, admin_id, technician_id, user_id, appointment_id, diagnosis_id,
     234K+ = {doctor_id, admin_id, technician_id, appointment_id, diagnosis_id,
    227235      proc_result_id, performed_id, restriction_id, lab_result_id, performed_test_id,
    228236      allergy_id, symptom_id, referral_id, report_id, bill_id}
    229237
    230238From F4:
    231 doctor_id -> d_first_name, d_last_name, d_email, level_id, specialization_id, department_id
     239doctor_id -> d_first_name, d_last_name, d_email, level_id, specialization_id, department_id, user_id
    232240
    233241From F1 using level_id:
     
    240248department_id -> department_name
    241249
     250From F8 using user_id (just derived from F4):
     251user_id -> users_username, users_password, users_role, users_first_name, users_last_name, is_active
     252
    242253From F6:
    243 admin_id -> admin_username, admin_name, admin_lastname, admin_email
     254admin_id -> admin_username, admin_name, admin_lastname, admin_email, user_id
    244255
    245256From F7:
    246 technician_id -> tech_username, tech_name, tech_lastname, tech_email
    247 
    248 From F8:
    249 user_id -> users_username, users_password, users_role, users_first_name, users_last_name, is_active
     257technician_id -> tech_username, tech_name, tech_lastname, tech_email, user_id
    250258
    251259From F9:
     
    260268From F11 using procedure_id:
    261269procedure_id -> procedure_type, procedure_sched_date, procedure_description,
    262                 procedure_cost, proc_doctor_id, proc_diagnosis_id
     270                procedure_cost, proc_doctor_id
    263271
    264272From F13:
     
    293301
    294302From F5 using patient_id:
    295 patient_id -> p_first_name, p_last_name, p_email, date_of_birth, blood_type, gender, phone_number, embg
     303patient_id -> p_first_name, p_last_name, p_email, date_of_birth, blood_type, gender, phone_number, embg, user_id
    296304
    297305From F23:
     
    318326that no other candidate key exists.
    319327
    320 None of the 16 identifiers in K is functionally determined by another determinant in the
     328None of the 15 identifiers in K is functionally determined by another determinant in the
    321329given FD set:
    322330
     
    327335 **technician_id** does not appear on the right-hand side of any FD, so it cannot be derived.
    328336
    329  **user_id** does not appear on the right-hand side of any FD, so it cannot be derived.
    330 
    331337 **appointment_id** does not appear on the right-hand side of any FD, so it cannot be derived.
    332338
     
    353359 **bill_id** does not appear on the right-hand side of any FD, so it cannot be derived.
    354360
     361`user_id` is deliberately excluded from this list: it *does* appear on the right-hand
     362side of F4 (`doctor_id -> ..., user_id`), so it is derivable once `doctor_id` — already
     363irreducible and already in K — is known, and including it separately in K would be
     364redundant.
     365
    355366Every superkey of R must contain all attributes in:
    356367
    357368{{{
    358 E = { doctor_id, admin_id, technician_id, user_id, appointment_id, diagnosis_id,
     369E = { doctor_id, admin_id, technician_id, appointment_id, diagnosis_id,
    359370      proc_result_id, performed_id, restriction_id, lab_result_id, performed_test_id,
    360371      allergy_id, symptom_id, referral_id, report_id, bill_id }
     
    363374Because K and E are the same set, and we already showed K+ = R, K qualifies as a
    364375superkey. And because every superkey is required to contain E, so nothing smaller than K
    365 could ever work as a key. There's also no way to swap out one of these 16 attributes for
     376could ever work as a key. There's also no way to swap out one of these 15 attributes for
    366377a different one and still get a valid candidate key, since none of the attributes in E
    367378can be reached from any other determinant in the functional dependency set so there's simply no substitute
     
    374385
    375386{{{
    376 PK = { doctor_id, admin_id, technician_id, user_id, appointment_id, diagnosis_id,
     387PK = { doctor_id, admin_id, technician_id, appointment_id, diagnosis_id,
    377388       proc_result_id, performed_id, restriction_id, lab_result_id, performed_test_id,
    378389       allergy_id, symptom_id, referral_id, report_id, bill_id }
     
    387398== 2NF decomposition ==
    388399
    389 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:
    390 
    391 {{{
    392 doctor_id      -> d_first_name, d_last_name, d_email, level_id, specialization_id, department_id
     400R has a composite primary key of 15 attributes, and none of its non-key attributes depends on the whole 15 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:
     401
     402{{{
     403doctor_id      -> d_first_name, d_last_name, d_email, level_id, specialization_id, department_id, user_id
    393404
    394405diagnosis_id   -> diagnosis_name, diagnosis_description, diag_patient_id, diag_doctor_id
     
    402413
    403414{{{
    404 doctor_id        -> d_first_name, d_last_name, d_email, level_id, specialization_id, department_id
     415doctor_id        -> d_first_name, d_last_name, d_email, level_id, specialization_id, department_id, user_id
    405416
    406417level_id         -> level_name
     
    410421department_id    -> department_name
    411422
    412 patient_id        -> p_first_name, p_last_name, p_email, date_of_birth, blood_type, gender, phone_number, embg
    413 
    414 admin_id         -> admin_username, admin_name, admin_lastname, admin_email
    415 
    416 technician_id    -> tech_username, tech_name, tech_lastname, tech_email
     423patient_id        -> p_first_name, p_last_name, p_email, date_of_birth, blood_type, gender, phone_number, embg, user_id
     424
     425admin_id         -> admin_username, admin_name, admin_lastname, admin_email, user_id
     426
     427technician_id    -> tech_username, tech_name, tech_lastname, tech_email, user_id
    417428
    418429user_id          -> users_username, users_password, users_role, users_first_name, users_last_name, is_active
     
    423434
    424435procedure_id     -> procedure_type, procedure_sched_date, procedure_description, procedure_cost,
    425                      proc_doctor_id, proc_diagnosis_id
     436                     proc_doctor_id
    426437
    427438proc_result_id   -> proc_result_description, proc_result_date, procedure_id
     
    463474
    464475{{{
    465 Doctors(doctor_id, d_first_name, d_last_name, d_email, level_id, specialization_id, department_id)
     476Doctors(doctor_id, d_first_name, d_last_name, d_email, level_id, specialization_id, department_id, user_id)
    466477DoctorLevels(level_id, level_name)
    467478Specializations(specialization_id, specialization_name)
    468479Departments(department_id, department_name)
    469 Patients(patient_id, p_first_name, p_last_name, p_email, date_of_birth, blood_type, gender, phone_number, embg)
    470 Admin(admin_id, admin_username, admin_name, admin_lastname, admin_email)
    471 LabTechnician(technician_id, tech_username, tech_name, tech_lastname, tech_email)
     480Patients(patient_id, p_first_name, p_last_name, p_email, date_of_birth, blood_type, gender, phone_number, embg, user_id)
     481Admin(admin_id, admin_username, admin_name, admin_lastname, admin_email, user_id)
     482LabTechnician(technician_id, tech_username, tech_name, tech_lastname, tech_email, user_id)
    472483Users(user_id, users_username, users_password, users_role, users_first_name, users_last_name, is_active)
    473484Appointments(appointment_id, appt_patient_id, appt_doctor_id, appointment_date, appointment_time, appointment_status)
    474485Diagnoses(diagnosis_id, diagnosis_name, diagnosis_description, diag_patient_id, diag_doctor_id)
    475 Procedures(procedure_id, procedure_type, procedure_sched_date, procedure_description, procedure_cost, proc_doctor_id, proc_diagnosis_id)
     486Procedures(procedure_id, procedure_type, procedure_sched_date, procedure_description, procedure_cost, proc_doctor_id)
    476487ProcedureResults(proc_result_id, proc_result_description, proc_result_date, procedure_id)
    477488PerformedProcedures(performed_id, perf_proc_id, perf_doctor_id, perf_patient_id, perf_diagnosis_id, perf_date, perf_notes)
     
    504515=== Decomposition into relations ===
    505516
    506 '''`Doctors`(doctor_id, d_first_name, d_last_name, d_email, level_id, specialization_id, department_id)'''
    507 
    508 R1 = 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)
     517'''`Doctors`(doctor_id, d_first_name, d_last_name, d_email, level_id, specialization_id, department_id, user_id)'''
     518
     519R1 = R - {d_first_name, d_last_name, d_email} (level_id, specialization_id, department_id, user_id remain in R since they are needed as determinants for steps 2-4 and for `Users`)
    509520
    510521Lossless 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`.
     
    536547Dependency preservation: F3 preserved entirely in `Departments`.
    537548
    538 '''`Patients`(patient_id, p_first_name, p_last_name, p_email, date_of_birth, blood_type, gender, phone_number, embg)'''
    539 
    540 R5 = 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)
     549'''`Patients`(patient_id, p_first_name, p_last_name, p_email, date_of_birth, blood_type, gender, phone_number, embg, user_id)'''
     550
     551R5 = 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; user_id remains - needed for `Users`)
    541552
    542553Lossless join: Shared column `patient_id`. `patient_id -> Patients` holds (F5).
     
    544555Dependency preservation: F5 preserved entirely in `Patients`.
    545556
    546 '''`Admin`(admin_id, admin_username, admin_name, admin_lastname, admin_email)'''
    547 
    548 R6 = R5 - {admin_username, admin_name, admin_lastname, admin_email}
     557'''`Admin`(admin_id, admin_username, admin_name, admin_lastname, admin_email, user_id)'''
     558
     559R6 = R5 - {admin_username, admin_name, admin_lastname, admin_email} (user_id remains - needed for `Users`)
    549560
    550561Lossless join: Shared column `admin_id`. `admin_id -> Admin` holds (F6).
     
    552563Dependency preservation: F6 preserved entirely in `Admin`.
    553564
    554 '''`LabTechnician`(technician_id, tech_username, tech_name, tech_lastname, tech_email)'''
    555 
    556 R7 = R6 - {tech_username, tech_name, tech_lastname, tech_email}
     565'''`LabTechnician`(technician_id, tech_username, tech_name, tech_lastname, tech_email, user_id)'''
     566
     567R7 = R6 - {tech_username, tech_name, tech_lastname, tech_email} (user_id remains - needed for `Users`)
    557568
    558569Lossless join: Shared column `technician_id`. `technician_id -> LabTechnician` holds (F7).
     
    562573'''`Users`(user_id, users_username, users_password, users_role, users_first_name, users_last_name, is_active)'''
    563574
    564 R8 = R7 - {users_username, users_password, users_role, users_first_name, users_last_name, is_active}
    565 
    566 Lossless join: Shared column `user_id`. `user_id -> Users` holds (F8).
     575R8 = R7 - {users_username, users_password, users_role, users_first_name, users_last_name, is_active, user_id} (user_id itself is dropped here too, since once `Users` is split off, every remaining relation that needs it — `Doctors`, `Patients`, `Admin`, `LabTechnician` — already carries it as its own foreign key)
     576
     577Lossless join: Shared column `user_id`. `user_id -> Users` holds (F8), and `user_id` is already present in `Doctors`, `Patients`, `Admin`, and `LabTechnician` from steps 1, 5, 6, 7, so the join back is via any of those four tables' `user_id` column.
    567578
    568579Dependency preservation: F8 preserved entirely in `Users`.
     
    584595Dependency preservation: F10 preserved entirely in `Diagnoses`.
    585596
    586 '''`Procedures`(procedure_id, procedure_type, procedure_sched_date, procedure_description, procedure_cost, proc_doctor_id, proc_diagnosis_id)'''
    587 
    588 R11 = R10 - {procedure_type, procedure_sched_date, procedure_description, procedure_cost, proc_doctor_id, proc_diagnosis_id} (procedure_id remains - needed for ProcedureResults)
     597'''`Procedures`(procedure_id, procedure_type, procedure_sched_date, procedure_description, procedure_cost, proc_doctor_id)'''
     598
     599R11 = R10 - {procedure_type, procedure_sched_date, procedure_description, procedure_cost, proc_doctor_id} (procedure_id remains - needed for ProcedureResults)
    589600
    590601Lossless join: Shared column `procedure_id`. `procedure_id -> Procedures` holds (F11).
     
    722733----
    723734
    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.
     735'''Final result:''' after step 27, R27 = { doctor_id, admin_id, technician_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 15 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.
    725736
    726737Since 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.
     
    7317422NF 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:
    732743
    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`.
     744* `Doctors` keeps level_id, specialization_id, department_id, and user_id as foreign keys, but level_name, specialization_name, department_name, and the rest of the login account's own fields stay in `DoctorLevels`, `Specializations`, `Departments`, and `Users` respectively — they are never duplicated back into `Doctors`.
     745* `ProcedureResults` keeps only the foreign key procedure_id, the procedure's own procedure_type, procedure_cost, etc. stay in `Procedures`.
    735746* `LabResults` keeps only the foreign key test_id. test_name, test_description and test_cost stay in `LabTests`.
    736747* `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.
     
    746757|| `Specializations` || '''specialization_id''', alt. key `specialization_name` || Yes ||
    747758|| `Departments` || '''department_id''', alt. key `department_name` || Yes ||
    748 || `Doctors` || '''doctor_id''', alt. key `d_email` || Yes ||
    749 || `Patients` || '''patient_id''', alt. key `embg` || Yes ||
    750 || `Admin` || '''admin_id''', alt. keys `admin_username`, `admin_email` || Yes ||
    751 || `LabTechnician` || '''technician_id''', alt. keys `tech_username`, `tech_email` || Yes ||
     759|| `Doctors` || '''doctor_id''', alt. keys `d_email`, `user_id` || Yes ||
     760|| `Patients` || '''patient_id''', alt. keys `embg`, `user_id` || Yes ||
     761|| `Admin` || '''admin_id''', alt. keys `admin_username`, `admin_email`, `user_id` || Yes ||
     762|| `LabTechnician` || '''technician_id''', alt. keys `tech_username`, `tech_email`, `user_id` || Yes ||
    752763|| `Users` || '''user_id''', alt. key `users_username` || Yes ||
    753764|| `Appointments` || '''appointment_id''' || Yes ||
     
    771782|| `PrescriptionRecords` || '''{prescription_id, record_id}''' || Yes ||
    772783
    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.
     784`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. `user_id` in `Doctors`, `Patients`, `Admin`, and `LabTechnician` is a genuine alternate key: each carries its own `UNIQUE` constraint and is required (`NOT NULL`).
    774785
    775786No relation has a non-key attribute determining part of a candidate key without itself being a superkey, so the schema **satisfies BCNF**.
     
    779790== 4NF check ==
    780791
    781 BCNF 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 
    783 Four 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.
     792BCNF only removes anomalies caused by functional dependencies. This domain still has the twelve 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.
     793
     794Three 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.
     795
     796* A '''medical record''' can be linked to several diagnoses, several procedures, and several lab results, and each of those three lists grows and shrinks on its own. Adding another diagnosis to a record says nothing about how many procedures or lab results are attached to that same record. (A record's link to its doctors is not modeled as a separate many-to-many relationship in this schema — a doctor is reached only indirectly, through the diagnoses, procedures and reports already tied to that record — so there is no fourth competing group here.)
     797
     798* A '''procedure''' can be linked to several specializations qualified to perform it, several departments that perform it, and several bills it appears on.
    790799
    791800* A '''patient''' can be linked to several allergies and several symptoms, tracked completely separately from one another.
    792801
    793 This gives eleven multivalued dependencies:
     802This gives eight multivalued dependencies:
    794803
    795804{{{
    796805record_id     ->> diagnosis_id
    797 record_id     ->> doctor_id
    798806record_id     ->> procedure_id
    799807record_id     ->> lab_result_id
    800 
    801 diagnosis_id  ->> symptom_id
    802 diagnosis_id  ->> procedure_id
    803808
    804809procedure_id  ->> specialization_id
     
    810815}}}
    811816
    812 The 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 
    814 None 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.
     817The remaining four pairs, `{diagnosis_id, symptom_id}`, `{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 three groups above are.
     818
     819None 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 twelve pairs — the eight grouped above plus these four standalone ones — each gets their own table.
    815820
    816821----
     
    818823== MVD check ==
    819824
    820 Nothing 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.
     825Nothing is actually being split here. These twelve 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.
    821826
    822827=== Lossless join & dependency preservation per relation ===
     
    830835Dependency preservation: This relation carries no non-trivial FD of its own. Both attributes are needed together simply to identify a membership row.
    831836
    832 '''`DoctorMedicalRecords`(doctor_id, record_id)'''
    833 
    834 MVD: `record_id ->> doctor_id`
    835 
    836 Lossless join: `X = record_id`, `Y = doctor_id`. `record_id ->> doctor_id` holds, so `(record_id, doctor_id)` splits losslessly from the rest.
    837 
    838 Dependency preservation: This relation carries no non-trivial FD of its own. Both attributes are needed together simply to identify a membership row.
    839 
    840837'''`MedicalRecordProcedures`(record_id, procedure_id)'''
    841838
     
    854851Dependency preservation: This relation carries no non-trivial FD of its own. Both attributes are needed together simply to identify a membership row.
    855852
     853'''`SpecializationProcedures`(specialization_id, procedure_id)'''
     854
     855MVD: `procedure_id ->> specialization_id`
     856
     857Lossless join: `X = procedure_id`, `Y = specialization_id`. `procedure_id ->> specialization_id` holds, so `(procedure_id, specialization_id)` splits losslessly from the rest.
     858
     859Dependency preservation: This relation carries no non-trivial FD of its own. Both attributes are needed together simply to identify a membership row.
     860
     861'''`DepartmentProcedures`(department_id, procedure_id)'''
     862
     863MVD: `procedure_id ->> department_id`
     864
     865Lossless join: `X = procedure_id`, `Y = department_id`. `procedure_id ->> department_id` holds, so `(procedure_id, department_id)` splits losslessly from the rest.
     866
     867Dependency preservation: This relation carries no non-trivial FD of its own. Both attributes are needed together simply to identify a membership row.
     868
     869'''`BillingProcedures`(bill_id, procedure_id)'''
     870
     871MVD: `procedure_id ->> bill_id`
     872
     873Lossless join: `X = procedure_id`, `Y = bill_id`. `procedure_id ->> bill_id` holds, so `(procedure_id, bill_id)` splits losslessly from the rest.
     874
     875Dependency preservation: This relation carries no non-trivial FD of its own. Both attributes are needed together simply to identify a membership row.
     876
     877'''`PatientAllergies`(patient_id, allergy_id)'''
     878
     879MVD: `patient_id ->> allergy_id`
     880
     881Lossless join: `X = patient_id`, `Y = allergy_id`. `patient_id ->> allergy_id` holds, so `(patient_id, allergy_id)` splits losslessly from the rest.
     882
     883Dependency preservation: This relation carries no non-trivial FD of its own. Both attributes are needed together simply to identify a membership row.
     884
     885'''`PatientSymptoms`(patient_id, symptom_id)'''
     886
     887MVD: `patient_id ->> symptom_id`
     888
     889Lossless join: `X = patient_id`, `Y = symptom_id`. `patient_id ->> symptom_id` holds, so `(patient_id, symptom_id)` splits losslessly from the rest.
     890
     891Dependency preservation: This relation carries no non-trivial FD of its own. Both attributes are needed together simply to identify a membership row.
     892
     893----
     894
     895The remaining four 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:
     896
    856897'''`DiagnosisSymptoms`(diagnosis_id, symptom_id)'''
    857898
    858 MVD: `diagnosis_id ->> symptom_id`
    859 
    860 Lossless join: `X = diagnosis_id`, `Y = symptom_id`. `diagnosis_id ->> symptom_id` holds, so `(diagnosis_id, symptom_id)` splits losslessly from the rest.
    861 
    862 Dependency 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 
    866 MVD: `diagnosis_id ->> procedure_id`
    867 
    868 Lossless join: `X = diagnosis_id`, `Y = procedure_id`. `diagnosis_id ->> procedure_id` holds, so `(diagnosis_id, procedure_id)` splits losslessly from the rest.
    869 
    870 Dependency 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 
    874 MVD: `procedure_id ->> specialization_id`
    875 
    876 Lossless join: `X = procedure_id`, `Y = specialization_id`. `procedure_id ->> specialization_id` holds, so `(procedure_id, specialization_id)` splits losslessly from the rest.
    877 
    878 Dependency 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 
    882 MVD: `procedure_id ->> department_id`
    883 
    884 Lossless join: `X = procedure_id`, `Y = department_id`. `procedure_id ->> department_id` holds, so `(procedure_id, department_id)` splits losslessly from the rest.
    885 
    886 Dependency 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 
    890 MVD: `procedure_id ->> bill_id`
    891 
    892 Lossless join: `X = procedure_id`, `Y = bill_id`. `procedure_id ->> bill_id` holds, so `(procedure_id, bill_id)` splits losslessly from the rest.
    893 
    894 Dependency 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 
    898 MVD: `patient_id ->> allergy_id`
    899 
    900 Lossless join: `X = patient_id`, `Y = allergy_id`. `patient_id ->> allergy_id` holds, so `(patient_id, allergy_id)` splits losslessly from the rest.
    901 
    902 Dependency 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 
    906 MVD: `patient_id ->> symptom_id`
    907 
    908 Lossless join: `X = patient_id`, `Y = symptom_id`. `patient_id ->> symptom_id` holds, so `(patient_id, symptom_id)` splits losslessly from the rest.
    909 
    910 Dependency 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 
    914 The 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:
     899Lossless join: `{diagnosis_id, symptom_id}` is the only many-to-many link at either anchor (diagnosis no longer links to procedures directly), so it 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 `{diagnosis_id, symptom_id}`.
     900
     901Dependency preservation: This relation carries no non-trivial FD of its own. Both attributes are needed together simply to identify a membership row.
    915902
    916903'''`BillingLabTests`(bill_id, test_id)'''
     
    938925* {{{Specializations(specialization_id, specialization_name)}}}
    939926* {{{Departments(department_id, department_name)}}}
    940 * {{{Doctors(doctor_id, d_first_name, d_last_name, d_email, level_id, specialization_id, department_id)}}}
    941 * {{{Patients(patient_id, p_first_name, p_last_name, p_email, date_of_birth, blood_type, gender, phone_number, embg)}}}
    942 * {{{Admin(admin_id, admin_username, admin_name, admin_lastname, admin_email)}}}
    943 * {{{LabTechnician(technician_id, tech_username, tech_name, tech_lastname, tech_email)}}}
     927* {{{Doctors(doctor_id, d_first_name, d_last_name, d_email, level_id, specialization_id, department_id, user_id)}}}
     928* {{{Patients(patient_id, p_first_name, p_last_name, p_email, date_of_birth, blood_type, gender, phone_number, embg, user_id)}}}
     929* {{{Admin(admin_id, admin_username, admin_name, admin_lastname, admin_email, user_id)}}}
     930* {{{LabTechnician(technician_id, tech_username, tech_name, tech_lastname, tech_email, user_id)}}}
    944931* {{{Users(user_id, users_username, users_password, users_role, users_first_name, users_last_name, is_active)}}}
    945932* {{{Appointments(appointment_id, appt_patient_id, appt_doctor_id, appointment_date, appointment_time, appointment_status)}}}
    946933* {{{Diagnoses(diagnosis_id, diagnosis_name, diagnosis_description, diag_patient_id, diag_doctor_id)}}}
    947 * {{{Procedures(procedure_id, procedure_type, procedure_sched_date, procedure_description, procedure_cost, proc_doctor_id, proc_diagnosis_id)}}}
     934* {{{Procedures(procedure_id, procedure_type, procedure_sched_date, procedure_description, procedure_cost, proc_doctor_id)}}}
    948935* {{{ProcedureResults(proc_result_id, proc_result_description, proc_result_date, procedure_id)}}}
    949936* {{{PerformedProcedures(performed_id, perf_proc_id, perf_doctor_id, perf_patient_id, perf_diagnosis_id, perf_date, perf_notes)}}}
     
    963950* {{{PrescriptionRecords(prescription_id, record_id, dosage, frequency, duration, prescription_notes)}}}
    964951* {{{DiagnosisMedicalRecords(diagnosis_id, record_id)}}}
    965 * {{{DoctorMedicalRecords(doctor_id, record_id)}}}
    966952* {{{MedicalRecordProcedures(record_id, procedure_id)}}}
    967953* {{{MedicalRecordLabResults(record_id, lab_result_id)}}}
     
    969955* {{{PatientSymptoms(patient_id, symptom_id)}}}
    970956* {{{DiagnosisSymptoms(diagnosis_id, symptom_id)}}}
    971 * {{{DiagnosisProcedures(diagnosis_id, procedure_id)}}}
    972957* {{{SpecializationProcedures(specialization_id, procedure_id)}}}
    973958* {{{DepartmentProcedures(department_id, procedure_id)}}}
     
    980965== Conclusion ==
    981966
    982 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 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.
     967Decomposing the de-normalized relation from scratch, following only the formal rules through 1NF, 2NF, 3NF, BCNF, and 4NF, produced the same 39 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.