Changes between Version 2 and Version 3 of Normalization


Ignore:
Timestamp:
09/25/26 15:57:36 (5 days ago)
Author:
201178
Comment:

--

Legend:

Unmodified
Added
Removed
Modified
  • Normalization

    v2 v3  
    6262|| currency || ден ||
    6363
     64=== Функционални зависности (почетно множество) ===
     65
    6466Во денормализираната табела постојат следниве основни функционални зависности:
    6567
    66  * {user_id} → {username, password_hash, role_id, active}
    67  * {role_id} → {role_name, description}
    68  * {session_token} → {user_id, created_at, expires_at}
    69  * {shift_id} → {user_id, start_time, end_time}
    70  * {shift_close_id} → {shift_id, closed_by, total, order_count, closed_at}
    71  * {table_id} → {table_number, capacity, status}
    72  * {order_id} → {user_id, table_id, created_at, status}
    73  * {order_id, item_number} → {product_id, quantity, unit_price, status}
    74  * {product_id} → {category_id, name, description, price, active, min_stock}
    75  * {category_id} → {name, description, active}
    76  * {payment_id} → {order_id, amount, method, payment_date}
    77  * {invoice_id} → {payment_id, invoice_number, issued_at}
    78  * {ingredient_id} → {name, unit, min_stock, active}
    79  * {recipe_id} → {product_id}
    80  * {recipe_id, ingredient_id} → {quantity_needed}
    81  * {inventory_id} → {product_id, quantity_change, operation_type, created_at}
    82  * {ingredient_inventory_id} → {ingredient_id, quantity_change, operation_type, created_at}
    83  * {log_id} → {user_id, action, entity_name, created_at}
    84  * {setting_id} → {restaurant_name, vat_rate, currency}
    85 
    86 == Прва нормална форма (1NF) ==
    87 
    88 Форма во која повторно има една табела, но овојпат таа е ограничена со следниве правила:
    89 
    90  * Подредувањето на редовите не претставува никакво значење
    91  * Во ќелиите на секоја колона има по една вредност
    92  * Не се мешаат типови на податоци во една ќелија
    93  * Табелата има примарен композитен клуч
    94  * Нема повторувачки групи
    95 
    96 Примарен композитен клуч: (user_id, order_id, item_number, payment_id, invoice_id, ingredient_id, recipe_id, inventory_id, ingredient_inventory_id, log_id, shift_id, shift_close_id, session_token, table_id, category_id, role_id, setting_id)
    97 
    98 Овој клуч уникатно го идентификува секој ред бидејќи комбинира ги сите клучеви на ентитетите.
    99 
    100 === Избор на примарен клуч во 1NF ===
    101 
    102 Поради големиот број на атрибути и сложеноста, композитниот клуч е многу голем. Ова е нормално за денормализирана форма и ќе се намали со декомпозиција.
    103 
    104 == Втора нормална форма (2NF) ==
    105 
    106 Форма која ги следи овие правила:
    107 
    108  * Веќе е во прва нормална форма
    109  * Секој атрибут кој не е клуч, зависи од целосниот примарен клуч (нема парцијални зависности)
    110 
    111 === Проблем во 1NF: парцијални зависности ===
    112 
    113 Во 1NF постојат атрибути кои зависат само од дел од примарниот клуч:
    114 
    115  * {user_id} → {username, password_hash, role_id, active}
    116  * {role_id} → {role_name, description}
    117  * {session_token} → {user_id, created_at, expires_at}
    118  * {shift_id} → {user_id, start_time, end_time}
    119  * {table_id} → {table_number, capacity, status}
    120  * {order_id} → {user_id, table_id, created_at, status}
    121  * {product_id} → {category_id, name, description, price, active, min_stock}
    122  * {category_id} → {name, description, active}
    123  * {payment_id} → {order_id, amount, method, payment_date}
    124  * {invoice_id} → {payment_id, invoice_number, issued_at}
    125  * {ingredient_id} → {name, unit, min_stock, active}
    126  * {recipe_id} → {product_id}
    127  * {inventory_id} → {product_id, quantity_change, operation_type, created_at}
    128  * {ingredient_inventory_id} → {ingredient_id, quantity_change, operation_type, created_at}
    129  * {log_id} → {user_id, action, entity_name, created_at}
    130  * {setting_id} → {restaurant_name, vat_rate, currency}
    131  * {shift_close_id} → {shift_id, closed_by, total, order_count, closed_at}
    132 
    133 Ова ја крши 2NF. Решение: декомпозиција - секоја група на атрибути која зависи од дел од композитниот клуч се издвојува во посебна табела каде тој дел станува примарен клуч.
     68{{{
     69F = {
     70  f1:  {user_id} → {username, password_hash, role_id, first_name, last_name, email, active}
     71  f2:  {role_id} → {role_name, description}
     72  f3:  {session_token} → {user_id, session_created_at, expires_at}
     73  f4:  {shift_id} → {user_id, shift_start_time, shift_end_time}
     74  f5:  {shift_close_id} → {shift_id, closed_by, total, order_count, closed_at}
     75  f6:  {table_id} → {table_number, capacity, table_status}
     76  f7:  {order_id} → {user_id, table_id, order_created_at, order_status}
     77  f8:  {order_id, item_number} → {product_id, quantity, unit_price, item_status}
     78  f9:  {product_id} → {category_id, product_name, description, price, active, product_min_stock}
     79  f10: {category_id} → {category_name, description, active}
     80  f11: {payment_id} → {order_id, amount, method, payment_date}
     81  f12: {invoice_id} → {payment_id, invoice_number, issued_at}
     82  f13: {ingredient_id} → {ingredient_name, unit, ingredient_min_stock, active}
     83  f14: {recipe_id} → {product_id}
     84  f15: {recipe_id, ingredient_id} → {quantity_needed}
     85  f16: {inventory_id} → {product_id, quantity_change, operation_type, inventory_created_at}
     86  f17: {ingredient_inventory_id} → {ingredient_id, quantity_change, operation_type, ingredient_inventory_created_at}
     87  f18: {log_id} → {user_id, action, entity_name, log_created_at}
     88  f19: {setting_id} → {restaurant_name, vat_rate, currency}
     89}
     90}}}
     91
     92''Забелешка:'' Поради преклопување на имиња на атрибути (на пр. `name` во PRODUCT, CATEGORY, INGREDIENT; `status` во RESTAURANT_TABLE и ORDER_ITEM; `min_stock` во PRODUCT и INGREDIENT; `created_at` во SESSION, ORDERS, INVENTORY, INGREDIENT_INVENTORY, AUDIT_LOG), во глобалната релација истите се преименувани со префикс за да нема дупликати.
     93
     94=== Canonical cover (минимално покритие) ===
     95
     96За формална анализа се користи '''canonical cover''' `F_c` — минимално множество ФЗ еквивалентно на `F`, без излишни атрибути и без излишни зависности.
     97
     98'''Чекор 1 — Дескомпозиција на десните страни (RHS) на единечни атрибути:'''
     99
     100{{{
     101f1:  user_id → username, user_id → password_hash, user_id → role_id,
     102     user_id → first_name, user_id → last_name, user_id → email, user_id → active
     103f2:  role_id → role_name, role_id → description
     104f3:  session_token → user_id, session_token → session_created_at, session_token → expires_at
     105f4:  shift_id → user_id, shift_id → shift_start_time, shift_id → shift_end_time
     106f5:  shift_close_id → shift_id, shift_close_id → closed_by, shift_close_id → total,
     107     shift_close_id → order_count, shift_close_id → closed_at
     108f6:  table_id → table_number, table_id → capacity, table_id → table_status
     109f7:  order_id → user_id, order_id → table_id, order_id → order_created_at, order_id → order_status
     110f8:  (order_id, item_number) → product_id, (order_id, item_number) → quantity,
     111     (order_id, item_number) → unit_price, (order_id, item_number) → item_status
     112f9:  product_id → category_id, product_id → product_name, product_id → description,
     113     product_id → price, product_id → active, product_id → product_min_stock
     114f10: category_id → category_name, category_id → description, category_id → active
     115f11: payment_id → order_id, payment_id → amount, payment_id → method, payment_id → payment_date
     116f12: invoice_id → payment_id, invoice_id → invoice_number, invoice_id → issued_at
     117f13: ingredient_id → ingredient_name, ingredient_id → unit, ingredient_id → ingredient_min_stock,
     118     ingredient_id → active
     119f14: recipe_id → product_id
     120f15: (recipe_id, ingredient_id) → quantity_needed
     121f16: inventory_id → product_id, inventory_id → quantity_change, inventory_id → operation_type,
     122     inventory_id → inventory_created_at
     123f17: ingredient_inventory_id → ingredient_id, ingredient_inventory_id → quantity_change,
     124     ingredient_inventory_id → operation_type, ingredient_inventory_id → ingredient_inventory_created_at
     125f18: log_id → user_id, log_id → action, log_id → entity_name, log_id → log_created_at
     126f19: setting_id → restaurant_name, setting_id → vat_rate, setting_id → currency
     127}}}
     128
     129'''Чекор 2 — Отстранување на излишни атрибути од левите страни (LHS):'''
     130
     131Сите LHS се единечни атрибути освен f8 и f15:
     132 * `(order_id, item_number)`: ниту `order_id` сам, ниту `item_number` сам не определува `product_id`, `quantity`, `unit_price`, `item_status` → '''ниту еден атрибут не е излишен'''.
     133 * `(recipe_id, ingredient_id)`: ниту `recipe_id` сам, ниту `ingredient_id` сам не определува `quantity_needed` → '''ниту еден атрибут не е излишен'''.
     134
     135'''Чекор 3 — Отстранување на излишни ФЗ:'''
     136
     137Ниту една ФЗ не е излишна (не може да се изведе од останатите):
     138 * `product_id → category_id` не може да се изведе од `category_id → ...` (обратна насока).
     139 * `recipe_id → product_id` не може да се изведе од `product_id → ...`.
     140 * `invoice_id → payment_id` не може да се изведе од `payment_id → ...`.
     141
     142'''Заклучок:''' `F_c = F` (почетното множество е веќе минимално покритие по единечни атрибути).
     143
     144== Кандидат-клучеви и избор на примарен клуч ==
     145
     146=== Формална дефиниција ===
     147
     148Нека `R` е денормализираната релација со множество атрибути `U` и множество ФЗ `F`. '''Кандидат-клуч (К.К.)''' е подмножество `K ⊆ U` такво што:
     149
     150 # '''Уникатност:''' `K → U` (K е супер-клуч).
     151 # '''Минималност:''' не постои вистинско подмножество `K' ⊂ K` со `K' → U`.
     152
     153'''Примарен клуч (PK)''' е еден избран кандидат-клуч.
     154
     155=== Пресметка на затворање на атрибути (attribute closure) ===
     156
     157За секој атрибут/подмножество се пресметува `X⁺` (затворање) со алгоритмот:
     158
     159{{{
     160result := X
     161repeat
     162  for each FD Y → Z in F:
     163    if Y ⊆ result then result := result ∪ Z
     164until no change
     165return result
     166}}}
     167
     168Кандидат-клуч е `X` ако `X⁺ = U` и не постои вистинско подмножество со истото својство.
     169
     170=== Определување на кандидат-клучеви ===
     171
     172'''Атрибути кои НЕ се појавуваат на RHS на ниту една ФЗ''' (мора да бидат во секој кандидат-клуч):
     173
     174{{{
     175session_token, shift_close_id, inventory_id, ingredient_inventory_id,
     176log_id, setting_id, item_number, recipe_id, invoice_id
     177}}}
     178
     179Овие 9 атрибути мора да бидат дел од секој кандидат-клуч. Да го пресметаме нивното затворање:
     180
     181{{{
     182X0 = {session_token, shift_close_id, inventory_id, ingredient_inventory_id,
     183      log_id, setting_id, item_number, recipe_id, invoice_id}
     184X0⁺ = X0
     185  ∪ {user_id, session_created_at, expires_at}                       (од session_token)
     186  ∪ {shift_id, closed_by, total, order_count, closed_at}            (од shift_close_id)
     187  ∪ {product_id, quantity_change, operation_type, inventory_created_at}  (од inventory_id)
     188  ∪ {ingredient_id, ..., ingredient_inventory_created_at}           (од ingredient_inventory_id)
     189  ∪ {user_id, action, entity_name, log_created_at}                  (од log_id)
     190  ∪ {restaurant_name, vat_rate, currency}                           (од setting_id)
     191  ∪ {quantity_needed}                                               (од (recipe_id, ingredient_id) → quantity_needed)
     192  ∪ {payment_id, invoice_number, issued_at}                         (од invoice_id → payment_id, ...)
     193}}}
     194
     195Ова не е целото `U` — недостигаат `order_id`, `table_id`, `category_id`, `role_id`, `username`, `password_hash`, `first_name`, `last_name`, `email`, `active`, `role_name`, `description`, `table_number`, `capacity`, `table_status`, `order_status`, `order_created_at`, `quantity`, `unit_price`, `item_status`, `product_name`, `price`, `product_min_stock`, `category_name`, `amount`, `method`, `payment_date`, `ingredient_name`, `unit`, `ingredient_min_stock`, `shift_start_time`, `shift_end_time`.
     196
     197Значи `X0` не е супер-клуч. Треба да се додадат атрибути.
     198
     199'''Додавање на `order_id` и `payment_id`:'''
     200
     201{{{
     202X1 = X0 ∪ {order_id, payment_id}
     203X1⁺ = X0⁺ ∪ {user_id, table_id, order_created_at, order_status}   (од order_id → ...)
     204         ∪ {amount, method, payment_date}                          (од payment_id → ...)
     205}}}
     206
     207Сега преку `order_id → user_id`:
     208 * `user_id → username, password_hash, role_id, first_name, last_name, email, active`
     209 * `role_id → role_name, description`
     210
     211Преку `order_id → table_id`:
     212 * `table_id → table_number, capacity, table_status`
     213
     214Преку `product_id` (веќе во X0 преку inventory_id и recipe_id):
     215 * `product_id → category_id, product_name, description, price, active, product_min_stock`
     216 * `category_id → category_name, description, active`
     217
     218'''Сега `X1⁺ = U`.''' Значи:
     219
     220{{{
     221K = {session_token, shift_close_id, inventory_id, ingredient_inventory_id,
     222     log_id, setting_id, order_id, item_number, payment_id, invoice_id, recipe_id}
     223}}}
     224
     225е супер-клуч.
     226
     227'''Проверка на минималност:''' Да провериме дали некој атрибут е излишен:
     228
     229 * Отстранување на `session_token` → `session_created_at`, `expires_at` не можат да се изведат → '''неопходен'''.
     230 * Отстранување на `shift_close_id` → `shift_id`, `closed_by`, `total`, `order_count`, `closed_at` не можат да се изведат → '''неопходен'''.
     231 * Отстранување на `inventory_id` → `quantity_change`, `operation_type`, `inventory_created_at` не можат да се изведат → '''неопходен'''.
     232 * Отстранување на `ingredient_inventory_id` → `ingredient_id` не може да се изведе → '''неопходен'''.
     233 * Отстранување на `log_id` → `action`, `entity_name`, `log_created_at` не можат да се изведат → '''неопходен'''.
     234 * Отстранување на `setting_id` → `restaurant_name`, `vat_rate`, `currency` не можат да се изведат → '''неопходен'''.
     235 * Отстранување на `order_id` → `table_id`, `order_created_at`, `order_status` не можат да се изведат → '''неопходен'''.
     236 * Отстранување на `item_number` → `quantity`, `unit_price`, `item_status` не можат да се изведат → '''неопходен'''.
     237 * Отстранување на `payment_id` → `amount`, `method`, `payment_date` не можат да се изведат → '''неопходен'''.
     238 * Отстранување на `invoice_id` → `invoice_number`, `issued_at` не можат да се изведат → '''неопходен'''.
     239 * Отстранување на `recipe_id` → `quantity_needed` не може да се изведе → '''неопходен'''.
     240
     241'''Заклучок:''' `K` е '''минимален супер-клуч''', значи е '''кандидат-клуч'''.
     242
     243=== Дали има друг кандидат-клуч? ===
     244
     245Секој кандидат-клуч мора да ги содржи сите атрибути што не се појавуваат на RHS:
     246
     247{{{
     248{session_token, shift_close_id, inventory_id, ingredient_inventory_id,
     249 log_id, setting_id, item_number, recipe_id, invoice_id}
     250}}}
     251
     252Понатаму:
     253 * За да се добијат `order_id`-зависните атрибути (`table_id`, `order_created_at`, `order_status`), мора да има `order_id` (или `payment_id` од кој се изведува `order_id`).
     254 * За `amount`, `method`, `payment_date` мора да има `payment_id` (бидејќи `order_id → payment_id` не постои).
     255 * За `invoice_number`, `issued_at` мора да има `invoice_id`.
     256
     257Значи секој кандидат-клуч мора да го содржи множеството:
     258
     259{{{
     260K_min = {session_token, shift_close_id, inventory_id, ingredient_inventory_id,
     261         log_id, setting_id, item_number, recipe_id, invoice_id, order_id, payment_id}
     262}}}
     263
     264Ова е токму `K`. Бидејќи `K` е минимален и секој кандидат-клуч мора да го содржи `K_min = K`, следи дека '''не постои друг кандидат-клуч'''.
     265
     266=== Избор на примарен клуч ===
     267
     268За примарен клуч се избира кандидат-клучот `K`:
     269
     270{{{
     271PK = (session_token, shift_close_id, inventory_id, ingredient_inventory_id,
     272      log_id, setting_id, order_id, item_number, payment_id, invoice_id, recipe_id)
     273}}}
     274
     275'''Забелешка:''' Овој PK е многу голем (11 атрибути) поради денормализираната природа. Со декомпозицијата тој ќе се намали.
     276
     277=== Во која NF е денормализираната релација? ===
     278
     279 * '''1NF:''' Да — сите ќелии содржат атомски вредности (по една вредност), нема повторувачки групи, нема мешање на типови.
     280 * '''2NF:''' Не — постојат '''парцијални зависности''': многу не-клучни атрибути зависат само од дел од композитниот клуч (на пр. `user_id → username, ...` каде `user_id` е дел од PK).
     281 * '''3NF:''' Не — поради парцијалните зависности (2NF не е задоволен).
     282 * '''BCNF:''' Не.
     283
     284'''Заклучок:''' Денормализираната релација е во '''1NF''', но не е во 2NF. Декомпозицијата започнува од 2NF.
     285
     286== 1NF декомпозиција ==
     287
     288Релацијата е веќе во 1NF (види погоре). Не е потребна дополнителна декомпозиција за 1NF. Се преминува на 2NF.
     289
     290== 2NF декомпозиција ==
     291
     292'''Проблем:''' Во 1NF постојат парцијални зависности — не-клучни атрибути зависат само од '''дел''' од композитниот PK. Секоја таква група се издвојува во посебна табела каде тој дел станува PK.
     293
     294'''Постапка:''' За секоја ФЗ `X → Y` каде `X ⊂ PK`, се создава нова релација `R_i(X ∪ Y)` со PK = `X`. Потоа се проверува '''lossless join''' и '''preservation of FDs'''.
     295
     296'''Проверка на lossless join (теорема):''' Ако `R1` и `R2` се декомпозиција на `R`, и `R1 ∩ R2 → R1` или `R1 ∩ R2 → R2`, тогаш декомпозицијата е lossless.
     297
     298'''Проверка на preservation of FDs:''' Се проверува дали `(F1 ∪ F2 ∪ ... ∪ Fn)⁺ = F⁺`, каде `Fi` се ФЗ што важат во новата релација `Ri`.
    134299
    135300=== R1: ROLE ===
    136301
     302{{{
    137303{role_id} → {role_name, description}
     304}}}
    138305
    139306|| role_id || role_name || description ||
    … …  
    142309|| 3 || MANAGER || Restaurant manager ||
    143310
    144 '''Lossless join test:'''
    145 
    146 R ∩ R1 = {role_id, role_name, description}
    147 
    148 Бидејќи {role_id} → {role_name, description}, а role_id ∈ (R ∩ R1), следи дека (R ∩ R1) → R1.
    149 
    150 ⇒ Декомпозицијата е lossless.
    151 
    152 R1.1 = R - {role_name, description}
     311 * '''ФЗ што важат:''' `{role_id} → {role_name, description}`.
     312 * '''К.К.:''' `{role_id}` (единствен; `role_name` не е уникатен во општ случај).
     313 * '''PK:''' `role_id`.
     314 * '''NF:''' BCNF (единствена ФЗ, LHS = PK = супер-клуч).
     315 * '''Lossless join test:''' `R ∩ R1 = {role_id, role_name, description}`. Бидејќи `{role_id} → {role_name, description}` и `role_id ∈ (R ∩ R1)`, следи `(R ∩ R1) → R1`. ⇒ '''lossless'''.
     316 * '''Preservation of FDs:''' `f2` е зачувана во R1. ⇒ '''preserved'''.
     317 * '''Остаток:''' `R1.1 = R − {role_name, description}`.
    153318
    154319=== R2: APP_USER ===
    155320
     321{{{
    156322{user_id} → {username, password_hash, role_id, first_name, last_name, email, active}
     323}}}
    157324
    158325|| user_id || username || password_hash || role_id || first_name || last_name || email || active ||
    … …  
    162329|| 7 || (null) || $2a$06$... || 2 || Georgi || Paunkov || (null) || t ||
    163330
    164 '''Lossless join test:'''
    165 
    166 R1.1 ∩ R2 = {user_id, username, password_hash, role_id, first_name, last_name, email, active}
    167 
    168 Бидејќи {user_id} → {username, password_hash, role_id, first_name, last_name, email, active}, а user_id ∈ (R1.1 ∩ R2), следи дека (R1.1 ∩ R2) → R2.
    169 
    170 ⇒ Декомпозицијата е lossless.
    171 
    172 R2.1 = R1.1 - {username, password_hash, role_id, first_name, last_name, email, active}
     331 * '''ФЗ што важат:''' `{user_id} → {username, password_hash, role_id, first_name, last_name, email, active}`.
     332 * '''К.К.:''' `{user_id}` (единствен; `username` и `email` имаат NULL вредности, не се уникатни).
     333 * '''PK:''' `user_id`.
     334 * '''NF:''' BCNF.
     335 * '''Lossless join test:''' `R1.1 ∩ R2 = {user_id, username, password_hash, role_id, first_name, last_name, email, active}`. Бидејќи `{user_id} → {...}` и `user_id ∈ (R1.1 ∩ R2)`, следи `(R1.1 ∩ R2) → R2`. ⇒ '''lossless'''.
     336 * '''Preservation of FDs:''' `f1` е зачувана. ⇒ '''preserved'''.
     337 * '''Остаток:''' `R2.1 = R1.1 − {username, password_hash, role_id, first_name, last_name, email, active}`.
    173338
    174339=== R3: SESSION ===
    175340
    176 {session_token} → {user_id, created_at, expires_at}
     341{{{
     342{session_token} → {user_id, session_created_at, expires_at}
     343}}}
    177344
    178345|| token || user_id || created_at || expires_at ||
    … …  
    180347(празна табела)
    181348
    182 '''Lossless join test:'''
    183 
    184 R2.1 ∩ R3 = {session_token, user_id, created_at, expires_at}
    185 
    186 Бидејќи {session_token} → {user_id, created_at, expires_at}, а session_token ∈ (R2.1 ∩ R3), следи дека (R2.1 ∩ R3) → R3.
    187 
    188 ⇒ Декомпозицијата е lossless.
    189 
    190 R3.1 = R2.1 - {user_id, created_at, expires_at}
     349 * '''ФЗ што важат:''' `{session_token} → {user_id, session_created_at, expires_at}`.
     350 * '''К.К.:''' `{session_token}`.
     351 * '''PK:''' `token`.
     352 * '''NF:''' BCNF.
     353 * '''Lossless join test:''' `R2.1 ∩ R3 = {session_token, user_id, session_created_at, expires_at}`. `{session_token} → {...}` и `session_token ∈ (R2.1 ∩ R3)` ⇒ `(R2.1 ∩ R3) → R3`. ⇒ '''lossless'''.
     354 * '''Preservation of FDs:''' `f3` зачувана. ⇒ '''preserved'''.
     355 * '''Остаток:''' `R3.1 = R2.1 − {user_id, session_created_at, expires_at}`.
    191356
    192357=== R4: SHIFT ===
    193358
    194 {shift_id} → {user_id, start_time, end_time}
     359{{{
     360{shift_id} → {user_id, shift_start_time, shift_end_time}
     361}}}
    195362
    196363|| shift_id || user_id || start_time || end_time ||
    197364|| 1 || 2 || 2026-01-10 08:00:00 || 2026-01-10 16:00:00 ||
    198365
    199 '''Lossless join test:'''
    200 
    201 R3.1 ∩ R4 = {shift_id, user_id, start_time, end_time}
    202 
    203 Бидејќи {shift_id} → {user_id, start_time, end_time}, а shift_id ∈ (R3.1 ∩ R4), следи дека (R3.1 ∩ R4) → R4.
    204 
    205 ⇒ Декомпозицијата е lossless.
    206 
    207 R4.1 = R3.1 - {user_id, start_time, end_time}
     366 * '''ФЗ што важат:''' `{shift_id} → {user_id, shift_start_time, shift_end_time}`.
     367 * '''К.К.:''' `{shift_id}`.
     368 * '''PK:''' `shift_id`.
     369 * '''NF:''' BCNF.
     370 * '''Lossless join test:''' `R3.1 ∩ R4 = {shift_id, user_id, shift_start_time, shift_end_time}`. `{shift_id} → {...}` ⇒ `(R3.1 ∩ R4) → R4`. ⇒ '''lossless'''.
     371 * '''Preservation of FDs:''' `f4` зачувана. ⇒ '''preserved'''.
     372 * '''Остаток:''' `R4.1 = R3.1 − {user_id, shift_start_time, shift_end_time}`.
    208373
    209374=== R5: RESTAURANT_TABLE ===
    210375
    211 {table_id} → {table_number, capacity, status}
     376{{{
     377{table_id} → {table_number, capacity, table_status}
     378}}}
    212379
    213380|| table_id || table_number || capacity || status ||
    … …  
    219386|| ... || ... || ... || ... ||
    220387
    221 '''Lossless join test:'''
    222 
    223 R4.1 ∩ R5 = {table_id, table_number, capacity, status}
    224 
    225 Бидејќи {table_id} → {table_number, capacity, status}, а table_id ∈ (R4.1 ∩ R5), следи дека (R4.1 ∩ R5) → R5.
    226 
    227 ⇒ Декомпозицијата е lossless.
    228 
    229 R5.1 = R4.1 - {table_number, capacity, status}
     388 * '''ФЗ што важат:''' `{table_id} → {table_number, capacity, table_status}`.
     389 * '''К.К.:''' `{table_id}`.
     390 * '''PK:''' `table_id`.
     391 * '''NF:''' BCNF.
     392 * '''Lossless join test:''' `R4.1 ∩ R5 = {table_id, table_number, capacity, table_status}`. `{table_id} → {...}` ⇒ `(R4.1 ∩ R5) → R5`. ⇒ '''lossless'''.
     393 * '''Preservation of FDs:''' `f6` зачувана. ⇒ '''preserved'''.
     394 * '''Остаток:''' `R5.1 = R4.1 − {table_number, capacity, table_status}`.
    230395
    231396=== R6: CATEGORY ===
    232397
    233 {category_id} → {name, description, active}
     398{{{
     399{category_id} → {category_name, description, active}
     400}}}
    234401
    235402|| category_id || name || description || active ||
    … …  
    239406|| ... || ... || ... || ... ||
    240407
    241 '''Lossless join test:'''
    242 
    243 R5.1 ∩ R6 = {category_id, name, description, active}
    244 
    245 Бидејќи {category_id} → {name, description, active}, а category_id ∈ (R5.1 ∩ R6), следи дека (R5.1 ∩ R6) → R6.
    246 
    247 ⇒ Декомпозицијата е lossless.
    248 
    249 R6.1 = R5.1 - {name, description, active}
     408 * '''ФЗ што важат:''' `{category_id} → {category_name, description, active}`.
     409 * '''К.К.:''' `{category_id}`.
     410 * '''PK:''' `category_id`.
     411 * '''NF:''' BCNF.
     412 * '''Lossless join test:''' `R5.1 ∩ R6 = {category_id, category_name, description, active}`. `{category_id} → {...}` ⇒ `(R5.1 ∩ R6) → R6`. ⇒ '''lossless'''.
     413 * '''Preservation of FDs:''' `f10` зачувана. ⇒ '''preserved'''.
     414 * '''Остаток:''' `R6.1 = R5.1 − {category_name, description, active}`.
    250415
    251416=== R7: PRODUCT ===
    252417
    253 {product_id} → {category_id, name, description, price, active, min_stock}
     418{{{
     419{product_id} → {category_id, product_name, description, price, active, product_min_stock}
     420}}}
    254421
    255422|| product_id || category_id || name || description || price || active || min_stock ||
    … …  
    258425|| ... || ... || ... || ... || ... || ... || ... ||
    259426
    260 '''Lossless join test:'''
    261 
    262 R6.1 ∩ R7 = {product_id, category_id, name, description, price, active, min_stock}
    263 
    264 Бидејќи {product_id} → {category_id, name, description, price, active, min_stock}, а product_id ∈ (R6.1 ∩ R7), следи дека (R6.1 ∩ R7) → R7.
    265 
    266 ⇒ Декомпозицијата е lossless.
    267 
    268 R7.1 = R6.1 - {category_id, name, description, price, active, min_stock}
     427 * '''ФЗ што важат:''' `{product_id} → {category_id, product_name, description, price, active, product_min_stock}`.
     428 * '''К.К.:''' `{product_id}`.
     429 * '''PK:''' `product_id`.
     430 * '''NF:''' BCNF.
     431 * '''Lossless join test:''' `R6.1 ∩ R7 = {product_id, category_id, product_name, description, price, active, product_min_stock}`. `{product_id} → {...}` ⇒ `(R6.1 ∩ R7) → R7`. ⇒ '''lossless'''.
     432 * '''Preservation of FDs:''' `f9` зачувана. ⇒ '''preserved'''.
     433 * '''Остаток:''' `R7.1 = R6.1 − {category_id, product_name, description, price, active, product_min_stock}`.
    269434
    270435=== R8: ORDERS ===
    271436
    272 {order_id} → {user_id, table_id, created_at, status}
     437{{{
     438{order_id} → {user_id, table_id, order_created_at, order_status}
     439}}}
    273440
    274441|| order_id || user_id || table_id || created_at || status ||
    … …  
    277444|| ... || ... || ... || ... || ... ||
    278445
    279 '''Lossless join test:'''
    280 
    281 R7.1 ∩ R8 = {order_id, user_id, table_id, created_at, status}
    282 
    283 Бидејќи {order_id} → {user_id, table_id, created_at, status}, а order_id ∈ (R7.1 ∩ R8), следи дека (R7.1 ∩ R8) → R8.
    284 
    285 ⇒ Декомпозицијата е lossless.
    286 
    287 R8.1 = R7.1 - {user_id, table_id, created_at, status}
     446 * '''ФЗ што важат:''' `{order_id} → {user_id, table_id, order_created_at, order_status}`.
     447 * '''К.К.:''' `{order_id}`.
     448 * '''PK:''' `order_id`.
     449 * '''NF:''' BCNF.
     450 * '''Lossless join test:''' `R7.1 ∩ R8 = {order_id, user_id, table_id, order_created_at, order_status}`. `{order_id} → {...}` ⇒ `(R7.1 ∩ R8) → R8`. ⇒ '''lossless'''.
     451 * '''Preservation of FDs:''' `f7` зачувана. ⇒ '''preserved'''.
     452 * '''Остаток:''' `R8.1 = R7.1 − {user_id, table_id, order_created_at, order_status}`.
    288453
    289454=== R9: PAYMENT ===
    290455
     456{{{
    291457{payment_id} → {order_id, amount, method, payment_date}
     458}}}
    292459
    293460|| payment_id || order_id || amount || method || payment_date ||
    … …  
    296463|| ... || ... || ... || ... || ... ||
    297464
    298 '''Lossless join test:'''
    299 
    300 R8.1 ∩ R9 = {payment_id, order_id, amount, method, payment_date}
    301 
    302 Бидејќи {payment_id} → {order_id, amount, method, payment_date}, а payment_id ∈ (R8.1 ∩ R9), следи дека (R8.1 ∩ R9) → R9.
    303 
    304 ⇒ Декомпозицијата е lossless.
    305 
    306 R9.1 = R8.1 - {order_id, amount, method, payment_date}
     465 * '''ФЗ што важат:''' `{payment_id} → {order_id, amount, method, payment_date}`.
     466 * '''К.К.:''' `{payment_id}`.
     467 * '''PK:''' `payment_id`.
     468 * '''NF:''' BCNF.
     469 * '''Lossless join test:''' `R8.1 ∩ R9 = {payment_id, order_id, amount, method, payment_date}`. `{payment_id} → {...}` ⇒ `(R8.1 ∩ R9) → R9`. ⇒ '''lossless'''.
     470 * '''Preservation of FDs:''' `f11` зачувана. ⇒ '''preserved'''.
     471 * '''Остаток:''' `R9.1 = R8.1 − {order_id, amount, method, payment_date}`.
    307472
    308473=== R10: INVOICE ===
    309474
     475{{{
    310476{invoice_id} → {payment_id, invoice_number, issued_at}
     477}}}
    311478
    312479|| invoice_id || payment_id || invoice_number || issued_at ||
    … …  
    315482|| ... || ... || ... || ... ||
    316483
    317 '''Lossless join test:'''
    318 
    319 R9.1 ∩ R10 = {invoice_id, payment_id, invoice_number, issued_at}
    320 
    321 Бидејќи {invoice_id} → {payment_id, invoice_number, issued_at}, а invoice_id ∈ (R9.1 ∩ R10), следи дека (R9.1 ∩ R10) → R10.
    322 
    323 ⇒ Декомпозицијата е lossless.
    324 
    325 R10.1 = R9.1 - {payment_id, invoice_number, issued_at}
     484 * '''ФЗ што важат:''' `{invoice_id} → {payment_id, invoice_number, issued_at}`.
     485 * '''К.К.:''' `{invoice_id}`.
     486 * '''PK:''' `invoice_id`.
     487 * '''NF:''' BCNF.
     488 * '''Lossless join test:''' `R9.1 ∩ R10 = {invoice_id, payment_id, invoice_number, issued_at}`. `{invoice_id} → {...}` ⇒ `(R9.1 ∩ R10) → R10`. ⇒ '''lossless'''.
     489 * '''Preservation of FDs:''' `f12` зачувана. ⇒ '''preserved'''.
     490 * '''Остаток:''' `R10.1 = R9.1 − {payment_id, invoice_number, issued_at}`.
    326491
    327492=== R11: INGREDIENT ===
    328493
    329 {ingredient_id} → {name, unit, min_stock, active}
     494{{{
     495{ingredient_id} → {ingredient_name, unit, ingredient_min_stock, active}
     496}}}
    330497
    331498(празна табела)
    332499
    333 '''Lossless join test:'''
    334 
    335 R10.1 ∩ R11 = {ingredient_id, name, unit, min_stock, active}
    336 
    337 Бидејќи {ingredient_id} → {name, unit, min_stock, active}, а ingredient_id ∈ (R10.1 ∩ R11), следи дека (R10.1 ∩ R11) → R11.
    338 
    339 ⇒ Декомпозицијата е lossless.
    340 
    341 R11.1 = R10.1 - {name, unit, min_stock, active}
     500 * '''ФЗ што важат:''' `{ingredient_id} → {ingredient_name, unit, ingredient_min_stock, active}`.
     501 * '''К.К.:''' `{ingredient_id}`.
     502 * '''PK:''' `ingredient_id`.
     503 * '''NF:''' BCNF.
     504 * '''Lossless join test:''' `R10.1 ∩ R11 = {ingredient_id, ingredient_name, unit, ingredient_min_stock, active}`. `{ingredient_id} → {...}` ⇒ `(R10.1 ∩ R11) → R11`. ⇒ '''lossless'''.
     505 * '''Preservation of FDs:''' `f13` зачувана. ⇒ '''preserved'''.
     506 * '''Остаток:''' `R11.1 = R10.1 − {ingredient_name, unit, ingredient_min_stock, active}`.
    342507
    343508=== R12: RECIPE ===
    344509
     510{{{
    345511{recipe_id} → {product_id}
     512}}}
    346513
    347514(празна табела)
    348515
    349 '''Lossless join test:'''
    350 
    351 R11.1 ∩ R12 = {recipe_id, product_id}
    352 
    353 Бидејќи {recipe_id} → {product_id}, а recipe_id ∈ (R11.1 ∩ R12), следи дека (R11.1 ∩ R12) → R12.
    354 
    355 ⇒ Декомпозицијата е lossless.
    356 
    357 R12.1 = R11.1 - {product_id}
    358 
    359 === R13: INVENTORY ===
    360 
    361 {inventory_id} → {product_id, quantity_change, operation_type, created_at}
     516 * '''ФЗ што важат:''' `{recipe_id} → {product_id}`.
     517 * '''К.К.:''' `{recipe_id}`.
     518 * '''PK:''' `recipe_id`.
     519 * '''NF:''' BCNF.
     520 * '''Lossless join test:''' `R11.1 ∩ R12 = {recipe_id, product_id}`. `{recipe_id} → {product_id}` ⇒ `(R11.1 ∩ R12) → R12`. ⇒ '''lossless'''.
     521 * '''Preservation of FDs:''' `f14` зачувана. ⇒ '''preserved'''.
     522 * '''Остаток:''' `R12.1 = R11.1 − {product_id}`.
     523
     524=== R13: INVENTORY ==={{{
     525{inventory_id} → {product_id, quantity_change, operation_type, inventory_created_at}
     526}}}
    362527
    363528|| inventory_id || product_id || quantity_change || operation_type || created_at ||
    … …  
    366531|| ... || ... || ... || ... || ... ||
    367532
    368 '''Lossless join test:'''
    369 
    370 R12.1 ∩ R13 = {inventory_id, product_id, quantity_change, operation_type, created_at}
    371 
    372 Бидејќи {inventory_id} → {product_id, quantity_change, operation_type, created_at}, а inventory_id ∈ (R12.1 ∩ R13), следи дека (R12.1 ∩ R13) → R13.
    373 
    374 ⇒ Декомпозицијата е lossless.
    375 
    376 R13.1 = R12.1 - {product_id, quantity_change, operation_type, created_at}
     533 * '''ФЗ што важат:''' `{inventory_id} → {product_id, quantity_change, operation_type, inventory_created_at}`.
     534 * '''К.К.:''' `{inventory_id}`.
     535 * '''PK:''' `inventory_id`.
     536 * '''NF:''' BCNF.
     537 * '''Lossless join test:''' `R12.1 ∩ R13 = {inventory_id, product_id, quantity_change, operation_type, inventory_created_at}`. `{inventory_id} → {...}` ⇒ `(R12.1 ∩ R13) → R13`. ⇒ '''lossless'''.
     538 * '''Preservation of FDs:''' `f16` зачувана. ⇒ '''preserved'''.
     539 * '''Остаток:''' `R13.1 = R12.1 − {product_id, quantity_change, operation_type, inventory_created_at}`.
    377540
    378541=== R14: INGREDIENT_INVENTORY ===
    379542
    380 {ingredient_inventory_id} → {ingredient_id, quantity_change, operation_type, created_at}
     543{{{
     544{ingredient_inventory_id} → {ingredient_id, quantity_change, operation_type, ingredient_inventory_created_at}
     545}}}
    381546
    382547(празна табела)
    383548
    384 '''Lossless join test:'''
    385 
    386 R13.1 ∩ R14 = {id, ingredient_id, quantity_change, operation_type, created_at}
    387 
    388 ⇒ Декомпозицијата е lossless.
    389 
    390 R14.1 = R13.1 - {ingredient_id, quantity_change, operation_type, created_at}
     549 * '''ФЗ што важат:''' `{ingredient_inventory_id} → {ingredient_id, quantity_change, operation_type, ingredient_inventory_created_at}`.
     550 * '''К.К.:''' `{ingredient_inventory_id}`.
     551 * '''PK:''' `ingredient_inventory_id` (во финалниот дизајн `id`).
     552 * '''NF:''' BCNF.
     553 * '''Lossless join test:''' `R13.1 ∩ R14 = {ingredient_inventory_id, ingredient_id, quantity_change, operation_type, ingredient_inventory_created_at}`. `{ingredient_inventory_id} → {...}` ⇒ `(R13.1 ∩ R14) → R14`. ⇒ '''lossless'''.
     554 * '''Preservation of FDs:''' `f17` зачувана. ⇒ '''preserved'''.
     555 * '''Остаток:''' `R14.1 = R13.1 − {ingredient_id, quantity_change, operation_type, ingredient_inventory_created_at}`.
    391556
    392557=== R15: AUDIT_LOG ===
    393558
    394 {log_id} → {user_id, action, entity_name, created_at}
     559{{{
     560{log_id} → {user_id, action, entity_name, log_created_at}
     561}}}
    395562
    396563|| log_id || user_id || action || entity_name || created_at ||
    397564|| 1 || 1 || Направен е продукт Еспресо || PRODUCT || 2026-07-07 14:45 ||
    398565
    399 '''Lossless join test:'''
    400 
    401 R14.1 ∩ R15 = {log_id, user_id, action, entity_name, created_at}
    402 
    403 ⇒ Декомпозицијата е lossless.
    404 
    405 R15.1 = R14.1 - {user_id, action, entity_name, created_at}
     566 * '''ФЗ што важат:''' `{log_id} → {user_id, action, entity_name, log_created_at}`.
     567 * '''К.К.:''' `{log_id}`.
     568 * '''PK:''' `log_id`.
     569 * '''NF:''' BCNF.
     570 * '''Lossless join test:''' `R14.1 ∩ R15 = {log_id, user_id, action, entity_name, log_created_at}`. `{log_id} → {...}` ⇒ `(R14.1 ∩ R15) → R15`. ⇒ '''lossless'''.
     571 * '''Preservation of FDs:''' `f18` зачувана. ⇒ '''preserved'''.
     572 * '''Остаток:''' `R15.1 = R14.1 − {user_id, action, entity_name, log_created_at}`.
    406573
    407574=== R16: APP_SETTINGS ===
    408575
    409 {id} → {restaurant_name, vat_rate, currency, printer_config}
     576{{{
     577{setting_id} → {restaurant_name, vat_rate, currency, printer_config}
     578}}}
    410579
    411580|| id || restaurant_name || vat_rate || currency || printer_config ||
    412581|| 1 || Емпорио || 18.00 || ден || 2 ||
    413582
    414 '''Lossless join test:'''
    415 
    416 R15.1 ∩ R16 = {id, restaurant_name, vat_rate, currency, printer_config}
    417 
    418 ⇒ Декомпозицијата е lossless.
    419 
    420 R16.1 = R15.1 - {restaurant_name, vat_rate, currency, printer_config}
     583 * '''ФЗ што важат:''' `{setting_id} → {restaurant_name, vat_rate, currency, printer_config}`.
     584 * '''К.К.:''' `{setting_id}`.
     585 * '''PK:''' `setting_id` (во финалниот дизајн `id`).
     586 * '''NF:''' BCNF.
     587 * '''Lossless join test:''' `R15.1 ∩ R16 = {setting_id, restaurant_name, vat_rate, currency, printer_config}`. `{setting_id} → {...}` ⇒ `(R15.1 ∩ R16) → R16`. ⇒ '''lossless'''.
     588 * '''Preservation of FDs:''' `f19` зачувана. ⇒ '''preserved'''.
     589 * '''Остаток:''' `R16.1 = R15.1 − {restaurant_name, vat_rate, currency, printer_config}`.
    421590
    422591=== R17: SETTINGS ===
    423592
    424 {id} → {restaurant_name, vat_percent, currency, printer_ip, printer_port}
     593{{{
     594{setting_id} → {restaurant_name, vat_percent, currency, printer_ip, printer_port}
     595}}}
    425596
    426597|| id || restaurant_name || vat_percent || currency || printer_ip || printer_port ||
    427598|| 1 || EMPORIO CAFFE || 18 || ден || (null) || 9100 ||
    428599
    429 '''Lossless join test:'''
    430 
    431 R16.1 ∩ R17 = {id, restaurant_name, vat_percent, currency, printer_ip, printer_port}
    432 
    433 ⇒ Декомпозицијата е lossless.
    434 
    435 R17.1 = R16.1 - {restaurant_name, vat_percent, currency, printer_ip, printer_port}
     600 * '''ФЗ што важат:''' `{setting_id} → {restaurant_name, vat_percent, currency, printer_ip, printer_port}`.
     601 * '''К.К.:''' `{setting_id}`.
     602 * '''PK:''' `setting_id` (во финалниот дизајн `id`).
     603 * '''NF:''' BCNF.
     604 * '''Lossless join test:''' `R16.1 ∩ R17 = {setting_id, restaurant_name, vat_percent, currency, printer_ip, printer_port}`. `{setting_id} → {...}` ⇒ `(R16.1 ∩ R17) → R17`. ⇒ '''lossless'''.
     605 * '''Preservation of FDs:''' зачувана. ⇒ '''preserved'''.
     606 * '''Остаток:''' `R17.1 = R16.1 − {restaurant_name, vat_percent, currency, printer_ip, printer_port}`.
    436607
    437608=== R18: ORDER_ITEM ===
    438609
    439 {order_id, item_number} → {product_id, quantity, unit_price, status}
     610{{{
     611{order_id, item_number} → {product_id, quantity, unit_price, item_status}
     612}}}
    440613
    441614|| order_id || item_number || product_id || quantity || unit_price || status ||
    … …  
    444617|| ... || ... || ... || ... || ... || ... ||
    445618
    446 '''Lossless join test:'''
    447 
    448 R17.1 ∩ R18 = {order_id, item_number, product_id, quantity, unit_price, status}
    449 
    450 ⇒ Декомпозицијата е lossless.
     619 * '''ФЗ што важат:''' `{order_id, item_number} → {product_id, quantity, unit_price, item_status}`.
     620 * '''К.К.:''' `{order_id, item_number}` (единствен; ниту `order_id` сам, ниту `item_number` сам не е уникатен).
     621 * '''PK:''' `(order_id, item_number)`.
     622 * '''NF:''' BCNF.
     623 * '''Lossless join test:''' `R17.1 ∩ R18 = {order_id, item_number, product_id, quantity, unit_price, item_status}`. `{order_id, item_number} → {...}` ⇒ `(R17.1 ∩ R18) → R18`. ⇒ '''lossless'''.
     624 * '''Preservation of FDs:''' `f8` зачувана. ⇒ '''preserved'''.
    451625
    452626=== R19: SHIFT_CLOSE ===
    453627
    454 {shift_id} → {closed_at, closed_by, total, order_count}
     628{{{
     629{shift_close_id} → {shift_id, closed_by, total, order_count, closed_at}
     630}}}
    455631
    456632|| shift_id || closed_at || closed_by || total || order_count ||
    … …  
    459635|| ... || ... || ... || ... || ... ||
    460636
    461 '''Lossless join test:'''
    462 
    463 R18.1 ∩ R19 = {shift_id, closed_at, closed_by, total, order_count}
    464 
    465 ⇒ Декомпозицијата е lossless.
     637 * '''ФЗ што важат:''' `{shift_close_id} → {shift_id, closed_by, total, order_count, closed_at}`.
     638 * '''К.К.:''' `{shift_close_id}` (единствен; `shift_id` е уникатен само ако секој shift има најмногу едно затворање — што е случај, но формално `shift_close_id` е деклариран како PK; `shift_id` може да се смета за алтернативен К.К. доколку е уникатен).
     639 * '''PK:''' `shift_close_id`.
     640 * '''NF:''' BCNF.
     641 * '''Lossless join test:''' `R18.1 ∩ R19 = {shift_close_id, shift_id, closed_by, total, order_count, closed_at}`. `{shift_close_id} → {...}` ⇒ `(R18.1 ∩ R19) → R19`. ⇒ '''lossless'''.
     642 * '''Preservation of FDs:''' `f5` зачувана. ⇒ '''preserved'''.
    466643
    467644=== R20: RECIPE_ITEM ===
    468645
     646{{{
    469647{recipe_id, ingredient_id} → {quantity_needed}
     648}}}
    470649
    471650(празна табела)
    472651
    473 '''Lossless join test:'''
    474 
    475 R19.1 ∩ R20 = {recipe_id, ingredient_id, quantity_needed}
    476 
    477 ⇒ Декомпозицијата е lossless.
    478 
    479 == Трета нормална форма (3NF) ==
     652 * '''ФЗ што важат:''' `{recipe_id, ingredient_id} → {quantity_needed}`.
     653 * '''К.К.:''' `{recipe_id, ingredient_id}` (композитен; ниту еден дел сам не е уникатен).
     654 * '''PK:''' `(recipe_id, ingredient_id)`.
     655 * '''NF:''' BCNF.
     656 * '''Lossless join test:''' `R19.1 ∩ R20 = {recipe_id, ingredient_id, quantity_needed}`. `{recipe_id, ingredient_id} → {quantity_needed}` ⇒ `(R19.1 ∩ R20) → R20`. ⇒ '''lossless'''.
     657 * '''Preservation of FDs:''' `f15` зачувана. ⇒ '''preserved'''.
     658
     659== 3NF декомпозиција ==
    480660
    481661Форма која ги следи овие правила:
    482662
    483  * Веќе е во втора нормална форма
    484  * Се отстрануваат транзитивните функционални зависности
     663 * Веќе е во втора нормална форма.
     664 * Се отстрануваат транзитивните функционални зависности.
    485665
    486666=== Проверка за транзитивни зависности ===
    … …  
    490670Единствена потенцијална транзитивна зависност:
    491671
    492  * {recipe_id} → {product_id} → {category_id} (но recipe не содржи category_id, па нема транзитивност)
     672 * `{recipe_id} → {product_id} → {category_id}` (но RECIPE не содржи `category_id`, па нема транзитивност)
    493673
    494674Сите табели се веќе во 3NF.
    495675
     676'''Preservation of FDs (глобална проверка):''' Сите ФЗ `f1`–`f19` се зачувани во поединечните табели. ⇒ `(F1 ∪ ... ∪ F20)⁺ = F⁺`.
     677
     678'''Lossless join (глобална проверка):''' Секоја декомпозиција во 2NF беше lossless. Со композиција на lossless декомпозиции, финалната декомпозиција е lossless.
     679
    496680== BCNF (Boyce-Codd Normal Form) ==
    497681
    498682Форма која ги следи овие правила:
    499683
    500  * Веќе е во трета нормална форма
    501  * За секоја функционална зависност X → Y, X мора да биде супер-клуч
     684 * Веќе е во трета нормална форма.
     685 * За секоја функционална зависност `X → Y`, `X` мора да биде супер-клуч.
    502686
    503687=== Проверка за BCNF ===
    504688
    505 Сите функционални зависности во сите табели имаат X кој е примарен клуч (или композитен клуч), што значи X е супер-клуч.
     689Сите функционални зависности во сите табели имаат `X` кој е примарен клуч (или композитен клуч), што значи `X` е супер-клуч.
    506690
    507691Сите табели се веќе во BCNF.
    … …  
    513697 * '''SESSION''' (token PK, user_id FK, created_at, expires_at)
    514698 * '''SHIFT''' (shift_id PK, user_id FK, start_time, end_time)
    515  * '''SHIFT_CLOSE''' (shift_id PK, closed_by FK, total, order_count, closed_at)
     699 * '''SHIFT_CLOSE''' (shift_close_id PK, shift_id FK, closed_by FK, total, order_count, closed_at)
    516700 * '''RESTAURANT_TABLE''' (table_id PK, table_number, capacity, status)
    517701 * '''CATEGORY''' (category_id PK, name, description, active)
    … …  
    536720=== Разлики помеѓу нормализираниот дизајн и дизајнот од фаза P2 ===
    537721
    538 Нормализираниот дизајн **е идентичен** со дизајнот од фаза P2. Ова значи дека дизајнот од P2 беше веќе правилно нормализиран.
     722Нормализираниот дизајн '''е идентичен''' со дизајнот од фаза P2. Ова значи дека дизајнот од P2 беше веќе правилно нормализиран.
    539723
    540724Сите табели се во BCNF, сите функционални зависности се запазени, и сите декомпозиции се lossless.
    541725
    542 За следните фази на проектот **ќе се користи истиот дизајн** од P2, бидејќи е веќе целосно нормализиран.
     726За следните фази на проектот '''ќе се користи истиот дизајн''' од P2, бидејќи е веќе целосно нормализиран.