= **Нормализација** = == **Де-нормализирана база на податоци** == Се тргнува од една глобална, де-нормализирана релација што ги содржи атрибутите од целиот модел. Атрибутите со исто логичко значење но различно потекло се именуваат со префикс за да се избегне колизија на имиња во рамките на глобалната релација (на пример `training_weight` vs `weight_current`, `training_date` vs `daily_intake_date`): `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 }` **Напомена:** Атрибутите `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` итн. во тие табели. == **Функционални зависности** == Канонски покривач на функционалните зависности: - **FD1:** `user_id -> email, username, password` - **FD2:** `user_id -> training_gender, training_age, training_weight` - **FD3:** `training_user_id -> user_id` (конкретно кон `TRAINING_USERS.user_id`, кој преку shared PK pattern е ист со `USERS.user_id`) - **FD4:** `training_id -> training_user_id, training_date, training_type, training_duration, training_calories` - **FD5:** `user_id -> weight_current, weight_height, weight_goal_weight, weight_goal_calories` - **FD6:** `daily_intake_id -> user_id, daily_intake_date, daily_intake_calories` - **FD7:** `asset_id -> user_id, asset_ticker_symbol, asset_buy_price, asset_buy_date, asset_quantity` - **FD8:** `discipline_user_id -> user_id` - **FD9:** `custom_tracking_id -> user_id, custom_tracking_name` - **FD10:** `task_id -> discipline_user_id, custom_tracking_id, task_name, task_is_finished` - **FD11:** `daily_completion_id -> user_id, daily_completion_date, daily_completion_procent` - **FD12:** `(task_id, daily_completion_id) -> /` (нема дополнителни атрибути — само врска) - **FD13:** `user_id -> finance_spending_budget, finance_saving_budget, finance_investing_budget, finance_donation_budget, finance_credit` - **FD14:** `income_id -> user_id, income_date, income_amount` == **Кандидат клучеви и примарен клуч** == === **Пресметување на затворања (closures)** === За да се најдат кандидат клучевите, пресметуваме затворање за секој атрибут (или комбинација) и проверуваме дали го покрива целиот `R`. **Атрибути кои се појавуваат само на десна страна** (никогаш детерминанти): - `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`, `task_name`, `task_is_finished` - `daily_completion_date`, `daily_completion_procent` - `finance_spending_budget`, `finance_saving_budget`, `finance_investing_budget`, `finance_donation_budget`, `finance_credit` - `income_date`, `income_amount` - `email`, `username`, `password` **Атрибути кои се појавуваат на лева и десна страна:** `user_id`, `training_user_id`, `discipline_user_id`, `custom_tracking_id` Овие атрибути се детерминанти во одредени FD (на пример `user_id` детерминира `email`, `training_gender` итн.), но не мора да бидат дел од минималниот кандидат клуч бидејќи **веќе се изведливи** од атрибутите кои се само на левата страна (на пример `user_id` се изведува од `asset_id` преку FD7, `training_user_id` се изведува од `training_id` преку FD4 итн.). **Атрибути кои се само на лева страна** (мора да бидат во секој кандидат клуч): - `training_id`, `asset_id`, `daily_intake_id`, `task_id`, `daily_completion_id`, `income_id` === **Затворање на минималниот суперклуч** === Покривачи: 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 -> **Не ги содржи сите атрибути** 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 -> **Не ги содржи сите атрибути** 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 -> **Не ги содржи сите атрибути** 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 -> **Не ги содржи сите атрибути** 5. daily_completion_id+ -> {daily_completion_id, user_id, daily_completion_date, daily_completion_procent} via FD11 -> {email, ...} via FD1,FD2,FD5,FD13 -> **Не ги содржи сите атрибути** 6. income_id+ -> {income_id, user_id, income_date, income_amount} via FD14 -> {email, ...} via FD1,FD2,FD5,FD13 -> **Не ги содржи сите атрибути** Бидејќи атрибутите кои се наоѓаат само на левата страна не може да се изведат на никој друг начин, тие мора да бидат дел од примарниот клуч. {training_id, asset_id, daily_intake_id, task_id, daily_completion_id, income_id}+ -> {training_id, training_user_id, training_date, training_type, training_duration, training_calories, asset_id, user_id, asset_ticker_symbol, asset_buy_price, asset_buy_date, asset_quantity, email, username, password, training_gender, training_age, training_weight, 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} Покривачот составен од сите атрибути кои се наоѓаат само од левата страна ги содржи сите атрибути на глобалната релација и претставува минимален суперклуч, па затоа го избираме за примарен клуч. Избран примарен клуч: **К = {training_id, asset_id, daily_intake_id, task_id, daily_completion_id, income_id}** **Минималност:** Отстранувањето на кој било атрибут од `K` резултира во непокриени атрибути (на пример, без `training_id` не можеме да добиеме `training_date`, `training_type` итн.), па `K` е минимален. == **Проверка за 1НФ** == Бидејќи релацијата не содржи повеќевредносни атрибути, неуникатни атрибути, вредности од различен домен за еден атрибут, а притоа секоја редица е уникатно определена преку примарниот клуч, **ја задоволува 1НФ**. == **Проверка за 2НФ** == **2НФ бара:** релацијата е во 1НФ и **нема парцијална зависност** — секој не-клучен атрибут зависи од **целиот** примарен клуч, не само од дел од него. Бидејќи секој атрибут зависи само од еден дел на примарниот клуч (на пример, `training_date` зависи само од `training_id`, а не од целиот K), релацијата **не ја задоволува 2НФ**. Групирање на атрибутите според клучот од кој зависат: - training_id -> training_user_id, training_date, training_type, training_duration, training_calories - asset_id -> user_id, asset_ticker_symbol, asset_buy_price, asset_buy_date, asset_quantity - daily_intake_id -> user_id, daily_intake_date, daily_intake_calories - task_id -> discipline_user_id, custom_tracking_id, task_name, task_is_finished - daily_completion_id -> user_id, daily_completion_date, daily_completion_procent - income_id -> user_id, income_date, income_amount **Напомена:** Атрибутите зависни од `user_id` (`email`, `username`, `password`, `training_gender`, `training_age`, `training_weight`, `weight_current`, `weight_height`, `weight_goal_weight`, `weight_goal_calories`, `finance_spending_budget`, `finance_saving_budget`, `finance_investing_budget`, `finance_donation_budget`, `finance_credit`) не се групираат тука и остануваат во остатокот R6. Причината е што нивниот директен детерминант е `user_id`, кој е **не-примарен** атрибут (не е дел од K). Затоа тие претставуваат транзитивни зависности (проблем за 3НФ), а не парцијални зависности (проблем за 2НФ), и се обработуваат во чекорот за 3НФ. == **Декомпозиција по 2НФ** == 1. **TrainingSessions(training_id, training_user_id, training_date, training_type, training_duration, training_calories)** R1 = R - {training_user_id, training_date, training_type, training_duration, training_calories} R1 = {user_id, email, username, password, training_gender, training_age, training_weight, training_id, 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} Lossless join: Релацијата може да се реконструира преку join со `training_id`. Dependency preservation: FD4 е сочувана преку новата релација `TrainingSessions`. FD3 (training_user_id -> user_id) е сочувана преку FK `TrainingSessions.training_user_id -> TRAINING_USERS(user_id)`. 2. **Assets(asset_id, user_id, asset_ticker_symbol, asset_buy_price, asset_buy_date, asset_quantity)** R2 = R1 - {asset_ticker_symbol, asset_buy_price, asset_buy_date, asset_quantity} R2 = {user_id, email, username, password, training_gender, training_age, training_weight, training_id, asset_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_spending_budget, finance_saving_budget, finance_investing_budget, finance_donation_budget, finance_credit, income_id, income_date, income_amount} Lossless join: Релацијата може да се реконструира преку join со `asset_id`. Dependency preservation: FD7 е сочувана преку новата релација `Assets`. 3. **DailyIntakes(daily_intake_id, user_id, daily_intake_date, daily_intake_calories)** R3 = R2 - {daily_intake_date, daily_intake_calories} R3 = {user_id, email, username, password, training_gender, training_age, training_weight, training_id, asset_id, daily_intake_id, weight_current, weight_height, weight_goal_weight, weight_goal_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} Lossless join: Релацијата може да се реконструира преку join со `daily_intake_id`. Dependency preservation: FD6 е сочувана преку новата релација `DailyIntakes`. 4. **Tasks(task_id, discipline_user_id, custom_tracking_id, task_name, task_is_finished)** R4 = R3 - {task_name, task_is_finished} **Напомена:** Иако FD10 ги одредува и `discipline_user_id` и `custom_tracking_id`, тие НЕ се отстрануваат од R4 бидејќи се истовремено детерминанти (FD8, FD9) и ќе бидат формално обработени во 3НФ чекорите. Се отстрануваат само „листовите" атрибути `task_name` и `task_is_finished` кои не детерминираат ништо друго. R4 = {user_id, email, username, password, training_gender, training_age, training_weight, training_id, asset_id, daily_intake_id, weight_current, weight_height, weight_goal_weight, weight_goal_calories, discipline_user_id, custom_tracking_id, custom_tracking_name, task_id, 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} Lossless join: Релацијата може да се реконструира преку join со `task_id`. Dependency preservation: FD10 е сочувана преку новата релација `Tasks`. 5. **DailyCompletion(daily_completion_id, user_id, daily_completion_date, daily_completion_procent)** **Напомена:** `DailyCompletion.user_id` е FK директно кон `USERS`, не кон `DISCIPLINE_USERS`. Ова е дизајнерска одлука: дневното завршување (`procent`) е агрегатна мера за целиот корисник, не е врзана само за корисници со дисциплински профил. Затоа нема потреба да минува низ `DISCIPLINE_USERS`. R5 = R4 - {daily_completion_date, daily_completion_procent} R5 = {user_id, email, username, password, training_gender, training_age, training_weight, training_id, asset_id, daily_intake_id, weight_current, weight_height, weight_goal_weight, weight_goal_calories, discipline_user_id, custom_tracking_id, custom_tracking_name, task_id, daily_completion_id, finance_spending_budget, finance_saving_budget, finance_investing_budget, finance_donation_budget, finance_credit, income_id, income_date, income_amount} Lossless join: Релацијата може да се реконструира преку join со `daily_completion_id`. Dependency preservation: FD11 е сочувана преку новата релација `DailyCompletion`. 6. **Incomes(income_id, user_id, income_date, income_amount)** R6 = R5 - {income_date, income_amount} R6 = {user_id, email, username, password, training_gender, training_age, training_weight, training_id, asset_id, daily_intake_id, weight_current, weight_height, weight_goal_weight, weight_goal_calories, discipline_user_id, custom_tracking_id, custom_tracking_name, task_id, daily_completion_id, income_id, finance_spending_budget, finance_saving_budget, finance_investing_budget, finance_donation_budget, finance_credit} Lossless join: Релацијата може да се реконструира преку join со `income_id`. Dependency preservation: FD14 е сочувана преку новата релација `Incomes`. Добиени релации по декомпозицијата по 2НФ: 1. TrainingSessions(training_id, training_user_id, training_date, training_type, training_duration, training_calories) 2. Assets(asset_id, user_id, asset_ticker_symbol, asset_buy_price, asset_buy_date, asset_quantity) 3. DailyIntakes(daily_intake_id, user_id, daily_intake_date, daily_intake_calories) 4. Tasks(task_id, discipline_user_id, custom_tracking_id, task_name, task_is_finished) 5. DailyCompletion(daily_completion_id, user_id, daily_completion_date, daily_completion_procent) 6. Incomes(income_id, user_id, income_date, income_amount) 7. R6 = {user_id, email, username, password, training_gender, training_age, training_weight, training_id, asset_id, daily_intake_id, weight_current, weight_height, weight_goal_weight, weight_goal_calories, discipline_user_id, custom_tracking_id, custom_tracking_name, task_id, daily_completion_id, income_id, finance_spending_budget, finance_saving_budget, finance_investing_budget, finance_donation_budget, finance_credit} == **Проверка за 3НФ** == **3НФ бара:** релацијата е во 2НФ и **нема транзитивна зависност** — не-клучен атрибут не смее да зависи од друг не-клучен атрибут. Поради постоење на транзитивни зависности во R6, заклучуваме дека **е нарушена 3НФ**. На пример: `user_id` (не-клучен во R6) детерминира `email`, `username`, `password`, `training_gender`, `weight_current`, `finance_spending_budget` итн. == **Декомпозиција по 3НФ** == Сите транзитивни зависности се наоѓаат во R6. Ги разгледуваме чекор по чекор: 1. Во **R6** важи FD1: `user_id -> email, username, password`. Декомпозираме по FD1 и добиваме **Users(user_id, email, username, password)**. R7 = R6 - {email, username, password} R7 = {user_id, training_gender, training_age, training_weight, training_id, asset_id, daily_intake_id, weight_current, weight_height, weight_goal_weight, weight_goal_calories, discipline_user_id, custom_tracking_id, custom_tracking_name, task_id, daily_completion_id, income_id, finance_spending_budget, finance_saving_budget, finance_investing_budget, finance_donation_budget, finance_credit} Lossless join: Релацијата може да се реконструира преку join со `user_id`. Dependency preservation: FD1 е сочувана преку `Users`. 2. Во **R7** важи FD2: `user_id -> training_gender, training_age, training_weight`. Декомпозираме по FD2 и добиваме **TrainingUsers(user_id, training_gender, training_age, training_weight)** — `user_id` е примарен и надворешен клуч кон `Users`. R8 = R7 - {training_gender, training_age, training_weight} R8 = {user_id, training_id, asset_id, daily_intake_id, weight_current, weight_height, weight_goal_weight, weight_goal_calories, discipline_user_id, custom_tracking_id, custom_tracking_name, task_id, daily_completion_id, income_id, finance_spending_budget, finance_saving_budget, finance_investing_budget, finance_donation_budget, finance_credit} Lossless join: Релацијата може да се реконструира преку join со `user_id`. Dependency preservation: FD2 е сочувана преку `TrainingUsers`. 3. Во **R8** важи FD5: `user_id -> weight_current, weight_height, weight_goal_weight, weight_goal_calories`. Декомпозираме по FD5 и добиваме **WeightUsers(user_id, weight_current, weight_height, weight_goal_weight, weight_goal_calories)** — `user_id` е примарен и надворешен клуч кон `Users`. `DailyIntakes.user_id` е FK кон `WeightUsers.user_id`. R9 = R8 - {weight_current, weight_height, weight_goal_weight, weight_goal_calories} R9 = {user_id, training_id, asset_id, daily_intake_id, discipline_user_id, custom_tracking_id, custom_tracking_name, task_id, daily_completion_id, income_id, finance_spending_budget, finance_saving_budget, finance_investing_budget, finance_donation_budget, finance_credit} Lossless join: Релацијата може да се реконструира преку join со `user_id`. Dependency preservation: FD5 е сочувана преку `WeightUsers`. 4. Во **R9** важи FD13: `user_id -> finance_spending_budget, finance_saving_budget, finance_investing_budget, finance_donation_budget, finance_credit`. Декомпозираме по FD13 и добиваме **FinanceUsers(user_id, spending_budget, saving_budget, investing_budget, donation_budget, credit)** — `user_id` е примарен и надворешен клуч кон `Users`. `Incomes.user_id` е FK кон `FinanceUsers.user_id`. R10 = R9 - {finance_spending_budget, finance_saving_budget, finance_investing_budget, finance_donation_budget, finance_credit} R10 = {user_id, training_id, asset_id, daily_intake_id, discipline_user_id, custom_tracking_id, custom_tracking_name, task_id, daily_completion_id, income_id} Lossless join: Релацијата може да се реконструира преку join со `user_id`. Dependency preservation: FD13 е сочувана преку `FinanceUsers`. 5. Во **R10** важи FD9: `custom_tracking_id -> user_id, custom_tracking_name`. Декомпозираме по FD9 и добиваме **CustomTrackingCategories(custom_tracking_id, user_id, custom_tracking_name)** — `custom_tracking_id` е примарен клуч, `user_id` е надворешен клуч кон `Users`. R11 = R10 - {custom_tracking_name} R11 = {user_id, training_id, asset_id, daily_intake_id, discipline_user_id, custom_tracking_id, task_id, daily_completion_id, income_id} Lossless join: Релацијата може да се реконструира преку join со `custom_tracking_id`. Dependency preservation: FD9 е сочувана преку `CustomTrackingCategories`. 6. Во **R11** важи FD8: `discipline_user_id -> user_id`. И `discipline_user_id` и `user_id` се не-клучни атрибути во R11, па постои транзитивна зависност. Декомпозираме: **DisciplineUsers(user_id)** — `user_id` е примарен и надворешен клуч кон `Users`. `discipline_user_id` во `Tasks` е FK кон `DisciplineUsers.user_id`. Lossless join: Релацијата може да се реконструира преку join со `discipline_user_id`. Dependency preservation: FD8 е сочувана преку FK врската `discipline_user_id -> DisciplineUsers.user_id`. 7. **TaskDailyCompletion(task_id, daily_completion_id)** е структурна спојна (junction) табела — и `task_id` и `daily_completion_id` се клучни атрибути (дел од K), па FD12 не е транзитивна зависност и не претставува 3НФ нарушување. Релацијата произлегува од дизајнерската одлука за M:N врска помеѓу `TASKS` и `DAILY_COMPLETION`: една задача може да биде дел од повеќе дневни завршувања и обратно. `task_id` е FK кон `Tasks`, `daily_completion_id` е FK кон `DailyCompletion`. Lossless join: Релацијата може да се реконструира преку join со `(task_id, daily_completion_id)`. 8. **InvestorUsers(user_id)** е структурна (role) табела — `user_id` е примарен и надворешен клуч кон `Users`. `Assets.user_id` е FK кон `InvestorUsers.user_id`, со што се обезбедува дека само корисници со инвеститорски профил можат да поседуваат активи. Не произлегува од FD нарушување туку од дизајнерскиот избор за разграничување на профилите. Lossless join: Релацијата може да се реконструира преку join со `user_id`. Добиени релации по декомпозицијата по 3НФ: 1. Users(user_id, email, username, password) 2. TrainingUsers(user_id, training_gender, training_age, training_weight) 3. TrainingSessions(training_id, training_user_id, training_date, training_type, training_duration, training_calories) 4. WeightUsers(user_id, weight_current, weight_height, weight_goal_weight, weight_goal_calories) 5. DailyIntakes(daily_intake_id, user_id, daily_intake_date, daily_intake_calories) 6. InvestorUsers(user_id) 7. Assets(asset_id, user_id, asset_ticker_symbol, asset_buy_price, asset_buy_date, asset_quantity) 8. DisciplineUsers(user_id) 9. CustomTrackingCategories(custom_tracking_id, user_id, custom_tracking_name) 10. Tasks(task_id, discipline_user_id, custom_tracking_id, task_name, task_is_finished) 11. DailyCompletion(daily_completion_id, user_id, daily_completion_date, daily_completion_procent) 12. TaskDailyCompletion(task_id, daily_completion_id) 13. FinanceUsers(user_id, spending_budget, saving_budget, investing_budget, donation_budget, credit) 14. Incomes(income_id, user_id, income_date, income_amount) 15. R11 = {user_id, training_id, asset_id, daily_intake_id, discipline_user_id, custom_tracking_id, task_id, daily_completion_id, income_id} (R11 не е физичка табела — сите нејзини атрибути се примарни или надворешни клучеви кои веќе живеат во нивните соодветни извлечени релации. R11 е артефакт на процесот на декомпозиција и се „раствора" во финалниот модел.) == **Проверка дали добиените релации се во 3НФ** == **3НФ бара:** за секоја нетривијална FD X → Y, X мора да биде суперклуч или Y мора да биде дел од некој кандидат клуч. **TRAINING_SESSIONS(training_id, training_user_id, training_date, training_type, training_duration, training_calories)** - PK: `training_id` - Не-клучен `training_user_id` не детерминира ништо во оваа релација → во 3НФ ✓ **ASSETS(asset_id, user_id, asset_ticker_symbol, asset_buy_price, asset_buy_date, asset_quantity)** - PK: `asset_id` - Не-клучен `user_id` не детерминира ништо во оваа релација → во 3НФ ✓ **DAILY_INTAKES(daily_intake_id, user_id, daily_intake_date, daily_intake_calories)** - PK: `daily_intake_id` - Не-клучен `user_id` не детерминира ништо во оваа релација → во 3НФ ✓ **CUSTOM_TRACKING_CATEGORIES(custom_tracking_id, user_id, custom_tracking_name)** - PK: `custom_tracking_id` - Не-клучен `user_id` не детерминира ништо во оваа релација → во 3НФ ✓ **TASKS(task_id, discipline_user_id, custom_tracking_id, task_name, task_is_finished)** - PK: `task_id` - Ниту `discipline_user_id` ниту `custom_tracking_id` детерминираат ништо во оваа релација → во 3НФ ✓ - (Условот CHECK (точно еден FK е NOT NULL) е ограничување на домен, не FD) **DAILY_COMPLETION(daily_completion_id, user_id, daily_completion_date, daily_completion_procent)** - PK: `daily_completion_id` - Не-клучен `user_id` не детерминира ништо во оваа релација → во 3НФ ✓ **INCOMES(income_id, user_id, income_date, income_amount)** - PK: `income_id` - Не-клучен `user_id` не детерминира ништо во оваа релација → во 3НФ ✓ **Заклучок:** Сите релации се во **3НФ**. Понатамошна декомпозиција не е потребна. == **Проверка за БКНФ** == **БКНФ бара:** за секоја нетривијална `FD X -> Y`, `X` мора да биде **суперклуч**. Примарните клучеви се болдирани. 1. Users(**user_id**, email, username, password) -> **задоволува БКНФ** (`user_id`, `email` и `username` се сите кандидат клучеви — сите детерминанти во оваа релација се суперклучеви. `UNIQUE` constraints на `email` и `username` ги енфорцираат алтернативните кандидат клучеви.) 2. TrainingUsers(**user_id**, training_gender, training_age, training_weight) -> **задоволува БКНФ** 3. TrainingSessions(**training_id**, training_user_id, training_date, training_type, training_duration, training_calories) -> **задоволува БКНФ** 4. WeightUsers(**user_id**, weight_current, weight_height, weight_goal_weight, weight_goal_calories) -> **задоволува БКНФ** 5. DailyIntakes(**daily_intake_id**, user_id, daily_intake_date, daily_intake_calories) -> **задоволува БКНФ** (Покрај `daily_intake_id`, и `{user_id, daily_intake_date}` е кандидат клуч заради `UNIQUE (user_id, date)` constraint-от. Двете детерминанти се суперклучеви, па БКНФ важи.) 6. InvestorUsers(**user_id**) -> **задоволува БКНФ** 7. Assets(**asset_id**, user_id, asset_ticker_symbol, asset_buy_price, asset_buy_date, asset_quantity) -> **задоволува БКНФ** 8. DisciplineUsers(**user_id**) -> **задоволува БКНФ** 9. CustomTrackingCategories(**custom_tracking_id**, user_id, custom_tracking_name) -> **задоволува БКНФ** 10. Tasks(**task_id**, discipline_user_id, custom_tracking_id, task_name, task_is_finished) -> **задоволува БКНФ** 11. DailyCompletion(**daily_completion_id**, user_id, daily_completion_date, daily_completion_procent) -> **задоволува БКНФ** 12. TaskDailyCompletion(**task_id, daily_completion_id**) -> **задоволува БКНФ** 13. FinanceUsers(**user_id**, spending_budget, saving_budget, investing_budget, donation_budget, credit) -> **задоволува БКНФ** 14. Incomes(**income_id**, user_id, income_date, income_amount) -> **задоволува БКНФ** 15. R11 = {**training_id, asset_id, user_id, daily_intake_id, discipline_user_id, custom_tracking_id, task_id, daily_completion_id, income_id**} -> **задоволува БКНФ** (R11 е артефакт на декомпозицијата — не е физичка табела; сите атрибути се клучеви кои живеат во нивните соодветни релации) **Заклучок:** **Целиот модел е во БКНФ.** == **Проверка за 4НФ** == **4НФ бара:** релацијата е во БКНФ и **нема нетривијални мултивредносни зависности** — за секоја MVD X →→ Y, X мора да биде суперклуч. Сите профилски релации (`TrainingUsers`, `WeightUsers`, `InvestorUsers`, `DisciplineUsers`, `FinanceUsers`) се во **1:1** врска со `Users` — секој корисник има точно еден профил од секој тип. Ова значи дека постои функционална, не мултивредносна зависност, и MVD не се применува. R11 содржи само примарни и надворешни клучеви кои меѓусебно имаат 1:1 зависности — нема независни мултивредносни факти во истата релација. `TaskDailyCompletion(**task_id, daily_completion_id**)` е спојна релација со композитен примарен клуч — нема не-клучни атрибути, па MVD не се применува. **Заклучок:** Ниту една релација не содржи нетривијална мултивредносна зависност. **Целиот модел е во 4НФ.** Понатамошна декомпозиција не е потребна. == **Финален резултат и дискусија** == === **Нормализиран релациски модел** === USERS(user_id, email, username, password) PK: user_id CK: {user_id}, {email}, {username} TRAINING_USERS(user_id, gender, age, weight) PK: user_id FK: user_id -> USERS TRAINING_SESSIONS(training_id, training_user_id, date, type, duration, calories) PK: training_id FK: training_user_id -> TRAINING_USERS(user_id) WEIGHT_USERS(user_id, weight, height, goal_weight, goal_calories) PK: user_id FK: user_id -> USERS DAILY_INTAKES(daily_intake_id, user_id, date, calories) PK: daily_intake_id CK: {daily_intake_id}, {user_id, date} FK: user_id -> WEIGHT_USERS(user_id) UNIQUE: (user_id, date) — бизнис правило: еден корисник има најмногу еден внес по датум INVESTOR_USERS(user_id) PK: user_id FK: user_id -> USERS ASSETS(asset_id, user_id, ticker_symbol, buy_price, buy_date, quantity) PK: asset_id FK: user_id -> INVESTOR_USERS(user_id) DISCIPLINE_USERS(user_id) PK: user_id FK: user_id -> USERS CUSTOM_TRACKING_CATEGORIES(custom_tracking_id, user_id, name) PK: custom_tracking_id FK: user_id -> USERS TASKS(task_id, discipline_user_id, custom_tracking_id, name, is_finished) PK: task_id FK: discipline_user_id -> DISCIPLINE_USERS(user_id) FK: custom_tracking_id -> CUSTOM_TRACKING_CATEGORIES CHECK: точно еден од двата FK е NOT NULL DAILY_COMPLETION(daily_completion_id, user_id, date, procent) PK: daily_completion_id FK: user_id -> USERS TASK_DAILY_COMPLETION(task_id, daily_completion_id) PK: {task_id, daily_completion_id} FK: task_id -> TASKS FK: daily_completion_id -> DAILY_COMPLETION FINANCE_USERS(user_id, spending_budget, saving_budget, investing_budget, donation_budget, credit) PK: user_id FK: user_id -> USERS INCOMES(income_id, user_id, date, amount) PK: income_id FK: user_id -> FINANCE_USERS(user_id) === **Дискусија** === **Разлики во однос на претходниот дизајн (Phase 2):** 1. **Отстранети `num_tasks` и `tasks` од `DISCIPLINE_USERS` и `CUSTOM_TRACKING_CATEGORIES`.** `num_tasks` е изведен атрибут (`COUNT` на `TASKS`) — чување на изведени вредности го нарушува принципот на нормализација и создава ризик од инконзистентност. `tasks` (`TEXT` листа) е не-атомски атрибут и директно ја нарушува `1НФ`. 2. **Профилските табели користат `user_id` и како PK и како FK.** `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` соодветно. 3. **Додадени `UNIQUE` constraints на `email` и `username` во `USERS`.** Нормализацијата формално идентификуваше `{email}` и `{username}` како кандидат клучеви, па DDL мора да ги енфорцира. 4. **`GENERATED BY DEFAULT AS IDENTITY` за сите сурогатни `PK`-а.** Технички подобрување усогласено со нормализираниот модел. **Модел за употреба во понатамошните фази:** нормализираниот модел (финалниот DDL од Phase 5). == **Заклучок** == При нормализацијата ги добивме истите релации од претходниот дизајн, со исклучок на формалното отстранување на изведените и не-атомски атрибути (`num_tasks`, `tasks`). Профилските табели правилно го користат `user_id` и како примарен и надворешен клуч, со што финалниот нормализиран модел целосно се совпаѓа со DDL. Конечниот модел задоволува **1НФ, 2НФ, 3НФ и БКНФ**, обезбедувајќи целосна декомпозиција без губење на податоци (lossless join) и без губење на функционални зависности (dependency preservation).