| 1 | --6 funkcii / 3 po covek
|
|---|
| 2 | --KIKO FUNKCII::
|
|---|
| 3 | --get_avg_rating(content_id) — vrakja prosecen rejting na film/serija
|
|---|
| 4 |
|
|---|
| 5 | CREATE FUNCTION get_avg_rating(p_content_id INT)
|
|---|
| 6 | RETURNS NUMERIC AS $$
|
|---|
| 7 | DECLARE
|
|---|
| 8 | v_avg NUMERIC;
|
|---|
| 9 | BEGIN
|
|---|
| 10 | SELECT ROUND(AVG(RatingValue), 2)
|
|---|
| 11 | INTO v_avg
|
|---|
| 12 | FROM Rating
|
|---|
| 13 | WHERE ContentContentID = p_content_id;
|
|---|
| 14 |
|
|---|
| 15 | RETURN v_avg;
|
|---|
| 16 | END;
|
|---|
| 17 | $$ LANGUAGE plpgsql;
|
|---|
| 18 |
|
|---|
| 19 |
|
|---|
| 20 |
|
|---|
| 21 |
|
|---|
| 22 | --get_user_watch_count(user_id) — vrakja kolku sodrzini gledal nekoj korisnik
|
|---|
| 23 |
|
|---|
| 24 | CREATE FUNCTION get_user_watch_count(p_user_id INT)
|
|---|
| 25 | RETURNS INT AS $$
|
|---|
| 26 | DECLARE
|
|---|
| 27 | v_count INT;
|
|---|
| 28 | BEGIN
|
|---|
| 29 | SELECT COUNT(*)
|
|---|
| 30 | INTO v_count
|
|---|
| 31 | FROM WatchHistory
|
|---|
| 32 | WHERE UserUserID = p_user_id;
|
|---|
| 33 |
|
|---|
| 34 | RETURN v_count;
|
|---|
| 35 | END;
|
|---|
| 36 | $$ LANGUAGE plpgsql;
|
|---|
| 37 |
|
|---|
| 38 |
|
|---|
| 39 |
|
|---|
| 40 |
|
|---|
| 41 |
|
|---|
| 42 | --get_content_genres(content_id) — gi vrakja site zhanri za nekoja sodrzina (deka se posebni tabeli, namesto da se prai join, da se dobie direkt sho zhanra e nekoj film/serija)
|
|---|
| 43 |
|
|---|
| 44 | CREATE FUNCTION get_content_genres(p_content_id INT)
|
|---|
| 45 | RETURNS VARCHAR AS $$
|
|---|
| 46 | DECLARE
|
|---|
| 47 | v_genres VARCHAR;
|
|---|
| 48 | BEGIN
|
|---|
| 49 | SELECT STRING_AGG(g.Name, ', ' ORDER BY g.Name)
|
|---|
| 50 | INTO v_genres
|
|---|
| 51 | FROM Genre g
|
|---|
| 52 | JOIN Content_Genre cg ON cg.GenreGenreID = g.GenreID
|
|---|
| 53 | WHERE cg.ContentContentID = p_content_id;
|
|---|
| 54 |
|
|---|
| 55 | RETURN COALESCE(v_genres, 'No genres found');
|
|---|
| 56 | END;
|
|---|
| 57 | $$ LANGUAGE plpgsql;
|
|---|
| 58 |
|
|---|
| 59 |
|
|---|
| 60 |
|
|---|
| 61 |
|
|---|
| 62 |
|
|---|
| 63 | --Damjan FUNKCII:
|
|---|
| 64 | --get_subscription_info(user_id) — sto platil nekoj korisnik
|
|---|
| 65 |
|
|---|
| 66 | CREATE FUNCTION get_subscription_info(p_user_id INT)
|
|---|
| 67 | RETURNS TABLE(plan_name VARCHAR, price NUMERIC, max_devices INT, status VARCHAR) AS $$
|
|---|
| 68 | BEGIN
|
|---|
| 69 | RETURN QUERY
|
|---|
| 70 | SELECT
|
|---|
| 71 | s.Name,
|
|---|
| 72 | s.Price,
|
|---|
| 73 | s.MaxDevices,
|
|---|
| 74 | us.Status
|
|---|
| 75 | FROM User_Subscription us
|
|---|
| 76 | JOIN Subscription s ON s.SubscriptionID = us.SubscriptionSubscriptionID
|
|---|
| 77 | WHERE us.UserUserID = p_user_id
|
|---|
| 78 | LIMIT 1;
|
|---|
| 79 | END;
|
|---|
| 80 | $$ LANGUAGE plpgsql;
|
|---|
| 81 |
|
|---|
| 82 |
|
|---|
| 83 |
|
|---|
| 84 |
|
|---|
| 85 |
|
|---|
| 86 |
|
|---|
| 87 | --get_series_episode_count(series_id) — vkupen broj na epizodi za serija
|
|---|
| 88 |
|
|---|
| 89 |
|
|---|
| 90 | CREATE FUNCTION get_series_episode_count(p_series_id INT)
|
|---|
| 91 | RETURNS INT AS $$
|
|---|
| 92 | DECLARE
|
|---|
| 93 | v_count INT;
|
|---|
| 94 | BEGIN
|
|---|
| 95 | SELECT COUNT(e.EpisodeID)
|
|---|
| 96 | INTO v_count
|
|---|
| 97 | FROM Episode e
|
|---|
| 98 | JOIN Season sea ON sea.SeasonID = e.SeasonSeasonID
|
|---|
| 99 | WHERE sea.SeriesSeriesID = p_series_id;
|
|---|
| 100 |
|
|---|
| 101 | RETURN v_count;
|
|---|
| 102 | END;
|
|---|
| 103 | $$ LANGUAGE plpgsql;
|
|---|
| 104 |
|
|---|
| 105 |
|
|---|
| 106 |
|
|---|
| 107 |
|
|---|
| 108 |
|
|---|
| 109 |
|
|---|
| 110 | --get_top_content_by_genre(genre_name) — vrati top sodrzina spored zhanra
|
|---|
| 111 |
|
|---|
| 112 | CREATE FUNCTION get_top_content_by_genre(p_genre_name VARCHAR)
|
|---|
| 113 | RETURNS TABLE(title VARCHAR, avg_rating NUMERIC, total_ratings BIGINT) AS $$
|
|---|
| 114 | BEGIN
|
|---|
| 115 | RETURN QUERY
|
|---|
| 116 | SELECT
|
|---|
| 117 | m.title,
|
|---|
| 118 | ROUND(AVG(r.RatingValue), 2) AS avg_rating,
|
|---|
| 119 | COUNT(r.RatingID) AS total_ratings
|
|---|
| 120 | FROM Media m
|
|---|
| 121 | JOIN Content_Genre cg ON cg.ContentContentID = m.ContentID
|
|---|
| 122 | JOIN Genre g ON g.GenreID = cg.GenreGenreID
|
|---|
| 123 | JOIN Rating r ON r.ContentContentID = m.ContentID
|
|---|
| 124 | WHERE g.Name = p_genre_name
|
|---|
| 125 | GROUP BY m.title
|
|---|
| 126 | ORDER BY avg_rating DESC
|
|---|
| 127 | LIMIT 10;
|
|---|
| 128 | END;
|
|---|
| 129 | $$ LANGUAGE plpgsql;
|
|---|
| 130 |
|
|---|
| 131 |
|
|---|
| 132 |
|
|---|
| 133 |
|
|---|
| 134 |
|
|---|
| 135 | --KIKO TRIGERI
|
|---|
| 136 |
|
|---|
| 137 | --Trigger 1 — koga ke se izbrishe korisnik, da se logira vo nova tabela
|
|---|
| 138 |
|
|---|
| 139 | --ni trea nova tabela za ova:
|
|---|
| 140 | CREATE TABLE IF NOT EXISTS User_Delete_Log (
|
|---|
| 141 | LogID SERIAL PRIMARY KEY,
|
|---|
| 142 | UserID INT,
|
|---|
| 143 | Username VARCHAR(255),
|
|---|
| 144 | Email VARCHAR(255),
|
|---|
| 145 | DeletedAt TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
|---|
| 146 | );
|
|---|
| 147 |
|
|---|
| 148 |
|
|---|
| 149 | --eve go trigerot:
|
|---|
| 150 | CREATE FUNCTION trg_log_user_delete()
|
|---|
| 151 | RETURNS TRIGGER AS $$
|
|---|
| 152 | BEGIN
|
|---|
| 153 | INSERT INTO User_Delete_Log (UserID, Username, Email)
|
|---|
| 154 | VALUES (OLD.UserID, OLD.Username, OLD.Email);
|
|---|
| 155 | RETURN OLD;
|
|---|
| 156 | END;
|
|---|
| 157 | $$ LANGUAGE plpgsql;
|
|---|
| 158 |
|
|---|
| 159 | CREATE TRIGGER trigger_log_user_delete
|
|---|
| 160 | BEFORE DELETE ON "User"
|
|---|
| 161 | FOR EACH ROW
|
|---|
| 162 | EXECUTE FUNCTION trg_log_user_delete();
|
|---|
| 163 |
|
|---|
| 164 |
|
|---|
| 165 | --za da testiram deletnav user: DELETE FROM "User" WHERE UserID = 159089;
|
|---|
| 166 | --za proverka deka stvarno e vo novata tabela: SELECT * FROM User_Delete_Log;
|
|---|
| 167 |
|
|---|
| 168 |
|
|---|
| 169 |
|
|---|
| 170 |
|
|---|
| 171 |
|
|---|
| 172 |
|
|---|
| 173 | --Trigger 2 — koga ke se vnese rating, da se proveri dali e megju 1 i 10
|
|---|
| 174 |
|
|---|
| 175 | CREATE FUNCTION trg_validate_rating()
|
|---|
| 176 | RETURNS TRIGGER AS $$
|
|---|
| 177 | BEGIN
|
|---|
| 178 | IF NEW.RatingValue < 1 OR NEW.RatingValue > 10 THEN
|
|---|
| 179 | RAISE EXCEPTION 'RatingValue mora da bide pomegu 1 i 10, vnesena vrednost: %', NEW.RatingValue;
|
|---|
| 180 | END IF;
|
|---|
| 181 | RETURN NEW;
|
|---|
| 182 | END;
|
|---|
| 183 | $$ LANGUAGE plpgsql;
|
|---|
| 184 |
|
|---|
| 185 | CREATE TRIGGER trigger_validate_rating
|
|---|
| 186 | BEFORE INSERT OR UPDATE ON Rating
|
|---|
| 187 | FOR EACH ROW
|
|---|
| 188 | EXECUTE FUNCTION trg_validate_rating();
|
|---|
| 189 |
|
|---|
| 190 | --za proverka deka ke frli greska: INSERT INTO Rating (RatingValue, UserUserID, ContentContentID)
|
|---|
| 191 | --VALUES (15, 159090, 3);
|
|---|
| 192 |
|
|---|
| 193 |
|
|---|
| 194 |
|
|---|
| 195 |
|
|---|
| 196 |
|
|---|
| 197 |
|
|---|
| 198 | --Trigger 3 — koga ke se vnese nov watch history zapis, avtomatski da se azhurira na progress = 100 ako e pominato vremetraenjeto
|
|---|
| 199 |
|
|---|
| 200 | CREATE FUNCTION trg_auto_complete_progress()
|
|---|
| 201 | RETURNS TRIGGER AS $$
|
|---|
| 202 | DECLARE
|
|---|
| 203 | v_duration INT;
|
|---|
| 204 | BEGIN
|
|---|
| 205 | -- da se zeme traenjeto na watchable
|
|---|
| 206 | SELECT duration INTO v_duration
|
|---|
| 207 | FROM Watchable
|
|---|
| 208 | WHERE WatchableID = NEW.WatchableWatchableID;
|
|---|
| 209 |
|
|---|
| 210 | -- ako progress e 90% ili povekje, set na 100%
|
|---|
| 211 | IF NEW.Progress_percentage >= 90 THEN
|
|---|
| 212 | NEW.Progress_percentage := 100;
|
|---|
| 213 | END IF;
|
|---|
| 214 |
|
|---|
| 215 | RETURN NEW;
|
|---|
| 216 | END;
|
|---|
| 217 | $$ LANGUAGE plpgsql;
|
|---|
| 218 |
|
|---|
| 219 | CREATE TRIGGER trigger_auto_complete_progress
|
|---|
| 220 | BEFORE INSERT ON WatchHistory
|
|---|
| 221 | FOR EACH ROW
|
|---|
| 222 | EXECUTE FUNCTION trg_auto_complete_progress();
|
|---|
| 223 |
|
|---|
| 224 | --proverka:
|
|---|
| 225 |
|
|---|
| 226 | --vnesuvanje so progress 95. treba da stane 100
|
|---|
| 227 |
|
|---|
| 228 | --INSERT INTO WatchHistory (WatchedAt, Progress_percentage, UserUserID, ContentContentID, WatchableWatchableID)
|
|---|
| 229 | --VALUES (CURRENT_DATE, 95, 159090, 1, 1);
|
|---|
| 230 |
|
|---|
| 231 |
|
|---|
| 232 | --proverka za dali stanalo (vekje e izvrshena gornata sho znaci mozeme so ovaa samo da proverime na labot):
|
|---|
| 233 |
|
|---|
| 234 | --SELECT Progress_percentage FROM WatchHistory
|
|---|
| 235 | --WHERE UserUserID = 159090
|
|---|
| 236 | --ORDER BY HistoryID DESC
|
|---|
| 237 | --LIMIT 1;
|
|---|
| 238 |
|
|---|
| 239 |
|
|---|
| 240 |
|
|---|
| 241 |
|
|---|
| 242 |
|
|---|
| 243 |
|
|---|
| 244 |
|
|---|
| 245 | --Damjan TRIGERI:
|
|---|
| 246 |
|
|---|
| 247 | --TRIGGER 1 - koga end_date e pominat na subscription, avtomatski da se stavi status EXPIRED
|
|---|
| 248 |
|
|---|
| 249 | CREATE FUNCTION trg_auto_expire_subscription()
|
|---|
| 250 | RETURNS TRIGGER AS $$
|
|---|
| 251 | BEGIN
|
|---|
| 252 | IF NEW.End_date IS NOT NULL AND NEW.End_date < CURRENT_DATE AND NEW.Status != 'Expired' THEN
|
|---|
| 253 | NEW.Status := 'Expired';
|
|---|
| 254 | END IF;
|
|---|
| 255 | RETURN NEW;
|
|---|
| 256 | END;
|
|---|
| 257 | $$ LANGUAGE plpgsql;
|
|---|
| 258 |
|
|---|
| 259 | CREATE TRIGGER trigger_auto_expire_subscription
|
|---|
| 260 | BEFORE UPDATE ON User_Subscription
|
|---|
| 261 | FOR EACH ROW
|
|---|
| 262 | EXECUTE FUNCTION trg_auto_expire_subscription();
|
|---|
| 263 |
|
|---|
| 264 | --za da proveram smeniv end date na user subscription 3 za da vidam dali proraboti
|
|---|
| 265 | --UPDATE User_Subscription
|
|---|
| 266 | --SET End_date = '2026-05-01'
|
|---|
| 267 | --WHERE UserSubscriptionID = 3;
|
|---|
| 268 |
|
|---|
| 269 | --eve ja komandata za da se proveri dali stvarno se promeni samoto vo expired:
|
|---|
| 270 | --SELECT UserSubscriptionID, Start_date, End_date, Status
|
|---|
| 271 | --FROM User_Subscription
|
|---|
| 272 | --WHERE UserSubscriptionID = 3;
|
|---|
| 273 |
|
|---|
| 274 |
|
|---|
| 275 |
|
|---|
| 276 |
|
|---|
| 277 |
|
|---|
| 278 |
|
|---|
| 279 |
|
|---|
| 280 |
|
|---|
| 281 |
|
|---|
| 282 | --TRIGGER 2 - da se spreci premnogu uredi, odnosno koga ke se dodava nov ured, da se proveruva max_devices spored planot na subscriptionot
|
|---|
| 283 |
|
|---|
| 284 | CREATE FUNCTION trg_check_max_devices()
|
|---|
| 285 | RETURNS TRIGGER AS $$
|
|---|
| 286 | DECLARE
|
|---|
| 287 | v_max_devices INT;
|
|---|
| 288 | v_current_devices INT;
|
|---|
| 289 | BEGIN
|
|---|
| 290 | -- maksimalniot broj na uredi od planot
|
|---|
| 291 | SELECT s.MaxDevices INTO v_max_devices
|
|---|
| 292 | FROM User_Subscription us
|
|---|
| 293 | JOIN Subscription s ON s.SubscriptionID = us.SubscriptionSubscriptionID
|
|---|
| 294 | WHERE us.UserSubscriptionID = NEW.UserSubscriptionID;
|
|---|
| 295 |
|
|---|
| 296 | -- proverka za kolku uredi vekje ima
|
|---|
| 297 | SELECT COUNT(*) INTO v_current_devices
|
|---|
| 298 | FROM Devices
|
|---|
| 299 | WHERE UserSubscriptionID = NEW.UserSubscriptionID;
|
|---|
| 300 |
|
|---|
| 301 | IF v_current_devices >= v_max_devices THEN
|
|---|
| 302 | RAISE EXCEPTION 'Maksimalen broj na uredi e dostигнат: % od %', v_current_devices, v_max_devices;
|
|---|
| 303 | END IF;
|
|---|
| 304 |
|
|---|
| 305 | RETURN NEW;
|
|---|
| 306 | END;
|
|---|
| 307 | $$ LANGUAGE plpgsql;
|
|---|
| 308 |
|
|---|
| 309 | CREATE TRIGGER trigger_check_max_devices
|
|---|
| 310 | BEFORE INSERT ON Devices
|
|---|
| 311 | FOR EACH ROW
|
|---|
| 312 | EXECUTE FUNCTION trg_check_max_devices();
|
|---|
| 313 |
|
|---|
| 314 |
|
|---|
| 315 | --probav so user subscription id 3 kade sto imase 0 uredi a maksimalno 1 ured.
|
|---|
| 316 |
|
|---|
| 317 | -- dodavanje prv ured pomina
|
|---|
| 318 | --INSERT INTO Devices (DeviceType, LastLogIn, UserSubscriptionID)
|
|---|
| 319 | --VALUES ('Mobile Phone', CURRENT_DATE, 3);
|
|---|
| 320 |
|
|---|
| 321 | -- dodavanje vtor ured ne pomina
|
|---|
| 322 | --INSERT INTO Devices (DeviceType, LastLogIn, UserSubscriptionID)
|
|---|
| 323 | --VALUES ('Tablet', CURRENT_DATE, 3);
|
|---|
| 324 |
|
|---|
| 325 |
|
|---|
| 326 |
|
|---|
| 327 |
|
|---|
| 328 |
|
|---|
| 329 |
|
|---|
| 330 |
|
|---|
| 331 |
|
|---|
| 332 |
|
|---|
| 333 |
|
|---|
| 334 | --TRIGGER 3 - log pri promena na status. koga user subscription ke smeni status, da se logira taa promena
|
|---|
| 335 |
|
|---|
| 336 | CREATE TABLE Subscription_Status_Log (
|
|---|
| 337 | LogID SERIAL PRIMARY KEY,
|
|---|
| 338 | UserSubID INT,
|
|---|
| 339 | UserID INT,
|
|---|
| 340 | OldStatus VARCHAR(255),
|
|---|
| 341 | NewStatus VARCHAR(255),
|
|---|
| 342 | ChangedAt TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
|---|
| 343 | );
|
|---|
| 344 |
|
|---|
| 345 |
|
|---|
| 346 |
|
|---|
| 347 | CREATE FUNCTION trg_log_subscription_status_change()
|
|---|
| 348 | RETURNS TRIGGER AS $$
|
|---|
| 349 | BEGIN
|
|---|
| 350 | IF OLD.Status <> NEW.Status THEN
|
|---|
| 351 | INSERT INTO Subscription_Status_Log (UserSubID, UserID, OldStatus, NewStatus)
|
|---|
| 352 | VALUES (OLD.UserSubscriptionID, OLD.UserUserID, OLD.Status, NEW.Status);
|
|---|
| 353 | END IF;
|
|---|
| 354 | RETURN NEW;
|
|---|
| 355 | END;
|
|---|
| 356 | $$ LANGUAGE plpgsql;
|
|---|
| 357 |
|
|---|
| 358 | CREATE TRIGGER trigger_log_subscription_status_change
|
|---|
| 359 | AFTER UPDATE ON User_Subscription
|
|---|
| 360 | FOR EACH ROW
|
|---|
| 361 | EXECUTE FUNCTION trg_log_subscription_status_change();
|
|---|
| 362 |
|
|---|
| 363 | --testiranje:
|
|---|
| 364 | --promena na status:
|
|---|
| 365 | --UPDATE User_Subscription
|
|---|
| 366 | --SET Status = 'Cancelled'
|
|---|
| 367 | --WHERE UserSubscriptionID = (
|
|---|
| 368 | --SELECT UserSubscriptionID FROM User_Subscription LIMIT 1
|
|---|
| 369 | --);
|
|---|
| 370 |
|
|---|
| 371 | --proverkata od tabelata:::: (za lab ova)
|
|---|
| 372 | --SELECT * FROM Subscription_Status_Log;
|
|---|
| 373 |
|
|---|
| 374 |
|
|---|
| 375 |
|
|---|
| 376 |
|
|---|
| 377 |
|
|---|
| 378 |
|
|---|
| 379 |
|
|---|
| 380 |
|
|---|
| 381 |
|
|---|
| 382 |
|
|---|
| 383 | --on the lowkey staiv indeksi na rating watch history i review deka se ogromni
|
|---|
| 384 | CREATE INDEX idx_rating_content ON Rating (ContentContentID);
|
|---|
| 385 | CREATE INDEX idx_rating_user ON Rating (UserUserID);
|
|---|
| 386 | CREATE INDEX idx_watchhistory_content ON WatchHistory (ContentContentID);
|
|---|
| 387 | CREATE INDEX idx_watchhistory_user ON WatchHistory (UserUserID);
|
|---|
| 388 | CREATE INDEX idx_review_content ON Review (ContentContentID);
|
|---|
| 389 | CREATE INDEX idx_review_user ON Review (UserUserID);
|
|---|
| 390 |
|
|---|
| 391 | --So dodavanje na indeksi na Rating tabelata, vremeto za izvrshuvanje na funkcija so taa tabela se namali od 1 cas na 1.7 minuti — 35x zabrzuvanje na 24 milioni zapisi
|
|---|
| 392 |
|
|---|
| 393 |
|
|---|
| 394 |
|
|---|
| 395 |
|
|---|