Changes between Version 8 and Version 9 of Normalization


Ignore:
Timestamp:
06/12/26 19:25:21 (2 days ago)
Author:
233062
Comment:

--

Legend:

Unmodified
Added
Removed
Modified
  • Normalization

    v8 v9  
    1 # **Нормализација**
    2 
    3 == **Де-нормализирана база на податоци**
     1= **Нормализација** =
     2
     3== **Де-нормализирана база на податоци** ==
    44
    55Се тргнува од една глобална, де-нормализирана релација што ги содржи атрибутите од целиот модел:
    66
    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, name, is_finished, daily_completion_id, daily_completion_date, 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 Оваа релација е де-нормализирана затоа што содржи повторливи групи, како и зависности од атрибути што не се клуч.
    10 
    11 == **Функционални зависности**
     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` е отстранет бидејќи претставува непотребна вкрстена зависност меѓу два независни профили на ист корисник.
     10
     11== **Функционални зависности** ==
     12
     13Канонски покривач на функционалните зависности:
    1214
    1315- **FD1:** `user_id -> email, username, password`
    14 - **FD2:** `email -> user_id, username, password`
    15 - **FD3:** `username -> user_id, email, password`
     16- **FD2:** `email -> user_id`
     17- **FD3:** `username -> user_id`
    1618- **FD4:** `training_user_id -> training_gender, training_age, training_weight`
    1719- **FD5:** `training_id -> training_user_id, training_date, training_type, training_duration, training_calories`
     
    2224- **FD10:** `discipline_user_id -> user_id`
    2325- **FD11:** `custom_tracking_id -> user_id, custom_tracking_name`
    24 - **FD12:** `task_id -> discipline_user_id, custom_tracking_id, name, is_finished`
    25 - **FD13:** `daily_completion_id -> user_id, daily_completion_date, procent`
    26 - **FD14:** `(task_id, daily_completion_id) -> /`
     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) -> /` (нема дополнителни атрибути — само врска)
    2729- **FD15:** `finance_user_id -> finance_spending_budget, finance_saving_budget, finance_investing_budget, finance_donation_budget, finance_credit`
    2830- **FD16:** `income_id -> finance_user_id, income_date, income_amount`
    2931
    30 **Лев и десен дел:**
    31 
    32 - **Леви:** `user_id, email, username, training_user_id, training_id, investor_user_id, asset_id, weight_user_id, daily_intake_id, discipline_user_id, custom_tracking_id, task_id, daily_completion_id, finance_user_id, income_id`
    33 - **Десни:** `password, training_gender, training_age, training_weight, training_date, training_type, training_duration, training_calories, asset_ticker_symbol, asset_buy_price, asset_buy_date, asset_quantity, weight_current, weight_height, weight_goal_weight, weight_goal_calories, daily_intake_date, daily_intake_calories, custom_tracking_name, name, is_finished, daily_completion_date, procent, finance_spending_budget, finance_saving_budget, finance_investing_budget, finance_donation_budget, finance_credit, income_date, income_amount`
    34 
    35 == **Глобална релација**
    36 
    37 `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, name, is_finished, daily_completion_id, daily_completion_date, procent, finance_user_id, finance_spending_budget, finance_saving_budget, finance_investing_budget, finance_donation_budget, finance_credit, income_id, income_date, income_amount }`
    38 
    39 == **Покривачи**
    40 
    41 - **`user_id+`** = { `user_id`, `email`, `username`, `password` } -> **не ги содржи сите атрибути**
    42 - **`training_user_id+`** = { `training_user_id`, `training_gender`, `training_age`, `training_weight` } -> **не ги содржи сите атрибути**
    43 - **`training_id+`** = { `training_id`, `training_user_id`, `training_date`, `training_type`, `training_duration`, `training_calories` } -> **не ги содржи сите атрибути**
    44 - **`investor_user_id+`** = { `investor_user_id`, `user_id` } -> **не ги содржи сите атрибути**
    45 - **`asset_id+`** = { `asset_id`, `investor_user_id`, `asset_ticker_symbol`, `asset_buy_price`, `asset_buy_date`, `asset_quantity` } -> **не ги содржи сите атрибути**
    46 - **`weight_user_id+`** = { `weight_user_id`, `weight_current`, `weight_height`, `weight_goal_weight`, `weight_goal_calories` } -> **не ги содржи сите атрибути**
    47 - **`daily_intake_id+`** = { `daily_intake_id`, `weight_user_id`, `daily_intake_date`, `daily_intake_calories` } -> **не ги содржи сите атрибути**
    48 - **`discipline_user_id+`** = { `discipline_user_id`, `user_id` } -> **не ги содржи сите атрибути**
    49 - **`custom_tracking_id+`** = { `custom_tracking_id`, `user_id`, `custom_tracking_name` } -> **не ги содржи сите атрибути**
    50 - **`task_id+`** = { `task_id`, `discipline_user_id`, `custom_tracking_id`, `name`, `is_finished` } -> **не ги содржи сите атрибути**
    51 - **`daily_completion_id+`** = { `daily_completion_id`, `user_id`, `daily_completion_date`, `procent` } -> **не ги содржи сите атрибути**
    52 - **`finance_user_id+`** = { `finance_user_id`, `finance_spending_budget`, `finance_saving_budget`, `finance_investing_budget`, `finance_donation_budget`, `finance_credit` } -> **не ги содржи сите атрибути**
    53 - **`income_id+`** = { `income_id`, `finance_user_id`, `income_date`, `income_amount` } -> **не ги содржи сите атрибути**
    54 - **`{task_id, daily_completion_id}+`** = { `task_id`, `daily_completion_id` } -> **не ги содржи сите атрибути**
    55 
    56 == **Спојување покривачи**
    57 
    58 Бидејќи атрибутите што се појавуваат само на левата страна не можат да се изведат на друг начин, тие мора да бидат дел од примарниот клуч.
    59 
    60 Земајќи ги сите леви атрибути заедно, добиваме минимален суперклуч:
    61 
    62 `{ user_id, training_user_id, training_id, investor_user_id, asset_id, weight_user_id, daily_intake_id, discipline_user_id, custom_tracking_id, task_id, daily_completion_id, finance_user_id, income_id }`
    63 
    64 Во нашата нормализирана конструкција, ова е теоретскиот глобален кандидат-клуч на де-нормализираната релација.
    65 
    66 Избран примарен клуч: **`{ user_id, training_user_id, training_id, investor_user_id, asset_id, weight_user_id, daily_intake_id, discipline_user_id, custom_tracking_id, task_id, daily_completion_id, finance_user_id, income_id }`**
    67 
    68 == **Проверка за 1НФ**
    69 
    70 Бидејќи релацијата содржи повторливи групи и неатомски листи во де-нормализираниот поглед, **не ја задоволува 1НФ**.
    71 
    72 == **Декомпозиција по 1НФ**
    73 
    74 **Релација што се анализира**
    75 
    76 `R`
    77 
    78 **Проблем**
    79 
    80 Лист атрибутите и повторливите групи не се атомски.
    81 
    82 **Прва декомпозиција**
    83 
    84 - `TASKS(task_id, discipline_user_id, custom_tracking_id, name, is_finished)`
    85 - `TASK_DAILY_COMPLETION(task_id, daily_completion_id)`
    86 
    87 **Резултат**
    88 
    89 Се добиваат атомски вредности и релации со локални клучеви.
    90 
    91 == **Проверка за 2НФ**
    92 
    93 `R` **не ја задоволува 2НФ** поради парцијални зависности, на пример:
    94 
    95 - `user_id -> email, username, password`
    96 - `training_user_id -> training_gender, training_age, training_weight`
    97 - `asset_id -> asset_ticker_symbol, asset_buy_price, asset_buy_date, asset_quantity`
    98 
    99 == **Декомпозиција по 2НФ**
    100 
    101 Атрибутите се групираат според тоа од кој клуч зависат:
    102 
    103 - **`USERS(user_id, email, username, password)`**
    104 - **`TRAINING_USERS(user_id, gender, age, weight)`**
    105 - **`TRAINING_SESSIONS(training_id, training_user_id, date, type, duration, calories)`**
    106 - **`INVESTOR_USERS(user_id)`**
    107 - **`ASSETS(asset_id, user_id, ticker_symbol, buy_price, buy_date, quantity)`**
    108 - **`WEIGHT_USERS(user_id, weight, height, goal_weight, goal_calories)`**
    109 - **`DAILY_INTAKES(daily_intake_id, user_id, date, calories)`**
    110 - **`DISCIPLINE_USERS(user_id)`**
    111 - **`CUSTOM_TRACKING_CATEGORIES(custom_tracking_id, user_id, name)`**
    112 - **`TASKS(task_id, discipline_user_id, custom_tracking_id, name, is_finished)`**
    113 - **`DAILY_COMPLETION(daily_completion_id, user_id, date, procent)`**
    114 - **`TASK_DAILY_COMPLETION(task_id, daily_completion_id)`**
    115 - **`FINANCE_USERS(user_id, spending_budget, saving_budget, investing_budget, donation_budget, credit)`**
    116 - **`INCOMES(income_id, user_id, date, amount)`**
    117 
    118 == **Проверка за 3НФ**
    119 
    120 Ги разгледуваме релациите добиени по 2НФ.
    121 
    122 Проблематични транзитивни/изведени атрибути се:
    123 
    124 - **`num_tasks`**
    125 - **`tasks`**
    126 - **`weight_user_id`** во `TRAINING_SESSIONS` како непотребна зависност за оваа верзија на моделот
    127 
    128 == **Декомпозиција по 3НФ**
    129 
    130 1. **`DISCIPLINE_USERS(user_id)`** -> се задржува само идентификаторот што ја врзува дисциплинската улога со `USERS`.
    131 
    132 2. **`CUSTOM_TRACKING_CATEGORIES(custom_tracking_id, user_id, name)`** -> се задржува само името на категоријата, без броење и листи на задачи.
    133 
    134 3. **`TRAINING_SESSIONS(training_id, training_user_id, date, type, duration, calories)`** -> сесијата се врзува само со `training_user_id`.
    135 
    136 **Резултат**
    137 
    138 Нема не-клучен атрибут што зависи од друг не-клучен атрибут во истата релација.
    139 
    140 == **Проверка за БКНФ**
    141 
    142 Сите добиени релации се во БКНФ, бидејќи секој детерминант е кандидат-клуч во својата релација.
    143 
    144 Особено:
    145 
    146 - **`USERS`** ги задржува алтернативните клучеви `email` и `username`
    147 - **`TASK_DAILY_COMPLETION(task_id, daily_completion_id)`** е спојна релација без не-клучни атрибути
    148 - профилните релации со `user_id` се чисти 1:1 продолжувања на `USERS`
    149 
    150 == **Финален резултат и дискусија**
    151 
    152 == **Нормализиран релациски модел**
    153 
    154 Финалниот модел што го користиме во тековната имплементација е:
    155 
    156 - **`USERS`**
    157 - **`TRAINING_USERS`**
    158 - **`TRAINING_SESSIONS`**
    159 - **`INVESTOR_USERS`**
    160 - **`ASSETS`**
    161 - **`WEIGHT_USERS`**
    162 - **`DAILY_INTAKES`**
    163 - **`DISCIPLINE_USERS`**
    164 - **`CUSTOM_TRACKING_CATEGORIES`**
    165 - **`TASKS`**
    166 - **`DAILY_COMPLETION`**
    167 - **`TASK_DAILY_COMPLETION`**
    168 - **`FINANCE_USERS`**
    169 - **`INCOMES`**
    170 
    171 == **Дискусија**
    172 
    173 Разлики во однос на претходниот дизајн:
    174 
    175 - Се отстранети **`num_tasks`** и **`tasks`** од дисциплинските табели.
    176 - Се отстранета непотребната релација **`weight_user_id`** од `TRAINING_SESSIONS`.
    177 - Се задржуваат профилните табели со **`user_id`** како заеднички примарен и странски клуч.
    178 
     32== **Кандидат клучеви и примарен клуч** ==
     33
     34=== **Пресметување на затворања (closures)** ===
     35
     36За да се најдат кандидат клучевите, пресметуваме затворање за секој атрибут (или комбинација) и проверуваме дали го покрива целиот `R`.
     37
     38**Атрибути кои се појавуваат само на десна страна** (никогаш детерминанти):
     39- `email`, `username` — само преку `FD2`/`FD3` се детерминанти, но се и десни во `FD1`
     40- `training_gender`, `training_age`, `training_weight`, `training_date`, `training_type`, `training_duration`, `training_calories`
     41- `asset_ticker_symbol`, `asset_buy_price`, `asset_buy_date`, `asset_quantity`
     42- `weight_current`, `weight_height`, `weight_goal_weight`, `weight_goal_calories`
     43- `daily_intake_date`, `daily_intake_calories`
     44- `custom_tracking_name`, `task_name`, `task_is_finished`
     45- `daily_completion_date`, `daily_completion_procent`
     46- `finance_spending_budget`, `finance_saving_budget`, `finance_investing_budget`, `finance_donation_budget`, `finance_credit`
     47- `income_date`, `income_amount`
     48
     49Овие атрибути **не можат** да бидат дел од кандидат клуч бидејќи не детерминираат ништо надвор од себе.
     50
     51**Атрибути кои се само на лева страна** (мора да бидат во секој кандидат клуч):
     52- `training_id`, `asset_id`, `daily_intake_id`, `task_id`, `daily_completion_id`, `income_id`
     53
     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
     57=== **Затворање на минималниот суперклуч** ===
     58
     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` е суперклуч** ✓
     79
     80**Минималност:** Отстранувањето на кој било атрибут од `K` резултира во непокриени атрибути (на пример, без `training_id` не можеме да добиеме `training_date`, `training_type` итн.), па `K` е минимален.
     81
     82**Примарен клуч:**
     83
     84`PK = { user_id, training_id, asset_id, daily_intake_id, task_id,
     85daily_completion_id, income_id, training_user_id, investor_user_id,
     86weight_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== **Проверка за 1НФ** ==
     94
     95Релацијата `R` **НЕ ја задоволува 1НФ** поради:
     961. **Повторливи групи** — еден корисник има повеќе `training_sessions`, повеќе `incomes`, повеќе `assets` итн., што значи дека редот би морал да се повторува или атрибутите би биле листи.
     972. **Не-атомски атрибути** — во оригиналниот дизајн постоеја `tasks` (`TEXT` листа) и `num_tasks` (изведена вредност) во `DISCIPLINE_USERS` и `CUSTOM_TRACKING_CATEGORIES`.
     98
     99== **Декомпозиција по 1НФ** ==
     100
     101**Релација што се анализира:** `R` (глобална)
     102
     103**Проблем:** Повторливи групи и не-атомски атрибути.
     104
     105**Решение:** Секој ентитет и врска добива своја посебна релација со атомски атрибути и локален примарен клуч.
     106
     107**Посебен случај — `TASK_DAILY_COMPLETION`:**
     108Задачите и дневните завршувања се во врска **многу-кон-многу**. Се воведува посредна релација:
     109
     110TASK_DAILY_COMPLETION(task_id, daily_completion_id)
     111
     112
     113**Резултат по 1НФ декомпозиција:**
     114- `R1: USERS(user_id, email, username, password)`
     115- `R2: TRAINING_USERS(training_user_id, training_gender, training_age, training_weight)`
     116- `R3: TRAINING_SESSIONS(training_id, training_user_id, training_date, training_type, training_duration, training_calories)`
     117- `R4: INVESTOR_USERS(investor_user_id)`
     118- `R5: ASSETS(asset_id, investor_user_id, asset_ticker_symbol, asset_buy_price, asset_buy_date, asset_quantity)`
     119- `R6: WEIGHT_USERS(weight_user_id, weight_current, weight_height, weight_goal_weight, weight_goal_calories)`
     120- `R7: DAILY_INTAKES(daily_intake_id, weight_user_id, daily_intake_date, daily_intake_calories)`
     121- `R8: DISCIPLINE_USERS(discipline_user_id)`
     122- `R9: CUSTOM_TRACKING_CATEGORIES(custom_tracking_id, user_id, custom_tracking_name)`
     123- `R10: TASKS(task_id, discipline_user_id, custom_tracking_id, task_name, task_is_finished)`
     124- `R11: DAILY_COMPLETION(daily_completion_id, user_id, daily_completion_date, daily_completion_procent)`
     125- `R12: TASK_DAILY_COMPLETION(task_id, daily_completion_id)`
     126- `R13: FINANCE_USERS(finance_user_id, finance_spending_budget, finance_saving_budget, finance_investing_budget, finance_donation_budget, finance_credit)`
     127- `R14: INCOMES(income_id, finance_user_id, income_date, income_amount)`
     128
     129- Сите атрибути се атомски. ✓
     130- Нема повторливи групи. ✓
     131
     132== **Проверка за 2НФ** ==
     133
     134**2НФ бара:** релацијата е во 1НФ и **нема парцијална зависност** — секој не-клучен атрибут зависи од **целиот** примарен клуч, не само од дел од него.
     135
     136Парцијална зависност постои само кога примарниот клуч е **сложен**. Ги разгледуваме само релациите со сложен `PK`:
     137
     138**`R12: TASK_DAILY_COMPLETION(task_id, daily_completion_id)`**
     139- `PK: {task_id, daily_completion_id}`
     140- Нема не-клучни атрибути → нема парцијални зависности → во 2НФ ✓
     141
     142Сите останати релации (`R1`–`R11`, `R13`–`R14`) имаат **прост примарен клуч**, па парцијална зависност е невозможна → автоматски во 2НФ ✓
     143
     144**Заклучок:** Сите релации се во **2НФ**. Декомпозиција не е потребна.
     145
     146== **Декомпозиција по 2НФ** ==
     147
     148Бидејќи сите релации веќе се во 2НФ, нема декомпозиција. Релациите од 1НФ чекорот се пренесуваат непроменети.
     149
     150== **Проверка за 3НФ** ==
     151
     152**3НФ бара:** релацијата е во 2НФ и **нема транзитивна зависност** — не-клучен атрибут не смее да зависи од друг не-клучен атрибут.
     153
     154Ги разгледуваме релациите каде транзитивна зависност е можна:
     155
     156**`R3: TRAINING_SESSIONS`**
     157- Атрибути: `training_id`, `training_user_id`, `training_date`, `training_type`, `training_duration`, `training_calories`
     158- `PK`: `training_id`
     159- `FDs`: `training_id -> training_user_id, training_date, training_type, training_duration, training_calories`
     160- Не-клучни атрибути: `training_user_id`, `training_date`, `training_type`, `training_duration`, `training_calories`
     161- Постои ли транзитивност? `training_user_id` е не-клучен, но не детерминира ништо во оваа релација → нема транзитивност → во 3НФ ✓
     162
     163**`R5: ASSETS`**
     164- Атрибути: `asset_id`, `investor_user_id`, `asset_ticker_symbol`, `asset_buy_price`, `asset_buy_date`, `asset_quantity`
     165- `PK`: `asset_id`
     166- `FDs`: `asset_id ->` сите атрибути
     167- Не-клучен `investor_user_id` не детерминира ништо во оваа релација → во 3НФ ✓
     168
     169**`R7: DAILY_INTAKES`**
     170- Атрибути: `daily_intake_id`, `weight_user_id`, `daily_intake_date`, `daily_intake_calories`
     171- `PK`: `daily_intake_id`
     172- `FDs`: `daily_intake_id -> weight_user_id, daily_intake_date, daily_intake_calories`
     173- `weight_user_id` не детерминира ништо во оваа релација → во 3НФ ✓
     174
     175**`R9: CUSTOM_TRACKING_CATEGORIES`**
     176- Атрибути: `custom_tracking_id`, `user_id`, `custom_tracking_name`
     177- `PK`: `custom_tracking_id`
     178- `FDs`: `custom_tracking_id -> user_id, custom_tracking_name`
     179- `user_id` не детерминира ништо во оваа релација → во 3НФ ✓
     180
     181**`R10: TASKS`**
     182- Атрибути: `task_id`, `discipline_user_id`, `custom_tracking_id`, `task_name`, `task_is_finished`
     183- `PK`: `task_id`
     184- `FDs`: `task_id -> discipline_user_id, custom_tracking_id, task_name, task_is_finished`
     185- Ниту `discipline_user_id` ниту `custom_tracking_id` детерминираат ништо во оваа релација → во 3НФ ✓
     186
     187**`R14: INCOMES`**
     188- Атрибути: `income_id`, `finance_user_id`, `income_date`, `income_amount`
     189- `PK`: `income_id`
     190- `FDs`: `income_id -> finance_user_id, income_date, income_amount`
     191- `finance_user_id` не детерминира ништо во оваа релација → во 3НФ ✓
     192
     193**Заклучок:** Сите релации се во **3НФ**. Декомпозиција не е потребна.
     194
     195== **Проверка за БКНФ** ==
     196
     197**БКНФ бара:** за секоја нетривијална `FD X -> Y`, `X` мора да биде **суперклуч**.
     198
     199Ги разгледуваме релациите каде постојат повеќе кандидат клучеви:
     200
     201**`R1: USERS(user_id, email, username, password)`**
     202- `FDs`:
     203  - `user_id -> email, username, password`
     204  - `email -> user_id` (FD2)
     205  - `username -> user_id` (FD3)
     206- Кандидат клучеви: `{user_id}`, `{email}`, `{username}`
     207- Проверка:
     208  - `user_id -> ...` : `user_id` е кандидат клуч ✓
     209  - `email -> ...` : `email` е кандидат клуч ✓
     210  - `username -> ...` : `username` е кандидат клуч ✓
     211- Сите детерминанти се кандидат клучеви → `USERS` е во БКНФ ✓
     212
     213**`R10: TASKS`**
     214- `FDs`: `task_id -> discipline_user_id, custom_tracking_id, task_name, task_is_finished`
     215- Единствен кандидат клуч: `{task_id}`
     216- `task_id` е кандидат клуч → во БКНФ ✓
     217
     218**Напомена за CHECK constraint:**
     219Условот `(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)` не е функционална зависност туку ограничување на домен — не влијае на БКНФ.
     220
     221Сите останати релации имаат единствен кандидат клуч (нивниот сурогатен `PK`) и сите детерминанти во нив се суперклучеви → сите се во БКНФ ✓
     222
     223**Заклучок:** **Целиот модел е во БКНФ.**
     224
     225=== **Lossless Join проверка** ===
     226
     227За секоја декомпозиција `R -> R1, R2`, `join`-от е lossless ако и само ако:
     228`(R1 ∩ R2) -> R1` **ИЛИ** `(R1 ∩ R2) -> R2`
     229
     230Бидејќи декомпозицијата е вршена директно од глобалната релација `R` во посебни релации (не постепено `R->R1,R2->R3,R4...`), ја применуваме проверката за секој пар поврзани релации преку странски клуч:
     231
     232- `USERS` — `TRAINING_USERS`: Пресек `{user_id}` → `user_id` е `PK` во `USERS` → суперклуч ✓
     233- `USERS` — `FINANCE_USERS`: Пресек `{user_id}` → `user_id` е `PK` во `USERS` → суперклуч ✓
     234- `USERS` — `INVESTOR_USERS`: Пресек `{user_id}` → `user_id` е `PK` во `USERS` → суперклуч ✓
     235- `USERS` — `DISCIPLINE_USERS`: Пресек `{user_id}` → `user_id` е `PK` во `USERS` → суперклуч ✓
     236- `USERS` — `CUSTOM_TRACKING_CATEGORIES`: Пресек `{user_id}` → `user_id` е `PK` во `USERS` → суперклуч ✓
     237- `USERS` — `DAILY_COMPLETION`: Пресек `{user_id}` → `user_id` е `PK` во `USERS` → суперклуч ✓
     238- `TRAINING_USERS` — `TRAINING_SESSIONS`: Пресек `{training_user_id}` → `training_user_id` е `PK` во `TRAINING_USERS` → суперклуч ✓
     239- `INVESTOR_USERS` — `ASSETS`: Пресек `{investor_user_id}` → `investor_user_id` е `PK` во `INVESTOR_USERS` → суперклуч ✓
     240- `WEIGHT_USERS` — `DAILY_INTAKES`: Пресек `{weight_user_id}` → `weight_user_id` е `PK` во `WEIGHT_USERS` → суперклуч ✓
     241- `FINANCE_USERS` — `INCOMES`: Пресек `{finance_user_id}` → `finance_user_id` е `PK` во `FINANCE_USERS` → суперклуч ✓
     242- `DISCIPLINE_USERS` — `TASKS`: Пресек `{discipline_user_id}` → `discipline_user_id` е `PK` во `DISCIPLINE_USERS` → суперклуч ✓
     243- `CUSTOM_TRACKING_CATEGORIES` — `TASKS`: Пресек `{custom_tracking_id}` → `custom_tracking_id` е `PK` во `CUSTOM_TRACKING_CATEGORIES` → суперклуч ✓
     244- `TASKS` — `TASK_DAILY_COMPLETION`: Пресек `{task_id}` → `task_id` е `PK` во `TASKS` → суперклуч ✓
     245- `DAILY_COMPLETION` — `TASK_DAILY_COMPLETION`: Пресек `{daily_completion_id}` → `daily_completion_id` е `PK` во `DAILY_COMPLETION` → суперклуч ✓
     246
     247**Заклучок:** Сите декомпозиции имаат **lossless join** својство. ✓
     248
     249=== **Dependency Preservation проверка** ===
     250
     251Проверуваме дека секоја `FD` од оригиналниот сет е зачувана во барем една од финалните релации:
     252
     253- `FD1: user_id -> email, username, password` → `USERS` ✓
     254- `FD2: email -> user_id` → `USERS` ✓
     255- `FD3: username -> user_id` → `USERS` ✓
     256- `FD4: training_user_id -> training_gender, training_age, training_weight` → `TRAINING_USERS` ✓
     257- `FD5: training_id -> training_user_id, ...` → `TRAINING_SESSIONS` ✓
     258- `FD6: investor_user_id -> user_id` → `INVESTOR_USERS` (преку `FK`) ✓
     259- `FD7: asset_id -> investor_user_id, ...` → `ASSETS` ✓
     260- `FD8: weight_user_id -> weight_current, ...` → `WEIGHT_USERS` ✓
     261- `FD9: daily_intake_id -> weight_user_id, ...` → `DAILY_INTAKES` ✓
     262- `FD10: discipline_user_id -> user_id` → `DISCIPLINE_USERS` (преку `FK`) ✓
     263- `FD11: custom_tracking_id -> user_id, name` → `CUSTOM_TRACKING_CATEGORIES` ✓
     264- `FD12: task_id -> discipline_user_id, ...` → `TASKS` ✓
     265- `FD13: daily_completion_id -> user_id, ...` → `DAILY_COMPLETION` ✓
     266- `FD14: (task_id, daily_completion_id) -> /` → `TASK_DAILY_COMPLETION` ✓
     267- `FD15: finance_user_id -> finance_spending_budget, ...` → `FINANCE_USERS` ✓
     268- `FD16: income_id -> finance_user_id, ...` → `INCOMES` ✓
     269
     270**Заклучок:** **Сите функционални зависности се зачувани.** ✓
     271
     272== **Финален резултат и дискусија** ==
     273
     274=== **Нормализиран релациски модел** ===
     275
     276USERS(user_id, email, username, password)
     277CK: {user_id}, {email}, {username}
     278
     279TRAINING_USERS(user_id, gender, age, weight)
     280PK: user_id FK: user_id -> USERS
     281
     282TRAINING_SESSIONS(training_id, training_user_id, date, type, duration, calories)
     283PK: training_id FK: training_user_id -> TRAINING_USERS
     284
     285INVESTOR_USERS(user_id)
     286PK: user_id FK: user_id -> USERS
     287
     288ASSETS(asset_id, user_id, ticker_symbol, buy_price, buy_date, quantity)
     289PK: asset_id FK: user_id -> INVESTOR_USERS
     290
     291WEIGHT_USERS(user_id, weight, height, goal_weight, goal_calories)
     292PK: user_id FK: user_id -> USERS
     293
     294DAILY_INTAKES(daily_intake_id, user_id, date, calories)
     295PK: daily_intake_id FK: user_id -> WEIGHT_USERS
     296
     297DISCIPLINE_USERS(user_id)
     298PK: user_id FK: user_id -> USERS
     299
     300CUSTOM_TRACKING_CATEGORIES(custom_tracking_id, user_id, name)
     301PK: custom_tracking_id FK: user_id -> USERS
     302
     303TASKS(task_id, discipline_user_id, custom_tracking_id, name, is_finished)
     304PK: task_id
     305FK: discipline_user_id -> DISCIPLINE_USERS
     306FK: custom_tracking_id -> CUSTOM_TRACKING_CATEGORIES
     307CHECK: точно еден од двата FK е NOT NULL
     308
     309DAILY_COMPLETION(daily_completion_id, user_id, date, procent)
     310PK: daily_completion_id FK: user_id -> USERS
     311
     312TASK_DAILY_COMPLETION(task_id, daily_completion_id)
     313PK: {task_id, daily_completion_id}
     314FK: task_id -> TASKS
     315FK: daily_completion_id -> DAILY_COMPLETION
     316
     317FINANCE_USERS(user_id, spending_budget, saving_budget, investing_budget, donation_budget, credit)
     318PK: user_id FK: user_id -> USERS
     319
     320INCOMES(income_id, user_id, date, amount)
     321PK: income_id FK: user_id -> FINANCE_USERS
     322
     323
     324=== **Дискусија** ===
     325
     326**Разлики во однос на претходниот дизајн (Phase 2):**
     327
     3281. **Отстранет `weight_user_id` од `TRAINING_SESSIONS`.** 
     329   Во оригиналниот DDL, `TRAINING_SESSIONS` имаше `FK` кон `WEIGHT_USERS`. Ова е непотребна вкрстена зависност — тренинг сесијата логички припаѓа на тренинг профилот, не на weight профилот. Двата профила независно се врзуваат со `USERS` преку `user_id`.
     330
     3312. **Отстранети `num_tasks` и `tasks` од `DISCIPLINE_USERS` и `CUSTOM_TRACKING_CATEGORIES`.** 
     332   `num_tasks` е изведен атрибут (`COUNT` на `TASKS`) — чување на изведени вредности го нарушува принципот на нормализација и создава ризик од инконзистентност. `tasks` (`TEXT` листа) е не-атомски атрибут и директно ја нарушува `1НФ`.
     333
     3343. **Додадени `UNIQUE` constraints на `email` и `username` во `USERS`.** 
     335   Нормализацијата формално идентификуваше `{email}` и `{username}` како кандидат клучеви, па DDL мора да ги енфорцира.
     336
     3374. **`GENERATED BY DEFAULT AS IDENTITY` за сите сурогатни `PK`-а.** 
     338   Технички подобрување усогласено со нормализираниот модел.
     339
     340**Модел за употреба во понатамошните фази:** нормализираниот модел (финалниот DDL од Phase 5).