Changes between Initial Version and Version 1 of normalization


Ignore:
Timestamp:
01/29/26 18:24:11 (23 hours ago)
Author:
231175
Comment:

--

Legend:

Unmodified
Added
Removed
Modified
  • normalization

    v1 v1  
     1= Формална анализа и доказ за нормализација на Shifter =
     2
     3== 1. Дефиниција на универзалната релација ==
     4
     5Тргнуваме од една единствена универзална релација која ги содржи сите атрибути од доменот.
     6
     7{{{
     8Universal_Relation_Shifter(
     9user_id, user_name, user_email, user_login_provider, user_password_hash, user_verified, user_profile_complete, user_used_free_consultation, user_company_size, user_work_position, user_points,
     10meeting_reminder_id, meeting_at, meeting_scheduled_at, meeting_sent, meeting_link,
     11expert_id, expert_name, expert_email, expert_login_provider, expert_password_hash,
     12course_id, course_image_url, course_color, course_difficulty, course_duration_minutes, course_price,
     13course_version_id, version_number, version_creation_date, version_active,
     14enrollment_id, enrollment_status, enrollment_purchase_date, enrollment_activation_date, enrollment_completion_date,
     15payment_id, payment_amount, payment_date, payment_method, payment_status,
     16review_id, review_rating, review_comment, review_date,
     17course_translate_id, course_translate_language, course_title_short, course_title, course_description_short, course_description, course_description_long,
     18course_what_will_be_learned_text,
     19course_content_id, content_position,
     20course_content_translate_id, content_translate_title, content_translate_language,
     21course_lecture_id, lecture_duration_minutes, lecture_position, lecture_content_type,
     22course_lecture_translate_id, lecture_title, lecture_language, lecture_content_file_name, lecture_description, lecture_content_text,
     23user_course_progress_id, progress_completed, progress_completed_at,
     24verification_token_uuid, token_created_at, token_expired_at,
     25tag_id, tag_type,
     26tag_translate_id, tag_translate_language, tag_value
     27)
     28}}}
     29
     30----
     31
     32== 2. Функционални зависности ==
     33
     34Ги идентификуваме сите функционални зависности што важат во универзалната релација:
     35
     36'''F = {'''
     37 * '''FD1:''' user_id → user_name, user_email, user_login_provider, user_password_hash, user_verified, user_profile_complete, user_used_free_consultation, user_company_size, user_work_position, user_points
     38 * '''FD2:''' verification_token_uuid → token_created_at, token_expired_at, user_id
     39 * '''FD3:''' meeting_reminder_id → meeting_at, meeting_scheduled_at, meeting_sent, meeting_link, user_id
     40 * '''FD4:''' expert_id → expert_name, expert_email, expert_login_provider, expert_password_hash
     41 * '''FD5:''' course_id → course_image_url, course_color, course_difficulty, course_duration_minutes, course_price
     42 * '''FD6:''' enrollment_id → enrollment_status, enrollment_purchase_date, enrollment_activation_date, enrollment_completion_date, user_id, course_version_id, payment_id, review_id
     43 * '''FD7:''' payment_id → payment_amount, payment_date, payment_method, payment_status, enrollment_id
     44 * '''FD8:''' review_id → review_rating, review_comment, review_date, enrollment_id
     45 * '''FD9:''' tag_id → tag_type
     46 * '''FD10:''' tag_translate_id → tag_translate_language, tag_value, tag_id
     47 * '''FD11:''' course_version_id → version_number, version_creation_date, version_active, course_id
     48 * '''FD12:''' course_translate_id → course_translate_language, course_title_short, course_title, course_description_short, course_description, course_description_long, course_id
     49 * '''FD13:''' course_content_id → content_position, course_version_id
     50 * '''FD14:''' course_content_translate_id → content_translate_title, content_translate_language, course_content_id
     51 * '''FD15:''' course_lecture_id → lecture_duration_minutes, lecture_position, lecture_content_type, course_content_id
     52 * '''FD16:''' course_lecture_translate_id → lecture_title, lecture_language, lecture_content_file_name, lecture_description, lecture_content_text, course_lecture_id
     53 * '''FD17:''' user_course_progress_id → progress_completed, progress_completed_at, enrollment_id, course_lecture_id
     54 * '''FD18:''' user_email → user_id
     55 * '''FD19:''' expert_email → expert_id
     56 * '''FD20:''' (course_id, version_number) → course_version_id
     57 * '''FD21:''' (course_id, course_translate_language) → course_translate_id
     58 * '''FD22:''' (course_lecture_id, lecture_language) → course_lecture_translate_id
     59 * '''FD23:''' (course_content_id, content_translate_language) → course_content_translate_id
     60 * '''FD24:''' (tag_id, tag_translate_language) → tag_translate_id
     61 * '''FD25:''' (enrollment_id, course_lecture_id) → user_course_progress_id
     62 * '''FD26:''' (course_version_id, content_position) → course_content_id
     63 * '''FD27:''' (course_content_id, lecture_position) → course_lecture_id
     64
     65'''}'''
     66
     67----
     68
     69== 3. Кандидат клучеви и примарен клуч ==
     70
     71=== 3.1 Определување на кандидат клучеви ===
     72
     73За да најдеме кандидат клуч, треба да најдеме минимално множество атрибути чиј затворач ги содржи сите атрибути на релацијата.
     74
     75=== 3.2 Класификација на атрибути (Лева / Десна страна) ===
     76
     77||= Атрибут =||= Лева страна =||= Десна страна =||= Класификација =||
     78|| userId || ✓ (FD1) || ✓ (FD2, FD3, FD10) || и лево и десно ||
     79|| user_username || ✓ (FD2) || ✓ (FD1, FD3) || и лево и десно ||
     80|| user_email || ✓ (FD3) || ✓ (FD1, FD2) || и лево и десно ||
     81|| user_password || ✗ || ✓ (FD1, FD2, FD3) || Само десно ||
     82|| user_full_name || ✗ || ✓ (FD1, FD2, FD3) || Само десно ||
     83|| user_role || ✗ || ✓ (FD1, FD2, FD3) || Само десно ||
     84|| user_is_active || ✗ || ✓ (FD1, FD2, FD3) || Само десно ||
     85|| customerId || ✓ (FD4) || ✓ (FD10) || и лево и десно ||
     86|| customer_name || ✗ || ✓ (FD4) || Само десно ||
     87|| customer_email || ✗ || ✓ (FD4) || Само десно ||
     88|| customer_phone || ✗ || ✓ (FD4) || Само десно ||
     89|| customer_address || ✗ || ✓ (FD4) || Само десно ||
     90|| productId || ✓ (FD5) || ✓ (FD6) || и лево и десно ||
     91|| product_sku || ✓ (FD6) || ✓ (FD5) || и лево и десно ||
     92|| product_name || ✗ || ✓ (FD5, FD6) || Само десно ||
     93|| product_description || ✗ || ✓ (FD5, FD6) || Само десно ||
     94|| product_unit_price || ✗ || ✓ (FD5, FD6) || Само десно ||
     95|| product_reorder_level || ✗ || ✓ (FD5, FD6) || Само десно ||
     96|| categoryId || ✓ (FD7) || ✓ (FD5, FD6) || и лево и десно ||
     97|| category_name || ✗ || ✓ (FD7) || Само десно ||
     98|| category_description || ✗ || ✓ (FD7) || Само десно ||
     99|| supplierId || ✓ (FD8) || ✓ (FD5, FD6, FD11) || и лево и десно ||
     100|| supplier_name || ✗ || ✓ (FD8) || Само десно ||
     101|| supplier_contact_person || ✗ || ✓ (FD8) || Само десно ||
     102|| supplier_phone || ✗ || ✓ (FD8) || Само десно ||
     103|| supplier_email || ✗ || ✓ (FD8) || Само десно ||
     104|| supplier_address || ✗ || ✓ (FD8) || Само десно ||
     105|| warehouseId || ✓ (FD9) || ✓ (FD10, FD11, FD14) || и лево и десно ||
     106|| warehouse_name || ✗ || ✓ (FD9) || Само десно ||
     107|| warehouse_location || ✗ || ✓ (FD9) || Само десно ||
     108|| warehouse_capacity || ✗ || ✓ (FD9) || Само десно ||
     109|| '''saleId''' || ✓ (FD10, FD12) || ✗ || '''Само лево''' ||
     110|| sale_date_time || ✗ || ✓ (FD10) || Само десно ||
     111|| sale_total_amount || ✗ || ✓ (FD10) || Само десно ||
     112|| '''poId''' || ✓ (FD11, FD13) || ✗ || '''Само лево''' ||
     113|| po_order_date || ✗ || ✓ (FD11) || Само десно ||
     114|| po_expected_delivery_date || ✗ || ✓ (FD11) || Само десно ||
     115|| po_status || ✗ || ✓ (FD11) || Само десно ||
     116|| saleitem_quantity || ✗ || ✓ (FD12) || Само десно ||
     117|| saleitem_unit_price_at_sale || ✗ || ✓ (FD12) || Само десно ||
     118|| poitem_quantity || ✗ || ✓ (FD13) || Само десно ||
     119|| poitem_unit_cost || ✗ || ✓ (FD13) || Само десно ||
     120|| stock_quantity_on_hand || ✗ || ✓ (FD14) || Само десно ||
     121|| stock_last_updated || ✗ || ✓ (FD14) || Само десно ||
     122
     123
     124
     125
     126=== 3.3 Атрибути кои се појавуваат САМО на лева страна ===
     127
     128 * '''saleId''' - мора да биде дел од секој кандидат клуч
     129 * '''poId''' - мора да биде дел од секој кандидат клуч
     130
     131=== 3.4 Пресметка на затворач ===
     132
     133'''Чекор 1:''' Започнуваме со {saleId, poId} и пресметуваме затворач:
     134
     135{{{
     136{saleId, poId}⁺:
     137- Од FD10 (saleId →): добиваме sale_date_time, sale_total_amount, userId, customerId, warehouseId
     138- Од FD11 (poId →): добиваме po_order_date, po_expected_delivery_date, po_status, supplierId, warehouseId
     139- Од FD1 (userId →): добиваме user_username, user_password, user_full_name, user_email, user_role, user_is_active
     140- Од FD4 (customerId →): добиваме customer_name, customer_email, customer_phone, customer_address
     141- Од FD8 (supplierId →): добиваме supplier_name, supplier_contact_person, supplier_phone, supplier_email, supplier_address
     142- Од FD9 (warehouseId →): добиваме warehouse_name, warehouse_location, warehouse_capacity
     143}}}
     144
     145'''{saleId, poId}⁺ ≠ U'''
     146
     147'''Недостасуваат:''' productId, product_name, product_description, product_sku, product_unit_price, product_reorder_level, categoryId, category_name, category_description, saleitem_quantity, saleitem_unit_price_at_sale, poitem_quantity, poitem_unit_cost, stock_quantity_on_hand, stock_last_updated
     148
     149'''Чекор 2:''' Додаваме productId:
     150
     151{{{
     152{saleId, poId, productId}⁺:
     153- Сè од претходно, плус:
     154- Од FD5 (productId →): добиваме product_name, product_description, product_sku, product_unit_price, product_reorder_level, categoryId, supplierId
     155- Од FD7 (categoryId →): добиваме category_name, category_description
     156- Од FD12 ({saleId, productId} →): добиваме saleitem_quantity, saleitem_unit_price_at_sale
     157- Од FD13 ({poId, productId} →): добиваме poitem_quantity, poitem_unit_cost
     158- Од FD14 ({warehouseId, productId} →): добиваме stock_quantity_on_hand, stock_last_updated
     159}}}
     160
     161'''{saleId, poId, productId}⁺ = U''' (ги содржи сите атрибути) ✓
     162
     163=== 3.5 Проверка за минималност ===
     164
     165||= Подмножество =||= Затворач =||= = U? =||
     166|| {saleId, poId}⁺ || Недостасуваат атрибути поврзани со productId || ✗ НЕ ||
     167|| {saleId, productId}⁺ || Недостасуваат атрибути од poId: po_order_date, po_expected_delivery_date, po_status, poitem_quantity, poitem_unit_cost || ✗ НЕ ||
     168|| {poId, productId}⁺ || Недостасуваат атрибути од saleId: sale_date_time, sale_total_amount, saleitem_quantity, saleitem_unit_price_at_sale, userId→атрибути, customerId→атрибути || ✗ НЕ ||
     169
     170'''Заклучок:''' {saleId, poId, productId} е минимален и е кандидат клуч.
     171
     172=== 3.6 Други кандидат клучеви ===
     173
     174Бидејќи постојат еквиваленции (FD5 и FD6 покажуваат дека productId ↔ product_sku), можеме да го замениме productId со product_sku:
     175
     176'''Кандидат клуч 2:''' {saleId, poId, product_sku}
     177
     178{{{
     179Проверка: {saleId, poId, product_sku}⁺:
     180- Од FD6 (product_sku →): добиваме productId и сите атрибути на продукт
     181- Понатаму идентично како горе
     182
     183{saleId, poId, product_sku}⁺ = U ✓
     184}}}
     185
     186=== 3.7 Избор на примарен клуч ===
     187
     188||= Кандидат клуч =||= Број на атрибути =||= Тип на атрибути =||
     189|| {saleId, poId, productId} || 3 || Сурогат клучеви (стабилни) ||
     190|| {saleId, poId, product_sku} || 3 || Мешано (saleId, poId сурогат; sku природен) ||
     191
     192'''Избран примарен клуч: {saleId, poId, productId}'''
     193
     194'''Образложение:'''
     195 1. Сите три атрибути се сурогат клучеви кои не се менуваат со тек на време
     196 2. productId е понумерички и поефикасен за индексирање од product_sku (кој е VARCHAR)
     197 3. Сурогат клучевите обезбедуваат стабилност - ако се промени SKU кодот на продукт, примарниот клуч останува непроменет
     198
     199----
     200
     201== 4. 1NF Декомпозиција ==
     202
     203=== 4.1 Проверка за 1NF ===
     204
     205Универзалната релација U е во 1NF бидејќи:
     206 * Сите атрибути се атомски (нема повеќевредносни атрибути)
     207 * Постои примарен клуч: {saleId, poId, productId}
     208 * Нема повторувачки групи
     209
     210'''Заклучок:''' U е во 1NF. ✓
     211
     212=== 4.2 Проверка која FD ја нарушува 2NF ===
     213
     214За да биде во 2NF, секој не-клучен атрибут мора целосно да зависи од примарниот клуч (нема парцијални зависности).
     215
     216Примарен клуч: '''{saleId, poId, productId}'''
     217
     218'''Парцијални зависности (атрибути кои зависат од дел од клучот):'''
     219
     220||= FD =||= Детерминант =||= Дел од клуч? =||= Парцијална зависност? =||
     221|| FD1: userId → user_* || userId || НЕ (транзитивно преку saleId) || Не директно ||
     222|| FD5: productId → product_*, categoryId, supplierId || productId || ДА (productId е дел од клучот) || '''ДА''' ||
     223|| FD10: saleId → sale_*, userId, customerId, warehouseId || saleId || ДА (saleId е дел од клучот) || '''ДА''' ||
     224|| FD11: poId → po_*, supplierId, warehouseId || poId || ДА (poId е дел од клучот) || '''ДА''' ||
     225|| FD12: {saleId, productId} → saleitem_* || {saleId, productId} || ДА (подмножество на клучот) || '''ДА''' ||
     226|| FD13: {poId, productId} → poitem_* || {poId, productId} || ДА (подмножество на клучот) || '''ДА''' ||
     227|| FD14: {warehouseId, productId} → stock_* || {warehouseId, productId} || warehouseId не е во клучот директно || Посебен случај* ||
     228
     229*Забелешка за FD14: warehouseId се добива транзитивно од saleId (FD10) и од poId (FD11), но {warehouseId, productId} не е подмножество на примарниот клуч.
     230
     231'''Идентификувани парцијални зависности што ја нарушуваат 2NF:'''
     232
     233 1. '''FD5:''' productId → product_name, product_description, product_sku, product_unit_price, product_reorder_level, categoryId, supplierId
     234 2. '''FD10:''' saleId → sale_date_time, sale_total_amount, userId, customerId, warehouseId
     235 3. '''FD11:''' poId → po_order_date, po_expected_delivery_date, po_status, supplierId, warehouseId
     236 4. '''FD12:''' {saleId, productId} → saleitem_quantity, saleitem_unit_price_at_sale
     237 5. '''FD13:''' {poId, productId} → poitem_quantity, poitem_unit_cost
     238
     239----
     240
     241== 5. 2NF Декомпозиција ==
     242
     243=== Чекор 5.1: Декомпозиција по FD5 (productId → ...) ===
     244
     245'''Нарушува 2NF:''' productId е дел од примарниот клуч, а product_name, product_description, итн. зависат само од productId.
     246
     247'''Декомпозиција:'''
     248
     249{{{
     250R1(productId, product_name, product_description, product_sku, product_unit_price, product_reorder_level, categoryId, supplierId)
     251   PK: productId
     252
     253U1 = U - {product_name, product_description, product_sku, product_unit_price, product_reorder_level}
     254
     255U1(saleId, poId, productId, userId, user_username, user_password, user_full_name, user_email, user_role, user_is_active, customerId, customer_name, customer_email, customer_phone, customer_address, categoryId, category_name, category_description, supplierId, supplier_name, supplier_contact_person, supplier_phone, supplier_email, supplier_address, warehouseId, warehouse_name, warehouse_location, warehouse_capacity, sale_date_time, sale_total_amount, po_order_date, po_expected_delivery_date, po_status, saleitem_quantity, saleitem_unit_price_at_sale, poitem_quantity, poitem_unit_cost, stock_quantity_on_hand, stock_last_updated)
     256}}}
     257
     258'''Проверка за lossless join:'''
     259 * R1 ∩ U1 = {productId, categoryId, supplierId}
     260 * productId → R1 (FD5)
     261 * Декомпозицијата е lossless ✓
     262
     263'''Проверка за зачувување на FD:''' FD5 е зачувана во R1 ✓
     264
     265=== Чекор 5.2: Декомпозиција по FD10 (saleId → ...) ===
     266
     267'''Нарушува 2NF:''' saleId е дел од примарниот клуч на U1.
     268
     269'''Декомпозиција:'''
     270
     271{{{
     272R2(saleId, sale_date_time, sale_total_amount, userId, customerId, warehouseId)
     273   PK: saleId
     274
     275U2 = U1 - {sale_date_time, sale_total_amount}
     276
     277U2(saleId, poId, productId, userId, user_username, user_password, user_full_name, user_email, user_role, user_is_active, customerId, customer_name, customer_email, customer_phone, customer_address, categoryId, category_name, category_description, supplierId, supplier_name, supplier_contact_person, supplier_phone, supplier_email, supplier_address, warehouseId, warehouse_name, warehouse_location, warehouse_capacity, po_order_date, po_expected_delivery_date, po_status, saleitem_quantity, saleitem_unit_price_at_sale, poitem_quantity, poitem_unit_cost, stock_quantity_on_hand, stock_last_updated)
     278}}}
     279
     280'''Проверка за lossless join:'''
     281 * R2 ∩ U2 = {saleId, userId, customerId, warehouseId}
     282 * saleId → R2 (FD10)
     283 * Декомпозицијата е lossless ✓
     284
     285=== Чекор 5.3: Декомпозиција по FD11 (poId → ...) ===
     286
     287'''Нарушува 2NF:''' poId е дел од примарниот клуч на U2.
     288
     289'''Декомпозиција:'''
     290
     291{{{
     292R3(poId, po_order_date, po_expected_delivery_date, po_status, supplierId, warehouseId)
     293   PK: poId
     294
     295U3 = U2 - {po_order_date, po_expected_delivery_date, po_status}
     296
     297U3(saleId, poId, productId, userId, user_username, user_password, user_full_name, user_email, user_role, user_is_active, customerId, customer_name, customer_email, customer_phone, customer_address, categoryId, category_name, category_description, supplierId, supplier_name, supplier_contact_person, supplier_phone, supplier_email, supplier_address, warehouseId, warehouse_name, warehouse_location, warehouse_capacity, saleitem_quantity, saleitem_unit_price_at_sale, poitem_quantity, poitem_unit_cost, stock_quantity_on_hand, stock_last_updated)
     298}}}
     299
     300'''Проверка за lossless join:'''
     301 * R3 ∩ U3 = {poId, supplierId, warehouseId}
     302 * poId → R3 (FD11)
     303 * Декомпозицијата е lossless ✓
     304
     305=== Чекор 5.4: Декомпозиција по FD12 ({saleId, productId} → ...) ===
     306
     307'''Нарушува 2NF:''' {saleId, productId} е подмножество на примарниот клуч.
     308
     309'''Декомпозиција:'''
     310
     311{{{
     312R4(saleId, productId, saleitem_quantity, saleitem_unit_price_at_sale)
     313   PK: {saleId, productId}
     314
     315U4 = U3 - {saleitem_quantity, saleitem_unit_price_at_sale}
     316
     317U4(saleId, poId, productId, userId, user_username, user_password, user_full_name, user_email, user_role, user_is_active, customerId, customer_name, customer_email, customer_phone, customer_address, categoryId, category_name, category_description, supplierId, supplier_name, supplier_contact_person, supplier_phone, supplier_email, supplier_address, warehouseId, warehouse_name, warehouse_location, warehouse_capacity, poitem_quantity, poitem_unit_cost, stock_quantity_on_hand, stock_last_updated)
     318}}}
     319
     320'''Проверка за lossless join:'''
     321 * R4 ∩ U4 = {saleId, productId}
     322 * {saleId, productId} → R4 (FD12)
     323 * Декомпозицијата е lossless ✓
     324
     325=== Чекор 5.5: Декомпозиција по FD13 ({poId, productId} → ...) ===
     326
     327'''Нарушува 2NF:''' {poId, productId} е подмножество на примарниот клуч.
     328
     329'''Декомпозиција:'''
     330
     331{{{
     332R5(poId, productId, poitem_quantity, poitem_unit_cost)
     333   PK: {poId, productId}
     334
     335U5 = U4 - {poitem_quantity, poitem_unit_cost}
     336
     337U5(saleId, poId, productId, userId, user_username, user_password, user_full_name, user_email, user_role, user_is_active, customerId, customer_name, customer_email, customer_phone, customer_address, categoryId, category_name, category_description, supplierId, supplier_name, supplier_contact_person, supplier_phone, supplier_email, supplier_address, warehouseId, warehouse_name, warehouse_location, warehouse_capacity, stock_quantity_on_hand, stock_last_updated)
     338}}}
     339
     340'''Проверка за lossless join:'''
     341 * R5 ∩ U5 = {poId, productId}
     342 * {poId, productId} → R5 (FD13)
     343 * Декомпозицијата е lossless ✓
     344
     345=== Состојба после 2NF декомпозиција ===
     346
     347{{{
     348R1(productId, product_name, product_description, product_sku, product_unit_price, product_reorder_level, categoryId, supplierId)
     349   PK: productId
     350
     351R2(saleId, sale_date_time, sale_total_amount, userId, customerId, warehouseId)
     352   PK: saleId
     353
     354R3(poId, po_order_date, po_expected_delivery_date, po_status, supplierId, warehouseId)
     355   PK: poId
     356
     357R4(saleId, productId, saleitem_quantity, saleitem_unit_price_at_sale)
     358   PK: {saleId, productId}
     359
     360R5(poId, productId, poitem_quantity, poitem_unit_cost)
     361   PK: {poId, productId}
     362
     363U5(saleId, poId, productId, userId, user_username, user_password, user_full_name, user_email, user_role, user_is_active,
     364   customerId, customer_name, customer_email, customer_phone, customer_address,
     365   categoryId, category_name, category_description,
     366   supplierId, supplier_name, supplier_contact_person, supplier_phone, supplier_email, supplier_address,
     367   warehouseId, warehouse_name, warehouse_location, warehouse_capacity,
     368   stock_quantity_on_hand, stock_last_updated)
     369   PK: {saleId, poId, productId}
     370}}}
     371
     372----
     373
     374== 6. 3NF Декомпозиција ==
     375
     376=== Проверка за транзитивни зависности во секоја релација ===
     377
     378==== R1(productId, product_name, product_description, product_sku, product_unit_price, product_reorder_level, categoryId, supplierId) ====
     379
     380'''Транзитивни зависности:'''
     381 * productId → categoryId → category_name, category_description?
     382   * category_name и category_description НЕ се во R1, па нема транзитивна зависност во оваа релација.
     383 * productId → supplierId → supplier_*?
     384   * supplier_* атрибутите НЕ се во R1.
     385
     386'''Заклучок:''' R1 е во 3NF ✓
     387
     388==== R2(saleId, sale_date_time, sale_total_amount, userId, customerId, warehouseId) ====
     389
     390'''Транзитивни зависности:'''
     391 * saleId → userId → user_*?
     392   * user_* атрибутите НЕ се во R2.
     393 * saleId → customerId → customer_*?
     394   * customer_* атрибутите НЕ се во R2.
     395 * saleId → warehouseId → warehouse_*?
     396   * warehouse_* атрибутите НЕ се во R2.
     397
     398'''Заклучок:''' R2 е во 3NF ✓
     399
     400==== R3(poId, po_order_date, po_expected_delivery_date, po_status, supplierId, warehouseId) ====
     401
     402'''Транзитивни зависности:'''
     403 * Слично како R2, supplier_* и warehouse_* не се во R3.
     404
     405'''Заклучок:''' R3 е во 3NF ✓
     406
     407==== R4(saleId, productId, saleitem_quantity, saleitem_unit_price_at_sale) ====
     408
     409'''Транзитивни зависности:'''
     410 * Нема не-клучни атрибути кои одредуваат други не-клучни атрибути.
     411
     412'''Заклучок:''' R4 е во 3NF ✓
     413
     414==== R5(poId, productId, poitem_quantity, poitem_unit_cost) ====
     415
     416'''Заклучок:''' R5 е во 3NF ✓
     417
     418==== U5 - Проверка за транзитивни зависности ====
     419
     420Примарен клуч на U5: '''{saleId, poId, productId}'''
     421
     422'''Транзитивни зависности во U5:'''
     423
     424 1. '''FD1 транзитивно:''' {saleId, poId, productId} → userId (преку R2/saleId) → user_username, user_password, user_full_name, user_email, user_role, user_is_active
     425    * userId не е клуч на U5, а user_* зависат од userId
     426    * '''НАРУШУВА 3NF'''
     427
     428 2. '''FD4 транзитивно:''' {saleId, poId, productId} → customerId (преку R2/saleId) → customer_name, customer_email, customer_phone, customer_address
     429    * '''НАРУШУВА 3NF'''
     430
     431 3. '''FD7 транзитивно:''' {saleId, poId, productId} → categoryId (преку R1/productId) → category_name, category_description
     432    * '''НАРУШУВА 3NF'''
     433
     434 4. '''FD8 транзитивно:''' {saleId, poId, productId} → supplierId → supplier_name, supplier_contact_person, supplier_phone, supplier_email, supplier_address
     435    * '''НАРУШУВА 3NF'''
     436
     437 5. '''FD9 транзитивно:''' {saleId, poId, productId} → warehouseId → warehouse_name, warehouse_location, warehouse_capacity
     438    * '''НАРУШУВА 3NF'''
     439
     440 6. '''FD14:''' {warehouseId, productId} → stock_quantity_on_hand, stock_last_updated
     441    * warehouseId се добива транзитивно, а {warehouseId, productId} не е клуч на U5
     442    * '''НАРУШУВА 3NF'''
     443
     444=== Чекор 6.1: Декомпозиција по FD1 (userId → user_*) ===
     445
     446{{{
     447R6(userId, user_username, user_password, user_full_name, user_email, user_role, user_is_active)
     448   PK: userId
     449
     450U6 = U5 - {user_username, user_password, user_full_name, user_email, user_role, user_is_active}
     451
     452U6(saleId, poId, productId, userId, customerId, customer_name, customer_email,
     453   customer_phone, customer_address, categoryId, category_name, category_description,
     454   supplierId, supplier_name, supplier_contact_person, supplier_phone, supplier_email,
     455   supplier_address, warehouseId, warehouse_name, warehouse_location, warehouse_capacity,
     456   stock_quantity_on_hand, stock_last_updated)
     457}}}
     458
     459'''Проверка за lossless join:'''
     460 * R6 ∩ U6 = {userId}
     461 * userId → R6 (FD1)
     462 * Lossless ✓
     463
     464=== Чекор 6.2: Декомпозиција по FD4 (customerId → customer_*) ===
     465
     466{{{
     467R7(customerId, customer_name, customer_email, customer_phone, customer_address)
     468   PK: customerId
     469
     470U7 = U6 - {customer_name, customer_email, customer_phone, customer_address}
     471
     472U7(saleId, poId, productId, userId, customerId, categoryId, category_name,
     473   category_description, supplierId, supplier_name, supplier_contact_person,
     474   supplier_phone, supplier_email, supplier_address, warehouseId, warehouse_name,
     475   warehouse_location, warehouse_capacity, stock_quantity_on_hand, stock_last_updated)
     476}}}
     477
     478'''Проверка за lossless join:'''
     479 * R7 ∩ U7 = {customerId}
     480 * customerId → R7 (FD4)
     481 * Lossless ✓
     482
     483=== Чекор 6.3: Декомпозиција по FD7 (categoryId → category_*) ===
     484
     485{{{
     486R8(categoryId, category_name, category_description)
     487   PK: categoryId
     488
     489U8 = U7 - {category_name, category_description}
     490
     491U8(saleId, poId, productId, userId, customerId, categoryId, supplierId,
     492   supplier_name, supplier_contact_person, supplier_phone, supplier_email,
     493   supplier_address, warehouseId, warehouse_name, warehouse_location,
     494   warehouse_capacity, stock_quantity_on_hand, stock_last_updated)
     495}}}
     496
     497'''Проверка за lossless join:'''
     498 * R8 ∩ U8 = {categoryId}
     499 * categoryId → R8 (FD7)
     500 * Lossless ✓
     501
     502=== Чекор 6.4: Декомпозиција по FD8 (supplierId → supplier_*) ===
     503
     504{{{
     505R9(supplierId, supplier_name, supplier_contact_person, supplier_phone, supplier_email, supplier_address)
     506   PK: supplierId
     507
     508U9 = U8 - {supplier_name, supplier_contact_person, supplier_phone, supplier_email, supplier_address}
     509
     510U9(saleId, poId, productId, userId, customerId, categoryId, supplierId,
     511   warehouseId, warehouse_name, warehouse_location, warehouse_capacity,
     512   stock_quantity_on_hand, stock_last_updated)
     513}}}
     514
     515'''Проверка за lossless join:'''
     516 * R9 ∩ U9 = {supplierId}
     517 * supplierId → R9 (FD8)
     518 * Lossless ✓
     519
     520=== Чекор 6.5: Декомпозиција по FD9 (warehouseId → warehouse_*) ===
     521
     522{{{
     523R10(warehouseId, warehouse_name, warehouse_location, warehouse_capacity)
     524   PK: warehouseId
     525
     526U10 = U9 - {warehouse_name, warehouse_location, warehouse_capacity}
     527
     528U10(saleId, poId, productId, userId, customerId, categoryId, supplierId, warehouseId, stock_quantity_on_hand, stock_last_updated)
     529}}}
     530
     531'''Проверка за lossless join:'''
     532 * R10 ∩ U10 = {warehouseId}
     533 * warehouseId → R10 (FD9)
     534 * Lossless ✓
     535
     536=== Чекор 6.6: Декомпозиција по FD14 ({warehouseId, productId} → stock_*) ===
     537
     538{{{
     539R11(warehouseId, productId, stock_quantity_on_hand, stock_last_updated)
     540   PK: {warehouseId, productId}
     541
     542U11 = U10 - {stock_quantity_on_hand, stock_last_updated}
     543
     544U11(saleId, poId, productId, userId, customerId, categoryId, supplierId, warehouseId)
     545}}}
     546
     547'''Проверка за lossless join:'''
     548 * R11 ∩ U11 = {warehouseId, productId}
     549 * {warehouseId, productId} → R11 (FD14)
     550 * Lossless ✓
     551
     552=== Анализа на U11 ===
     553
     554{{{
     555U11(saleId, poId, productId, userId, customerId, categoryId, supplierId, warehouseId)
     556   PK: {saleId, poId, productId}
     557}}}
     558
     559Атрибутите userId, customerId, warehouseId се веќе дел од R2 (Sale).
     560Атрибутите categoryId, supplierId се веќе дел од R1 (Product).
     561
     562U11 претставува врска меѓу продажби (saleId), нарачки (poId) и продукти (productId).
     563
     564'''Прашање:''' Дали оваа релација има семантичка смисла?
     565
     566Во реалноста:
     567 * Една продажба (saleId) е независна од една нарачка (poId)
     568 * Нивната врска е само преку productId и warehouseId
     569
     570'''U11 може да се декомпонира понатаму''', бидејќи атрибутите userId, customerId, warehouseId зависат само од saleId (FD10), а supplierId зависи само од poId или productId.
     571
     572Но бидејќи овие атрибути се веќе издвоени во R2 и R1, U11 станува редундантна и може да се отстрани бидејќи информацијата е веќе зачувана во другите релации.
     573
     574=== Состојба после 3NF декомпозиција ===
     575
     576||= Релација =||= Атрибути =||= Примарен клуч =||
     577|| '''R1''' (Product) || productId, product_name, product_description, product_sku, product_unit_price, product_reorder_level, categoryId, supplierId || productId ||
     578|| '''R2''' (Sale) || saleId, sale_date_time, sale_total_amount, userId, customerId, warehouseId || saleId ||
     579|| '''R3''' (!PurchaseOrder) || poId, po_order_date, po_expected_delivery_date, po_status, supplierId, warehouseId || poId ||
     580|| '''R4''' (!SaleItem) || saleId, productId, saleitem_quantity, saleitem_unit_price_at_sale || {saleId, productId} ||
     581|| '''R5''' (!PurchaseOrderItem) || poId, productId, poitem_quantity, poitem_unit_cost || {poId, productId} ||
     582|| '''R6''' (User) || userId, user_username, user_password, user_full_name, user_email, user_role, user_is_active || userId ||
     583|| '''R7''' (Customer) || customerId, customer_name, customer_email, customer_phone, customer_address || customerId ||
     584|| '''R8''' (Category) || categoryId, category_name, category_description || categoryId ||
     585|| '''R9''' (Supplier) || supplierId, supplier_name, supplier_contact_person, supplier_phone, supplier_email, supplier_address || supplierId ||
     586|| '''R10''' (Warehouse) || warehouseId, warehouse_name, warehouse_location, warehouse_capacity || warehouseId ||
     587|| '''R11''' (!WarehouseStock) || warehouseId, productId, stock_quantity_on_hand, stock_last_updated || {warehouseId, productId} ||
     588
     589----
     590
     591== 7. BCNF Декомпозиција ==
     592
     593=== Проверка за BCNF ===
     594
     595За секоја релација проверуваме: за секоја нетривијална FD X → Y, дали X е суперклуч?
     596
     597==== R1 (Product) ====
     598
     599||= FD =||= Детерминант =||= Суперклуч? =||
     600|| productId → сите || productId || ✓ ДА ||
     601|| product_sku → сите (FD6) || product_sku || ✓ ДА (кандидат клуч) ||
     602
     603'''R1 е во BCNF''' ✓
     604
     605==== R2 (Sale) ====
     606
     607||= FD =||= Детерминант =||= Суперклуч? =||
     608|| saleId → сите || saleId || ✓ ДА ||
     609
     610'''R2 е во BCNF''' ✓
     611
     612==== R3 (!PurchaseOrder) ====
     613
     614||= FD =||= Детерминант =||= Суперклуч? =||
     615|| poId → сите || poId || ✓ ДА ||
     616
     617'''R3 е во BCNF''' ✓
     618
     619==== R4 (!SaleItem) ====
     620
     621||= FD =||= Детерминант =||= Суперклуч? =||
     622|| {saleId, productId} → сите || {saleId, productId} || ✓ ДА ||
     623
     624'''R4 е во BCNF''' ✓
     625
     626==== R5 (!PurchaseOrderItem) ====
     627
     628||= FD =||= Детерминант =||= Суперклуч? =||
     629|| {poId, productId} → сите || {poId, productId} || ✓ ДА ||
     630
     631'''R5 е во BCNF''' ✓
     632
     633==== R6 (User) ====
     634
     635||= FD =||= Детерминант =||= Суперклуч? =||
     636|| userId → сите || userId || ✓ ДА ||
     637|| user_username → сите (FD2) || user_username || ✓ ДА (кандидат клуч) ||
     638|| user_email → сите (FD3) || user_email || ✓ ДА (кандидат клуч) ||
     639
     640'''R6 е во BCNF''' ✓
     641
     642==== R7 (Customer) ====
     643
     644||= FD =||= Детерминант =||= Суперклуч? =||
     645|| customerId → сите || customerId || ✓ ДА ||
     646
     647'''R7 е во BCNF''' ✓
     648
     649==== R8 (Category) ====
     650
     651||= FD =||= Детерминант =||= Суперклуч? =||
     652|| categoryId → сите || categoryId || ✓ ДА ||
     653
     654'''R8 е во BCNF''' ✓
     655
     656==== R9 (Supplier) ====
     657
     658||= FD =||= Детерминант =||= Суперклуч? =||
     659|| supplierId → сите || supplierId || ✓ ДА ||
     660
     661'''R9 е во BCNF''' ✓
     662
     663==== R10 (Warehouse) ====
     664
     665||= FD =||= Детерминант =||= Суперклуч? =||
     666|| warehouseId → сите || warehouseId || ✓ ДА ||
     667
     668'''R10 е во BCNF''' ✓
     669
     670==== R11 (!WarehouseStock) ====
     671
     672||= FD =||= Детерминант =||= Суперклуч? =||
     673|| {warehouseId, productId} → сите || {warehouseId, productId} || ✓ ДА ||
     674
     675'''R11 е во BCNF''' ✓
     676
     677'''Заклучок: Сите релации се во BCNF.'''
     678
     679----
     680
     681== 8. Финален резултат и дискусија ==
     682
     683=== 8.1 Финални релации ===
     684
     685{{{
     686User(userId, username, password, full_name, email, role, is_active)
     687  - Примарен клуч: userId
     688  - Кандидат клучеви: userId, username, email
     689  - Надворешни клучеви: -
     690
     691Customer(customerId, name, email, phone, address)
     692  - Примарен клуч: customerId
     693  - Кандидат клучеви: customerId
     694  - Надворешни клучеви: -
     695
     696Category(categoryId, name, description)
     697  - Примарен клуч: categoryId
     698  - Кандидат клучеви: categoryId
     699  - Надворешни клучеви: -
     700
     701Supplier(supplierId, name, contact_person, phone, email, address)
     702  - Примарен клуч: supplierId
     703  - Кандидат клучеви: supplierId
     704  - Надворешни клучеви: -
     705
     706Warehouse(warehouseId, name, location, capacity)
     707  - Примарен клуч: warehouseId
     708  - Кандидат клучеви: warehouseId
     709  - Надворешни клучеви: -
     710
     711Product(productId, name, description, sku, unit_price, reorder_level, categoryId, supplierId)
     712  - Примарен клуч: productId
     713  - Кандидат клучеви: productId, sku
     714  - Надворешни клучеви: categoryId → Category, supplierId → Supplier
     715
     716Sale(saleId, date_time, total_amount, userId, customerId, warehouseId)
     717  - Примарен клуч: saleId
     718  - Кандидат клучеви: saleId
     719  - Надворешни клучеви: userId → User, customerId → Customer, warehouseId → Warehouse
     720
     721PurchaseOrder(poId, order_date, expected_delivery_date, status, supplierId, warehouseId)
     722  - Примарен клуч: poId
     723  - Кандидат клучеви: poId
     724  - Надворешни клучеви: supplierId → Supplier, warehouseId → Warehouse
     725
     726SaleItem(saleId, productId, quantity, unit_price_at_sale)
     727  - Примарен клуч: {saleId, productId}
     728  - Кандидат клучеви: {saleId, productId}
     729  - Надворешни клучеви: saleId → Sale, productId → Product
     730
     731PurchaseOrderItem(poId, productId, quantity, unit_cost)
     732  - Примарен клуч: {poId, productId}
     733  - Кандидат клучеви: {poId, productId}
     734  - Надворешни клучеви: poId → PurchaseOrder, productId → Product
     735
     736WarehouseStock(warehouseId, productId, quantity_on_hand, last_updated)
     737  - Примарен клуч: {warehouseId, productId}
     738  - Кандидат клучеви: {warehouseId, productId}
     739  - Надворешни клучеви: warehouseId → Warehouse, productId → Product
     740}}}
     741
     742=== 8.2 Дискусија на разликите од моделот во Фаза P2 ===
     743
     744Преку процесот на нормализација, тргнувајќи од една универзална релација, дојдовме до '''истите 11 релации''' како во концептуалниот модел од Фаза P2. Ова покажува дека:
     745
     746 1. '''Концептуалниот модел беше правилно дизајниран''' - ентитетите и нивните атрибути беа логички групирани.
     747
     748 2. '''Процесот на нормализација го потврди дизајнот''' - секоја декомпозиција базирана на функционалните зависности резултираше со релација која одговара на ентитет од оригиналниот модел.
     749
     750 3. '''Клучот на универзалната релација {saleId, poId, productId}''' беше идентификуван преку анализа на функционалните зависности и класификација лева/десна страна.
     751
     752 4. '''Сите релации се во BCNF''' - нема функционална зависност каде детерминантот не е суперклуч.
     753
     754 5. '''Декомпозицијата е lossless''' - оригиналната информација може да се реконструира.
     755
     756 6. '''Сите функционални зависности се зачувани''' - секоја FD е зачувана во барем една од финалните релации.