| 6 | | {{{ |
| 7 | | R = {univ_id, univ_name, univ_location, univ_is_private, fac_id, fac_name, fac_location, fac_study_field, prof_id, prof_name, prof_surname, prof_age, stud_id, stud_name, stud_surname, stud_location, stud_index, subj_id, subj_name, subj_semester, subj_credits, ss_id, enrollment_date, final_grade, status, absences_count, start_date, end_date} |
| 8 | | }}} |
| | 6 | === Initial Denormalized Relation === |
| | 7 | R( univ_id, univ_name, univ_location, univ_is_private, fac_id, fac_name, fac_location, fac_study_field, prof_id, prof_name, prof_surname, prof_age, stud_id, stud_name, stud_surname, stud_location, stud_index, subj_id, subj_name, subj_semester, subj_credits, ss_id, enrollment_date, final_grade, status, absences_count, start_date, end_date ) |
| 13 | | * FD1: univ_id -> {univ_name, univ_location, univ_is_private} |
| 14 | | * FD2: fac_id -> {fac_name, fac_location, fac_study_field, univ_id} |
| 15 | | * FD3: prof_id -> {prof_name, prof_surname, prof_age, fac_id} |
| 16 | | * FD4: stud_id -> {stud_name, stud_surname, stud_location, stud_index, fac_id} |
| 17 | | * FD5: subj_id -> {subj_name, subj_semester, subj_credits, fac_id} |
| 18 | | * FD6: ss_id -> {stud_id, subj_id, prof_id, enrollment_date, final_grade, status, absences_count} |
| 19 | | * FD7: {stud_id, prof_id, start_date} -> {end_date} |
| | 12 | * univ_id -> univ_name, univ_location, univ_is_private |
| | 13 | * fac_id -> fac_name, fac_location, fac_study_field, univ_id |
| | 14 | * prof_id -> prof_name, prof_surname, prof_age, fac_id |
| | 15 | * stud_id -> stud_name, stud_surname, stud_location, stud_index, fac_id |
| | 16 | * subj_id -> subj_name, subj_semester, subj_credits, fac_id |
| | 17 | * ss_id -> stud_id, subj_id, prof_id, enrollment_date, final_grade, status, absences_count |
| | 18 | * {stud_id, prof_id, start_date} -> end_date |
| 28 | | Let us test the composite set K = {ss_id, start_date, univ_id}: |
| | 28 | === Minimality Check === |
| | 29 | * Removing ss_id loses the student enrollment log branch, leaving grades and statuses completely undetermined. |
| | 30 | * Removing start_date loses the advice/mentorship timeline branch, making it impossible to determine the end_date. |
| | 31 | * Removing univ_id loses the university core branding and metadata branch. |
| | 32 | Therefore, every attribute within the composite set K is strictly required, proving the key is minimal. |
| 32 | | K^0 = {ss_id, start_date, univ_id} |
| 33 | | * **Step 2 (Apply FD1):** univ_id -> {univ_name, univ_location, univ_is_private} |
| | 39 | == First Normal Form (1NF) == |
| | 40 | === Definition === |
| | 41 | A relation is in 1NF when: |
| | 42 | * Row ordering carries no meaning |
| | 43 | * Every attribute contains atomic values |
| | 44 | * Data types are consistent |
| | 45 | * A primary key uniquely identifies each tuple |
| | 46 | * No repeating groups exist |
| 35 | | K^1 = {ss_id, start_date, univ_id, univ_name, univ_location, univ_is_private} |
| 36 | | * **Step 3 (Apply FD6):** ss_id -> {stud_id, subj_id, prof_id, enrollment_date, final_grade, status, absences_count} |
| | 48 | === Transformation to 1NF === |
| | 49 | The original denormalized relation contains multiple conceptual entities combined into one structure. Repeating groups are eliminated by separating repeating occurrences into individual tuples. All attributes contain strictly atomic values, satisfying the baseline 1NF constraints. |
| 38 | | K^2 = {ss_id, start_date, univ_id, univ_name, univ_location, univ_is_private, stud_id, subj_id, prof_id, enrollment_date, final_grade, status, absences_count} |
| 39 | | * **Step 4 (Apply FD3, FD4, FD5):** prof_id, stud_id, and subj_id extract their respective metadata: |
| | 51 | == Transition to Second Normal Form (2NF) == |
| | 52 | To achieve 2NF: |
| | 53 | * Partial dependencies must be systematically removed. |
| | 54 | * Attributes depending only on a part of a composite key must be separated into independent relations. |
| 41 | | K^3 = K^2 + {prof_name, prof_surname, prof_age, fac_id, stud_name, stud_surname, stud_location, stud_index, subj_name, subj_semester, subj_credits} |
| 42 | | * **Step 5 (Apply FD2):** fac_id -> {fac_name, fac_location, fac_study_field, univ_id} |
| | 56 | == Second Normal Form (2NF) Decomposition == |
| 47 | | K^5 = K^4 + {end_date} |
| | 61 | * '''Candidate Key Verification:''' univ_id is the surrogate primary key. No partial dependencies exist. |
| | 62 | * '''Sample Data:''' |
| | 63 | || univ_id || univ_name || univ_location || univ_is_private || |
| | 64 | || 1 || UKIM || Skopje || False || |
| | 65 | || 2 || UGD || Shtip || False || |
| | 66 | * '''Lossless Join Test:''' |
| | 67 | R intersection R1 = {univ_id, univ_name, univ_location, univ_is_private} |
| | 68 | Since univ_id -> R1, the decomposition is lossless. |
| 51 | | === Minimality Proof === |
| 52 | | * If we remove ss_id, the closure {start_date, univ_id}+ only yields {start_date, univ_id, univ_name, univ_location, univ_is_private}, failing to find any student, professor, or core entity data. |
| 53 | | * If we remove start_date, the closure cannot resolve end_date via FD7. |
| 54 | | * If we remove univ_id, the closure cannot resolve university and faculty metadata. |
| | 73 | * '''Candidate Key Verification:''' fac_id is the surrogate primary key. |
| | 74 | * '''Sample Data:''' |
| | 75 | || fac_id || fac_name || fac_location || fac_study_field || univ_id || |
| | 76 | || 1 || FINKI || Skopje || Computer Science || 1 || |
| | 77 | || 2 || Praven || Shtip || Law || 2 || |
| | 78 | * '''Lossless Join Test:''' |
| | 79 | R1.1 intersection R2 = {fac_id, fac_name, fac_location, fac_study_field, univ_id} |
| | 80 | Since fac_id -> R2, the decomposition is lossless. |
| 58 | | '''Candidate Key:''' {ss_id, start_date, univ_id} |
| 59 | | '''Primary Key:''' {ss_id, start_date, univ_id} |
| | 85 | * '''Candidate Key Verification:''' stud_id is the primary key. |
| | 86 | * '''Sample Data:''' |
| | 87 | || stud_id || stud_name || stud_surname || stud_location || stud_index || fac_id || |
| | 88 | || 1 || Elena || Petrova || Skopje || 211001 || 1 || |
| | 89 | || 2 || Marko || Jovanov || Veles || 222045 || 1 || |
| | 90 | * '''Lossless Join Test:''' |
| | 91 | R2.1 intersection R3 = {stud_id, stud_name, stud_surname, stud_location, stud_index, fac_id} |
| | 92 | Since stud_id -> R3, the decomposition is lossless. |
| 63 | | == 2NF decomposition == |
| 64 | | '''Analysis:''' A relation is in 2NF if it is in 1NF and no non-prime attribute is partially dependent on any candidate key. We must systematically check and extract every partial dependency violation. |
| | 97 | * '''Candidate Key Verification:''' prof_id is the primary key. |
| | 98 | * '''Sample Data:''' |
| | 99 | || prof_id || prof_name || prof_surname || prof_age || fac_id || |
| | 100 | || 1 || Ivan || Petrov || 45 || 1 || |
| | 101 | || 2 || Ana || Markoska || 38 || 1 || |
| | 102 | * '''Lossless Join Test:''' |
| | 103 | R3.1 intersection R4 = {prof_id, prof_name, prof_surname, prof_age, fac_id} |
| | 104 | Since prof_id -> R4, the decomposition is lossless. |
| 68 | | * '''Step 1: Isolation of University Entity (Resolving FD1)''' |
| 69 | | * *Violation:* The non-prime attributes {univ_name, univ_location, univ_is_private} depend entirely on univ_id, which is a proper subset of the primary key {ss_id, start_date, univ_id}. |
| 70 | | * *Action:* Separate into a distinct relation. |
| 71 | | * *Result:* '''R1 (University):''' {univ_id, univ_name, univ_location, univ_is_private} (Key: univ_id) |
| | 109 | * '''Candidate Key Verification:''' subj_id is the primary key. |
| | 110 | * '''Sample Data:''' |
| | 111 | || subj_id || subj_name || subj_semester || subj_credits || fac_id || |
| | 112 | || 1 || Databases || Summer || 6 || 1 || |
| | 113 | || 2 || APS || Winter || 6 || 1 || |
| | 114 | * '''Lossless Join Test:''' |
| | 115 | R4.1 intersection R5 = {subj_id, subj_name, subj_semester, subj_credits, fac_id} |
| | 116 | Since subj_id -> R5, the decomposition is lossless. |
| 73 | | * '''Step 2: Isolation of Faculty Entity (Resolving FD2)''' |
| 74 | | * *Violation:* Faculty metadata attributes depend transitively through the hierarchy via fac_id. |
| 75 | | * *Action:* Extract faculty structural descriptors to stop multi-attribute replication. |
| 76 | | * *Result:* '''R2 (Faculty):''' {fac_id, fac_name, fac_location, fac_study_field, univ_id} (Key: fac_id) |
| | 118 | === R6 - STUDENT_SUBJECT === |
| | 119 | {ss_id} -> {stud_id, subj_id, prof_id, enrollment_date, final_grade, status, absences_count} |
| 78 | | * '''Step 3: Isolation of Student Entity (Resolving FD4)''' |
| 79 | | * *Violation:* {stud_name, stud_surname, stud_location, stud_index, fac_id} depend purely on stud_id, which is determined via ss_id (a subset of the primary key's downstream components). This creates an implicit partial dependency in the flattened schema. |
| 80 | | * *Action:* Move student profile vectors to a dedicated entity table. |
| 81 | | * *Result:* '''R3 (Student):''' {stud_id, stud_name, stud_surname, stud_location, stud_index, fac_id} (Key: stud_id) |
| | 121 | * '''Candidate Key Verification:''' ss_id is the unique primary key log identifier. |
| | 122 | * '''Sample Data:''' |
| | 123 | || ss_id || stud_id || subj_id || prof_id || enrollment_date || final_grade || status || absences_count || |
| | 124 | || 1 || 1 || 1 || 1 || 2026-02-15 || 10 || Active || 2 || |
| | 125 | || 2 || 2 || 2 || 2 || 2026-02-15 || 8 || Active || 0 || |
| | 126 | * '''Lossless Join Test:''' |
| | 127 | R5.1 intersection R6 = {ss_id, stud_id, subj_id, prof_id, enrollment_date, final_grade, status, absences_count} |
| | 128 | Since ss_id -> R6, the decomposition is lossless. |
| 83 | | * '''Step 4: Isolation of Professor Entity (Resolving FD3)''' |
| 84 | | * *Violation:* {prof_name, prof_surname, prof_age, fac_id} depend directly on prof_id. |
| 85 | | * *Action:* Isolate professor demographics away from the transaction logs. |
| 86 | | * *Result:* '''R4 (Professor):''' {prof_id, prof_name, prof_surname, prof_age, fac_id} (Key: prof_id) |
| | 130 | === R7 - ADVICE === |
| | 131 | {stud_id, prof_id, start_date} -> {end_date} |
| 88 | | * '''Step 5: Isolation of Subject Entity (Resolving FD5)''' |
| 89 | | * *Violation:* Curriculum properties {subj_name, subj_semester, subj_credits, fac_id} depend entirely on subj_id. |
| 90 | | * *Action:* Isolate course syllabus metadata. |
| 91 | | * *Result:* '''R5 (Subject):''' {subj_id, subj_name, subj_semester, subj_credits, fac_id} (Key: subj_id) |
| | 133 | * '''Candidate Key Verification:''' Composite primary key consisting of {stud_id, prof_id, start_date}. |
| | 134 | * '''Sample Data:''' |
| | 135 | || stud_id || prof_id || start_date || end_date || |
| | 136 | || 1 || 1 || 2026-07-01 || 2026-12-31 || |
| | 137 | || 2 || 2 || 2026-07-01 || 2026-12-31 || |
| | 138 | * '''Lossless Join Test:''' |
| | 139 | R6.1 intersection R7 = {stud_id, prof_id, start_date, end_date} |
| | 140 | Since {stud_id, prof_id, start_date} -> R7, the decomposition is lossless. |
| 93 | | * '''Step 6: Isolation of Multi-Attribute Weak Log Relations (Resolving FD6 and FD7)''' |
| 94 | | * *Action:* Separate transactional operational mappings into clean associative tables matching the remaining prime components. |
| 95 | | * *Result (Enrollments):* '''R6 (Student_Subject):''' {ss_id, stud_id, subj_id, prof_id, enrollment_date, final_grade, status, absences_count} (Key: ss_id) |
| 96 | | * *Result (Mentorship):* '''R7 (Advice):''' {stud_id, prof_id, start_date, end_date} (Key: {stud_id, prof_id, start_date}) |
| 97 | | * *Result (Affiliation):* '''R8 (Affiliated):''' {univ_id, prof_id} (Key: {univ_id, prof_id}) |
| | 142 | === R8 - AFFILIATED === |
| | 143 | {univ_id, prof_id} -> No non-key attributes. |
| 104 | | == BCNF if possible == |
| 105 | | '''Analysis:''' A relation is in BCNF if for every non-trivial functional dependency X -> Y, X is a superkey. In all the decomposed schemas (including the junction tables R6, R7, and R8), the left-hand side of every functional dependency acts as a superkey for that specific relation. Therefore, the schema satisfies BCNF requirements without needing further structural splits. |
| | 161 | === Transition from 2NF to 3NF === |
| | 162 | During the 2NF phase, transitive paths like stud_id -> fac_id -> univ_id were systematically resolved by isolating R2 (Faculty) and R3 (Student). Thus, fac_id acts purely as a direct atomic foreign key constraint. There are no remaining non-key dependencies on other non-key values, meaning the schema satisfies 3NF parameters naturally. |
| 111 | | * '''University''' (Id, Name, Location, IsPrivate) |
| 112 | | * '''Faculty''' (Id, Name, Location, study_field, University_Id) |
| 113 | | * '''Professor''' (Id, Name, Surname, Age, Faculty_Id) |
| 114 | | * '''Student''' (Id, Name, Surname, Location, Student_Index, Faculty_Id) |
| 115 | | * '''Subject''' (Id, Name, Semester, Credits, Faculty_Id) |
| 116 | | * '''Student_Subject''' (Ss_Id, Student_Id, Subject_Id, Professor_Id, Enrollment_Date, Status, Final_Grade, Absences_Count) |
| 117 | | * '''Advice''' (Student_Id, Professor_Id, Start_Date, End_Date) |
| 118 | | * '''Affiliated''' (University_Id, Professor_Id) |
| | 168 | == Final Schema Summary == |
| | 169 | || Relation || Primary Key || Foreign Keys || Normal Form || |
| | 170 | || UNIVERSITY || univ_id || — || 3NF / BCNF || |
| | 171 | || FACULTY || fac_id || univ_id -> UNIVERSITY || 3NF / BCNF || |
| | 172 | || STUDENT || stud_id || fac_id -> FACULTY || 3NF / BCNF || |
| | 173 | || PROFESSOR || prof_id || fac_id -> FACULTY || 3NF / BCNF || |
| | 174 | || SUBJECT || subj_id || fac_id -> FACULTY || 3NF / BCNF || |
| | 175 | || STUDENT_SUBJECT || ss_id || stud_id -> STUDENT, subj_id -> SUBJECT, prof_id -> PROFESSOR || 3NF / BCNF || |
| | 176 | || ADVICE || stud_id, prof_id, start_date || stud_id -> STUDENT, prof_id -> PROFESSOR || 3NF / BCNF || |
| | 177 | || AFFILIATED || univ_id, prof_id || univ_id -> UNIVERSITY, prof_id -> PROFESSOR || 3NF / BCNF || |
| 130 | | |
| 131 | | '''Final result:''' I paired with the AI assistant to mathematically audit my step-by-step relational decompositions. It helped me verify that my universal relation R was properly split across 1NF, 2NF, 3NF, and BCNF boundaries, ensuring full dependency preservation and lossless joins without changing my design logic. |
| 132 | | |
| 133 | | '''Results in details / description:''' |
| 134 | | * '''Functional Dependency Validation:''' I formulated the initial canonical cover rules mapping entity relations (like tracking how fac_id resolves faculty metadata and stud_id maps indexes). The AI reviewed my dependency list to confirm no logical transitivity loops were left unaddressed. |
| 135 | | * '''Decomposition Step Auditing:''' I executed the structural extraction process to pull partial dependencies out of 1NF. The AI double-checked my 2NF math to ensure that composite key subsets (such as tracking multi-part student enrollment histories) were split into clean tables without dropping core variables. |
| 136 | | * '''BCNF Condition Checking:''' I tested the resulting intersection schemas against strict Boyce-Codd Normal Form constraints. The AI verified my observation that because every determinant field behaves as a functional superkey, the tables already meet BCNF requirements naturally. |
| 137 | | * '''Syntax Cleanliness:''' The AI helped verify that my notation, set brackets, and relational groupings matched the standard formats expected for our database engineering submission. |
| 138 | | |
| 139 | | '''Entire AI usage log:''' |
| 140 | | |
| 141 | | '''User:''' I have aggregated all the fields from my university ER diagram into a single universal relation R and mapped out my functional dependencies. Can you check my canonical cover list to verify if I missed any partial or transitive dependencies before I start the 2NF decomposition? |
| 142 | | |
| 143 | | '''AI:''' Reviewed your functional dependency list. The dependencies you mapped out are mathematically sound and align perfectly with your business constraints. The subset keys for student attributes, professor bio-data, and subject specs are clearly defined, confirming that your universal relation is currently at a baseline 1NF due to these massive partial dependencies. |
| 144 | | |
| 145 | | '''User:''' Excellent. I am running the 2NF decomposition now by breaking the universal set into 8 separate relations to isolate fields that rely on single identifiers versus composite keys. Can you look at my resulting tables (R1 through R8) to confirm that this breakdown eliminates the partial dependency anomalies? |
| 146 | | |
| 147 | | '''AI:''' Assisted by validating your 2NF steps. Your extraction strategy successfully isolates the distinct entities. Moving things like student course enrollment tracking details into R6 (Student_Subject) and advisor periods into R7 (Advice) completely isolates the partial overlaps, leaving all non-key fields fully dependent on whole primary keys. |
| 148 | | |
| 149 | | '''User:''' For 3NF and BCNF, since my non-key attributes are now solely determined by superkeys or primary keys across the junction configurations, I concluded that the schema passes both tests without further changes. Can you double-check my reasoning? |
| 150 | | |
| 151 | | '''AI:''' Audited your conclusion. Your analysis is entirely correct. Since your faculty relations were already moved to R5 to prevent transitive student loops, and every remaining determinant in your intersection tables functions directly as a superkey, the layout satisfies BCNF automatically. |
| 152 | | |
| 153 | | '''User:''' Great. Can you help format this complete normalization narrative and table list into clear Trac Wiki text headings so I can update my project space? |
| 154 | | |
| 155 | | '''AI:''' Structured the final results, analysis breakdowns, and discussion commentary into compliant wiki blocks for direct integration into your Phase 5 layout. |
| | 186 | '''Final result:''' Verified formal closures, mathematical integrity of keys, and formatting alignments for the Trac Wiki interface. |