= Nutritioneer | Нормализација и оптимизација на дизајн == Денормализирана форма Рамна табела од сите ентитети на Nutritioneer и нивните врски, без структурни ограничувања. Повеќевредносните факти се присутни како групи што се повторуваат: еден рецепт има многу состојки, многу диететски ограничувања и многу коментари, а една состојка носи многу нутриенти. === Функционални зависности * user_id → email, username, password, role * email → user_id (алтернативен клуч, UNIQUE) * username → user_id (алтернативен клуч, UNIQUE) * ingredient_id → ingredient_name, ingr_description, energy, kcal, ingr_type * ingredient_name → ingredient_id (алтернативен клуч, UNIQUE) * nutrient_id → nutr_description, rdi_quantity, unit, nutr_type * nutr_description → nutrient_id (алтернативен клуч, UNIQUE) * restriction_id → restr_description, restr_type * restr_description → restriction_id (алтернативен клуч, UNIQUE) * recipe_id → recipe_name, guide, kcal_sum, servings, user_id * post_id → media, media_type, media_name, is_private, is_favourite, status, post_created_at, user_id, recipe_id * recipe_id → post_id (POSTS_ON е 1:1, UNIQUE врз recipe_id) * comment_id → comment_text, comment_created_at, post_id, user_id * list_id → list_date_time, list_notes, is_bought, list_kcal, user_id * planner_id → planner_date_time, planner_notes, planner_kcal, is_consumed, user_id * bio_id → bio_date, weight, height, age, muscle_fat_ratio, user_id * {user_id, bio_date} → bio_id (едно мерење по корисник на ден) * {recipe_id, ingredient_id} → ingredient_quantity * {ingredient_id, nutrient_id} → nutrient_amount * {list_id, ingredient_id} → buy_quantity === Чисти M:N врски Врските како recipe - restriction и grocery_list - grocery_list_bulk_add_ingredient неможат да определат дополнителен атрибут. Не се функционални зависности. === Почетна денормализирана релација {{{ R( user_id, email, username, password, role, ingredient_id, ingredient_name, ingr_description, energy, kcal, ingr_type, nutrient_id, nutr_description, rdi_quantity, unit, nutr_type, restriction_id, restr_description, restr_type, recipe_id, recipe_name, guide, kcal_sum, servings, post_id, media, media_type, media_name, is_private, is_favourite, status, post_created_at, comment_id, comment_text, comment_created_at, list_id, list_date_time, list_notes, is_bought, list_kcal, planner_id, planner_date_time, planner_notes, planner_kcal, is_consumed, bio_id, bio_date, weight, height, age, muscle_fat_ratio, ingredient_quantity, nutrient_amount, buy_quantity ) }}} === Кандидат-клуч X е кандидат-клуч кога X+ = R (ги изведува сите атрибути) и ниедно вистинско подмножество на X го нема тоа својство -> минималност. 1. X+ = R 2. X е минимално K+ се пресметува на следен начин: {{{ K = { comment_id, ingredient_id, nutrient_id, restriction_id, list_id, planner_id, bio_id } }}} {{{ Чекор 0 - K+ = K: comment_id, ingredient_id, nutrient_id, restriction_id, list_id, planner_id, bio_id Чекор 1 - секој идентификатор ја активира сопствената зависност: comment_id → comment_text, comment_created_at, post_id, user_id; ingredient_id → ingredient_name, ingr_description, energy, kcal, ingr_type; nutrient_id → nutr_description, rdi_quantity, unit, nutr_type; restriction_id → restr_description, restr_type; list_id → list_date_time, list_notes, is_bought, list_kcal; planner_id → planner_date_time, planner_notes, planner_kcal, is_consumed; bio_id → bio_date, weight, height, age, muscle_fat_ratio; {ingredient_id, nutrient_id} → nutrient_amount; {list_id, ingredient_id} → buy_quantity Чекор 2 - post_id и user_id влегоа во K+ во чекор 1, па сега се активираат: post_id → media, media_type, media_name, is_private, is_favourite, status, post_created_at, recipe_id; user_id → email, username, password, role Чекор 3 - recipe_id влезе во K+ во чекор 2: recipe_id → recipe_name, guide, kcal_sum, servings; {recipe_id, ingredient_id} → ingredient_quantity Чекор 4 - цело поминување низ зависностите не додава ништо -> К+ = R }}} Од тоа следи: ||= K# =||= ingredient =||= nutrient =||= restriction =||= biometrics =|| || K1 || ingredient_id || nutrient_id || restriction_id || bio_id || || K2 || ingredient_id || nutrient_id || restriction_id || bio_date || || K3 || ingredient_id || nutrient_id || restr_description || bio_id || || K4 || ingredient_id || nutrient_id || restr_description || bio_date || || K5 || ingredient_id || nutr_description || restriction_id || bio_id || || K6 || ingredient_id || nutr_description || restriction_id || bio_date || || K7 || ingredient_id || nutr_description || restr_description || bio_id || || K8 || ingredient_id || nutr_description || restr_description || bio_date || || K9 || ingredient_name || nutrient_id || restriction_id || bio_id || || K10 || ingredient_name || nutrient_id || restriction_id || bio_date || || K11 || ingredient_name || nutrient_id || restr_description || bio_id || || K12 || ingredient_name || nutrient_id || restr_description || bio_date || || K13 || ingredient_name || nutr_description || restriction_id || bio_id || || K14 || ingredient_name || nutr_description || restriction_id || bio_date || || K15 || ingredient_name || nutr_description || restr_description || bio_id || || K16 || ingredient_name || nutr_description || restr_description || bio_date || Секој од шеснаесетте содржи и {comment_id, list_id, planner_id}. K1 е целосно сурогат формата и токму таа е избрана за примарен клуч. Сурогат (идентификаторите) атрибутите се непроменливи, додека корисник може да преименува состојка и со тоа K16 да добие друга вредност за истиот ред. Атрибут што не се појавува на десната страна на ниедна функционална зависност никогаш не може да биде изведен, па мора да припаѓа на секој клуч. Пресметка на затворањето на K: * comment_id → post_id, user_id; post_id → recipe_id; recipe_id → атрибути на рецептот; user_id → атрибути на корисникот * ingredient_id → атрибути на состојката; {recipe_id, ingredient_id} → ingredient_quantity * nutrient_id → атрибути на нутриентот; {ingredient_id, nutrient_id} → nutrient_amount * restriction_id, list_id, planner_id, bio_id → нивните гранки; {list_id, ingredient_id} → buy_quantity === Проверка на минималност 1. Отстранување на comment_id -> Се губи крајот на синџирот коментар → објава → рецепт → корисник. 2. Отстранување на ingredient_id -> Се губи каталогот на храна, а со него и сите три атрибути за количина на врските. 3. Oтстранување на nutrient_id -> Се губи гранката на нутриенти и nutrient_amount. 4. Oтстранување на restriction_id -> Се губи гранката на диететски ознаки 5. Oтстранување на list_id, planner_id или bio_id -> Се губат соодветните ентитети. Ниедна од нив не е определена од ништо друго. === Примерок од денормализирани податоци Истиот корисник, рецепт, состојка и количина на нутриент се повторуваат по еднаш за секој коментар и по еднаш за секое ограничување. Промена на име на рецепт би барала ажурирање на секој од овие редови — **аномалија при промена** — а бришењето на последниот коментар би го избришало и фактот дека рецептот содржи бел грав — **аномалија при бришење.** ||= username =||= recipe name =||= ingredient name =||= nutrient descr =||= nutrient amount =||= restriction type =||= comment =|| || marija_t || Tavce Gravce || Dry White Beans || Protein || 21.0 || vegan || How long should I let it simmer and soak? || || marija_t || Tavce Gravce || Dry White Beans || Protein || 21.0 || vegan || How long should you keep stirring the pot? || || marija_t || Tavce Gravce || Dry White Beans || Protein || 21.0 || gluten_free || What is in the sauce? || == ПРВА(1NF) {{{ Релацијата е во 1NF кога: • Редоследот на редовите нема значење • Секој атрибут содржи атомски вредности • Типовите на податоци се доследни • Примарен клуч еднозначно го идентификува секој запис • Не постојат групи што се повторуваат }}} Постапката за подготовка на рецептот се чува како една текстуална вредност наместо како листа чекори, бидејќи чекорите никогаш не се пребаруваат поединечно. Релацијата ја задоволува 1NF така што секој атрибут содржи една атомска вредност идентификувано со K. == ВТОРА(2NF) {{{ За да се постигне 2NF: • 1NF + • Парцијалните зависности мора да се отстранат. • Атрибутите што зависат само од дел од K мора да се издвојат во независни релации. }}} * R -> Целосна шема * R′ -> Шема после издвојување Секој атрибут на R зависи само од една компонента на K (или од пар извлечен од неа), никогаш од целиот K, па секоја гранка е парцијална зависност и се издвојува во сопствена релација. == Декомпозиција во 2NF === R1 - USER Проверка на кандидат-клучот: user_id е примарен клуч. email и username се алтернативни (unique) K. Не постојат парцијални зависности. {{{ {user_id} → {email, username, password, role} }}} Остаточна релација: {{{ R′1 ( user_id, ingredient_id, ingredient_name, ingr_description, energy, kcal, ingr_type, nutrient_id, nutr_description, rdi_quantity, unit, nutr_type, restriction_id, restr_description, restr_type, recipe_id, recipe_name, guide, kcal_sum, servings, post_id, media, media_type, media_name, is_private, is_favourite, status, post_created_at, comment_id, comment_text, comment_created_at, list_id, list_date_time, list_notes, is_bought, list_kcal, planner_id, planner_date_time, planner_notes, planner_kcal, is_consumed, bio_id, bio_date, weight, height, age, muscle_fat_ratio, ingredient_quantity, nutrient_amount, buy_quantity ) }}} * lossless тест: R1 ∩ R′1 = {user_id}; {user_id} → R1 ||= user_id =||= email =||= username =||= password =||= role =|| || 6 || acko@lavurovski.com || acko_l || testPass@1 || admin || || 8 || marija.trajko@gmail.com || marija_t || testPass@1 || user || === R2 - INGREDIENT Проверка на кандидат-клучот: ingredient_id е примарен клуч; ingredient_name е алтернативен К. {{{ {ingredient_id} → {ingredient_name, ingr_description, energy, kcal, ingr_type} }}} Остаточна релација: {{{ R′2 ( user_id, ingredient_id, nutrient_id, nutr_description, rdi_quantity, unit, nutr_type, restriction_id, restr_description, restr_type, recipe_id, recipe_name, guide, kcal_sum, servings, post_id, media, media_type, media_name, is_private, is_favourite, status, post_created_at, comment_id, comment_text, comment_created_at, list_id, list_date_time, list_notes, is_bought, list_kcal, planner_id, planner_date_time, planner_notes, planner_kcal, is_consumed, bio_id, bio_date, weight, height, age, muscle_fat_ratio, ingredient_quantity, nutrient_amount, buy_quantity ) }}} * lossless тест: R2 ∩ R′2 = {ingredient_id}; {ingredient_id} → R2 ||= ingredient_id =||= name =||= description =||= energy =||= kcal =||= type =|| || 26 || Onion || White or Red, will make you cry || 167 || 40 || vegetable || || 28 || Bread || Plain Bread || 1000 || 40 || carbs || === R3 - NUTRIENT Проверка на кандидат-клучот: nutrient_id е примарен клуч; nutr_description е алтернативен K. {{{ {nutrient_id} → {nutr_description, rdi_quantity, unit, nutr_type} }}} Остаточна релација: {{{ R′3 ( user_id, ingredient_id, nutrient_id, restriction_id, restr_description, restr_type, recipe_id, recipe_name, guide, kcal_sum, servings, post_id, media, media_type, media_name, is_private, is_favourite, status, post_created_at, comment_id, comment_text, comment_created_at, list_id, list_date_time, list_notes, is_bought, list_kcal, planner_id, planner_date_time, planner_notes, planner_kcal, is_consumed, bio_id, bio_date, weight, height, age, muscle_fat_ratio, ingredient_quantity, nutrient_amount, buy_quantity ) }}} * lossless тест: R3 ∩ R′3 = {nutrient_id}; {nutrient_id} → R3 ||= nutrient_id =||= description =||= quantity =||= unit=||= type =|| || 1 || Protein || 50 || gram || macro || || 9 || Vitamin C || 80 || milligram || micro || === R4 - RESTRICTION Проверка на кандидат-клучот: restriction_id е примарен клуч; restr_description е алтернативен K. {{{ {restriction_id} → {restr_description, restr_type} }}} Остаточна релација: {{{ R′4 ( user_id, ingredient_id, nutrient_id, restriction_id, recipe_id, recipe_name, guide, kcal_sum, servings, post_id, media, media_type, media_name, is_private, is_favourite, status, post_created_at, comment_id, comment_text, comment_created_at, list_id, list_date_time, list_notes, is_bought, list_kcal, planner_id, planner_date_time, planner_notes, planner_kcal, is_consumed, bio_id, bio_date, weight, height, age, muscle_fat_ratio, ingredient_quantity, nutrient_amount, buy_quantity ) }}} * lossless тест: R4 ∩ R′4 = {restriction_id}, а {restriction_id} → R4 ||= restriction_id =||= restr_description =||= type =|| || 12 || No animal origins || vegan || || 20 || Only animal origins || carnivore || === R5 - RECIPE Проверка на кандидат-клучот: recipe_id е примарен клуч. user_id е надворешен клуч кон R1. {{{ {recipe_id} → {recipe_name, guide, kcal_sum, servings, user_id} }}} Остаточна релација: {{{ R′5 ( user_id, ingredient_id, nutrient_id, restriction_id, recipe_id, post_id, media, media_type, media_name, is_private, is_favourite, status, post_created_at, comment_id, comment_text, comment_created_at, list_id, list_date_time, list_notes, is_bought, list_kcal, planner_id, planner_date_time, planner_notes, planner_kcal, is_consumed, bio_id, bio_date, weight, height, age, muscle_fat_ratio, ingredient_quantity, nutrient_amount, buy_quantity ) }}} * lossless тест: R5 ∩ R′5 = {recipe_id, user_id}, а {recipe_id} → R5. //Заедничкото множество е поширако од клучот затоа што надворешните клучеви на R5 остануваат во R′ за надмножество на детерминанта и понатаму е детерминанта.// ||= recipe_id =||= name =||= guide =||= kcal_sum =||= servings =||= user_id =|| || 1 || Omlette|| 1.Get Eggs 2... || 1167 || 1 || 2 || || 2 || Cesar Salad || 1. Get Bowl 2... || 2400 || 2 || 3 || === R6 - POST Проверка на кандидат-клучот: post_id е примарен клуч. recipe_id е алтернативен клуч (UNIQUE, дозволува NULL), кој ја реализира врската POSTS_ON 1:1. user_id ја реализира CREATES. {{{ {post_id} → {media, media_type, media_name, is_private, is_favourite, status, post_created_at, user_id, recipe_id} }}} Остаточна релација: {{{ R′6 ( user_id, ingredient_id, nutrient_id, restriction_id, recipe_id, post_id, comment_id, comment_text, comment_created_at, list_id, list_date_time, list_notes, is_bought, list_kcal, planner_id, planner_date_time, planner_notes, planner_kcal, is_consumed, bio_id, bio_date, weight, height, age, muscle_fat_ratio, ingredient_quantity, nutrient_amount, buy_quantity ) }}} * lossless тест: R6 ∩ R′6 = {post_id, user_id, recipe_id}, а {post_id} → R6 //Заедничкото множество е поширако од клучот затоа што надворешните клучеви на R6 остануваат во R′ за надмножество на детерминанта и понатаму е детерминанта.// ||= post_id =||= is_private =||= is_favourite =||= status =||= created_at =||= user_id =||= recipe_id =||= || 1 || true || true || published || 2026-01-30 07:30 || 2 || NULL || || 2 || true || false || published || 2026-03-20 09:35 || 3 || 1 || === R7 - COMMENT Проверка на кандидат-клучот: comment_id е примарен клуч. {{{ {comment_id} → {comment_text, comment_created_at, post_id, user_id} }}} Остаточна релација: {{{ R′7 ( user_id, ingredient_id, nutrient_id, restriction_id, recipe_id, post_id, comment_id, list_id, list_date_time, list_notes, is_bought, list_kcal, planner_id, planner_date_time, planner_notes, planner_kcal, is_consumed, bio_id, bio_date, weight, height, age, muscle_fat_ratio, ingredient_quantity, nutrient_amount, buy_quantity ) }}} * lossless тест: R7 ∩ R′7 = {comment_id, post_id, user_id}, а {comment_id} → R7 //Заедничкото множество е поширако од клучот затоа што надворешните клучеви на R7 остануваат во R′ за надмножество на детерминанта и понатаму е детерминанта.// ||= comment_id =||= description =||= created_at =||= post_id =||= user_id =|| || 12 || Pozdrav do Baba Rada || 2026-01-30 07:30 || 3 || 1 || || 29 || Vkusno i zdravo || 2026-12-10 08:30 || 2|| 5 || === R8 - GROCERY_LIST Проверка на кандидат-клучот: list_id е примарен клуч. {{{ {list_id} → {list_date_time, list_notes, is_bought, list_kcal, user_id} }}} Одделена релација: {{{ R′8 ( user_id, ingredient_id, nutrient_id, restriction_id, recipe_id, post_id, comment_id, list_id, planner_id, planner_date_time, planner_notes, planner_kcal, is_consumed, bio_id, bio_date, weight, height, age, muscle_fat_ratio, ingredient_quantity, nutrient_amount, buy_quantity ) }}} * lossless тест: R8 ∩ R′8 = {list_id, user_id}, а {list_id} → R8. //Заедничкото множество е поширако од клучот затоа што надворешните клучеви на R8 остануваат во R′ за надмножество на детерминанта и понатаму е детерминанта.// ||= list_id =||= date_time =||= notes =||= is_bought =||= kcal =||= user_id =|| || 12 || 2026-01-30 07:30 || Sabota Rucek || true || 1167 || 2 || || 23 || 2026-12-10 08:30 || Igor Rodenden || true || 2400 || 3 || === R9 - INTAKE_PLANNER Проверка на кандидат-клучот: planner_id е примарен клуч. {{{ {planner_id} → {planner_date_time, planner_notes, planner_kcal, is_consumed, user_id} }}} Остаточна релација: {{{ R′9 ( user_id, ingredient_id, nutrient_id, restriction_id, recipe_id, post_id, comment_id, list_id, planner_id, bio_id, bio_date, weight, height, age, muscle_fat_ratio, ingredient_quantity, nutrient_amount, buy_quantity ) }}} * lossless тест: R9 ∩ R′9 = {planner_id, user_id}, а {planner_id} → R9 //Заедничкото множество е поширако од клучот затоа што надворешните клучеви на R9 остануваат во R′ за надмножество на детерминанта и понатаму е детерминанта.// ||= planner_id =||= date_time =||= notes =||= kcal =||= is_consumed =||= user_id =|| || 12 || 2026-01-30 07:30 || Sabota Rucek || 1167 || true || 2 || || 23 || 2026-12-10 08:30 || Igor Rodenden || 2400 || true || 3 || === R10 - BIOMETRICS Проверка на кандидат-клучот: bio_id е примарен клуч; {user_id, bio_date} е алтернативен K. {{{ {bio_id} → {bio_date, weight, height, age, muscle_fat_ratio, user_id} }}} Остаточна релација: {{{ R′10 ( ingredient_id, nutrient_id, restriction_id, recipe_id, post_id, comment_id, list_id, planner_id, bio_id, ingredient_quantity, nutrient_amount, buy_quantity ) }}} * lossless тест: R10 ∩ R′10 = {bio_id}, а {bio_id} → R10 ||= bio_id =||= date =||= weight =||= height =||= age =||= muscle_fat_ratio =||= user_id =|| || 1 || 2026-01-10 || 100 || 189 || 25 || 1 || || 2 || 2026-01-11 || 99.5 || 189 || 25 || 1 || === R11 - RECIPE_CONTAINS_INGREDIENT Проверка на кандидат-клучот: Составен примарен клуч {recipe_id, ingredient_id}. Количина зависи од целиот пар — ни самиот рецепт, ни самата состојка не ја определуваат. {{{ {recipe_id, ingredient_id} → {ingredient_quantity} }}} Одделена релација: {{{ R′11 ( ingredient_id, nutrient_id, restriction_id, recipe_id, post_id, comment_id, list_id, planner_id, bio_id, nutrient_amount, buy_quantity ) }}} * lossless тест: R11 ∩ R′11 = {recipe_id, ingredient_id}, а {recipe_id, ingredient_id} → R11 ||= recipe_id =||= ingredient_id =||= ingredient_quantity =|| || 1 || 20 || 100 || || 2 || 30 || 450 || === R12 - INGREDIENT_CONTAINS_NUTRIENT Проверка на кандидат-клучот: Составен примарен клуч {ingredient_id, nutrient_id}. Количината на 100 g зависи од целиот пар. {{{ {ingredient_id, nutrient_id} → {nutrient_amount} }}} Остаточна релација: {{{ R′12 ( ingredient_id, nutrient_id, restriction_id, recipe_id, post_id, comment_id, list_id, planner_id, bio_id, buy_quantity ) }}} * lossless тест: R12 ∩ R′12 = {ingredient_id, nutrient_id}, а {ingredient_id, nutrient_id} → R12 ||= ingredient_id =||= nutrient_id =||= quantity =|| || 21 || 1 || 10.0 || || 22 || 2 || 5.0 || === R13 - RECIPE_CONTAINS_RESTRICTION Проверка на кандидат-клучот: Чиста составна релациска табела со клуч {recipe_id, restriction_id}. {{{ {ingredient_id, nutrient_id} → {nutrient_amount} }}} Остаточна релација: {{{ R′13 ( ingredient_id, nutrient_id, restriction_id, recipe_id, post_id, comment_id, list_id, planner_id, bio_id, buy_quantity ) }}} * lossless тест: R13 ∩ R′13 = {recipe_id, restriction_id}, а {recipe_id, restriction_id} → R13 ||= recipe_id =||= restriction_id =|| || 1 || 1 || || 2 || 2 || === R14 - GROCERY_LIST_BULK_ADD_INGREDIENT Проверка на кандидат-клучот: Чиста составна релациска табела со клуч {list_id, recipe_id}. {{{ {list_id, recipe_id} → Нема атрибути надвор од клучот. }}} Остаточна релација: {{{ R′14 ( ingredient_id, nutrient_id, restriction_id, recipe_id, post_id, comment_id, list_id, planner_id, bio_id, buy_quantity ) }}} * lossless тест: R14 ∩ R′14 = {list_id, recipe_id}, а {list_id, recipe_id} → R14. ||= list_id =||= recipe_id =|| || 1 || 3 || || 2 || 4 || === R15 - GROCERY_LIST_SINGLE_ADD_INGREDIENT Проверка на кандидат-клучот: Составен примарен клуч {list_id, ingredient_id}. {{{ {list_id, ingredient_id} → {buy_quantity} }}} Остаточна релација: {{{ R′15 ( ingredient_id, nutrient_id, restriction_id, recipe_id, post_id, comment_id, list_id, planner_id, bio_id ) }}} * lossless тест: R15 ∩ R′15 = {list_id, ingredient_id}, а {list_id, ingredient_id} → R15 ||= list_id =||= ingredient_id =||= buy_quantity =|| || 1 || 3 || 1000 || || 2 || 4 || 250 || === R16 - INTAKE_PLANNER_SAVE_TO_LIST_POST Проверка на кандидат-клучот: Чиста составна врзна табела со клуч {planner_id, post_id}. {{{ {planner_id, post_id} → Нема атрибути надвор од клучот. }}} Остаточна релација: {{{ R′16 ( ingredient_id, nutrient_id, restriction_id, recipe_id, post_id, comment_id, list_id, planner_id, bio_id ) }}} * lossless тест: R16 ∩ R′16 = {planner_id, post_id}, а {planner_id, post_id} → R16 ||= planner_id =||= post_id =|| || 1 || 3 || || 2 || 4 || == ТРЕТА(3NF) {{{ За да се постигне 2NF: • 2NF + • Не постојат транзитивни зависности. • Секој атрибут надвор од клучот зависи строго и директно само од примарниот клуч. }}} Транзитивноста е разрешена со декомпозицијата во 2FN: * comment_id → user_id → email, recipe_id → user_id → username * post_id → recipe_id → kcal_sum Ниеден атрибут надвор од клучот во ниедна релација не зависи од друг атрибут надвор од клучот, па шемата ја задоволува 3NF. Изведените атрибути за kcal и kcal_sum (R5, R8 и R9) можат да се гледаат како отстапување во овај случај, но тие се материјализираат при секое барање така што е повеќе отсликува редудентност меѓу релации. За подетална анализа посочете се кон DML скриптата за калкулации во Фаза 02. == БОЈС-КОДОВА (BCNF) {{{ Релацијата е во BCNF ако за секоја нетривијална функционална зависност X → Y, X е суперклуч. Во секоја релација детерминантите се: * самиот примарен клуч, кој по дефиниција е суперклуч; * алтернативен клуч — email и username во R1, ingredient_name во R2, nutr_description во R3, restr_description во R4, recipe_id во R6, {user_id, bio_date} во R10. Секој од нив е UNIQUE и NOT NULL (освен recipe_id, види подолу), па секој е самиот кандидат-клуч, а со тоа и суперклуч. }}} POST.recipe_id е UNIQUE но дозволува NULL, бидејќи објавата може да биде обична фотографија без рецепт. Меѓу вредностите што не се NULL тој се однесува како кандидат-клуч, па recipe_id → post_id не воведува редундантност. == Финален преглед на шемата ||= Релација =||= PK =||= FK =||= Нормална форма =|| ||USER||user_id||X|| || ||INGREDIENT||ingredient_id||X||3NF / BCNF || ||NUTRIENT||nutrient_id ||X||3NF / BCNF || ||RESTRICTION||restriction_id||X||3NF / BCNF || ||RECIPE||recipe_id||user_id → USER ||3NF / BCNF || ||POST||post_id||user_id → USER, recipe_id → RECIPE||3NF / BCNF || ||COMMENT ||comment_id||post_id → POST, user_id → USER||3NF / BCNF || ||GROCERY_LIST||list_id||user_id → USER||3NF / BCNF || ||INTAKE_PLANNER||planner_id||user_id → USER||3NF / BCNF || ||BIOMETRICS||bio_id||user_id → USER||3NF / BCNF || ||RECIPE_CONTAINS_INGREDIENT||recipe_id, nutrient_id||recipe_id → RECIPE, ingredient_id → INGREDIENT||3NF / BCNF || ||INGREDIENT_CONTAINS_NUTRIENT||ingredient_id, nutrient_id||ingredient_id → INGREDIENT, nutrient_id → NUTRIENT||3NF / BCNF || ||RECIPE_CONTAINS_RESTRICTION||recipe_id, restriction_id ||recipe_id → RECIPE, restriction_id → RESTRICTION||3NF / BCNF || ||GROCERY_LIST_BULK_ADD_INGREDIENT||list_id, recipe_id||list_id → GROCERY_LIST, recipe_id → RECIPE||3NF / BCNF || ||GROCERY_LIST_SINGLE_ADD_INGREDIENT||list_id, ingredient_id||list_id → GROCERY_LIST, ingredient_id → INGREDIENT||3NF / BCNF || ||INTAKE_PLANNER_SAVE_TO_LIST_POST||planner_id, post_id||planner_id → INTAKE_PLANNER, post_id → POST||3NF / BCNF || == Навигација низ проектот [https://develop.finki.ukim.mk/projects/nutritioneer/wiki/WikiStart Почетна страна] ||= Фаза =||= Име на фаза =||= Статус =|| || P0 || [https://develop.finki.ukim.mk/projects/nutritioneer/wiki/About Дефинирање проект] || Одобрен || || P1 || [https://develop.finki.ukim.mk/projects/nutritioneer/wiki/ERModel Концептуален дизајн и ЕР Дијаграм] || Одобрен || || P2 || [https://develop.finki.ukim.mk/projects/nutritioneer/wiki/RelationalDesign Логички и физички дизајн - DDL] || Одобрен || || P3 || [https://develop.finki.ukim.mk/projects/nutritioneer/wiki/UseCaseModel Кориснички/апикациски сценарија со базата на податоци] || Одобрен || || P4 || [https://develop.finki.ukim.mk/projects/nutritioneer/wiki/PrototypeApplication Протип со основни функционалности] || WIP || || М1 || Презентација на прототип || WIP || || P5 || [https://develop.finki.ukim.mk/projects/nutritioneer/wiki/Normalization Нормализација и оптимизација на дизајн] || WIP || || P6 || [https://develop.finki.ukim.mk/projects/nutritioneer/wiki/AdvancedReports Напредни извештаи од базата] || WIP || || P7 || [https://develop.finki.ukim.mk/projects/nutritioneer/wiki/AdvancedDatabaseDevelopment Понапреден развој на базата] || WIP || || P8 || [https://develop.finki.ukim.mk/projects/nutritioneer/wiki/AdvancedApplicationDevelopment Напреден апликативен развој] || WIP || || P9 || [https://develop.finki.ukim.mk/projects/nutritioneer/wiki/OtherDevelopment Дополнителни имплементации] || WIP || || М2 || Финализиран проект || WIP || === ^([#hn Trac навигација]) * TracGuide Documentation