| 5 | | Се тргнува од една глобална, де-нормализирана релација што ги содржи атрибутите од целиот модел: |
| 6 | | |
| 7 | | `R = { user_id, email, username, password, training_user_id, training_gender, training_age, training_weight, training_id, training_date, training_type, training_duration, training_calories, investor_user_id, asset_id, asset_ticker_symbol, asset_buy_price, asset_buy_date, asset_quantity, weight_user_id, weight_current, weight_height, weight_goal_weight, weight_goal_calories, daily_intake_id, daily_intake_date, daily_intake_calories, discipline_user_id, custom_tracking_id, custom_tracking_name, task_id, task_name, task_is_finished, daily_completion_id, daily_completion_date, daily_completion_procent, finance_user_id, finance_spending_budget, finance_saving_budget, finance_investing_budget, finance_donation_budget, finance_credit, income_id, income_date, income_amount }` |
| 8 | | |
| 9 | | **Напомена:** Атрибутите `num_tasks` и `tasks` се отстранети уште од првата нормализација бидејќи се изведени/не-атомски вредности и не припаѓаат на релацискиот модел. Слично, `weight_user_id` во `TRAINING_SESSIONS` е отстранет бидејќи претставува непотребна вкрстена зависност меѓу два независни профили на ист корисник. |
| | 5 | Се тргнува од една глобална, де-нормализирана релација што ги содржи атрибутите од целиот модел. Атрибутите со исто логичко значење но различно потекло се именуваат со префикс за да се избегне колизија на имиња во рамките на глобалната релација (на пример `training_weight` vs `weight_current`, `training_date` vs `daily_intake_date`): |
| | 6 | |
| | 7 | `R = { user_id, email, username, password, training_gender, training_age, training_weight, training_id, training_user_id, training_date, training_type, training_duration, training_calories, asset_id, asset_ticker_symbol, asset_buy_price, asset_buy_date, asset_quantity, weight_current, weight_height, weight_goal_weight, weight_goal_calories, daily_intake_id, daily_intake_date, daily_intake_calories, discipline_user_id, custom_tracking_id, custom_tracking_name, task_id, task_name, task_is_finished, daily_completion_id, daily_completion_date, daily_completion_procent, finance_spending_budget, finance_saving_budget, finance_investing_budget, finance_donation_budget, finance_credit, income_id, income_date, income_amount }` |
| | 8 | |
| | 9 | **Напомена:** Атрибутите `num_tasks` и `tasks` се отстранети уште од првата нормализација бидејќи се изведени/не-атомски вредности и не припаѓаат на релацискиот модел. Атрибутот `training_user_id` е реална FK колона во `TRAINING_SESSIONS` (References `TRAINING_USERS.user_id`) и затоа се наоѓа во R. Атрибутот `discipline_user_id` е реална FK колона во `TASKS` (References `DISCIPLINE_USERS.user_id`) и исто така се наоѓа во R. Сите профилски табели (`TRAINING_USERS`, `WEIGHT_USERS`, `INVESTOR_USERS`, `DISCIPLINE_USERS`, `FINANCE_USERS`) го користат `user_id` и како примарен и како надворешен клуч — затоа не постојат одделни атрибути `training_user_id`, `weight_user_id` итн. во тие табели. |
| 16 | | - **FD2:** `email -> user_id` |
| 17 | | - **FD3:** `username -> user_id` |
| 18 | | - **FD4:** `training_user_id -> training_gender, training_age, training_weight` |
| 19 | | - **FD5:** `training_id -> training_user_id, training_date, training_type, training_duration, training_calories` |
| 20 | | - **FD6:** `investor_user_id -> user_id` |
| 21 | | - **FD7:** `asset_id -> investor_user_id, asset_ticker_symbol, asset_buy_price, asset_buy_date, asset_quantity` |
| 22 | | - **FD8:** `weight_user_id -> weight_current, weight_height, weight_goal_weight, weight_goal_calories` |
| 23 | | - **FD9:** `daily_intake_id -> weight_user_id, daily_intake_date, daily_intake_calories` |
| 24 | | - **FD10:** `discipline_user_id -> user_id` |
| 25 | | - **FD11:** `custom_tracking_id -> user_id, custom_tracking_name` |
| 26 | | - **FD12:** `task_id -> discipline_user_id, custom_tracking_id, task_name, task_is_finished` |
| 27 | | - **FD13:** `daily_completion_id -> user_id, daily_completion_date, daily_completion_procent` |
| 28 | | - **FD14:** `(task_id, daily_completion_id) -> /` (нема дополнителни атрибути — само врска) |
| 29 | | - **FD15:** `finance_user_id -> finance_spending_budget, finance_saving_budget, finance_investing_budget, finance_donation_budget, finance_credit` |
| 30 | | - **FD16:** `income_id -> finance_user_id, income_date, income_amount` |
| | 16 | - **FD2:** `user_id -> training_gender, training_age, training_weight` |
| | 17 | - **FD3:** `training_user_id -> user_id` |
| | 18 | - **FD4:** `training_id -> training_user_id, training_date, training_type, training_duration, training_calories` |
| | 19 | - **FD5:** `user_id -> weight_current, weight_height, weight_goal_weight, weight_goal_calories` |
| | 20 | - **FD6:** `daily_intake_id -> user_id, daily_intake_date, daily_intake_calories` |
| | 21 | - **FD7:** `asset_id -> user_id, asset_ticker_symbol, asset_buy_price, asset_buy_date, asset_quantity` |
| | 22 | - **FD8:** `discipline_user_id -> user_id` |
| | 23 | - **FD9:** `custom_tracking_id -> user_id, custom_tracking_name` |
| | 24 | - **FD10:** `task_id -> discipline_user_id, custom_tracking_id, task_name, task_is_finished` |
| | 25 | - **FD11:** `daily_completion_id -> user_id, daily_completion_date, daily_completion_procent` |
| | 26 | - **FD12:** `(task_id, daily_completion_id) -> /` (нема дополнителни атрибути — само врска) |
| | 27 | - **FD13:** `user_id -> finance_spending_budget, finance_saving_budget, finance_investing_budget, finance_donation_budget, finance_credit` |
| | 28 | - **FD14:** `income_id -> user_id, income_date, income_amount` |
| 59 | | Пробуваме со комбинација на сите атрибути што се само леви + по еден претставник од групите поврзани со `user_id`: |
| 60 | | |
| 61 | | `K = { user_id, training_id, asset_id, daily_intake_id, task_id, daily_completion_id, income_id, training_user_id, investor_user_id, weight_user_id, discipline_user_id, custom_tracking_id, finance_user_id }` |
| 62 | | |
| 63 | | **`K+` пресметување:** |
| 64 | | - `user_id` -> `email, username, password` (FD1) |
| 65 | | - `training_user_id` -> `training_gender, training_age, training_weight` (FD4) |
| 66 | | - `training_id` -> `training_user_id, training_date, training_type, training_duration, training_calories` (FD5) |
| 67 | | - `investor_user_id` -> `user_id` (FD6) |
| 68 | | - `asset_id` -> `investor_user_id, asset_ticker_symbol, asset_buy_price, asset_buy_date, asset_quantity` (FD7) |
| 69 | | - `weight_user_id` -> `weight_current, weight_height, weight_goal_weight, weight_goal_calories` (FD8) |
| 70 | | - `daily_intake_id` -> `weight_user_id, daily_intake_date, daily_intake_calories` (FD9) |
| 71 | | - `discipline_user_id` -> `user_id` (FD10) |
| 72 | | - `custom_tracking_id` -> `user_id, custom_tracking_name` (FD11) |
| 73 | | - `task_id` -> `discipline_user_id, custom_tracking_id, task_name, task_is_finished` (FD12) |
| 74 | | - `daily_completion_id` -> `user_id, daily_completion_date, daily_completion_procent` (FD13) |
| 75 | | - `finance_user_id` -> `finance_spending_budget, finance_saving_budget, finance_investing_budget, finance_donation_budget, finance_credit` (FD15) |
| 76 | | - `income_id` -> `finance_user_id, income_date, income_amount` (FD16) |
| 77 | | |
| 78 | | `K+ = R` → **`K` е суперклуч** ✓ |
| | 57 | Покривачи: |
| | 58 | |
| | 59 | 1. training_id+ -> {training_id, training_user_id, training_date, training_type, training_duration, training_calories} via FD4 -> {user_id} via FD3 -> {email, username, password} via FD1 -> {training_gender, training_age, training_weight} via FD2 -> {weight_current, weight_height, weight_goal_weight, weight_goal_calories} via FD5 -> {finance_spending_budget, ..., finance_credit} via FD13 -> **Не ги содржи сите атрибути** |
| | 60 | 2. asset_id+ -> {asset_id, user_id, asset_ticker_symbol, asset_buy_price, asset_buy_date, asset_quantity} via FD7 -> {email, username, password, training_gender, ..., weight_current, ..., finance_...} via FD1,FD2,FD5,FD13 -> **Не ги содржи сите атрибути** |
| | 61 | 3. daily_intake_id+ -> {daily_intake_id, user_id, daily_intake_date, daily_intake_calories} via FD6 -> {email, username, password, ...} via FD1,FD2,FD5,FD13 -> **Не ги содржи сите атрибути** |
| | 62 | 4. task_id+ -> {task_id, discipline_user_id, custom_tracking_id, task_name, task_is_finished} via FD10 -> {user_id} via FD8 -> {user_id, custom_tracking_name} via FD9 -> {email, ...} via FD1,FD2,FD5,FD13 -> **Не ги содржи сите атрибути** |
| | 63 | 5. daily_completion_id+ -> {daily_completion_id, user_id, daily_completion_date, daily_completion_procent} via FD11 -> {email, ...} via FD1,FD2,FD5,FD13 -> **Не ги содржи сите атрибути** |
| | 64 | 6. income_id+ -> {income_id, user_id, income_date, income_amount} via FD14 -> {email, ...} via FD1,FD2,FD5,FD13 -> **Не ги содржи сите атрибути** |
| | 65 | |
| | 66 | Бидејќи атрибутите кои се наоѓаат само на левата страна не може да се изведат на никој друг начин, тие мора да бидат дел од примарниот клуч. |
| | 67 | |
| | 68 | {training_id, asset_id, daily_intake_id, task_id, daily_completion_id, income_id}+ -> |
| | 69 | {training_id, training_user_id, training_date, training_type, training_duration, training_calories, |
| | 70 | asset_id, user_id, asset_ticker_symbol, asset_buy_price, asset_buy_date, asset_quantity, |
| | 71 | email, username, password, |
| | 72 | training_gender, training_age, training_weight, |
| | 73 | weight_current, weight_height, weight_goal_weight, weight_goal_calories, |
| | 74 | daily_intake_id, daily_intake_date, daily_intake_calories, |
| | 75 | discipline_user_id, custom_tracking_id, custom_tracking_name, task_id, task_name, task_is_finished, |
| | 76 | daily_completion_id, daily_completion_date, daily_completion_procent, |
| | 77 | finance_spending_budget, finance_saving_budget, finance_investing_budget, finance_donation_budget, finance_credit, |
| | 78 | income_id, income_date, income_amount} |
| | 79 | |
| | 80 | Покривачот составен од сите атрибути кои се наоѓаат само од левата страна ги содржи сите атрибути на глобалната релација и претставува минимален суперклуч, па затоа го избираме за примарен клуч. |
| | 81 | |
| | 82 | Избран примарен клуч: **К = {training_id, asset_id, daily_intake_id, task_id, daily_completion_id, income_id}** |
| 110 | | Релацијата `R` **НЕ ја задоволува 1НФ** поради: |
| 111 | | 1. **Повторливи групи** — еден корисник има повеќе `training_sessions`, повеќе `incomes`, повеќе `assets` итн., што значи дека редот би морал да се повторува или атрибутите би биле листи. |
| 112 | | 2. **Не-атомски атрибути** — во оригиналниот дизајн постоеја `tasks` (`TEXT` листа) и `num_tasks` (изведена вредност) во `DISCIPLINE_USERS` и `CUSTOM_TRACKING_CATEGORIES`. |
| 113 | | |
| 114 | | == **Декомпозиција по 1НФ** == |
| 115 | | |
| 116 | | **Релација што се анализира:** `R` (глобална) |
| 117 | | |
| 118 | | **Проблем:** Повторливи групи и не-атомски атрибути. |
| 119 | | |
| 120 | | **Решение:** Секој ентитет и врска добива своја посебна релација со атомски атрибути и локален примарен клуч. |
| 121 | | |
| 122 | | **Посебен случај — `TASK_DAILY_COMPLETION`:** |
| 123 | | Задачите и дневните завршувања се во врска **многу-кон-многу**. Се воведува посредна релација: |
| 124 | | |
| 125 | | TASK_DAILY_COMPLETION(task_id, daily_completion_id) |
| 126 | | |
| 127 | | |
| 128 | | **Резултат по 1НФ декомпозиција:** |
| 129 | | - `R1: USERS(user_id, email, username, password)` |
| 130 | | - `R2: TRAINING_USERS(training_user_id, training_gender, training_age, training_weight)` |
| 131 | | - `R3: TRAINING_SESSIONS(training_id, training_user_id, training_date, training_type, training_duration, training_calories)` |
| 132 | | - `R4: INVESTOR_USERS(investor_user_id)` |
| 133 | | - `R5: ASSETS(asset_id, investor_user_id, asset_ticker_symbol, asset_buy_price, asset_buy_date, asset_quantity)` |
| 134 | | - `R6: WEIGHT_USERS(weight_user_id, weight_current, weight_height, weight_goal_weight, weight_goal_calories)` |
| 135 | | - `R7: DAILY_INTAKES(daily_intake_id, weight_user_id, daily_intake_date, daily_intake_calories)` |
| 136 | | - `R8: DISCIPLINE_USERS(discipline_user_id)` |
| 137 | | - `R9: CUSTOM_TRACKING_CATEGORIES(custom_tracking_id, user_id, custom_tracking_name)` |
| 138 | | - `R10: TASKS(task_id, discipline_user_id, custom_tracking_id, task_name, task_is_finished)` |
| 139 | | - `R11: DAILY_COMPLETION(daily_completion_id, user_id, daily_completion_date, daily_completion_procent)` |
| 140 | | - `R12: TASK_DAILY_COMPLETION(task_id, daily_completion_id)` |
| 141 | | - `R13: FINANCE_USERS(finance_user_id, finance_spending_budget, finance_saving_budget, finance_investing_budget, finance_donation_budget, finance_credit)` |
| 142 | | - `R14: INCOMES(income_id, finance_user_id, income_date, income_amount)` |
| 143 | | |
| 144 | | - Сите атрибути се атомски. ✓ |
| 145 | | - Нема повторливи групи. ✓ |
| | 88 | Бидејќи релацијата не содржи повеќевредносни атрибути, неуникатни атрибути, вредности од различен домен за еден атрибут, а притоа секоја редица е уникатно определена преку примарниот клуч, **ја задоволува 1НФ**. |
| 151 | | Парцијална зависност постои само кога примарниот клуч е **сложен**. Ги разгледуваме само релациите со сложен `PK`: |
| 152 | | |
| 153 | | **`R12: TASK_DAILY_COMPLETION(task_id, daily_completion_id)`** |
| 154 | | - `PK: {task_id, daily_completion_id}` |
| 155 | | - Нема не-клучни атрибути → нема парцијални зависности → во 2НФ ✓ |
| 156 | | |
| 157 | | Сите останати релации (`R1`–`R11`, `R13`–`R14`) имаат **прост примарен клуч**, па парцијална зависност е невозможна → автоматски во 2НФ ✓ |
| 158 | | |
| 159 | | === **2NF проверка по релации (по 1НФ декомпозицијата)** === |
| 160 | | |
| 161 | | | Релација | PK | Дали PK е сложен? | Не-клучни атрибути | Парцијална зависност? | 2НФ? | |
| 162 | | |----------|-----|------------------|-------------------|----------------------|------| |
| 163 | | | USERS | user_id | Не | email, username, password | Невозможна | ✓ | |
| 164 | | |
| 165 | | | TRAINING_USERS | training_user_id | Не | gender, age, weight | Невозможна | ✓ | |
| 166 | | |
| 167 | | | TRAINING_SESSIONS | training_id | Не | user_id, date, type, duration, calories | Невозможна | ✓ | |
| 168 | | |
| 169 | | | INVESTOR_USERS | investor_user_id | Не | (нема) | Невозможна | ✓ | |
| 170 | | |
| 171 | | | ASSETS | asset_id | Не | user_id, ticker_symbol, buy_price, buy_date, quantity | Невозможна | ✓ | |
| 172 | | |
| 173 | | | WEIGHT_USERS | weight_user_id | Не | weight, height, goal_weight, goal_calories | Невозможна | ✓ | |
| 174 | | |
| 175 | | | DAILY_INTAKES | daily_intake_id | Не | user_id, date, calories | Невозможна | ✓ | |
| 176 | | |
| 177 | | | DISCIPLINE_USERS | discipline_user_id | Не | (нема) | Невозможна | ✓ | |
| 178 | | |
| 179 | | | CUSTOM_TRACKING_CATEGORIES | custom_tracking_id | Не | user_id, name | Невозможна | ✓ | |
| 180 | | |
| 181 | | | TASKS | task_id | Не | discipline_user_id, custom_tracking_id, name, is_finished | Невозможна | ✓ | |
| 182 | | |
| 183 | | | DAILY_COMPLETION | daily_completion_id | Не | user_id, date, procent | Невозможна | ✓ | |
| 184 | | |
| 185 | | | TASK_DAILY_COMPLETION | {task_id, daily_completion_id} | **Да** | (нема) | Нема не-клучни атрибути | ✓ | |
| 186 | | |
| 187 | | | FINANCE_USERS | finance_user_id | Не | spending_budget, saving_budget, investing_budget, donation_budget, credit | Невозможна | ✓ | |
| 188 | | |
| 189 | | | INCOMES | income_id | Не | user_id, date, amount | Невозможна | ✓ | |
| 190 | | |
| 191 | | **Заклучок:** Сите 14 релации се во 2НФ. Декомпозиција не е потребна. |
| | 94 | Бидејќи секој атрибут зависи само од еден дел на примарниот клуч (на пример, `training_date` зависи само од `training_id`, а не од целиот K), релацијата **не ја задоволува 2НФ**. |
| | 95 | |
| | 96 | Групирање на атрибутите според клучот од кој зависат: |
| | 97 | |
| | 98 | - training_id -> training_user_id, training_date, training_type, training_duration, training_calories |
| | 99 | - asset_id -> user_id, asset_ticker_symbol, asset_buy_price, asset_buy_date, asset_quantity |
| | 100 | - daily_intake_id -> user_id, daily_intake_date, daily_intake_calories |
| | 101 | - task_id -> discipline_user_id, custom_tracking_id, task_name, task_is_finished |
| | 102 | - daily_completion_id -> user_id, daily_completion_date, daily_completion_procent |
| | 103 | - income_id -> user_id, income_date, income_amount |
| 195 | | Бидејќи сите релации веќе се во 2НФ, нема декомпозиција. Релациите од 1НФ чекорот се пренесуваат непроменети. |
| | 107 | 1. **TrainingSessions(training_id, training_user_id, training_date, training_type, training_duration, training_calories)** |
| | 108 | |
| | 109 | R1 = R - {training_user_id, training_date, training_type, training_duration, training_calories} |
| | 110 | |
| | 111 | R1 = {user_id, email, username, password, training_gender, training_age, training_weight, |
| | 112 | training_id, asset_id, asset_ticker_symbol, asset_buy_price, asset_buy_date, asset_quantity, |
| | 113 | weight_current, weight_height, weight_goal_weight, weight_goal_calories, |
| | 114 | daily_intake_id, daily_intake_date, daily_intake_calories, |
| | 115 | discipline_user_id, custom_tracking_id, custom_tracking_name, |
| | 116 | task_id, task_name, task_is_finished, |
| | 117 | daily_completion_id, daily_completion_date, daily_completion_procent, |
| | 118 | finance_spending_budget, finance_saving_budget, finance_investing_budget, finance_donation_budget, finance_credit, |
| | 119 | income_id, income_date, income_amount} |
| | 120 | |
| | 121 | Lossless join: Релацијата може да се реконструира преку join со `training_id`. |
| | 122 | |
| | 123 | Dependency preservation: FD4 е сочувана преку новата релација `TrainingSessions`. FD3 (training_user_id -> user_id) е сочувана преку FK `TrainingSessions.training_user_id -> TRAINING_USERS(user_id)`. |
| | 124 | |
| | 125 | |
| | 126 | 2. **Assets(asset_id, user_id, asset_ticker_symbol, asset_buy_price, asset_buy_date, asset_quantity)** |
| | 127 | |
| | 128 | R2 = R1 - {asset_ticker_symbol, asset_buy_price, asset_buy_date, asset_quantity} |
| | 129 | |
| | 130 | R2 = {user_id, email, username, password, training_gender, training_age, training_weight, |
| | 131 | training_id, asset_id, |
| | 132 | weight_current, weight_height, weight_goal_weight, weight_goal_calories, |
| | 133 | daily_intake_id, daily_intake_date, daily_intake_calories, |
| | 134 | discipline_user_id, custom_tracking_id, custom_tracking_name, |
| | 135 | task_id, task_name, task_is_finished, |
| | 136 | daily_completion_id, daily_completion_date, daily_completion_procent, |
| | 137 | finance_spending_budget, finance_saving_budget, finance_investing_budget, finance_donation_budget, finance_credit, |
| | 138 | income_id, income_date, income_amount} |
| | 139 | |
| | 140 | Lossless join: Релацијата може да се реконструира преку join со `asset_id`. |
| | 141 | |
| | 142 | Dependency preservation: FD7 е сочувана преку новата релација `Assets`. |
| | 143 | |
| | 144 | |
| | 145 | 3. **DailyIntakes(daily_intake_id, user_id, daily_intake_date, daily_intake_calories)** |
| | 146 | |
| | 147 | R3 = R2 - {daily_intake_date, daily_intake_calories} |
| | 148 | |
| | 149 | R3 = {user_id, email, username, password, training_gender, training_age, training_weight, |
| | 150 | training_id, asset_id, daily_intake_id, |
| | 151 | weight_current, weight_height, weight_goal_weight, weight_goal_calories, |
| | 152 | discipline_user_id, custom_tracking_id, custom_tracking_name, |
| | 153 | task_id, task_name, task_is_finished, |
| | 154 | daily_completion_id, daily_completion_date, daily_completion_procent, |
| | 155 | finance_spending_budget, finance_saving_budget, finance_investing_budget, finance_donation_budget, finance_credit, |
| | 156 | income_id, income_date, income_amount} |
| | 157 | |
| | 158 | Lossless join: Релацијата може да се реконструира преку join со `daily_intake_id`. |
| | 159 | |
| | 160 | Dependency preservation: FD6 е сочувана преку новата релација `DailyIntakes`. |
| | 161 | |
| | 162 | |
| | 163 | 4. **Tasks(task_id, discipline_user_id, custom_tracking_id, task_name, task_is_finished)** |
| | 164 | |
| | 165 | R4 = R3 - {task_name, task_is_finished} |
| | 166 | |
| | 167 | R4 = {user_id, email, username, password, training_gender, training_age, training_weight, |
| | 168 | training_id, asset_id, daily_intake_id, |
| | 169 | weight_current, weight_height, weight_goal_weight, weight_goal_calories, |
| | 170 | discipline_user_id, custom_tracking_id, custom_tracking_name, |
| | 171 | task_id, daily_completion_id, daily_completion_date, daily_completion_procent, |
| | 172 | finance_spending_budget, finance_saving_budget, finance_investing_budget, finance_donation_budget, finance_credit, |
| | 173 | income_id, income_date, income_amount} |
| | 174 | |
| | 175 | Lossless join: Релацијата може да се реконструира преку join со `task_id`. |
| | 176 | |
| | 177 | Dependency preservation: FD10 е сочувана преку новата релација `Tasks`. |
| | 178 | |
| | 179 | |
| | 180 | 5. **DailyCompletion(daily_completion_id, user_id, daily_completion_date, daily_completion_procent)** |
| | 181 | |
| | 182 | R5 = R4 - {daily_completion_date, daily_completion_procent} |
| | 183 | |
| | 184 | R5 = {user_id, email, username, password, training_gender, training_age, training_weight, |
| | 185 | training_id, asset_id, daily_intake_id, |
| | 186 | weight_current, weight_height, weight_goal_weight, weight_goal_calories, |
| | 187 | discipline_user_id, custom_tracking_id, custom_tracking_name, |
| | 188 | task_id, daily_completion_id, |
| | 189 | finance_spending_budget, finance_saving_budget, finance_investing_budget, finance_donation_budget, finance_credit, |
| | 190 | income_id, income_date, income_amount} |
| | 191 | |
| | 192 | Lossless join: Релацијата може да се реконструира преку join со `daily_completion_id`. |
| | 193 | |
| | 194 | Dependency preservation: FD11 е сочувана преку новата релација `DailyCompletion`. |
| | 195 | |
| | 196 | |
| | 197 | 6. **Incomes(income_id, user_id, income_date, income_amount)** |
| | 198 | |
| | 199 | R6 = R5 - {income_date, income_amount} |
| | 200 | |
| | 201 | R6 = {user_id, email, username, password, training_gender, training_age, training_weight, |
| | 202 | training_id, asset_id, daily_intake_id, |
| | 203 | weight_current, weight_height, weight_goal_weight, weight_goal_calories, |
| | 204 | discipline_user_id, custom_tracking_id, custom_tracking_name, |
| | 205 | task_id, daily_completion_id, income_id, |
| | 206 | finance_spending_budget, finance_saving_budget, finance_investing_budget, finance_donation_budget, finance_credit} |
| | 207 | |
| | 208 | Lossless join: Релацијата може да се реконструира преку join со `income_id`. |
| | 209 | |
| | 210 | Dependency preservation: FD14 е сочувана преку новата релација `Incomes`. |
| | 211 | |
| | 212 | |
| | 213 | Добиени релации по декомпозицијата по 2НФ: |
| | 214 | |
| | 215 | 1. TrainingSessions(training_id, training_user_id, training_date, training_type, training_duration, training_calories) |
| | 216 | 2. Assets(asset_id, user_id, asset_ticker_symbol, asset_buy_price, asset_buy_date, asset_quantity) |
| | 217 | 3. DailyIntakes(daily_intake_id, user_id, daily_intake_date, daily_intake_calories) |
| | 218 | 4. Tasks(task_id, discipline_user_id, custom_tracking_id, task_name, task_is_finished) |
| | 219 | 5. DailyCompletion(daily_completion_id, user_id, daily_completion_date, daily_completion_procent) |
| | 220 | 6. Incomes(income_id, user_id, income_date, income_amount) |
| | 221 | 7. R6 = {user_id, email, username, password, training_gender, training_age, training_weight, |
| | 222 | training_id, asset_id, daily_intake_id, |
| | 223 | weight_current, weight_height, weight_goal_weight, weight_goal_calories, |
| | 224 | discipline_user_id, custom_tracking_id, custom_tracking_name, |
| | 225 | task_id, daily_completion_id, income_id, |
| | 226 | finance_spending_budget, finance_saving_budget, finance_investing_budget, finance_donation_budget, finance_credit} |
| 201 | | Ги разгледуваме релациите каде транзитивна зависност е можна: |
| 202 | | |
| 203 | | **`R3: TRAINING_SESSIONS`** |
| 204 | | - Атрибути: `training_id`, `training_user_id`, `training_date`, `training_type`, `training_duration`, `training_calories` |
| 205 | | - `PK`: `training_id` |
| 206 | | - `FDs`: `training_id -> training_user_id, training_date, training_type, training_duration, training_calories` |
| 207 | | - Не-клучни атрибути: `training_user_id`, `training_date`, `training_type`, `training_duration`, `training_calories` |
| 208 | | - Постои ли транзитивност? `training_user_id` е не-клучен, но не детерминира ништо во оваа релација → нема транзитивност → во 3НФ ✓ |
| 209 | | |
| 210 | | **`R5: ASSETS`** |
| 211 | | - Атрибути: `asset_id`, `investor_user_id`, `asset_ticker_symbol`, `asset_buy_price`, `asset_buy_date`, `asset_quantity` |
| 212 | | - `PK`: `asset_id` |
| 213 | | - `FDs`: `asset_id ->` сите атрибути |
| 214 | | - Не-клучен `investor_user_id` не детерминира ништо во оваа релација → во 3НФ ✓ |
| 215 | | |
| 216 | | **`R7: DAILY_INTAKES`** |
| 217 | | - Атрибути: `daily_intake_id`, `weight_user_id`, `daily_intake_date`, `daily_intake_calories` |
| 218 | | - `PK`: `daily_intake_id` |
| 219 | | - `FDs`: `daily_intake_id -> weight_user_id, daily_intake_date, daily_intake_calories` |
| 220 | | - `weight_user_id` не детерминира ништо во оваа релација → во 3НФ ✓ |
| 221 | | |
| 222 | | **`R9: CUSTOM_TRACKING_CATEGORIES`** |
| 223 | | - Атрибути: `custom_tracking_id`, `user_id`, `custom_tracking_name` |
| 224 | | - `PK`: `custom_tracking_id` |
| 225 | | - `FDs`: `custom_tracking_id -> user_id, custom_tracking_name` |
| 226 | | - `user_id` не детерминира ништо во оваа релација → во 3НФ ✓ |
| 227 | | |
| 228 | | **`R10: TASKS`** |
| 229 | | - Атрибути: `task_id`, `discipline_user_id`, `custom_tracking_id`, `task_name`, `task_is_finished` |
| 230 | | - `PK`: `task_id` |
| 231 | | - `FDs`: `task_id -> discipline_user_id, custom_tracking_id, task_name, task_is_finished` |
| | 232 | Поради постоење на транзитивни зависности во R6, заклучуваме дека **е нарушена 3НФ**. На пример: `user_id` (не-клучен во R6) детерминира `email`, `username`, `password`, `training_gender`, `weight_current`, `finance_spending_budget` итн. |
| | 233 | |
| | 234 | == **Декомпозиција по 3НФ** == |
| | 235 | |
| | 236 | Сите транзитивни зависности се наоѓаат во R6. Ги разгледуваме чекор по чекор: |
| | 237 | |
| | 238 | 1. Во **R6** важи FD1: `user_id -> email, username, password`. |
| | 239 | |
| | 240 | Декомпозираме по FD1 и добиваме **Users(user_id, email, username, password)**. |
| | 241 | |
| | 242 | R7 = R6 - {email, username, password} |
| | 243 | |
| | 244 | R7 = {user_id, training_gender, training_age, training_weight, |
| | 245 | training_id, asset_id, daily_intake_id, |
| | 246 | weight_current, weight_height, weight_goal_weight, weight_goal_calories, |
| | 247 | discipline_user_id, custom_tracking_id, custom_tracking_name, |
| | 248 | task_id, daily_completion_id, income_id, |
| | 249 | finance_spending_budget, finance_saving_budget, finance_investing_budget, finance_donation_budget, finance_credit} |
| | 250 | |
| | 251 | Lossless join: Релацијата може да се реконструира преку join со `user_id`. |
| | 252 | |
| | 253 | Dependency preservation: FD1 е сочувана преку `Users`. |
| | 254 | |
| | 255 | |
| | 256 | 2. Во **R7** важи FD2: `user_id -> training_gender, training_age, training_weight`. |
| | 257 | |
| | 258 | Декомпозираме по FD2 и добиваме **TrainingUsers(user_id, training_gender, training_age, training_weight)** — `user_id` е примарен и надворешен клуч кон `Users`. |
| | 259 | |
| | 260 | R8 = R7 - {training_gender, training_age, training_weight} |
| | 261 | |
| | 262 | R8 = {user_id, training_id, asset_id, daily_intake_id, |
| | 263 | weight_current, weight_height, weight_goal_weight, weight_goal_calories, |
| | 264 | discipline_user_id, custom_tracking_id, custom_tracking_name, |
| | 265 | task_id, daily_completion_id, income_id, |
| | 266 | finance_spending_budget, finance_saving_budget, finance_investing_budget, finance_donation_budget, finance_credit} |
| | 267 | |
| | 268 | Lossless join: Релацијата може да се реконструира преку join со `user_id`. |
| | 269 | |
| | 270 | Dependency preservation: FD2 е сочувана преку `TrainingUsers`. |
| | 271 | |
| | 272 | |
| | 273 | 3. Во **R8** важи FD5: `user_id -> weight_current, weight_height, weight_goal_weight, weight_goal_calories`. |
| | 274 | |
| | 275 | Декомпозираме по FD5 и добиваме **WeightUsers(user_id, weight_current, weight_height, weight_goal_weight, weight_goal_calories)** — `user_id` е примарен и надворешен клуч кон `Users`. `DailyIntakes.user_id` е FK кон `WeightUsers.user_id`. |
| | 276 | |
| | 277 | R9 = R8 - {weight_current, weight_height, weight_goal_weight, weight_goal_calories} |
| | 278 | |
| | 279 | R9 = {user_id, training_id, asset_id, daily_intake_id, |
| | 280 | discipline_user_id, custom_tracking_id, custom_tracking_name, |
| | 281 | task_id, daily_completion_id, income_id, |
| | 282 | finance_spending_budget, finance_saving_budget, finance_investing_budget, finance_donation_budget, finance_credit} |
| | 283 | |
| | 284 | Lossless join: Релацијата може да се реконструира преку join со `user_id`. |
| | 285 | |
| | 286 | Dependency preservation: FD5 е сочувана преку `WeightUsers`. |
| | 287 | |
| | 288 | |
| | 289 | 4. Во **R9** важи FD13: `user_id -> finance_spending_budget, finance_saving_budget, finance_investing_budget, finance_donation_budget, finance_credit`. |
| | 290 | |
| | 291 | Декомпозираме по FD13 и добиваме **FinanceUsers(user_id, spending_budget, saving_budget, investing_budget, donation_budget, credit)** — `user_id` е примарен и надворешен клуч кон `Users`. `Incomes.user_id` е FK кон `FinanceUsers.user_id`. |
| | 292 | |
| | 293 | R10 = R9 - {finance_spending_budget, finance_saving_budget, finance_investing_budget, finance_donation_budget, finance_credit} |
| | 294 | |
| | 295 | R10 = {user_id, training_id, asset_id, daily_intake_id, |
| | 296 | discipline_user_id, custom_tracking_id, custom_tracking_name, |
| | 297 | task_id, daily_completion_id, income_id} |
| | 298 | |
| | 299 | Lossless join: Релацијата може да се реконструира преку join со `user_id`. |
| | 300 | |
| | 301 | Dependency preservation: FD13 е сочувана преку `FinanceUsers`. |
| | 302 | |
| | 303 | |
| | 304 | 5. Во **R10** важи FD9: `custom_tracking_id -> user_id, custom_tracking_name`. |
| | 305 | |
| | 306 | Декомпозираме по FD9 и добиваме **CustomTrackingCategories(custom_tracking_id, user_id, custom_tracking_name)** — `custom_tracking_id` е примарен клуч, `user_id` е надворешен клуч кон `Users`. |
| | 307 | |
| | 308 | R11 = R10 - {custom_tracking_name} |
| | 309 | |
| | 310 | R11 = {user_id, training_id, asset_id, daily_intake_id, |
| | 311 | discipline_user_id, custom_tracking_id, task_id, daily_completion_id, income_id} |
| | 312 | |
| | 313 | Lossless join: Релацијата може да се реконструира преку join со `custom_tracking_id`. |
| | 314 | |
| | 315 | Dependency preservation: FD9 е сочувана преку `CustomTrackingCategories`. |
| | 316 | |
| | 317 | |
| | 318 | 6. Во **Tasks(task_id, discipline_user_id, custom_tracking_id, task_name, task_is_finished)** важи FD8: `discipline_user_id -> user_id`. Бидејќи `discipline_user_id` во `Tasks` секогаш е еднакво на `user_id` на корисникот, декомпозираме: |
| | 319 | |
| | 320 | **DisciplineUsers(user_id)** — `user_id` е примарен и надворешен клуч кон `Users`. `discipline_user_id` во `Tasks` е FK кон `DisciplineUsers.user_id`. |
| | 321 | |
| | 322 | Lossless join: Релацијата може да се реконструира преку join со `discipline_user_id`. |
| | 323 | |
| | 324 | Dependency preservation: FD8 е сочувана преку FK врската `discipline_user_id -> DisciplineUsers.user_id`. |
| | 325 | |
| | 326 | |
| | 327 | 7. **InvestorUsers(user_id)** е структурна (role) табела — `user_id` е примарен и надворешен клуч кон `Users`. `Assets.user_id` е FK кон `InvestorUsers.user_id`, со што се обезбедува дека само корисници со инвеститорски профил можат да поседуваат активи. Не произлегува од FD нарушување туку од дизајнерскиот избор за разграничување на профилите. |
| | 328 | |
| | 329 | Lossless join: Релацијата може да се реконструира преку join со `user_id`. |
| | 330 | |
| | 331 | |
| | 332 | Добиени релации по декомпозицијата по 3НФ: |
| | 333 | |
| | 334 | 1. Users(user_id, email, username, password) |
| | 335 | 2. TrainingUsers(user_id, training_gender, training_age, training_weight) |
| | 336 | 3. TrainingSessions(training_id, training_user_id, training_date, training_type, training_duration, training_calories) |
| | 337 | 4. WeightUsers(user_id, weight_current, weight_height, weight_goal_weight, weight_goal_calories) |
| | 338 | 5. DailyIntakes(daily_intake_id, user_id, daily_intake_date, daily_intake_calories) |
| | 339 | 6. InvestorUsers(user_id) |
| | 340 | 7. Assets(asset_id, user_id, asset_ticker_symbol, asset_buy_price, asset_buy_date, asset_quantity) |
| | 341 | 8. DisciplineUsers(user_id) |
| | 342 | 9. CustomTrackingCategories(custom_tracking_id, user_id, custom_tracking_name) |
| | 343 | 10. Tasks(task_id, discipline_user_id, custom_tracking_id, task_name, task_is_finished) |
| | 344 | 11. DailyCompletion(daily_completion_id, user_id, daily_completion_date, daily_completion_procent) |
| | 345 | 12. TaskDailyCompletion(task_id, daily_completion_id) |
| | 346 | 13. FinanceUsers(user_id, spending_budget, saving_budget, investing_budget, donation_budget, credit) |
| | 347 | 14. Incomes(income_id, user_id, income_date, income_amount) |
| | 348 | 15. R11 = {user_id, training_id, asset_id, daily_intake_id, |
| | 349 | discipline_user_id, custom_tracking_id, task_id, daily_completion_id, income_id} |
| | 350 | |
| | 351 | == **Проверка дали добиените релации се во 3НФ** == |
| | 352 | |
| | 353 | **3НФ бара:** за секоја нетривијална FD X → Y, X мора да биде суперклуч или Y мора да биде дел од некој кандидат клуч. |
| | 354 | |
| | 355 | **TRAINING_SESSIONS(training_id, training_user_id, training_date, training_type, training_duration, training_calories)** |
| | 356 | - PK: `training_id` |
| | 357 | - Не-клучен `training_user_id` не детерминира ништо во оваа релација → во 3НФ ✓ |
| | 358 | |
| | 359 | **ASSETS(asset_id, user_id, asset_ticker_symbol, asset_buy_price, asset_buy_date, asset_quantity)** |
| | 360 | - PK: `asset_id` |
| | 361 | - Не-клучен `user_id` не детерминира ништо во оваа релација → во 3НФ ✓ |
| | 362 | |
| | 363 | **DAILY_INTAKES(daily_intake_id, user_id, daily_intake_date, daily_intake_calories)** |
| | 364 | - PK: `daily_intake_id` |
| | 365 | - Не-клучен `user_id` не детерминира ништо во оваа релација → во 3НФ ✓ |
| | 366 | |
| | 367 | **CUSTOM_TRACKING_CATEGORIES(custom_tracking_id, user_id, custom_tracking_name)** |
| | 368 | - PK: `custom_tracking_id` |
| | 369 | - Не-клучен `user_id` не детерминира ништо во оваа релација → во 3НФ ✓ |
| | 370 | |
| | 371 | **TASKS(task_id, discipline_user_id, custom_tracking_id, task_name, task_is_finished)** |
| | 372 | - PK: `task_id` |
| 246 | | Ги разгледуваме релациите каде постојат повеќе кандидат клучеви: |
| 247 | | |
| 248 | | **`R1: USERS(user_id, email, username, password)`** |
| 249 | | - `FDs`: |
| 250 | | - `user_id -> email, username, password` |
| 251 | | - `email -> user_id` (FD2) |
| 252 | | - `username -> user_id` (FD3) |
| 253 | | - Кандидат клучеви: `{user_id}`, `{email}`, `{username}` |
| 254 | | - Проверка: |
| 255 | | - `user_id -> ...` : `user_id` е кандидат клуч ✓ |
| 256 | | - `email -> ...` : `email` е кандидат клуч ✓ |
| 257 | | - `username -> ...` : `username` е кандидат клуч ✓ |
| 258 | | - Сите детерминанти се кандидат клучеви → `USERS` е во БКНФ ✓ |
| 259 | | |
| 260 | | **`R10: TASKS`** |
| 261 | | - `FDs`: `task_id -> discipline_user_id, custom_tracking_id, task_name, task_is_finished` |
| 262 | | - Единствен кандидат клуч: `{task_id}` |
| 263 | | - `task_id` е кандидат клуч → во БКНФ ✓ |
| 264 | | |
| 265 | | **Напомена за CHECK constraint:** |
| 266 | | Условот `(discipline_user_id IS NOT NULL AND custom_tracking_id IS NULL) OR (discipline_user_id IS NULL AND custom_tracking_id IS NOT NULL)` не е функционална зависност туку ограничување на домен — не влијае на БКНФ. |
| 267 | | |
| 268 | | Сите останати релации имаат единствен кандидат клуч (нивниот сурогатен `PK`) и сите детерминанти во нив се суперклучеви → сите се во БКНФ ✓ |
| | 386 | Примарните клучеви се болдирани. |
| | 387 | |
| | 388 | 1. Users(**user_id**, email, username, password) -> **задоволува БКНФ** |
| | 389 | (`user_id`, `email` и `username` се сите кандидат клучеви — сите детерминанти во оваа релација се суперклучеви. `UNIQUE` constraints на `email` и `username` ги енфорцираат алтернативните кандидат клучеви.) |
| | 390 | |
| | 391 | 2. TrainingUsers(**user_id**, training_gender, training_age, training_weight) -> **задоволува БКНФ** |
| | 392 | |
| | 393 | 3. TrainingSessions(**training_id**, training_user_id, training_date, training_type, training_duration, training_calories) -> **задоволува БКНФ** |
| | 394 | |
| | 395 | 4. WeightUsers(**user_id**, weight_current, weight_height, weight_goal_weight, weight_goal_calories) -> **задоволува БКНФ** |
| | 396 | |
| | 397 | 5. DailyIntakes(**daily_intake_id**, user_id, daily_intake_date, daily_intake_calories) -> **задоволува БКНФ** |
| | 398 | |
| | 399 | 6. InvestorUsers(**user_id**) -> **задоволува БКНФ** |
| | 400 | |
| | 401 | 7. Assets(**asset_id**, user_id, asset_ticker_symbol, asset_buy_price, asset_buy_date, asset_quantity) -> **задоволува БКНФ** |
| | 402 | |
| | 403 | 8. DisciplineUsers(**user_id**) -> **задоволува БКНФ** |
| | 404 | |
| | 405 | 9. CustomTrackingCategories(**custom_tracking_id**, user_id, custom_tracking_name) -> **задоволува БКНФ** |
| | 406 | |
| | 407 | 10. Tasks(**task_id**, discipline_user_id, custom_tracking_id, task_name, task_is_finished) -> **задоволува БКНФ** |
| | 408 | |
| | 409 | 11. DailyCompletion(**daily_completion_id**, user_id, daily_completion_date, daily_completion_procent) -> **задоволува БКНФ** |
| | 410 | |
| | 411 | 12. TaskDailyCompletion(**task_id, daily_completion_id**) -> **задоволува БКНФ** |
| | 412 | |
| | 413 | 13. FinanceUsers(**user_id**, spending_budget, saving_budget, investing_budget, donation_budget, credit) -> **задоволува БКНФ** |
| | 414 | |
| | 415 | 14. Incomes(**income_id**, user_id, income_date, income_amount) -> **задоволува БКНФ** |
| | 416 | |
| | 417 | 15. R11 = {**training_id, asset_id, user_id, daily_intake_id, discipline_user_id, custom_tracking_id, task_id, daily_completion_id, income_id**} -> **задоволува БКНФ** |
| 272 | | === **Lossless Join проверка** === |
| 273 | | |
| 274 | | **Теоретска основа:** За секоја декомпозиција на релација R во R1 и R2, join-от е lossless ако и само ако (R1 ∩ R2) → R1 или (R1 ∩ R2) → R2, т.е. заедничките атрибути формираат суперклуч барем во една од двете нови релации. |
| 275 | | |
| 276 | | Бидејќи нашата декомпозиција се изведува чекор-по-чекор, за секој чекор посебно ја проверуваме оваа особина. |
| 277 | | |
| 278 | | --- |
| 279 | | |
| 280 | | **ЧЕКОР 1: Декомпозиција од глобална R во 1НФ** |
| 281 | | |
| 282 | | **Чекор 1.1: Издвојување на USERS** |
| 283 | | |
| 284 | | R(сите атрибути) → USERS(user_id, email, username, password) + R1(останати атрибути) |
| 285 | | |
| 286 | | Пресек: {user_id} |
| 287 | | |
| 288 | | Дали {user_id} → USERS? Да, user_id е примарен клуч во USERS. |
| 289 | | |
| 290 | | Условот (R1 ∩ R2) → R1 е задоволен → **Lossless join ✓** |
| 291 | | |
| 292 | | --- |
| 293 | | |
| 294 | | **Чекор 1.2: Издвојување на TRAINING_USERS** |
| 295 | | |
| 296 | | R1 → TRAINING_USERS(training_user_id, training_gender, training_age, training_weight) + R2(останати атрибути) |
| 297 | | |
| 298 | | Пресек: {training_user_id} |
| 299 | | |
| 300 | | Дали {training_user_id} → TRAINING_USERS? Да, training_user_id е примарен клуч во TRAINING_USERS. |
| 301 | | |
| 302 | | **Lossless join ✓** |
| 303 | | |
| 304 | | --- |
| 305 | | |
| 306 | | **Чекор 1.3: Издвојување на TRAINING_SESSIONS** |
| 307 | | |
| 308 | | R2 → TRAINING_SESSIONS(training_id, training_user_id, training_date, training_type, training_duration, training_calories) + R3(останати атрибути) |
| 309 | | |
| 310 | | Пресек: {training_id} |
| 311 | | |
| 312 | | Дали {training_id} → TRAINING_SESSIONS? Да, training_id е примарен клуч во TRAINING_SESSIONS. |
| 313 | | |
| 314 | | **Lossless join ✓** |
| 315 | | |
| 316 | | --- |
| 317 | | |
| 318 | | **Чекор 1.4: Издвојување на INVESTOR_USERS** |
| 319 | | |
| 320 | | R3 → INVESTOR_USERS(investor_user_id) + R4(останати атрибути) |
| 321 | | |
| 322 | | Пресек: {investor_user_id} |
| 323 | | |
| 324 | | Дали {investor_user_id} → INVESTOR_USERS? Да, investor_user_id е примарен клуч во INVESTOR_USERS. |
| 325 | | |
| 326 | | **Lossless join ✓** |
| 327 | | |
| 328 | | --- |
| 329 | | |
| 330 | | **Чекор 1.5: Издвојување на ASSETS** |
| 331 | | |
| 332 | | R4 → ASSETS(asset_id, investor_user_id, asset_ticker_symbol, asset_buy_price, asset_buy_date, asset_quantity) + R5(останати атрибути) |
| 333 | | |
| 334 | | Пресек: {asset_id} |
| 335 | | |
| 336 | | Дали {asset_id} → ASSETS? Да, asset_id е примарен клуч во ASSETS. |
| 337 | | |
| 338 | | **Lossless join ✓** |
| 339 | | |
| 340 | | --- |
| 341 | | |
| 342 | | **Чекор 1.6: Издвојување на WEIGHT_USERS** |
| 343 | | |
| 344 | | R5 → WEIGHT_USERS(weight_user_id, weight_current, weight_height, weight_goal_weight, weight_goal_calories) + R6(останати атрибути) |
| 345 | | |
| 346 | | Пресек: {weight_user_id} |
| 347 | | |
| 348 | | Дали {weight_user_id} → WEIGHT_USERS? Да, weight_user_id е примарен клуч во WEIGHT_USERS. |
| 349 | | |
| 350 | | **Lossless join ✓** |
| 351 | | |
| 352 | | --- |
| 353 | | |
| 354 | | **Чекор 1.7: Издвојување на DAILY_INTAKES** |
| 355 | | |
| 356 | | R6 → DAILY_INTAKES(daily_intake_id, weight_user_id, daily_intake_date, daily_intake_calories) + R7(останати атрибути) |
| 357 | | |
| 358 | | Пресек: {daily_intake_id} |
| 359 | | |
| 360 | | Дали {daily_intake_id} → DAILY_INTAKES? Да, daily_intake_id е примарен клуч во DAILY_INTAKES. |
| 361 | | |
| 362 | | **Lossless join ✓** |
| 363 | | |
| 364 | | --- |
| 365 | | |
| 366 | | **Чекор 1.8: Издвојување на DISCIPLINE_USERS** |
| 367 | | |
| 368 | | R7 → DISCIPLINE_USERS(discipline_user_id) + R8(останати атрибути) |
| 369 | | |
| 370 | | Пресек: {discipline_user_id} |
| 371 | | |
| 372 | | Дали {discipline_user_id} → DISCIPLINE_USERS? Да, discipline_user_id е примарен клуч во DISCIPLINE_USERS. |
| 373 | | |
| 374 | | **Lossless join ✓** |
| 375 | | |
| 376 | | --- |
| 377 | | |
| 378 | | **Чекор 1.9: Издвојување на CUSTOM_TRACKING_CATEGORIES** |
| 379 | | |
| 380 | | R8 → CUSTOM_TRACKING_CATEGORIES(custom_tracking_id, user_id, custom_tracking_name) + R9(останати атрибути) |
| 381 | | |
| 382 | | Пресек: {custom_tracking_id} |
| 383 | | |
| 384 | | Дали {custom_tracking_id} → CUSTOM_TRACKING_CATEGORIES? Да, custom_tracking_id е примарен клуч во CUSTOM_TRACKING_CATEGORIES. |
| 385 | | |
| 386 | | **Lossless join ✓** |
| 387 | | |
| 388 | | --- |
| 389 | | |
| 390 | | **Чекор 1.10: Издвојување на TASKS** |
| 391 | | |
| 392 | | R9 → TASKS(task_id, discipline_user_id, custom_tracking_id, task_name, task_is_finished) + R10(останати атрибути) |
| 393 | | |
| 394 | | Пресек: {task_id} |
| 395 | | |
| 396 | | Дали {task_id} → TASKS? Да, task_id е примарен клуч во TASKS. |
| 397 | | |
| 398 | | **Lossless join ✓** |
| 399 | | |
| 400 | | --- |
| 401 | | |
| 402 | | **Чекор 1.11: Издвојување на DAILY_COMPLETION** |
| 403 | | |
| 404 | | R10 → DAILY_COMPLETION(daily_completion_id, user_id, daily_completion_date, daily_completion_procent) + R11(останати атрибути) |
| 405 | | |
| 406 | | Пресек: {daily_completion_id} |
| 407 | | |
| 408 | | Дали {daily_completion_id} → DAILY_COMPLETION? Да, daily_completion_id е примарен клуч во DAILY_COMPLETION. |
| 409 | | |
| 410 | | **Lossless join ✓** |
| 411 | | |
| 412 | | --- |
| 413 | | |
| 414 | | **Чекор 1.12: Издвојување на TASK_DAILY_COMPLETION** |
| 415 | | |
| 416 | | R11 → TASK_DAILY_COMPLETION(task_id, daily_completion_id) + R12(останати атрибути) |
| 417 | | |
| 418 | | Пресек: {task_id, daily_completion_id} |
| 419 | | |
| 420 | | Ова е спојна релација без не-клучни атрибути. Бидејќи нема дополнителни атрибути кои би можеле да се изгубат, join-от е lossless. |
| 421 | | |
| 422 | | **Lossless join ✓** |
| 423 | | |
| 424 | | --- |
| 425 | | |
| 426 | | **Чекор 1.13: Издвојување на FINANCE_USERS** |
| 427 | | |
| 428 | | R12 → FINANCE_USERS(finance_user_id, finance_spending_budget, finance_saving_budget, finance_investing_budget, finance_donation_budget, finance_credit) + R13(останати атрибути) |
| 429 | | |
| 430 | | Пресек: {finance_user_id} |
| 431 | | |
| 432 | | Дали {finance_user_id} → FINANCE_USERS? Да, finance_user_id е примарен клуч во FINANCE_USERS. |
| 433 | | |
| 434 | | **Lossless join ✓** |
| 435 | | |
| 436 | | --- |
| 437 | | |
| 438 | | **Чекор 1.14: Издвојување на INCOMES** |
| 439 | | |
| 440 | | R13 → INCOMES(income_id, finance_user_id, income_date, income_amount) + R14(празна) |
| 441 | | |
| 442 | | Пресек: {income_id} |
| 443 | | |
| 444 | | Дали {income_id} → INCOMES? Да, income_id е примарен клуч во INCOMES. |
| 445 | | |
| 446 | | **Lossless join ✓** |
| 447 | | |
| 448 | | --- |
| 449 | | |
| 450 | | **ЗАКЛУЧОК:** Сите 14 чекори на декомпозиција го задоволуваат условот за lossless join. Конечниот модел овозможува целосна реконструкција на оригиналната релација R без губење или создавање на лажни податоци. |
| 451 | | |
| 452 | | |
| 453 | | === **Dependency Preservation проверка** === |
| 454 | | |
| 455 | | Проверуваме дека секоја `FD` од оригиналниот сет е зачувана во барем една од финалните релации: |
| 456 | | |
| 457 | | - `FD1: user_id -> email, username, password` → `USERS` ✓ |
| 458 | | - `FD2: email -> user_id` → `USERS` ✓ |
| 459 | | - `FD3: username -> user_id` → `USERS` ✓ |
| 460 | | - `FD4: training_user_id -> training_gender, training_age, training_weight` → `TRAINING_USERS` ✓ |
| 461 | | - `FD5: training_id -> training_user_id, ...` → `TRAINING_SESSIONS` ✓ |
| 462 | | - `FD6: investor_user_id -> user_id` → `INVESTOR_USERS` (преку `FK`) ✓ |
| 463 | | - `FD7: asset_id -> investor_user_id, ...` → `ASSETS` ✓ |
| 464 | | - `FD8: weight_user_id -> weight_current, ...` → `WEIGHT_USERS` ✓ |
| 465 | | - `FD9: daily_intake_id -> weight_user_id, ...` → `DAILY_INTAKES` ✓ |
| 466 | | - `FD10: discipline_user_id -> user_id` → `DISCIPLINE_USERS` (преку `FK`) ✓ |
| 467 | | - `FD11: custom_tracking_id -> user_id, name` → `CUSTOM_TRACKING_CATEGORIES` ✓ |
| 468 | | - `FD12: task_id -> discipline_user_id, ...` → `TASKS` ✓ |
| 469 | | - `FD13: daily_completion_id -> user_id, ...` → `DAILY_COMPLETION` ✓ |
| 470 | | - `FD14: (task_id, daily_completion_id) -> /` → `TASK_DAILY_COMPLETION` ✓ |
| 471 | | - `FD15: finance_user_id -> finance_spending_budget, ...` → `FINANCE_USERS` ✓ |
| 472 | | - `FD16: income_id -> finance_user_id, ...` → `INCOMES` ✓ |
| 473 | | |
| 474 | | **Заклучок:** **Сите функционални зависности се зачувани.** ✓ |
| | 421 | == **Проверка за 4НФ** == |
| | 422 | |
| | 423 | **4НФ бара:** релацијата е во БКНФ и **нема нетривијални мултивредносни зависности** — за секоја MVD X →→ Y, X мора да биде суперклуч. |
| | 424 | |
| | 425 | Сите профилски релации (`TrainingUsers`, `WeightUsers`, `InvestorUsers`, `DisciplineUsers`, `FinanceUsers`) се во **1:1** врска со `Users` — секој корисник има точно еден профил од секој тип. Ова значи дека постои функционална, не мултивредносна зависност, и MVD не се применува. |
| | 426 | |
| | 427 | R11 содржи само примарни и надворешни клучеви кои меѓусебно имаат 1:1 зависности — нема независни мултивредносни факти во истата релација. |
| | 428 | |
| | 429 | `TaskDailyCompletion(**task_id, daily_completion_id**)` е спојна релација со композитен примарен клуч — нема не-клучни атрибути, па MVD не се применува. |
| | 430 | |
| | 431 | **Заклучок:** Ниту една релација не содржи нетривијална мултивредносна зависност. **Целиот модел е во 4НФ.** Понатамошна декомпозиција не е потребна. |