| 36 | | UserSubscriptionID SERIAL PRIMARY KEY, |
| 37 | | UserUserID int4 NOT NULL REFERENCES "User" (UserID) ON DELETE CASCADE ON UPDATE CASCADE, |
| 38 | | SubscriptionSubscriptionID int4 NOT NULL REFERENCES Subscription (SubscriptionID) ON DELETE RESTRICT ON UPDATE CASCADE, |
| 39 | | Start_date date NOT NULL DEFAULT CURRENT_DATE, |
| 40 | | End_date date CHECK (End_date IS NULL OR End_date >= Start_date), |
| 41 | | Status varchar(255), |
| 42 | | Auto_renew int4 DEFAULT 0 CHECK (Auto_renew IN (0, 1)) |
| 43 | | ); |
| 44 | | |
| 45 | | CREATE TABLE Devices ( |
| 46 | | DeviceID SERIAL PRIMARY KEY, |
| 47 | | DeviceType varchar(255), |
| 48 | | LastLogIn date, |
| 49 | | UserSubscriptionID int4 NOT NULL REFERENCES User_Subscription (UserSubscriptionID) ON DELETE RESTRICT ON UPDATE CASCADE |
| 50 | | ); |
| 51 | | |
| 52 | | CREATE TABLE User_Devices ( |
| 53 | | UserUserID int4 NOT NULL REFERENCES "User" (UserID) ON DELETE CASCADE ON UPDATE CASCADE, |
| 54 | | DevicesDeviceID int4 NOT NULL REFERENCES Devices (DeviceID) ON DELETE CASCADE ON UPDATE CASCADE, |
| 55 | | PRIMARY KEY (UserUserID, DevicesDeviceID) |
| 56 | | ); |
| 57 | | }}} |
| 58 | | |
| 59 | | === 2. Media Hierarchy & Watchables === |
| 60 | | Овој дел ја имплементира наследната структура каде филмовите (`Movie`) и сериите (`Series`) наследуваат од генералниот медиумски ентитет (`Media`). За ефикасно следење на репродукцијата, воведен е концептот на `Watchable` објекти. |
| 61 | | |
| 62 | | {{{ |
| 63 | | #!sql |
| | 47 | UserSubscriptionID SERIAL PRIMARY KEY, |
| | 48 | UserUserID int4 NOT NULL, |
| | 49 | SubscriptionSubscriptionID int4 NOT NULL, |
| | 50 | Start_date date, |
| | 51 | End_date date, |
| | 52 | Status varchar(255), |
| | 53 | Auto_renew int4, |
| | 54 | |
| | 55 | CONSTRAINT chk_auto_renew_bool |
| | 56 | CHECK (Auto_renew IN (0,1)), |
| | 57 | |
| | 58 | CONSTRAINT chk_subscription_dates |
| | 59 | CHECK (End_date IS NULL OR Start_date <= End_date), |
| | 60 | |
| | 61 | CONSTRAINT fk_us_user |
| | 62 | FOREIGN KEY (UserUserID) |
| | 63 | REFERENCES "User" (UserID), |
| | 64 | |
| | 65 | CONSTRAINT fk_us_subscription |
| | 66 | FOREIGN KEY (SubscriptionSubscriptionID) |
| | 67 | REFERENCES Subscription (SubscriptionID), |
| | 68 | |
| | 69 | CONSTRAINT unique_user_subscription |
| | 70 | UNIQUE (UserUserID, SubscriptionSubscriptionID, Start_date) |
| | 71 | ); |
| | 72 | -------------------------------------------------- |
| 141 | | ContentContentID int4 NOT NULL REFERENCES Media (ContentID) ON DELETE CASCADE ON UPDATE CASCADE, |
| 142 | | LanguageLanguageID int4 NOT NULL REFERENCES Language (LanguageID) ON DELETE CASCADE ON UPDATE CASCADE, |
| 143 | | PRIMARY KEY (ContentContentID, LanguageLanguageID) |
| | 213 | ContentContentID int4, |
| | 214 | LanguageLanguageID int4, |
| | 215 | PRIMARY KEY (ContentContentID, LanguageLanguageID), |
| | 216 | |
| | 217 | CONSTRAINT fk_cl_content |
| | 218 | FOREIGN KEY (ContentContentID) |
| | 219 | REFERENCES Media (ContentID), |
| | 220 | |
| | 221 | CONSTRAINT fk_cl_language |
| | 222 | FOREIGN KEY (LanguageLanguageID) |
| | 223 | REFERENCES Language (LanguageID) |
| | 224 | ); |
| | 225 | |
| | 226 | -------------------------------------------------- |
| | 227 | CREATE TABLE Rating ( |
| | 228 | RatingID SERIAL PRIMARY KEY, |
| | 229 | Rating_Date date, |
| | 230 | RatingValue int4, |
| | 231 | UserUserID int4 NOT NULL, |
| | 232 | ContentContentID int4 NOT NULL, |
| | 233 | |
| | 234 | CONSTRAINT chk_rating_value |
| | 235 | CHECK (RatingValue BETWEEN 1 AND 5), |
| | 236 | |
| | 237 | CONSTRAINT unique_user_content_rating |
| | 238 | UNIQUE (UserUserID, ContentContentID), |
| | 239 | |
| | 240 | CONSTRAINT fk_rating_user |
| | 241 | FOREIGN KEY (UserUserID) |
| | 242 | REFERENCES "User" (UserID), |
| | 243 | |
| | 244 | CONSTRAINT fk_rating_content |
| | 245 | FOREIGN KEY (ContentContentID) |
| | 246 | REFERENCES Media (ContentID) |
| | 247 | ); |
| | 248 | -------------------------------------------------- |
| | 249 | |
| | 250 | CREATE TABLE Review ( |
| | 251 | ReviewID SERIAL PRIMARY KEY, |
| | 252 | Comment varchar(255), |
| | 253 | ReviewDate date, |
| | 254 | UserUserID int4 NOT NULL, |
| | 255 | ContentContentID int4 NOT NULL, |
| | 256 | |
| | 257 | CONSTRAINT fk_review_user |
| | 258 | FOREIGN KEY (UserUserID) |
| | 259 | REFERENCES "User" (UserID), |
| | 260 | |
| | 261 | CONSTRAINT fk_review_content |
| | 262 | FOREIGN KEY (ContentContentID) |
| | 263 | REFERENCES Media (ContentID) |
| | 264 | ); |
| | 265 | |
| | 266 | -------------------------------------------------- |
| | 267 | |
| | 268 | CREATE TABLE Devices ( |
| | 269 | DeviceID SERIAL PRIMARY KEY, |
| | 270 | DeviceType varchar(255), |
| | 271 | LastLogIn date, |
| | 272 | UserSubscriptionID int4 NOT NULL, |
| | 273 | |
| | 274 | CONSTRAINT fk_device_subscription |
| | 275 | FOREIGN KEY (UserSubscriptionID) |
| | 276 | REFERENCES User_Subscription (UserSubscriptionID) |
| | 277 | ); |
| | 278 | |
| | 279 | -------------------------------------------------- |
| | 280 | CREATE TABLE WatchHistory ( |
| | 281 | HistoryID SERIAL PRIMARY KEY, |
| | 282 | WatchedAt date, |
| | 283 | Progress_percentage int4, |
| | 284 | UserUserID int4 NOT NULL, |
| | 285 | ContentContentID int4 NOT NULL, |
| | 286 | WatchableWatchableID int4 NOT NULL, |
| | 287 | DevicesDeviceID int4 NOT NULL, |
| | 288 | |
| | 289 | CONSTRAINT chk_progress_percentage |
| | 290 | CHECK (Progress_percentage BETWEEN 0 AND 100), |
| | 291 | |
| | 292 | CONSTRAINT fk_wh_user |
| | 293 | FOREIGN KEY (UserUserID) |
| | 294 | REFERENCES "User" (UserID), |
| | 295 | |
| | 296 | CONSTRAINT fk_wh_content |
| | 297 | FOREIGN KEY (ContentContentID) |
| | 298 | REFERENCES Media (ContentID), |
| | 299 | |
| | 300 | CONSTRAINT fk_wh_watchable |
| | 301 | FOREIGN KEY (WatchableWatchableID) |
| | 302 | REFERENCES Watchable (WatchableID), |
| | 303 | |
| | 304 | CONSTRAINT fk_wh_device |
| | 305 | FOREIGN KEY (DevicesDeviceID) |
| | 306 | REFERENCES Devices (DeviceID) |
| | 307 | ); |
| | 308 | -------------------------------------------------- |
| | 309 | |
| | 310 | CREATE TABLE Watchlist ( |
| | 311 | WatchlistID SERIAL PRIMARY KEY, |
| | 312 | dateAdded date, |
| | 313 | UserUserID int4 NOT NULL, |
| | 314 | ContentContentID int4 NOT NULL, |
| | 315 | WatchableWatchableID int4 NOT NULL, |
| | 316 | |
| | 317 | CONSTRAINT fk_wl_user |
| | 318 | FOREIGN KEY (UserUserID) |
| | 319 | REFERENCES "User" (UserID), |
| | 320 | |
| | 321 | CONSTRAINT fk_wl_content |
| | 322 | FOREIGN KEY (ContentContentID) |
| | 323 | REFERENCES Media (ContentID), |
| | 324 | |
| | 325 | CONSTRAINT fk_wl_watchable |
| | 326 | FOREIGN KEY (WatchableWatchableID) |
| | 327 | REFERENCES Watchable (WatchableID) |
| | 328 | ); |
| | 329 | |
| | 330 | -------------------------------------------------- |
| | 331 | |
| | 332 | CREATE TABLE User_Devices ( |
| | 333 | UserUserID int4, |
| | 334 | DevicesDeviceID int4, |
| | 335 | PRIMARY KEY (UserUserID, DevicesDeviceID), |
| | 336 | |
| | 337 | CONSTRAINT fk_ud_user |
| | 338 | FOREIGN KEY (UserUserID) |
| | 339 | REFERENCES "User" (UserID), |
| | 340 | |
| | 341 | CONSTRAINT fk_ud_device |
| | 342 | FOREIGN KEY (DevicesDeviceID) |
| | 343 | REFERENCES Devices (DeviceID) |
| 147 | | === 4. User Interaction & History === |
| 148 | | Овој сегмент ги следи сите кориснички акции во системот: оценување, пишување рецензии, додавање во листи за следење и детална историја на гледање со статус на прогрес. |
| 149 | | |
| 150 | | {{{ |
| 151 | | #!sql |
| 152 | | CREATE TABLE Rating ( |
| 153 | | RatingID SERIAL PRIMARY KEY, |
| 154 | | Rating_Date date DEFAULT CURRENT_DATE, |
| 155 | | RatingValue int4 NOT NULL CHECK (RatingValue BETWEEN 1 AND 10), |
| 156 | | UserUserID int4 NOT NULL REFERENCES "User" (UserID) ON DELETE CASCADE ON UPDATE CASCADE, |
| 157 | | ContentContentID int4 NOT NULL REFERENCES Media (ContentID) ON DELETE CASCADE ON UPDATE CASCADE, |
| 158 | | UNIQUE (UserUserID, ContentContentID) |
| 159 | | ); |
| 160 | | |
| 161 | | CREATE TABLE Review ( |
| 162 | | ReviewID SERIAL PRIMARY KEY, |
| 163 | | Comment varchar(255), |
| 164 | | ReviewDate date DEFAULT CURRENT_DATE, |
| 165 | | UserUserID int4 NOT NULL REFERENCES "User" (UserID) ON DELETE CASCADE ON UPDATE CASCADE, |
| 166 | | ContentContentID int4 NOT NULL REFERENCES Media (ContentID) ON DELETE CASCADE ON UPDATE CASCADE |
| 167 | | ); |
| 168 | | |
| 169 | | CREATE TABLE WatchHistory ( |
| 170 | | HistoryID SERIAL PRIMARY KEY, |
| 171 | | WatchedAt date DEFAULT CURRENT_DATE, |
| 172 | | Progress_percentage int4 CHECK (Progress_percentage BETWEEN 0 AND 100), |
| 173 | | UserUserID int4 NOT NULL REFERENCES "User" (UserID) ON DELETE CASCADE ON UPDATE CASCADE, |
| 174 | | ContentContentID int4 NOT NULL REFERENCES Media (ContentID) ON DELETE CASCADE ON UPDATE CASCADE, |
| 175 | | WatchableWatchableID int4 NOT NULL REFERENCES Watchable (WatchableID) ON DELETE CASCADE ON UPDATE CASCADE, |
| 176 | | DevicesDeviceID int4 REFERENCES Devices (DeviceID) ON DELETE SET NULL ON UPDATE CASCADE |
| 177 | | ); |
| 178 | | |
| 179 | | CREATE TABLE Watchlist ( |
| 180 | | WatchlistID SERIAL PRIMARY KEY, |
| 181 | | dateAdded date DEFAULT CURRENT_DATE, |
| 182 | | UserUserID int4 NOT NULL REFERENCES "User" (UserID) ON DELETE CASCADE ON UPDATE CASCADE, |
| 183 | | ContentContentID int4 NOT NULL REFERENCES Media (ContentID) ON DELETE CASCADE ON UPDATE CASCADE, |
| 184 | | WatchableWatchableID int4 NOT NULL REFERENCES Watchable (WatchableID) ON DELETE CASCADE ON UPDATE CASCADE, |
| 185 | | UNIQUE (UserUserID, ContentContentID) |
| 186 | | ); |
| 187 | | }}} |
| 188 | | |
| 189 | | |
| 190 | | == Key Implementation Highlights == |
| 191 | | |
| 192 | | * '''Е-mail Валидација преку Регуларен Израз (RegEx):''' Во табелата `"User"`, внесот на е-маил адресата е строго контролиран на ниво на база со `CHECK (Email ~ '^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}$')`. Ова спречува невалидни податоци уште пред да стигнат до апликативниот слој. |
| 193 | | * '''Интегритет на претплати (`ON DELETE RESTRICT`):''' За разлика од другите табели каде бришењето оди каскадно (`CASCADE`), кај `User_Subscription` кон `Subscription` е ставено `RESTRICT`. Тоа значи дека системот нема да дозволи да се избрише одреден претплатен пакет (на пр. "Premium") ако постојат активни корисници кои моментално го плаќаат и користат тој пакет. |
| 194 | | * '''Униформно следење на содржини преку `Watchable`:''' Посредничката логика во `Watchable` овозможува `WatchHistory` да не се грижи дали корисникот гледал филм или единечна епизода од серија – двата типа се сведуваат на ист апстрактен ID со времетраење, што ја прави базата исклучително скалабилна за идни проширувања. |
| | 347 | --- |
| | 348 | |
| | 349 | == 2. Стратегија и Процес за Популација на Податоци (Big Data Generation) == |
| | 350 | |
| | 351 | Генерирањето на релевантни податоци во вака структуриран симулациски систем беше поделено во неколку клучни чекори, користејќи комбинација од надворешно подготвени податоци и напредни програмски функции внатре во самиот DBMS (PostgreSQL). Целта беше да се достигне волумен од преку **40 милиони записи** за симулација на реално оптоварување. |
| | 352 | |
| | 353 | === Чекор 1: Внесување на Матични Податоци и Шифрарници === |
| | 354 | Првично, базата се полни со фиксни вредности за табелите кои служат како шифрарници: `Subscription` пакетите (димензионирани со соодветни цени и дозволен број уреди), основните јазици (`Language`) и филмските жанрови (`Genre`). На овој начин се овозможува понатамошно конзистентно референцирање од другите трансакциски табели. |
| | 355 | |
| | 356 | === Чекор 2: Импорт на Структурирани Корисници и Содржини === |
| | 357 | Со цел имињата, насловите и описите во базата да имаат реална форма, користен е надворешен софтвер за симулација на податоци (Mockaroo) за експорт во CSV датотеки. Преку оптимизираната `COPY FROM` наредба во PostgreSQL, во базата се внесени почетни множества на уметници, медиуми (филмови и серии) и кориснички профили. |
| | 358 | |
| | 359 | Почетното множество на корисници програмски се мултиплицираше до **1,000,000 кориснички записи** во табелата `User` со користење на Декартов производ и функцијата `generate_series(1, 1000)`. Со овој пристап се креираа уникатни комбинации на кориснички имиња и е-маил адреси кои целосно ги задоволуваат рестрикциите во `chk_user_email_format` и `UNIQUE` дефиницијата на мејлот. |
| | 360 | |
| | 361 | === Чекор 3: Автоматско Креирање Логички Врски (Сезони и Watchable) === |
| | 362 | Наместо мануелен внес, логиката за сезони на сериите се потпира на вредноста `TotalSeasons` од табелата `Series`. Преку внатрешен `SELECT` и `generate_series` за секоја серија се изгенерираа точен број на соодветни сезони во табелата `Season`. |
| | 363 | |
| | 364 | За задоволување на супер-тип/под-тип релацијата кај `Watchable`, извршени се две посебни `INSERT` процедури кои автоматски ги повлекоа времетраењата од соодветните филмски записи (`Movie`) и епизоди (`Episode`), распределувајќи го точниот ID опсег за да се зачува релацискиот интегритет и ограничувањето `chk_watchable_duration`. |
| | 365 | |
| | 366 | === Чекор 4: Масовна Дистрибуција (Доделување Жанрови, Јазици и Уметници) === |
| | 367 | За поврзување на табелите од тип повеќе-кон-повеќе (`Content_Genre`, `Content_Language`, `Content_Artist`), користена е конструкцијата `CROSS JOIN LATERAL` со `LIMIT (1 + random())`. Ова овозможи секој содржински запис во `Media` да добие случаен број на жанрови (од 1 до 3) или јазици, што ја рефлектира природната дистрибуција на реалните платформи. |
| | 368 | |
| | 369 | === Чекор 5: Генерирање на Милионски Трансакциски Податоци === |
| | 370 | Најголемиот волумен во базата се создаде во трансакциските табели, каде што се евидентира корисничкото однесување: |
| | 371 | |
| | 372 | * '''Review (10,000,000 записи):''' Симулирано преку внатрешно дефинирана низа од реални коментари. Со користење на латерално поврзување и случаен избор, се генерираа 10 милиони коментари распоредени низ временска рамка од две години наназад, поврзани со точните клучни релации на `User` и `Media`. |
| | 373 | * '''WatchHistory (10,000,000 записи):''' Податоците се генерирани со валидација на содржината (дали корисникот гледа епизода или филм), зачувувајќи го точниот уред кој е поврзан со неговата активна претплата, и генерирајќи случаен напредок (`Progress_percentage`) од 0 до 100 согласно соодветниот констреинт `chk_progress_percentage`. |
| | 374 | * '''Rating (Преку 24,000,000 записи):''' Со цел да се создаде масивна матрица за тестирање, земен е примерок од 50,000 корисници кои се споени со сите достапни медиуми во базата преку `CROSS JOIN`. Вредностите за рејтингот се генерирани случајно во опсег од 1 до 5 согласно ограничувањето `chk_rating_value`. Благодарение на клаузулата `ON CONFLICT DO NOTHING` и уникатниот индекс `unique_user_content_rating`, успешно се избегнати дупликати во комбинацијата корисник-медиум. |
| | 375 | |
| | 376 | > '''Заклучок за перформанси:''' Сите овие масовни трансакциски табели намерно се доведени до толкав волумен со цел во следниот чекор да се демонстрира клучната улога на '''индексирањето''' (Indexing) и извршните планови (`EXPLAIN ANALYZE`), каде што соодветно поставените индекси драстично го намалуваат времето на извршување на комплексните кверија од неколку часа на неколку милисекунди. |
| | 377 | |
| | 378 | === SQL Датотеки == |
| | 379 | Оригиналната SQL скрипта е достапна тука: [attachment:VidiDB_Creation.sql VidiDB_Creation.sql] |
| | 380 | Тука може да се видат сите инсерти : [attachment: console_2.sql console_2.sql] [attachment: console_4.sql console_4.sql] [attachment: console_5.sql console_5.sql] |
| | 381 | [attachment: console_6.sql console_6.sql] [attachment: console_7.sql console_7.sql] [attachment: console_8.sql console_8.sql] [attachment: console_9.sql console_9.sql] |