= Нормализација = Цел: Да се прикаже процесот на нормализација на базата BlissCore (јога часови, настани, пакети, мерч опрема) со проверка на 1NF, 2NF, фокус на 3NF и BCNF. На крај се наведени финалните релации и применетите ограничувања (UNIQUE, CHECK) и индекси за перформанси. == Поддомени и почетни (денормализирани) релации == За потребите на нормализација се издвоени најбитните поддомени: Евиденција за часови и резервации R₁ = {class_id, date, start_time, end_time, location, capacity, seats_available, instructor_id, instructor_email, training_id, training_name, user_id, username, email} Интуитивни зависности: class_id → date, start_time, end_time, location, capacity, seats_available, instructor_id instructor_id → instructor_email training_id → training_name user_id → username, email Евиденција за настани и пријави R₂ = {event_id, event_name, date, time, location, user_id, email} event_id → event_name, date, time, location user_id → email Евиденција за пакети и купувања R₃ = {package_id, package_name, price, num_classes, user_id, email} package_id → package_name, price, num_classes user_id → email Евиденција за мерч и купувања R₄ = {merch_id, item_name, price, user_id, email} merch_id → item_name, price user_id → email Сите атрибути се атомски → 1НФ е задоволена. == 2NF и 3NF / BCNF (декомпозиција) == Проблемите во R₁…R₄ се транзитивни зависности (пример: instructor_id → instructor_email, training_id → training_name, user_id → email) и мешање на фактите од различни ентитети во иста релација. Заради тоа се врши декомпозиција до 3НФ/BCNF. === R₁ (Часови и резервации) === Клучеви по домен: class_id (за клас), instructor_id (за инструктор), training_id (за тренинг), user_id (за корисник). Транзитивни зависности: class_id → instructor_id и instructor_id → instructor_email class_id → … и training_id → training_name user_id → email Декомпозиција (BCNF): Class(class_id, date, start_time, end_time, location, capacity, seats_available, instructor_id) Instructor(instructor_id, instructor_email, …) Training(training_id, training_name, …) User(user_id, username, email, …) Class_Has_Training(class_id, training_id) (M:N ако клас има повеќе тренинзи) User_Booked_Class(user_id, class_id) (резервации) Сите транзитивни зависности се извадени во посебни релации → 3НФ / BCNF. === R₂ (Настани и пријави) === event_id → event_name, date, time, location user_id → email Декомпозиција (BCNF): Event(event_id, event_name, date, time, location) User_Event(user_id, event_id) (пријави на корисник на настан) === R₃ (Пакети и купувања) === package_id → package_name, price, num_classes user_id → email Декомпозиција (BCNF): Package(package_id, package_name, price, num_classes) User_Purchased_Package(user_id, package_id) === R₄ (Мерч и купувања) === merch_id → item_name, price user_id → email Декомпозиција (BCNF): Merch_Items(merch_id, item_name, price, …) User_Purchased_Merch(user_id, merch_id) === Дополнително (пакети што вклучуваат часови) === Package_Includes_Class(package_id, class_id) (M:N врска) == Финални релации (сите во 3НФ / BCNF) == User(user_id, username, email, password_hash, …) Instructor(instructor_id, instructor_email, …) Training(training_id, training_name, …) Class(class_id, date, start_time, end_time, location, capacity, seats_available, instructor_id) Event(event_id, event_name, date, time, location) Package(package_id, package_name, price, num_classes) Merch_Items(merch_id, item_name, price, …) Мостови (M:N): Class_Has_Training(class_id, training_id) User_Booked_Class(user_id, class_id) User_Event(user_id, event_id) User_Purchased_Package(user_id, package_id) User_Purchased_Merch(user_id, merch_id) Package_Includes_Class(package_id, class_id) Секоја релација има примарен клуч; странските клучеви ги поврзуваат ентитетите и мостовите. == Интегритетни ограничувања (UNIQUE, CHECK) == За да спречиме аномалии и дупликати, додадовме: {{{ #!sql -- UNIQUE: деловни правила ALTER TABLE "User" ADD CONSTRAINT uq_user_email UNIQUE (email); ALTER TABLE "User" ADD CONSTRAINT uq_user_username UNIQUE (username); ALTER TABLE "Instructor" ADD CONSTRAINT uq_instr_email UNIQUE (instructor_email); ALTER TABLE "Training" ADD CONSTRAINT uq_training_name UNIQUE (training_name); ALTER TABLE "Package" ADD CONSTRAINT uq_package_name UNIQUE (package_name); ALTER TABLE "Merch_Items" ADD CONSTRAINT uq_merch_item UNIQUE (item_name); -- "Slot" уникатност: спречува дупликат термини/локации ALTER TABLE "Class" ADD CONSTRAINT uq_class_slot UNIQUE (date, start_time, location, instructor_id); ALTER TABLE "Event" ADD CONSTRAINT uq_event_slot UNIQUE (event_name, date, time, location); -- CHECK: едноставни бизнис правила ALTER TABLE "Class" ADD CONSTRAINT ck_class_time_order CHECK (end_time > start_time), ADD CONSTRAINT ck_class_capacity_nonneg CHECK (capacity IS NULL OR capacity >= 0), ADD CONSTRAINT ck_seats_not_overflow CHECK (seats_available IS NULL OR capacity IS NULL OR seats_available <= capacity), ADD CONSTRAINT ck_seats_nonneg CHECK (seats_available IS NULL OR seats_available >= 0); ALTER TABLE "Package" ADD CONSTRAINT ck_package_price_pos CHECK (price > 0), ADD CONSTRAINT ck_package_num_pos CHECK (num_classes > 0); ALTER TABLE "Merch_Items" ADD CONSTRAINT ck_merch_price_pos CHECK (price > 0); }}} Забелешка: при обид за дупликат UNIQUE, PostgreSQL враќа грешка 23505 (unique violation) – тоа во апликацијата го прикажуваме како “Email already taken”, итн. == Индекси за перформанси == Индекси на мостови (по „другата“ страна од PK, за побрзи филтри): {{{ #!sql CREATE INDEX IF NOT EXISTS idx_ubc_class ON "User_Booked_Class"(class_id); CREATE INDEX IF NOT EXISTS idx_cht_training ON "Class_Has_Training"(training_id); CREATE INDEX IF NOT EXISTS idx_ue_event ON "User_Event"(event_id); CREATE INDEX IF NOT EXISTS idx_upp_package ON "User_Purchased_Package"(package_id); CREATE INDEX IF NOT EXISTS idx_upm_merch ON "User_Purchased_Merch"(merch_id); CREATE INDEX IF NOT EXISTS idx_pic_class ON "Package_Includes_Class"(class_id); }}} Индекси за честите листања: {{{ #!sql CREATE INDEX IF NOT EXISTS idx_event_date_time ON "Event"(date, time); CREATE INDEX IF NOT EXISTS idx_class_date_time ON "Class"(date, start_time); }}} == Заклучок == Со декомпозиција по ентитети и мостови ги елиминиравме транзитивните зависности и постигнавме 3NФ/BCNF. UNIQUE/CHECK ограничувањата ги зацврстуваат бизнис правилата, а индексирањето ги забрзува најчестите барања (листaње часови/настани и повратни врски преку мостовите). ==============================