Changes between Version 12 and Version 13 of Normalization


Ignore:
Timestamp:
07/01/26 12:43:05 (5 days ago)
Author:
233062
Comment:

--

Legend:

Unmodified
Added
Removed
Modified
  • Normalization

    v12 v13  
    33== **Де-нормализирана база на податоци** ==
    44
    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` итн. во тие табели.
    1010
    1111== **Функционални зависности** ==
     
    1414
    1515- **FD1:** `user_id -> email, username, password`
    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`
    3129
    3230== **Кандидат клучеви и примарен клуч** ==
     
    3735
    3836**Атрибути кои се појавуваат само на десна страна** (никогаш детерминанти):
    39 - `email`, `username` — само преку `FD2`/`FD3` се детерминанти, но се и десни во `FD1`
    4037- `training_gender`, `training_age`, `training_weight`, `training_date`, `training_type`, `training_duration`, `training_calories`
    4138- `asset_ticker_symbol`, `asset_buy_price`, `asset_buy_date`, `asset_quantity`
     
    4643- `finance_spending_budget`, `finance_saving_budget`, `finance_investing_budget`, `finance_donation_budget`, `finance_credit`
    4744- `income_date`, `income_amount`
     45- `email`, `username`, `password`
     46
     47**Атрибути кои се појавуваат на лева и десна страна:**
     48`user_id`, `training_user_id`, `discipline_user_id`, `custom_tracking_id`
    4849
    4950Овие атрибути **не можат** да бидат дел од кандидат клуч бидејќи не детерминираат ништо надвор од себе.
     
    5253- `training_id`, `asset_id`, `daily_intake_id`, `task_id`, `daily_completion_id`, `income_id`
    5354
    54 **Останати детерминанти:**
    55 - `user_id`, `email`, `username`, `training_user_id`, `investor_user_id`, `weight_user_id`, `discipline_user_id`, `custom_tracking_id`, `finance_user_id`
    56 
    5755=== **Затворање на минималниот суперклуч** ===
    5856
    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
     591. 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 -> **Не ги содржи сите атрибути**
     602. 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 -> **Не ги содржи сите атрибути**
     613. daily_intake_id+ -> {daily_intake_id, user_id, daily_intake_date, daily_intake_calories} via FD6 -> {email, username, password, ...} via FD1,FD2,FD5,FD13 -> **Не ги содржи сите атрибути**
     624. 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 -> **Не ги содржи сите атрибути**
     635. daily_completion_id+ -> {daily_completion_id, user_id, daily_completion_date, daily_completion_procent} via FD11 -> {email, ...} via FD1,FD2,FD5,FD13 -> **Не ги содржи сите атрибути**
     646. 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,
     70asset_id, user_id, asset_ticker_symbol, asset_buy_price, asset_buy_date, asset_quantity,
     71email, username, password,
     72training_gender, training_age, training_weight,
     73weight_current, weight_height, weight_goal_weight, weight_goal_calories,
     74daily_intake_id, daily_intake_date, daily_intake_calories,
     75discipline_user_id, custom_tracking_id, custom_tracking_name, task_id, task_name, task_is_finished,
     76daily_completion_id, daily_completion_date, daily_completion_procent,
     77finance_spending_budget, finance_saving_budget, finance_investing_budget, finance_donation_budget, finance_credit,
     78income_id, income_date, income_amount}
     79
     80Покривачот составен од сите атрибути кои се наоѓаат само од левата страна ги содржи сите атрибути на глобалната релација и претставува минимален суперклуч, па затоа го избираме за примарен клуч.
     81
     82Избран примарен клуч: **К = {training_id, asset_id, daily_intake_id, task_id, daily_completion_id, income_id}**
    7983
    8084**Минималност:** Отстранувањето на кој било атрибут од `K` резултира во непокриени атрибути (на пример, без `training_id` не можеме да добиеме `training_date`, `training_type` итн.), па `K` е минимален.
    8185
    82 **Примарен клуч:**
    83 
    84 `PK = { user_id, training_id, asset_id, daily_intake_id, task_id,
    85 daily_completion_id, income_id, training_user_id, investor_user_id,
    86 weight_user_id, discipline_user_id, custom_tracking_id, finance_user_id }`
    87 
    88 
    89 **Напомена:** Постојат и алтернативни кандидат клучеви:
    90 - `email` може да замени `user_id` (FD2: `email -> user_id`)
    91 - `username` може да замени `user_id` (FD3: `username -> user_id`)
    92 
    93 === **Алтернативни кандидат клучеви** ===
    94 
    95 **Проверка на {email} како кандидат клуч:**
    96 - {email}+ = email -> user_id (FD2) → {user_id, email}
    97 - user_id -> username, password (FD1)
    98 - {email}+ = {email, user_id, username, password}
    99 - Не ги содржи training_id, asset_id, итн. → {email} САМ по себе НЕ е суперклуч за R.
    100 
    101 Бидејќи R содржи атрибути од повеќе независни групи (тренинг, средства, задачи...),
    102 ниту еден атрибут или мала комбинација не може да биде суперклуч за целата R.
    103 Затоа единствениот кандидат клуч за глобалната R е композитниот клуч наведен погоре.
    104 
    105 **Важно:** {email} и {username} се кандидат клучеви САМО во релацијата USERS по декомпозицијата,
    106 не во глобалната R.
    107 
    10886== **Проверка за 1НФ** ==
    10987
    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НФ**.
    14689
    14790== **Проверка за 2НФ** ==
     
    14992**2НФ бара:** релацијата е во 1НФ и **нема парцијална зависност** — секој не-клучен атрибут зависи од **целиот** примарен клуч, не само од дел од него.
    15093
    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
    192104
    193105== **Декомпозиција по 2НФ** ==
    194106
    195 Бидејќи сите релации веќе се во 2НФ, нема декомпозиција. Релациите од 1НФ чекорот се пренесуваат непроменети.
     1071. **TrainingSessions(training_id, training_user_id, training_date, training_type, training_duration, training_calories)**
     108
     109R1 = R - {training_user_id, training_date, training_type, training_duration, training_calories}
     110
     111R1 = {user_id, email, username, password, training_gender, training_age, training_weight,
     112training_id, asset_id, asset_ticker_symbol, asset_buy_price, asset_buy_date, asset_quantity,
     113weight_current, weight_height, weight_goal_weight, weight_goal_calories,
     114daily_intake_id, daily_intake_date, daily_intake_calories,
     115discipline_user_id, custom_tracking_id, custom_tracking_name,
     116task_id, task_name, task_is_finished,
     117daily_completion_id, daily_completion_date, daily_completion_procent,
     118finance_spending_budget, finance_saving_budget, finance_investing_budget, finance_donation_budget, finance_credit,
     119income_id, income_date, income_amount}
     120
     121Lossless join: Релацијата може да се реконструира преку join со `training_id`.
     122
     123Dependency preservation: FD4 е сочувана преку новата релација `TrainingSessions`. FD3 (training_user_id -> user_id) е сочувана преку FK `TrainingSessions.training_user_id -> TRAINING_USERS(user_id)`.
     124
     125
     1262. **Assets(asset_id, user_id, asset_ticker_symbol, asset_buy_price, asset_buy_date, asset_quantity)**
     127
     128R2 = R1 - {asset_ticker_symbol, asset_buy_price, asset_buy_date, asset_quantity}
     129
     130R2 = {user_id, email, username, password, training_gender, training_age, training_weight,
     131training_id, asset_id,
     132weight_current, weight_height, weight_goal_weight, weight_goal_calories,
     133daily_intake_id, daily_intake_date, daily_intake_calories,
     134discipline_user_id, custom_tracking_id, custom_tracking_name,
     135task_id, task_name, task_is_finished,
     136daily_completion_id, daily_completion_date, daily_completion_procent,
     137finance_spending_budget, finance_saving_budget, finance_investing_budget, finance_donation_budget, finance_credit,
     138income_id, income_date, income_amount}
     139
     140Lossless join: Релацијата може да се реконструира преку join со `asset_id`.
     141
     142Dependency preservation: FD7 е сочувана преку новата релација `Assets`.
     143
     144
     1453. **DailyIntakes(daily_intake_id, user_id, daily_intake_date, daily_intake_calories)**
     146
     147R3 = R2 - {daily_intake_date, daily_intake_calories}
     148
     149R3 = {user_id, email, username, password, training_gender, training_age, training_weight,
     150training_id, asset_id, daily_intake_id,
     151weight_current, weight_height, weight_goal_weight, weight_goal_calories,
     152discipline_user_id, custom_tracking_id, custom_tracking_name,
     153task_id, task_name, task_is_finished,
     154daily_completion_id, daily_completion_date, daily_completion_procent,
     155finance_spending_budget, finance_saving_budget, finance_investing_budget, finance_donation_budget, finance_credit,
     156income_id, income_date, income_amount}
     157
     158Lossless join: Релацијата може да се реконструира преку join со `daily_intake_id`.
     159
     160Dependency preservation: FD6 е сочувана преку новата релација `DailyIntakes`.
     161
     162
     1634. **Tasks(task_id, discipline_user_id, custom_tracking_id, task_name, task_is_finished)**
     164
     165R4 = R3 - {task_name, task_is_finished}
     166
     167R4 = {user_id, email, username, password, training_gender, training_age, training_weight,
     168training_id, asset_id, daily_intake_id,
     169weight_current, weight_height, weight_goal_weight, weight_goal_calories,
     170discipline_user_id, custom_tracking_id, custom_tracking_name,
     171task_id, daily_completion_id, daily_completion_date, daily_completion_procent,
     172finance_spending_budget, finance_saving_budget, finance_investing_budget, finance_donation_budget, finance_credit,
     173income_id, income_date, income_amount}
     174
     175Lossless join: Релацијата може да се реконструира преку join со `task_id`.
     176
     177Dependency preservation: FD10 е сочувана преку новата релација `Tasks`.
     178
     179
     1805. **DailyCompletion(daily_completion_id, user_id, daily_completion_date, daily_completion_procent)**
     181
     182R5 = R4 - {daily_completion_date, daily_completion_procent}
     183
     184R5 = {user_id, email, username, password, training_gender, training_age, training_weight,
     185training_id, asset_id, daily_intake_id,
     186weight_current, weight_height, weight_goal_weight, weight_goal_calories,
     187discipline_user_id, custom_tracking_id, custom_tracking_name,
     188task_id, daily_completion_id,
     189finance_spending_budget, finance_saving_budget, finance_investing_budget, finance_donation_budget, finance_credit,
     190income_id, income_date, income_amount}
     191
     192Lossless join: Релацијата може да се реконструира преку join со `daily_completion_id`.
     193
     194Dependency preservation: FD11 е сочувана преку новата релација `DailyCompletion`.
     195
     196
     1976. **Incomes(income_id, user_id, income_date, income_amount)**
     198
     199R6 = R5 - {income_date, income_amount}
     200
     201R6 = {user_id, email, username, password, training_gender, training_age, training_weight,
     202training_id, asset_id, daily_intake_id,
     203weight_current, weight_height, weight_goal_weight, weight_goal_calories,
     204discipline_user_id, custom_tracking_id, custom_tracking_name,
     205task_id, daily_completion_id, income_id,
     206finance_spending_budget, finance_saving_budget, finance_investing_budget, finance_donation_budget, finance_credit}
     207
     208Lossless join: Релацијата може да се реконструира преку join со `income_id`.
     209
     210Dependency preservation: FD14 е сочувана преку новата релација `Incomes`.
     211
     212
     213Добиени релации по декомпозицијата по 2НФ:
     214
     2151. TrainingSessions(training_id, training_user_id, training_date, training_type, training_duration, training_calories)
     2162. Assets(asset_id, user_id, asset_ticker_symbol, asset_buy_price, asset_buy_date, asset_quantity)
     2173. DailyIntakes(daily_intake_id, user_id, daily_intake_date, daily_intake_calories)
     2184. Tasks(task_id, discipline_user_id, custom_tracking_id, task_name, task_is_finished)
     2195. DailyCompletion(daily_completion_id, user_id, daily_completion_date, daily_completion_procent)
     2206. Incomes(income_id, user_id, income_date, income_amount)
     2217. 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}
    196227
    197228== **Проверка за 3НФ** ==
     
    199230**3НФ бара:** релацијата е во 2НФ и **нема транзитивна зависност** — не-клучен атрибут не смее да зависи од друг не-клучен атрибут.
    200231
    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
     2381. Во **R6** важи FD1: `user_id -> email, username, password`.
     239
     240Декомпозираме по FD1 и добиваме **Users(user_id, email, username, password)**.
     241
     242R7 = R6 - {email, username, password}
     243
     244R7 = {user_id, training_gender, training_age, training_weight,
     245training_id, asset_id, daily_intake_id,
     246weight_current, weight_height, weight_goal_weight, weight_goal_calories,
     247discipline_user_id, custom_tracking_id, custom_tracking_name,
     248task_id, daily_completion_id, income_id,
     249finance_spending_budget, finance_saving_budget, finance_investing_budget, finance_donation_budget, finance_credit}
     250
     251Lossless join: Релацијата може да се реконструира преку join со `user_id`.
     252
     253Dependency preservation: FD1 е сочувана преку `Users`.
     254
     255
     2562. Во **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
     260R8 = R7 - {training_gender, training_age, training_weight}
     261
     262R8 = {user_id, training_id, asset_id, daily_intake_id,
     263weight_current, weight_height, weight_goal_weight, weight_goal_calories,
     264discipline_user_id, custom_tracking_id, custom_tracking_name,
     265task_id, daily_completion_id, income_id,
     266finance_spending_budget, finance_saving_budget, finance_investing_budget, finance_donation_budget, finance_credit}
     267
     268Lossless join: Релацијата може да се реконструира преку join со `user_id`.
     269
     270Dependency preservation: FD2 е сочувана преку `TrainingUsers`.
     271
     272
     2733. Во **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
     277R9 = R8 - {weight_current, weight_height, weight_goal_weight, weight_goal_calories}
     278
     279R9 = {user_id, training_id, asset_id, daily_intake_id,
     280discipline_user_id, custom_tracking_id, custom_tracking_name,
     281task_id, daily_completion_id, income_id,
     282finance_spending_budget, finance_saving_budget, finance_investing_budget, finance_donation_budget, finance_credit}
     283
     284Lossless join: Релацијата може да се реконструира преку join со `user_id`.
     285
     286Dependency preservation: FD5 е сочувана преку `WeightUsers`.
     287
     288
     2894. Во **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
     293R10 = R9 - {finance_spending_budget, finance_saving_budget, finance_investing_budget, finance_donation_budget, finance_credit}
     294
     295R10 = {user_id, training_id, asset_id, daily_intake_id,
     296discipline_user_id, custom_tracking_id, custom_tracking_name,
     297task_id, daily_completion_id, income_id}
     298
     299Lossless join: Релацијата може да се реконструира преку join со `user_id`.
     300
     301Dependency preservation: FD13 е сочувана преку `FinanceUsers`.
     302
     303
     3045. Во **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
     308R11 = R10 - {custom_tracking_name}
     309
     310R11 = {user_id, training_id, asset_id, daily_intake_id,
     311discipline_user_id, custom_tracking_id, task_id, daily_completion_id, income_id}
     312
     313Lossless join: Релацијата може да се реконструира преку join со `custom_tracking_id`.
     314
     315Dependency preservation: FD9 е сочувана преку `CustomTrackingCategories`.
     316
     317
     3186. Во **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
     322Lossless join: Релацијата може да се реконструира преку join со `discipline_user_id`.
     323
     324Dependency preservation: FD8 е сочувана преку FK врската `discipline_user_id -> DisciplineUsers.user_id`.
     325
     326
     3277. **InvestorUsers(user_id)** е структурна (role) табела — `user_id` е примарен и надворешен клуч кон `Users`. `Assets.user_id` е FK кон `InvestorUsers.user_id`, со што се обезбедува дека само корисници со инвеститорски профил можат да поседуваат активи. Не произлегува од FD нарушување туку од дизајнерскиот избор за разграничување на профилите.
     328
     329Lossless join: Релацијата може да се реконструира преку join со `user_id`.
     330
     331
     332Добиени релации по декомпозицијата по 3НФ:
     333
     3341. Users(user_id, email, username, password)
     3352. TrainingUsers(user_id, training_gender, training_age, training_weight)
     3363. TrainingSessions(training_id, training_user_id, training_date, training_type, training_duration, training_calories)
     3374. WeightUsers(user_id, weight_current, weight_height, weight_goal_weight, weight_goal_calories)
     3385. DailyIntakes(daily_intake_id, user_id, daily_intake_date, daily_intake_calories)
     3396. InvestorUsers(user_id)
     3407. Assets(asset_id, user_id, asset_ticker_symbol, asset_buy_price, asset_buy_date, asset_quantity)
     3418. DisciplineUsers(user_id)
     3429. CustomTrackingCategories(custom_tracking_id, user_id, custom_tracking_name)
     34310. Tasks(task_id, discipline_user_id, custom_tracking_id, task_name, task_is_finished)
     34411. DailyCompletion(daily_completion_id, user_id, daily_completion_date, daily_completion_procent)
     34512. TaskDailyCompletion(task_id, daily_completion_id)
     34613. FinanceUsers(user_id, spending_budget, saving_budget, investing_budget, donation_budget, credit)
     34714. Incomes(income_id, user_id, income_date, income_amount)
     34815. 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`
    232373- Ниту `discipline_user_id` ниту `custom_tracking_id` детерминираат ништо во оваа релација → во 3НФ ✓
    233 
    234 **`R14: INCOMES`**
    235 - Атрибути: `income_id`, `finance_user_id`, `income_date`, `income_amount`
    236 - `PK`: `income_id`
    237 - `FDs`: `income_id -> finance_user_id, income_date, income_amount`
    238 - `finance_user_id` не детерминира ништо во оваа релација → во 3НФ ✓
    239 
    240 **Заклучок:** Сите релации се во **3НФ**. Декомпозиција не е потребна.
     374- (Условот CHECK (точно еден FK е NOT NULL) е ограничување на домен, не FD)
     375
     376**INCOMES(income_id, user_id, income_date, income_amount)**
     377- PK: `income_id`
     378- Не-клучен `user_id` не детерминира ништо во оваа релација → во 3НФ ✓
     379
     380**Заклучок:** Сите релации се во **3НФ**. Понатамошна декомпозиција не е потребна.
    241381
    242382== **Проверка за БКНФ** ==
     
    244384**БКНФ бара:** за секоја нетривијална `FD X -> Y`, `X` мора да биде **суперклуч**.
    245385
    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
     3881. Users(**user_id**, email, username, password) -> **задоволува БКНФ**
     389   (`user_id`, `email` и `username` се сите кандидат клучеви — сите детерминанти во оваа релација се суперклучеви. `UNIQUE` constraints на `email` и `username` ги енфорцираат алтернативните кандидат клучеви.)
     390
     3912. TrainingUsers(**user_id**, training_gender, training_age, training_weight) -> **задоволува БКНФ**
     392
     3933. TrainingSessions(**training_id**, training_user_id, training_date, training_type, training_duration, training_calories) -> **задоволува БКНФ**
     394
     3954. WeightUsers(**user_id**, weight_current, weight_height, weight_goal_weight, weight_goal_calories) -> **задоволува БКНФ**
     396
     3975. DailyIntakes(**daily_intake_id**, user_id, daily_intake_date, daily_intake_calories) -> **задоволува БКНФ**
     398
     3996. InvestorUsers(**user_id**) -> **задоволува БКНФ**
     400
     4017. Assets(**asset_id**, user_id, asset_ticker_symbol, asset_buy_price, asset_buy_date, asset_quantity) -> **задоволува БКНФ**
     402
     4038. DisciplineUsers(**user_id**) -> **задоволува БКНФ**
     404
     4059. CustomTrackingCategories(**custom_tracking_id**, user_id, custom_tracking_name) -> **задоволува БКНФ**
     406
     40710. Tasks(**task_id**, discipline_user_id, custom_tracking_id, task_name, task_is_finished) -> **задоволува БКНФ**
     408
     40911. DailyCompletion(**daily_completion_id**, user_id, daily_completion_date, daily_completion_procent) -> **задоволува БКНФ**
     410
     41112. TaskDailyCompletion(**task_id, daily_completion_id**) -> **задоволува БКНФ**
     412
     41313. FinanceUsers(**user_id**, spending_budget, saving_budget, investing_budget, donation_budget, credit) -> **задоволува БКНФ**
     414
     41514. Incomes(**income_id**, user_id, income_date, income_amount) -> **задоволува БКНФ**
     416
     41715. R11 = {**training_id, asset_id, user_id, daily_intake_id, discipline_user_id, custom_tracking_id, task_id, daily_completion_id, income_id**} -> **задоволува БКНФ**
    269418
    270419**Заклучок:** **Целиот модел е во БКНФ.**
    271420
    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
     427R11 содржи само примарни и надворешни клучеви кои меѓусебно имаат 1:1 зависности — нема независни мултивредносни факти во истата релација.
     428
     429`TaskDailyCompletion(**task_id, daily_completion_id**)` е спојна релација со композитен примарен клуч — нема не-клучни атрибути, па MVD не се применува.
     430
     431**Заклучок:** Ниту една релација не содржи нетривијална мултивредносна зависност. **Целиот модел е во 4НФ.** Понатамошна декомпозиција не е потребна.
    475432
    476433== **Финален резултат и дискусија** ==
     
    479436
    480437USERS(user_id, email, username, password)
    481 CK: {user_id}, {email}, {username}
     438PK: user_id   CK: {user_id}, {email}, {username}
    482439
    483440TRAINING_USERS(user_id, gender, age, weight)
    484 PK: user_id FK: user_id -> USERS
     441PK: user_id   FK: user_id -> USERS
    485442
    486443TRAINING_SESSIONS(training_id, training_user_id, date, type, duration, calories)
    487 PK: training_id FK: training_user_id -> TRAINING_USERS
     444PK: training_id   FK: training_user_id -> TRAINING_USERS(user_id)
     445
     446WEIGHT_USERS(user_id, weight, height, goal_weight, goal_calories)
     447PK: user_id   FK: user_id -> USERS
     448
     449DAILY_INTAKES(daily_intake_id, user_id, date, calories)
     450PK: daily_intake_id   FK: user_id -> WEIGHT_USERS(user_id)
    488451
    489452INVESTOR_USERS(user_id)
    490 PK: user_id FK: user_id -> USERS
     453PK: user_id   FK: user_id -> USERS
    491454
    492455ASSETS(asset_id, user_id, ticker_symbol, buy_price, buy_date, quantity)
    493 PK: asset_id FK: user_id -> INVESTOR_USERS
    494 
    495 WEIGHT_USERS(user_id, weight, height, goal_weight, goal_calories)
    496 PK: user_id FK: user_id -> USERS
    497 
    498 DAILY_INTAKES(daily_intake_id, user_id, date, calories)
    499 PK: daily_intake_id FK: user_id -> WEIGHT_USERS
     456PK: asset_id   FK: user_id -> INVESTOR_USERS(user_id)
    500457
    501458DISCIPLINE_USERS(user_id)
    502 PK: user_id FK: user_id -> USERS
     459PK: user_id   FK: user_id -> USERS
    503460
    504461CUSTOM_TRACKING_CATEGORIES(custom_tracking_id, user_id, name)
    505 PK: custom_tracking_id FK: user_id -> USERS
     462PK: custom_tracking_id   FK: user_id -> USERS
    506463
    507464TASKS(task_id, discipline_user_id, custom_tracking_id, name, is_finished)
    508465PK: task_id
    509 FK: discipline_user_id -> DISCIPLINE_USERS
     466FK: discipline_user_id -> DISCIPLINE_USERS(user_id)
    510467FK: custom_tracking_id -> CUSTOM_TRACKING_CATEGORIES
    511468CHECK: точно еден од двата FK е NOT NULL
    512469
    513470DAILY_COMPLETION(daily_completion_id, user_id, date, procent)
    514 PK: daily_completion_id FK: user_id -> USERS
     471PK: daily_completion_id   FK: user_id -> USERS
    515472
    516473TASK_DAILY_COMPLETION(task_id, daily_completion_id)
     
    520477
    521478FINANCE_USERS(user_id, spending_budget, saving_budget, investing_budget, donation_budget, credit)
    522 PK: user_id FK: user_id -> USERS
     479PK: user_id   FK: user_id -> USERS
    523480
    524481INCOMES(income_id, user_id, date, amount)
    525 PK: income_id FK: user_id -> FINANCE_USERS
    526 
     482PK: income_id   FK: user_id -> FINANCE_USERS(user_id)
    527483
    528484=== **Дискусија** ===
     
    530486**Разлики во однос на претходниот дизајн (Phase 2):**
    531487
    532 1. **Отстранет `weight_user_id` од `TRAINING_SESSIONS`.** 
    533    Во оригиналниот DDL, `TRAINING_SESSIONS` имаше `FK` кон `WEIGHT_USERS`. Ова е непотребна вкрстена зависност — тренинг сесијата логички припаѓа на тренинг профилот, не на weight профилот. Двата профила независно се врзуваат со `USERS` преку `user_id`.
    534 
    535 2. **Отстранети `num_tasks` и `tasks` од `DISCIPLINE_USERS` и `CUSTOM_TRACKING_CATEGORIES`.** 
     4881. **Отстранети `num_tasks` и `tasks` од `DISCIPLINE_USERS` и `CUSTOM_TRACKING_CATEGORIES`.**
    536489   `num_tasks` е изведен атрибут (`COUNT` на `TASKS`) — чување на изведени вредности го нарушува принципот на нормализација и создава ризик од инконзистентност. `tasks` (`TEXT` листа) е не-атомски атрибут и директно ја нарушува `1НФ`.
    537490
    538 3. **Додадени `UNIQUE` constraints на `email` и `username` во `USERS`.** 
     4912. **Профилските табели користат `user_id` и како PK и како FK.**
     492   `TRAINING_USERS`, `WEIGHT_USERS`, `INVESTOR_USERS`, `DISCIPLINE_USERS` и `FINANCE_USERS` го користат `user_id` и како примарен и како надворешен клуч кон `USERS`. Ова е shared primary key pattern (1:1 субтип врска). Во глобалната релација R, атрибутите `training_user_id` и `discipline_user_id` се задржуваат бидејќи се реални FK колони во `TRAINING_SESSIONS` и `TASKS` соодветно.
     493
     4943. **Додадени `UNIQUE` constraints на `email` и `username` во `USERS`.**
    539495   Нормализацијата формално идентификуваше `{email}` и `{username}` како кандидат клучеви, па DDL мора да ги енфорцира.
    540496
    541 4. **`GENERATED BY DEFAULT AS IDENTITY` за сите сурогатни `PK`-а.** 
     4974. **`GENERATED BY DEFAULT AS IDENTITY` за сите сурогатни `PK`-а.**
    542498   Технички подобрување усогласено со нормализираниот модел.
    543499
    544500**Модел за употреба во понатамошните фази:** нормализираниот модел (финалниот DDL од Phase 5).
     501
     502== **Заклучок** ==
     503
     504При нормализацијата ги добивме истите релации од претходниот дизајн, со исклучок на формалното отстранување на изведените и не-атомски атрибути (`num_tasks`, `tasks`). Профилските табели правилно го користат `user_id` и како примарен и надворешен клуч, со што финалниот нормализиран модел целосно се совпаѓа со DDL. Конечниот модел задоволува **1НФ, 2НФ, 3НФ и БКНФ**, обезбедувајќи целосна декомпозиција без губење на податоци (lossless join) и без губење на функционални зависности (dependency preservation).