Changes between Version 2 and Version 3 of Normalization
- Timestamp:
- 09/25/26 15:57:36 (5 days ago)
Legend:
- Unmodified
- Added
- Removed
- Modified
-
Normalization
v2 v3 62 62 || currency || ден || 63 63 64 === Функционални зависности (почетно множество) === 65 64 66 Во денормализираната табела постојат следниве основни функционални зависности: 65 67 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 {{{ 69 F = { 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 {{{ 101 f1: 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 103 f2: role_id → role_name, role_id → description 104 f3: session_token → user_id, session_token → session_created_at, session_token → expires_at 105 f4: shift_id → user_id, shift_id → shift_start_time, shift_id → shift_end_time 106 f5: 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 108 f6: table_id → table_number, table_id → capacity, table_id → table_status 109 f7: order_id → user_id, order_id → table_id, order_id → order_created_at, order_id → order_status 110 f8: (order_id, item_number) → product_id, (order_id, item_number) → quantity, 111 (order_id, item_number) → unit_price, (order_id, item_number) → item_status 112 f9: product_id → category_id, product_id → product_name, product_id → description, 113 product_id → price, product_id → active, product_id → product_min_stock 114 f10: category_id → category_name, category_id → description, category_id → active 115 f11: payment_id → order_id, payment_id → amount, payment_id → method, payment_id → payment_date 116 f12: invoice_id → payment_id, invoice_id → invoice_number, invoice_id → issued_at 117 f13: ingredient_id → ingredient_name, ingredient_id → unit, ingredient_id → ingredient_min_stock, 118 ingredient_id → active 119 f14: recipe_id → product_id 120 f15: (recipe_id, ingredient_id) → quantity_needed 121 f16: inventory_id → product_id, inventory_id → quantity_change, inventory_id → operation_type, 122 inventory_id → inventory_created_at 123 f17: ingredient_inventory_id → ingredient_id, ingredient_inventory_id → quantity_change, 124 ingredient_inventory_id → operation_type, ingredient_inventory_id → ingredient_inventory_created_at 125 f18: log_id → user_id, log_id → action, log_id → entity_name, log_id → log_created_at 126 f19: 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 {{{ 160 result := X 161 repeat 162 for each FD Y → Z in F: 163 if Y ⊆ result then result := result ∪ Z 164 until no change 165 return result 166 }}} 167 168 Кандидат-клуч е `X` ако `X⁺ = U` и не постои вистинско подмножество со истото својство. 169 170 === Определување на кандидат-клучеви === 171 172 '''Атрибути кои НЕ се појавуваат на RHS на ниту една ФЗ''' (мора да бидат во секој кандидат-клуч): 173 174 {{{ 175 session_token, shift_close_id, inventory_id, ingredient_inventory_id, 176 log_id, setting_id, item_number, recipe_id, invoice_id 177 }}} 178 179 Овие 9 атрибути мора да бидат дел од секој кандидат-клуч. Да го пресметаме нивното затворање: 180 181 {{{ 182 X0 = {session_token, shift_close_id, inventory_id, ingredient_inventory_id, 183 log_id, setting_id, item_number, recipe_id, invoice_id} 184 X0⁺ = 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 {{{ 202 X1 = X0 ∪ {order_id, payment_id} 203 X1⁺ = 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 {{{ 221 K = {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 {{{ 260 K_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 {{{ 271 PK = (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`. 134 299 135 300 === R1: ROLE === 136 301 302 {{{ 137 303 {role_id} → {role_name, description} 304 }}} 138 305 139 306 || role_id || role_name || description || … … 142 309 || 3 || MANAGER || Restaurant manager || 143 310 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}`. 153 318 154 319 === R2: APP_USER === 155 320 321 {{{ 156 322 {user_id} → {username, password_hash, role_id, first_name, last_name, email, active} 323 }}} 157 324 158 325 || user_id || username || password_hash || role_id || first_name || last_name || email || active || … … 162 329 || 7 || (null) || $2a$06$... || 2 || Georgi || Paunkov || (null) || t || 163 330 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}`. 173 338 174 339 === R3: SESSION === 175 340 176 {session_token} → {user_id, created_at, expires_at} 341 {{{ 342 {session_token} → {user_id, session_created_at, expires_at} 343 }}} 177 344 178 345 || token || user_id || created_at || expires_at || … … 180 347 (празна табела) 181 348 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}`. 191 356 192 357 === R4: SHIFT === 193 358 194 {shift_id} → {user_id, start_time, end_time} 359 {{{ 360 {shift_id} → {user_id, shift_start_time, shift_end_time} 361 }}} 195 362 196 363 || shift_id || user_id || start_time || end_time || 197 364 || 1 || 2 || 2026-01-10 08:00:00 || 2026-01-10 16:00:00 || 198 365 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}`. 208 373 209 374 === R5: RESTAURANT_TABLE === 210 375 211 {table_id} → {table_number, capacity, status} 376 {{{ 377 {table_id} → {table_number, capacity, table_status} 378 }}} 212 379 213 380 || table_id || table_number || capacity || status || … … 219 386 || ... || ... || ... || ... || 220 387 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}`. 230 395 231 396 === R6: CATEGORY === 232 397 233 {category_id} → {name, description, active} 398 {{{ 399 {category_id} → {category_name, description, active} 400 }}} 234 401 235 402 || category_id || name || description || active || … … 239 406 || ... || ... || ... || ... || 240 407 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}`. 250 415 251 416 === R7: PRODUCT === 252 417 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 }}} 254 421 255 422 || product_id || category_id || name || description || price || active || min_stock || … … 258 425 || ... || ... || ... || ... || ... || ... || ... || 259 426 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}`. 269 434 270 435 === R8: ORDERS === 271 436 272 {order_id} → {user_id, table_id, created_at, status} 437 {{{ 438 {order_id} → {user_id, table_id, order_created_at, order_status} 439 }}} 273 440 274 441 || order_id || user_id || table_id || created_at || status || … … 277 444 || ... || ... || ... || ... || ... || 278 445 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}`. 288 453 289 454 === R9: PAYMENT === 290 455 456 {{{ 291 457 {payment_id} → {order_id, amount, method, payment_date} 458 }}} 292 459 293 460 || payment_id || order_id || amount || method || payment_date || … … 296 463 || ... || ... || ... || ... || ... || 297 464 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}`. 307 472 308 473 === R10: INVOICE === 309 474 475 {{{ 310 476 {invoice_id} → {payment_id, invoice_number, issued_at} 477 }}} 311 478 312 479 || invoice_id || payment_id || invoice_number || issued_at || … … 315 482 || ... || ... || ... || ... || 316 483 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}`. 326 491 327 492 === R11: INGREDIENT === 328 493 329 {ingredient_id} → {name, unit, min_stock, active} 494 {{{ 495 {ingredient_id} → {ingredient_name, unit, ingredient_min_stock, active} 496 }}} 330 497 331 498 (празна табела) 332 499 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}`. 342 507 343 508 === R12: RECIPE === 344 509 510 {{{ 345 511 {recipe_id} → {product_id} 512 }}} 346 513 347 514 (празна табела) 348 515 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 }}} 362 527 363 528 || inventory_id || product_id || quantity_change || operation_type || created_at || … … 366 531 || ... || ... || ... || ... || ... || 367 532 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}`. 377 540 378 541 === R14: INGREDIENT_INVENTORY === 379 542 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 }}} 381 546 382 547 (празна табела) 383 548 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}`. 391 556 392 557 === R15: AUDIT_LOG === 393 558 394 {log_id} → {user_id, action, entity_name, created_at} 559 {{{ 560 {log_id} → {user_id, action, entity_name, log_created_at} 561 }}} 395 562 396 563 || log_id || user_id || action || entity_name || created_at || 397 564 || 1 || 1 || Направен е продукт Еспресо || PRODUCT || 2026-07-07 14:45 || 398 565 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}`. 406 573 407 574 === R16: APP_SETTINGS === 408 575 409 {id} → {restaurant_name, vat_rate, currency, printer_config} 576 {{{ 577 {setting_id} → {restaurant_name, vat_rate, currency, printer_config} 578 }}} 410 579 411 580 || id || restaurant_name || vat_rate || currency || printer_config || 412 581 || 1 || Емпорио || 18.00 || ден || 2 || 413 582 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}`. 421 590 422 591 === R17: SETTINGS === 423 592 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 }}} 425 596 426 597 || id || restaurant_name || vat_percent || currency || printer_ip || printer_port || 427 598 || 1 || EMPORIO CAFFE || 18 || ден || (null) || 9100 || 428 599 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}`. 436 607 437 608 === R18: ORDER_ITEM === 438 609 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 }}} 440 613 441 614 || order_id || item_number || product_id || quantity || unit_price || status || … … 444 617 || ... || ... || ... || ... || ... || ... || 445 618 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'''. 451 625 452 626 === R19: SHIFT_CLOSE === 453 627 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 }}} 455 631 456 632 || shift_id || closed_at || closed_by || total || order_count || … … 459 635 || ... || ... || ... || ... || ... || 460 636 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'''. 466 643 467 644 === R20: RECIPE_ITEM === 468 645 646 {{{ 469 647 {recipe_id, ingredient_id} → {quantity_needed} 648 }}} 470 649 471 650 (празна табела) 472 651 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 декомпозиција == 480 660 481 661 Форма која ги следи овие правила: 482 662 483 * Веќе е во втора нормална форма 484 * Се отстрануваат транзитивните функционални зависности 663 * Веќе е во втора нормална форма. 664 * Се отстрануваат транзитивните функционални зависности. 485 665 486 666 === Проверка за транзитивни зависности === … … 490 670 Единствена потенцијална транзитивна зависност: 491 671 492 * {recipe_id} → {product_id} → {category_id} (но recipe не содржи category_id, па нема транзитивност)672 * `{recipe_id} → {product_id} → {category_id}` (но RECIPE не содржи `category_id`, па нема транзитивност) 493 673 494 674 Сите табели се веќе во 3NF. 495 675 676 '''Preservation of FDs (глобална проверка):''' Сите ФЗ `f1`–`f19` се зачувани во поединечните табели. ⇒ `(F1 ∪ ... ∪ F20)⁺ = F⁺`. 677 678 '''Lossless join (глобална проверка):''' Секоја декомпозиција во 2NF беше lossless. Со композиција на lossless декомпозиции, финалната декомпозиција е lossless. 679 496 680 == BCNF (Boyce-Codd Normal Form) == 497 681 498 682 Форма која ги следи овие правила: 499 683 500 * Веќе е во трета нормална форма 501 * За секоја функционална зависност X → Y, X мора да биде супер-клуч684 * Веќе е во трета нормална форма. 685 * За секоја функционална зависност `X → Y`, `X` мора да биде супер-клуч. 502 686 503 687 === Проверка за BCNF === 504 688 505 Сите функционални зависности во сите табели имаат X кој е примарен клуч (или композитен клуч), што значи Xе супер-клуч.689 Сите функционални зависности во сите табели имаат `X` кој е примарен клуч (или композитен клуч), што значи `X` е супер-клуч. 506 690 507 691 Сите табели се веќе во BCNF. … … 513 697 * '''SESSION''' (token PK, user_id FK, created_at, expires_at) 514 698 * '''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) 516 700 * '''RESTAURANT_TABLE''' (table_id PK, table_number, capacity, status) 517 701 * '''CATEGORY''' (category_id PK, name, description, active) … … 536 720 === Разлики помеѓу нормализираниот дизајн и дизајнот од фаза P2 === 537 721 538 Нормализираниот дизајн **е идентичен**со дизајнот од фаза P2. Ова значи дека дизајнот од P2 беше веќе правилно нормализиран.722 Нормализираниот дизајн '''е идентичен''' со дизајнот од фаза P2. Ова значи дека дизајнот од P2 беше веќе правилно нормализиран. 539 723 540 724 Сите табели се во BCNF, сите функционални зависности се запазени, и сите декомпозиции се lossless. 541 725 542 За следните фази на проектот **ќе се користи истиот дизајн**од P2, бидејќи е веќе целосно нормализиран.726 За следните фази на проектот '''ќе се користи истиот дизајн''' од P2, бидејќи е веќе целосно нормализиран.
