| | 1 | = Нормализација = |
| | 2 | |
| | 3 | Цел: |
| | 4 | Да се прикаже процесот на нормализација на базата BlissCore (јога часови, настани, пакети, мерч опрема) со проверка на 1NF, 2NF, фокус на 3NF и BCNF. На крај се наведени финалните релации и применетите ограничувања (UNIQUE, CHECK) и индекси за перформанси. |
| | 5 | |
| | 6 | == Поддомени и почетни (денормализирани) релации == |
| | 7 | За потребите на нормализација се издвоени најбитните поддомени: |
| | 8 | |
| | 9 | Евиденција за часови и резервации |
| | 10 | R₁ = {class_id, date, start_time, end_time, location, capacity, seats_available, instructor_id, instructor_email, training_id, training_name, user_id, username, email} |
| | 11 | Интуитивни зависности: |
| | 12 | |
| | 13 | class_id → date, start_time, end_time, location, capacity, seats_available, instructor_id |
| | 14 | |
| | 15 | instructor_id → instructor_email |
| | 16 | |
| | 17 | training_id → training_name |
| | 18 | |
| | 19 | user_id → username, email |
| | 20 | |
| | 21 | Евиденција за настани и пријави |
| | 22 | R₂ = {event_id, event_name, date, time, location, user_id, email} |
| | 23 | |
| | 24 | event_id → event_name, date, time, location |
| | 25 | |
| | 26 | user_id → email |
| | 27 | |
| | 28 | Евиденција за пакети и купувања |
| | 29 | R₃ = {package_id, package_name, price, num_classes, user_id, email} |
| | 30 | |
| | 31 | package_id → package_name, price, num_classes |
| | 32 | |
| | 33 | user_id → email |
| | 34 | |
| | 35 | Евиденција за мерч и купувања |
| | 36 | R₄ = {merch_id, item_name, price, user_id, email} |
| | 37 | |
| | 38 | merch_id → item_name, price |
| | 39 | |
| | 40 | user_id → email |
| | 41 | |
| | 42 | Сите атрибути се атомски → 1НФ е задоволена. |
| | 43 | |
| | 44 | == 2NF и 3NF / BCNF (декомпозиција) == |
| | 45 | |
| | 46 | Проблемите во R₁…R₄ се транзитивни зависности (пример: instructor_id → instructor_email, training_id → training_name, user_id → email) и мешање на фактите од различни ентитети во иста релација. Заради тоа се врши декомпозиција до 3НФ/BCNF. |
| | 47 | |
| | 48 | === R₁ (Часови и резервации) === |
| | 49 | |
| | 50 | Клучеви по домен: class_id (за клас), instructor_id (за инструктор), training_id (за тренинг), user_id (за корисник). |
| | 51 | |
| | 52 | Транзитивни зависности: |
| | 53 | |
| | 54 | class_id → instructor_id и instructor_id → instructor_email |
| | 55 | |
| | 56 | class_id → … и training_id → training_name |
| | 57 | |
| | 58 | user_id → email |
| | 59 | |
| | 60 | Декомпозиција (BCNF): |
| | 61 | |
| | 62 | Class(class_id, date, start_time, end_time, location, capacity, seats_available, instructor_id) |
| | 63 | |
| | 64 | Instructor(instructor_id, instructor_email, …) |
| | 65 | |
| | 66 | Training(training_id, training_name, …) |
| | 67 | |
| | 68 | User(user_id, username, email, …) |
| | 69 | |
| | 70 | Class_Has_Training(class_id, training_id) (M:N ако клас има повеќе тренинзи) |
| | 71 | |
| | 72 | User_Booked_Class(user_id, class_id) (резервации) |
| | 73 | |
| | 74 | Сите транзитивни зависности се извадени во посебни релации → 3НФ / BCNF. |
| | 75 | |
| | 76 | === R₂ (Настани и пријави) === |
| | 77 | |
| | 78 | event_id → event_name, date, time, location |
| | 79 | |
| | 80 | user_id → email |
| | 81 | |
| | 82 | Декомпозиција (BCNF): |
| | 83 | |
| | 84 | Event(event_id, event_name, date, time, location) |
| | 85 | |
| | 86 | User_Event(user_id, event_id) (пријави на корисник на настан) |
| | 87 | |
| | 88 | === R₃ (Пакети и купувања) === |
| | 89 | |
| | 90 | package_id → package_name, price, num_classes |
| | 91 | |
| | 92 | user_id → email |
| | 93 | |
| | 94 | Декомпозиција (BCNF): |
| | 95 | |
| | 96 | Package(package_id, package_name, price, num_classes) |
| | 97 | |
| | 98 | User_Purchased_Package(user_id, package_id) |
| | 99 | |
| | 100 | === R₄ (Мерч и купувања) === |
| | 101 | |
| | 102 | merch_id → item_name, price |
| | 103 | |
| | 104 | user_id → email |
| | 105 | |
| | 106 | Декомпозиција (BCNF): |
| | 107 | |
| | 108 | Merch_Items(merch_id, item_name, price, …) |
| | 109 | |
| | 110 | User_Purchased_Merch(user_id, merch_id) |
| | 111 | |
| | 112 | === Дополнително (пакети што вклучуваат часови) === |
| | 113 | |
| | 114 | Package_Includes_Class(package_id, class_id) (M:N врска) |
| | 115 | |
| | 116 | == Финални релации (сите во 3НФ / BCNF) == |
| | 117 | |
| | 118 | User(user_id, username, email, password_hash, …) |
| | 119 | |
| | 120 | Instructor(instructor_id, instructor_email, …) |
| | 121 | |
| | 122 | Training(training_id, training_name, …) |
| | 123 | |
| | 124 | Class(class_id, date, start_time, end_time, location, capacity, seats_available, instructor_id) |
| | 125 | |
| | 126 | Event(event_id, event_name, date, time, location) |
| | 127 | |
| | 128 | Package(package_id, package_name, price, num_classes) |
| | 129 | |
| | 130 | Merch_Items(merch_id, item_name, price, …) |
| | 131 | |
| | 132 | Мостови (M:N): |
| | 133 | |
| | 134 | Class_Has_Training(class_id, training_id) |
| | 135 | |
| | 136 | User_Booked_Class(user_id, class_id) |
| | 137 | |
| | 138 | User_Event(user_id, event_id) |
| | 139 | |
| | 140 | User_Purchased_Package(user_id, package_id) |
| | 141 | |
| | 142 | User_Purchased_Merch(user_id, merch_id) |
| | 143 | |
| | 144 | Package_Includes_Class(package_id, class_id) |
| | 145 | |
| | 146 | Секоја релација има примарен клуч; странските клучеви ги поврзуваат ентитетите и мостовите. |
| | 147 | |
| | 148 | == Интегритетни ограничувања (UNIQUE, CHECK) == |
| | 149 | За да спречиме аномалии и дупликати, додадовме: |
| | 150 | |
| | 151 | {{{ |
| | 152 | #!sql |
| | 153 | -- UNIQUE: деловни правила |
| | 154 | ALTER TABLE "User" ADD CONSTRAINT uq_user_email UNIQUE (email); |
| | 155 | ALTER TABLE "User" ADD CONSTRAINT uq_user_username UNIQUE (username); |
| | 156 | ALTER TABLE "Instructor" ADD CONSTRAINT uq_instr_email UNIQUE (instructor_email); |
| | 157 | ALTER TABLE "Training" ADD CONSTRAINT uq_training_name UNIQUE (training_name); |
| | 158 | ALTER TABLE "Package" ADD CONSTRAINT uq_package_name UNIQUE (package_name); |
| | 159 | ALTER TABLE "Merch_Items" ADD CONSTRAINT uq_merch_item UNIQUE (item_name); |
| | 160 | |
| | 161 | -- "Slot" уникатност: спречува дупликат термини/локации |
| | 162 | ALTER TABLE "Class" |
| | 163 | ADD CONSTRAINT uq_class_slot UNIQUE (date, start_time, location, instructor_id); |
| | 164 | ALTER TABLE "Event" |
| | 165 | ADD CONSTRAINT uq_event_slot UNIQUE (event_name, date, time, location); |
| | 166 | |
| | 167 | -- CHECK: едноставни бизнис правила |
| | 168 | ALTER TABLE "Class" |
| | 169 | ADD CONSTRAINT ck_class_time_order CHECK (end_time > start_time), |
| | 170 | ADD CONSTRAINT ck_class_capacity_nonneg CHECK (capacity IS NULL OR capacity >= 0), |
| | 171 | ADD CONSTRAINT ck_seats_not_overflow CHECK (seats_available IS NULL OR capacity IS NULL OR seats_available <= capacity), |
| | 172 | ADD CONSTRAINT ck_seats_nonneg CHECK (seats_available IS NULL OR seats_available >= 0); |
| | 173 | |
| | 174 | ALTER TABLE "Package" |
| | 175 | ADD CONSTRAINT ck_package_price_pos CHECK (price > 0), |
| | 176 | ADD CONSTRAINT ck_package_num_pos CHECK (num_classes > 0); |
| | 177 | |
| | 178 | ALTER TABLE "Merch_Items" |
| | 179 | ADD CONSTRAINT ck_merch_price_pos CHECK (price > 0); |
| | 180 | }}} |
| | 181 | |
| | 182 | Забелешка: при обид за дупликат UNIQUE, PostgreSQL враќа грешка 23505 (unique violation) – тоа во апликацијата го прикажуваме како “Email already taken”, итн. |
| | 183 | |
| | 184 | == Индекси за перформанси == |
| | 185 | |
| | 186 | Индекси на мостови (по „другата“ страна од PK, за побрзи филтри): |
| | 187 | {{{ |
| | 188 | #!sql |
| | 189 | CREATE INDEX IF NOT EXISTS idx_ubc_class ON "User_Booked_Class"(class_id); |
| | 190 | CREATE INDEX IF NOT EXISTS idx_cht_training ON "Class_Has_Training"(training_id); |
| | 191 | CREATE INDEX IF NOT EXISTS idx_ue_event ON "User_Event"(event_id); |
| | 192 | CREATE INDEX IF NOT EXISTS idx_upp_package ON "User_Purchased_Package"(package_id); |
| | 193 | CREATE INDEX IF NOT EXISTS idx_upm_merch ON "User_Purchased_Merch"(merch_id); |
| | 194 | CREATE INDEX IF NOT EXISTS idx_pic_class ON "Package_Includes_Class"(class_id); |
| | 195 | }}} |
| | 196 | |
| | 197 | Индекси за честите листања: |
| | 198 | {{{ |
| | 199 | #!sql |
| | 200 | CREATE INDEX IF NOT EXISTS idx_event_date_time ON "Event"(date, time); |
| | 201 | CREATE INDEX IF NOT EXISTS idx_class_date_time ON "Class"(date, start_time); |
| | 202 | }}} |
| | 203 | |
| | 204 | == Заклучок == |
| | 205 | Со декомпозиција по ентитети и мостови ги елиминиравме транзитивните зависности и постигнавме 3NФ/BCNF. UNIQUE/CHECK ограничувањата ги зацврстуваат бизнис правилата, а индексирањето ги забрзува најчестите барања (листaње часови/настани и повратни врски преку мостовите). |
| | 206 | |
| | 207 | ============================== |