Нормализација
Де-нормализирана база на податоци
Се тргнува од една глобална, де-нормализирана релација што ги содржи атрибутите од целиот модел:
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 }
Напомена: Атрибутите num_tasks и tasks се отстранети уште од првата нормализација бидејќи се изведени/не-атомски вредности и не припаѓаат на релацискиот модел. Слично, weight_user_id во TRAINING_SESSIONS е отстранет бидејќи претставува непотребна вкрстена зависност меѓу два независни профили на ист корисник.
Функционални зависности
Канонски покривач на функционалните зависности:
- FD1:
user_id -> email, username, password - FD2:
email -> user_id - FD3:
username -> user_id - FD4:
training_user_id -> training_gender, training_age, training_weight - FD5:
training_id -> training_user_id, training_date, training_type, training_duration, training_calories - FD6:
investor_user_id -> user_id - FD7:
asset_id -> investor_user_id, asset_ticker_symbol, asset_buy_price, asset_buy_date, asset_quantity - FD8:
weight_user_id -> weight_current, weight_height, weight_goal_weight, weight_goal_calories - FD9:
daily_intake_id -> weight_user_id, daily_intake_date, daily_intake_calories - FD10:
discipline_user_id -> user_id - FD11:
custom_tracking_id -> user_id, custom_tracking_name - FD12:
task_id -> discipline_user_id, custom_tracking_id, task_name, task_is_finished - FD13:
daily_completion_id -> user_id, daily_completion_date, daily_completion_procent - FD14:
(task_id, daily_completion_id) -> /(нема дополнителни атрибути — само врска) - FD15:
finance_user_id -> finance_spending_budget, finance_saving_budget, finance_investing_budget, finance_donation_budget, finance_credit - FD16:
income_id -> finance_user_id, income_date, income_amount
Кандидат клучеви и примарен клуч
Пресметување на затворања (closures)
За да се најдат кандидат клучевите, пресметуваме затворање за секој атрибут (или комбинација) и проверуваме дали го покрива целиот R.
Атрибути кои се појавуваат само на десна страна (никогаш детерминанти):
email,username— само прекуFD2/FD3се детерминанти, но се и десни воFD1training_gender,training_age,training_weight,training_date,training_type,training_duration,training_caloriesasset_ticker_symbol,asset_buy_price,asset_buy_date,asset_quantityweight_current,weight_height,weight_goal_weight,weight_goal_caloriesdaily_intake_date,daily_intake_caloriescustom_tracking_name,task_name,task_is_finisheddaily_completion_date,daily_completion_procentfinance_spending_budget,finance_saving_budget,finance_investing_budget,finance_donation_budget,finance_creditincome_date,income_amount
Овие атрибути не можат да бидат дел од кандидат клуч бидејќи не детерминираат ништо надвор од себе.
Атрибути кои се само на лева страна (мора да бидат во секој кандидат клуч):
training_id,asset_id,daily_intake_id,task_id,daily_completion_id,income_id
Останати детерминанти:
user_id,email,username,training_user_id,investor_user_id,weight_user_id,discipline_user_id,custom_tracking_id,finance_user_id
Затворање на минималниот суперклуч
Пробуваме со комбинација на сите атрибути што се само леви + по еден претставник од групите поврзани со user_id:
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 }
K+ пресметување:
user_id->email, username, password(FD1)training_user_id->training_gender, training_age, training_weight(FD4)training_id->training_user_id, training_date, training_type, training_duration, training_calories(FD5)investor_user_id->user_id(FD6)asset_id->investor_user_id, asset_ticker_symbol, asset_buy_price, asset_buy_date, asset_quantity(FD7)weight_user_id->weight_current, weight_height, weight_goal_weight, weight_goal_calories(FD8)daily_intake_id->weight_user_id, daily_intake_date, daily_intake_calories(FD9)discipline_user_id->user_id(FD10)custom_tracking_id->user_id, custom_tracking_name(FD11)task_id->discipline_user_id, custom_tracking_id, task_name, task_is_finished(FD12)daily_completion_id->user_id, daily_completion_date, daily_completion_procent(FD13)finance_user_id->finance_spending_budget, finance_saving_budget, finance_investing_budget, finance_donation_budget, finance_credit(FD15)income_id->finance_user_id, income_date, income_amount(FD16)
K+ = R → K е суперклуч ✓
Минималност: Отстранувањето на кој било атрибут од K резултира во непокриени атрибути (на пример, без training_id не можеме да добиеме training_date, training_type итн.), па K е минимален.
Примарен клуч:
`PK = { 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 }`
Напомена: Постојат и алтернативни кандидат клучеви:
emailможе да замениuser_id(FD2:email -> user_id)usernameможе да замениuser_id(FD3:username -> user_id)
Алтернативни кандидат клучеви
Проверка на {email} како кандидат клуч:
- {email}+ = email -> user_id (FD2) → {user_id, email}
- user_id -> username, password (FD1)
- {email}+ = {email, user_id, username, password}
- Не ги содржи training_id, asset_id, итн. → {email} САМ по себе НЕ е суперклуч за R.
Бидејќи R содржи атрибути од повеќе независни групи (тренинг, средства, задачи...), ниту еден атрибут или мала комбинација не може да биде суперклуч за целата R. Затоа единствениот кандидат клуч за глобалната R е композитниот клуч наведен погоре.
Важно: {email} и {username} се кандидат клучеви САМО во релацијата USERS по декомпозицијата, не во глобалната R.
Проверка за 1НФ
Релацијата R НЕ ја задоволува 1НФ поради:
- Повторливи групи — еден корисник има повеќе
training_sessions, повеќеincomes, повеќеassetsитн., што значи дека редот би морал да се повторува или атрибутите би биле листи. - Не-атомски атрибути — во оригиналниот дизајн постоеја
tasks(TEXTлиста) иnum_tasks(изведена вредност) воDISCIPLINE_USERSиCUSTOM_TRACKING_CATEGORIES.
Декомпозиција по 1НФ
Релација што се анализира: R (глобална)
Проблем: Повторливи групи и не-атомски атрибути.
Решение: Секој ентитет и врска добива своја посебна релација со атомски атрибути и локален примарен клуч.
Посебен случај — TASK_DAILY_COMPLETION:
Задачите и дневните завршувања се во врска многу-кон-многу. Се воведува посредна релација:
TASK_DAILY_COMPLETION(task_id, daily_completion_id)
Резултат по 1НФ декомпозиција:
R1: USERS(user_id, email, username, password)R2: TRAINING_USERS(training_user_id, training_gender, training_age, training_weight)R3: TRAINING_SESSIONS(training_id, training_user_id, training_date, training_type, training_duration, training_calories)R4: INVESTOR_USERS(investor_user_id)R5: ASSETS(asset_id, investor_user_id, asset_ticker_symbol, asset_buy_price, asset_buy_date, asset_quantity)R6: WEIGHT_USERS(weight_user_id, weight_current, weight_height, weight_goal_weight, weight_goal_calories)R7: DAILY_INTAKES(daily_intake_id, weight_user_id, daily_intake_date, daily_intake_calories)R8: DISCIPLINE_USERS(discipline_user_id)R9: CUSTOM_TRACKING_CATEGORIES(custom_tracking_id, user_id, custom_tracking_name)R10: TASKS(task_id, discipline_user_id, custom_tracking_id, task_name, task_is_finished)R11: DAILY_COMPLETION(daily_completion_id, user_id, daily_completion_date, daily_completion_procent)R12: TASK_DAILY_COMPLETION(task_id, daily_completion_id)R13: FINANCE_USERS(finance_user_id, finance_spending_budget, finance_saving_budget, finance_investing_budget, finance_donation_budget, finance_credit)R14: INCOMES(income_id, finance_user_id, income_date, income_amount)
- Сите атрибути се атомски. ✓
- Нема повторливи групи. ✓
Проверка за 2НФ
2НФ бара: релацијата е во 1НФ и нема парцијална зависност — секој не-клучен атрибут зависи од целиот примарен клуч, не само од дел од него.
Парцијална зависност постои само кога примарниот клуч е сложен. Ги разгледуваме само релациите со сложен PK:
R12: TASK_DAILY_COMPLETION(task_id, daily_completion_id)
PK: {task_id, daily_completion_id}- Нема не-клучни атрибути → нема парцијални зависности → во 2НФ ✓
Сите останати релации (R1–R11, R13–R14) имаат прост примарен клуч, па парцијална зависност е невозможна → автоматски во 2НФ ✓
2NF проверка по релации (по 1НФ декомпозицијата)
| Релација | PK | Дали PK е сложен? | Не-клучни атрибути | Парцијална зависност? | 2НФ? |
| USERS | user_id | Не | email, username, password | Невозможна | ✓ |
| TRAINING_USERS | training_user_id | Не | gender, age, weight | Невозможна | ✓ |
| TRAINING_SESSIONS | training_id | Не | user_id, date, type, duration, calories | Невозможна | ✓ |
| INVESTOR_USERS | investor_user_id | Не | (нема) | Невозможна | ✓ |
| ASSETS | asset_id | Не | user_id, ticker_symbol, buy_price, buy_date, quantity | Невозможна | ✓ |
| WEIGHT_USERS | weight_user_id | Не | weight, height, goal_weight, goal_calories | Невозможна | ✓ |
| DAILY_INTAKES | daily_intake_id | Не | user_id, date, calories | Невозможна | ✓ |
| DISCIPLINE_USERS | discipline_user_id | Не | (нема) | Невозможна | ✓ |
| CUSTOM_TRACKING_CATEGORIES | custom_tracking_id | Не | user_id, name | Невозможна | ✓ |
| TASKS | task_id | Не | discipline_user_id, custom_tracking_id, name, is_finished | Невозможна | ✓ |
| DAILY_COMPLETION | daily_completion_id | Не | user_id, date, procent | Невозможна | ✓ |
| TASK_DAILY_COMPLETION | {task_id, daily_completion_id} | Да | (нема) | Нема не-клучни атрибути | ✓ |
| FINANCE_USERS | finance_user_id | Не | spending_budget, saving_budget, investing_budget, donation_budget, credit | Невозможна | ✓ |
| INCOMES | income_id | Не | user_id, date, amount | Невозможна | ✓ |
Заклучок: Сите 14 релации се во 2НФ. Декомпозиција не е потребна.
Декомпозиција по 2НФ
Бидејќи сите релации веќе се во 2НФ, нема декомпозиција. Релациите од 1НФ чекорот се пренесуваат непроменети.
Проверка за 3НФ
3НФ бара: релацијата е во 2НФ и нема транзитивна зависност — не-клучен атрибут не смее да зависи од друг не-клучен атрибут.
Ги разгледуваме релациите каде транзитивна зависност е можна:
R3: TRAINING_SESSIONS
- Атрибути:
training_id,training_user_id,training_date,training_type,training_duration,training_calories PK:training_idFDs:training_id -> training_user_id, training_date, training_type, training_duration, training_calories- Не-клучни атрибути:
training_user_id,training_date,training_type,training_duration,training_calories - Постои ли транзитивност?
training_user_idе не-клучен, но не детерминира ништо во оваа релација → нема транзитивност → во 3НФ ✓
R5: ASSETS
- Атрибути:
asset_id,investor_user_id,asset_ticker_symbol,asset_buy_price,asset_buy_date,asset_quantity PK:asset_idFDs:asset_id ->сите атрибути- Не-клучен
investor_user_idне детерминира ништо во оваа релација → во 3НФ ✓
R7: DAILY_INTAKES
- Атрибути:
daily_intake_id,weight_user_id,daily_intake_date,daily_intake_calories PK:daily_intake_idFDs:daily_intake_id -> weight_user_id, daily_intake_date, daily_intake_caloriesweight_user_idне детерминира ништо во оваа релација → во 3НФ ✓
R9: CUSTOM_TRACKING_CATEGORIES
- Атрибути:
custom_tracking_id,user_id,custom_tracking_name PK:custom_tracking_idFDs:custom_tracking_id -> user_id, custom_tracking_nameuser_idне детерминира ништо во оваа релација → во 3НФ ✓
R10: TASKS
- Атрибути:
task_id,discipline_user_id,custom_tracking_id,task_name,task_is_finished PK:task_idFDs:task_id -> discipline_user_id, custom_tracking_id, task_name, task_is_finished- Ниту
discipline_user_idнитуcustom_tracking_idдетерминираат ништо во оваа релација → во 3НФ ✓
R14: INCOMES
- Атрибути:
income_id,finance_user_id,income_date,income_amount PK:income_idFDs:income_id -> finance_user_id, income_date, income_amountfinance_user_idне детерминира ништо во оваа релација → во 3НФ ✓
Заклучок: Сите релации се во 3НФ. Декомпозиција не е потребна.
Проверка за БКНФ
БКНФ бара: за секоја нетривијална FD X -> Y, X мора да биде суперклуч.
Ги разгледуваме релациите каде постојат повеќе кандидат клучеви:
R1: USERS(user_id, email, username, password)
FDs:user_id -> email, username, passwordemail -> user_id(FD2)username -> user_id(FD3)
- Кандидат клучеви:
{user_id},{email},{username} - Проверка:
user_id -> ...:user_idе кандидат клуч ✓email -> ...:emailе кандидат клуч ✓username -> ...:usernameе кандидат клуч ✓
- Сите детерминанти се кандидат клучеви →
USERSе во БКНФ ✓
R10: TASKS
FDs:task_id -> discipline_user_id, custom_tracking_id, task_name, task_is_finished- Единствен кандидат клуч:
{task_id} task_idе кандидат клуч → во БКНФ ✓
Напомена за CHECK constraint:
Условот (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) не е функционална зависност туку ограничување на домен — не влијае на БКНФ.
Сите останати релации имаат единствен кандидат клуч (нивниот сурогатен PK) и сите детерминанти во нив се суперклучеви → сите се во БКНФ ✓
Заклучок: Целиот модел е во БКНФ.
Lossless Join проверка
Теоретска основа: За секоја декомпозиција на релација R во R1 и R2, join-от е lossless ако и само ако (R1 ∩ R2) → R1 или (R1 ∩ R2) → R2, т.е. заедничките атрибути формираат суперклуч барем во една од двете нови релации.
Бидејќи нашата декомпозиција се изведува чекор-по-чекор, за секој чекор посебно ја проверуваме оваа особина.
---
ЧЕКОР 1: Декомпозиција од глобална R во 1НФ
Чекор 1.1: Издвојување на USERS
R(сите атрибути) → USERS(user_id, email, username, password) + R1(останати атрибути)
Пресек: {user_id}
Дали {user_id} → USERS? Да, user_id е примарен клуч во USERS.
Условот (R1 ∩ R2) → R1 е задоволен → Lossless join ✓
---
Чекор 1.2: Издвојување на TRAINING_USERS
R1 → TRAINING_USERS(training_user_id, training_gender, training_age, training_weight) + R2(останати атрибути)
Пресек: {training_user_id}
Дали {training_user_id} → TRAINING_USERS? Да, training_user_id е примарен клуч во TRAINING_USERS.
Lossless join ✓
---
Чекор 1.3: Издвојување на TRAINING_SESSIONS
R2 → TRAINING_SESSIONS(training_id, training_user_id, training_date, training_type, training_duration, training_calories) + R3(останати атрибути)
Пресек: {training_id}
Дали {training_id} → TRAINING_SESSIONS? Да, training_id е примарен клуч во TRAINING_SESSIONS.
Lossless join ✓
---
Чекор 1.4: Издвојување на INVESTOR_USERS
R3 → INVESTOR_USERS(investor_user_id) + R4(останати атрибути)
Пресек: {investor_user_id}
Дали {investor_user_id} → INVESTOR_USERS? Да, investor_user_id е примарен клуч во INVESTOR_USERS.
Lossless join ✓
---
Чекор 1.5: Издвојување на ASSETS
R4 → ASSETS(asset_id, investor_user_id, asset_ticker_symbol, asset_buy_price, asset_buy_date, asset_quantity) + R5(останати атрибути)
Пресек: {asset_id}
Дали {asset_id} → ASSETS? Да, asset_id е примарен клуч во ASSETS.
Lossless join ✓
---
Чекор 1.6: Издвојување на WEIGHT_USERS
R5 → WEIGHT_USERS(weight_user_id, weight_current, weight_height, weight_goal_weight, weight_goal_calories) + R6(останати атрибути)
Пресек: {weight_user_id}
Дали {weight_user_id} → WEIGHT_USERS? Да, weight_user_id е примарен клуч во WEIGHT_USERS.
Lossless join ✓
---
Чекор 1.7: Издвојување на DAILY_INTAKES
R6 → DAILY_INTAKES(daily_intake_id, weight_user_id, daily_intake_date, daily_intake_calories) + R7(останати атрибути)
Пресек: {daily_intake_id}
Дали {daily_intake_id} → DAILY_INTAKES? Да, daily_intake_id е примарен клуч во DAILY_INTAKES.
Lossless join ✓
---
Чекор 1.8: Издвојување на DISCIPLINE_USERS
R7 → DISCIPLINE_USERS(discipline_user_id) + R8(останати атрибути)
Пресек: {discipline_user_id}
Дали {discipline_user_id} → DISCIPLINE_USERS? Да, discipline_user_id е примарен клуч во DISCIPLINE_USERS.
Lossless join ✓
---
Чекор 1.9: Издвојување на CUSTOM_TRACKING_CATEGORIES
R8 → CUSTOM_TRACKING_CATEGORIES(custom_tracking_id, user_id, custom_tracking_name) + R9(останати атрибути)
Пресек: {custom_tracking_id}
Дали {custom_tracking_id} → CUSTOM_TRACKING_CATEGORIES? Да, custom_tracking_id е примарен клуч во CUSTOM_TRACKING_CATEGORIES.
Lossless join ✓
---
Чекор 1.10: Издвојување на TASKS
R9 → TASKS(task_id, discipline_user_id, custom_tracking_id, task_name, task_is_finished) + R10(останати атрибути)
Пресек: {task_id}
Дали {task_id} → TASKS? Да, task_id е примарен клуч во TASKS.
Lossless join ✓
---
Чекор 1.11: Издвојување на DAILY_COMPLETION
R10 → DAILY_COMPLETION(daily_completion_id, user_id, daily_completion_date, daily_completion_procent) + R11(останати атрибути)
Пресек: {daily_completion_id}
Дали {daily_completion_id} → DAILY_COMPLETION? Да, daily_completion_id е примарен клуч во DAILY_COMPLETION.
Lossless join ✓
---
Чекор 1.12: Издвојување на TASK_DAILY_COMPLETION
R11 → TASK_DAILY_COMPLETION(task_id, daily_completion_id) + R12(останати атрибути)
Пресек: {task_id, daily_completion_id}
Ова е спојна релација без не-клучни атрибути. Бидејќи нема дополнителни атрибути кои би можеле да се изгубат, join-от е lossless.
Lossless join ✓
---
Чекор 1.13: Издвојување на FINANCE_USERS
R12 → FINANCE_USERS(finance_user_id, finance_spending_budget, finance_saving_budget, finance_investing_budget, finance_donation_budget, finance_credit) + R13(останати атрибути)
Пресек: {finance_user_id}
Дали {finance_user_id} → FINANCE_USERS? Да, finance_user_id е примарен клуч во FINANCE_USERS.
Lossless join ✓
---
Чекор 1.14: Издвојување на INCOMES
R13 → INCOMES(income_id, finance_user_id, income_date, income_amount) + R14(празна)
Пресек: {income_id}
Дали {income_id} → INCOMES? Да, income_id е примарен клуч во INCOMES.
Lossless join ✓
---
ЗАКЛУЧОК: Сите 14 чекори на декомпозиција го задоволуваат условот за lossless join. Конечниот модел овозможува целосна реконструкција на оригиналната релација R без губење или создавање на лажни податоци.
Dependency Preservation проверка
Проверуваме дека секоја FD од оригиналниот сет е зачувана во барем една од финалните релации:
FD1: user_id -> email, username, password→USERS✓FD2: email -> user_id→USERS✓FD3: username -> user_id→USERS✓FD4: training_user_id -> training_gender, training_age, training_weight→TRAINING_USERS✓FD5: training_id -> training_user_id, ...→TRAINING_SESSIONS✓FD6: investor_user_id -> user_id→INVESTOR_USERS(прекуFK) ✓FD7: asset_id -> investor_user_id, ...→ASSETS✓FD8: weight_user_id -> weight_current, ...→WEIGHT_USERS✓FD9: daily_intake_id -> weight_user_id, ...→DAILY_INTAKES✓FD10: discipline_user_id -> user_id→DISCIPLINE_USERS(прекуFK) ✓FD11: custom_tracking_id -> user_id, name→CUSTOM_TRACKING_CATEGORIES✓FD12: task_id -> discipline_user_id, ...→TASKS✓FD13: daily_completion_id -> user_id, ...→DAILY_COMPLETION✓FD14: (task_id, daily_completion_id) -> /→TASK_DAILY_COMPLETION✓FD15: finance_user_id -> finance_spending_budget, ...→FINANCE_USERS✓FD16: income_id -> finance_user_id, ...→INCOMES✓
Заклучок: Сите функционални зависности се зачувани. ✓
Финален резултат и дискусија
Нормализиран релациски модел
USERS(user_id, email, username, password) 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
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
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 FK: user_id -> WEIGHT_USERS
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 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
Дискусија
Разлики во однос на претходниот дизајн (Phase 2):
- Отстранет
weight_user_idодTRAINING_SESSIONS. Во оригиналниот DDL,TRAINING_SESSIONSимашеFKконWEIGHT_USERS. Ова е непотребна вкрстена зависност — тренинг сесијата логички припаѓа на тренинг профилот, не на weight профилот. Двата профила независно се врзуваат соUSERSпрекуuser_id.
- Отстранети
num_tasksиtasksодDISCIPLINE_USERSиCUSTOM_TRACKING_CATEGORIES.num_tasksе изведен атрибут (COUNTнаTASKS) — чување на изведени вредности го нарушува принципот на нормализација и создава ризик од инконзистентност.tasks(TEXTлиста) е не-атомски атрибут и директно ја нарушува1НФ.
- Додадени
UNIQUEconstraints наemailиusernameвоUSERS. Нормализацијата формално идентификуваше{email}и{username}како кандидат клучеви, па DDL мора да ги енфорцира.
GENERATED BY DEFAULT AS IDENTITYза сите сурогатниPK-а. Технички подобрување усогласено со нормализираниот модел.
Модел за употреба во понатамошните фази: нормализираниот модел (финалниот DDL од Phase 5).
