| | 1 | --------------Функции------------------------------ |
| | 2 | ---------- Вкупна цена со попуст--------- |
| | 3 | CREATE OR REPLACE FUNCTION get_reservation_total_with_discount( |
| | 4 | p_reservation_id INT, |
| | 5 | p_promotion_id INT |
| | 6 | ) |
| | 7 | RETURNS FLOAT AS $$ |
| | 8 | DECLARE |
| | 9 | v_total FLOAT; |
| | 10 | v_discount FLOAT; |
| | 11 | v_final FLOAT; |
| | 12 | BEGIN |
| | 13 | -- Вкупна цена на сите тикети за резервацијата |
| | 14 | SELECT SUM(price) |
| | 15 | INTO v_total |
| | 16 | FROM TICKET |
| | 17 | WHERE reservation_id = p_reservation_id; |
| | 18 | |
| | 19 | -- Попустот од промоцијата (во проценти) |
| | 20 | SELECT discount |
| | 21 | INTO v_discount |
| | 22 | FROM PROMOTION |
| | 23 | WHERE promotion_id = p_promotion_id; |
| | 24 | |
| | 25 | -- Пресметај крајна цена |
| | 26 | v_final := v_total - (v_total * v_discount / 100); |
| | 27 | |
| | 28 | RETURN v_final; |
| | 29 | END; |
| | 30 | $$ LANGUAGE plpgsql; |
| | 31 | -----------------Слободни места------------- |
| | 32 | CREATE OR REPLACE FUNCTION get_available_seats( |
| | 33 | p_screening_id INT |
| | 34 | ) |
| | 35 | RETURNS INT AS $$ |
| | 36 | DECLARE |
| | 37 | v_capacity INT; |
| | 38 | v_sold INT; |
| | 39 | BEGIN |
| | 40 | -- Капацитет на салата |
| | 41 | SELECT ch.capacity |
| | 42 | INTO v_capacity |
| | 43 | FROM SCREENING s |
| | 44 | JOIN CINEMA_HALL ch ON ch.cinemahall_id = s.cinemahall_id |
| | 45 | WHERE s.screening_id = p_screening_id; |
| | 46 | |
| | 47 | -- Продадени тикети |
| | 48 | SELECT COUNT(*) |
| | 49 | INTO v_sold |
| | 50 | FROM TICKET |
| | 51 | WHERE screening_id = p_screening_id; |
| | 52 | |
| | 53 | RETURN v_capacity - v_sold; |
| | 54 | END; |
| | 55 | $$ LANGUAGE plpgsql; |
| | 56 | -------------------Просечен рејтинг-------------- |
| | 57 | CREATE OR REPLACE FUNCTION get_movie_avg_rating( |
| | 58 | p_movie_id INT |
| | 59 | ) |
| | 60 | RETURNS FLOAT AS $$ |
| | 61 | DECLARE |
| | 62 | v_avg FLOAT; |
| | 63 | BEGIN |
| | 64 | SELECT ROUND(AVG(rating)::numeric, 2) |
| | 65 | INTO v_avg |
| | 66 | FROM REVIEW |
| | 67 | WHERE movie_id = p_movie_id; |
| | 68 | |
| | 69 | RETURN v_avg; |
| | 70 | END; |
| | 71 | $$ LANGUAGE plpgsql; |
| | 72 | ----------------Процедури-------------------- |
| | 73 | ----------------Генерирање тикети за проекција — ------------ |
| | 74 | CREATE OR REPLACE PROCEDURE generate_tickets_for_screening( |
| | 75 | p_screening_id INT, |
| | 76 | p_price FLOAT |
| | 77 | ) |
| | 78 | LANGUAGE plpgsql AS $$ |
| | 79 | DECLARE |
| | 80 | v_cinemahall_id INT; |
| | 81 | v_cinema_id INT; |
| | 82 | v_seat RECORD; |
| | 83 | v_count INT := 0; |
| | 84 | v_screening_date DATE; |
| | 85 | BEGIN |
| | 86 | -- Провери дали проекцијата постои |
| | 87 | IF NOT EXISTS (SELECT 1 FROM SCREENING WHERE screening_id = p_screening_id) THEN |
| | 88 | RAISE EXCEPTION 'Проекцијата со ID % не постои!', p_screening_id; |
| | 89 | END IF; |
| | 90 | |
| | 91 | -- Провери дали датумот на проекцијата не е во минатото |
| | 92 | SELECT date INTO v_screening_date FROM SCREENING WHERE screening_id = p_screening_id; |
| | 93 | IF v_screening_date < CURRENT_DATE THEN |
| | 94 | RAISE EXCEPTION 'Не може да се генерираат тикети за минати проекции! Датум: %', v_screening_date; |
| | 95 | END IF; |
| | 96 | |
| | 97 | -- Провери дали цената е валидна |
| | 98 | IF p_price <= 0 THEN |
| | 99 | RAISE EXCEPTION 'Цената мора да биде поголема од 0!'; |
| | 100 | END IF; |
| | 101 | |
| | 102 | -- Земи ја салата |
| | 103 | SELECT cinemahall_id INTO v_cinemahall_id |
| | 104 | FROM SCREENING WHERE screening_id = p_screening_id; |
| | 105 | |
| | 106 | -- Провери дали салата постои |
| | 107 | IF NOT EXISTS (SELECT 1 FROM CINEMA_HALL WHERE cinemahall_id = v_cinemahall_id) THEN |
| | 108 | RAISE EXCEPTION 'Салата со ID % не постои!', v_cinemahall_id; |
| | 109 | END IF; |
| | 110 | |
| | 111 | -- Провери дали салата е активна |
| | 112 | IF NOT EXISTS (SELECT 1 FROM CINEMA_HALL WHERE cinemahall_id = v_cinemahall_id AND status = 'ACTIVE') THEN |
| | 113 | RAISE EXCEPTION 'Салата со ID % не е активна!', v_cinemahall_id; |
| | 114 | END IF; |
| | 115 | |
| | 116 | -- Провери дали киното постои |
| | 117 | SELECT cinema_id INTO v_cinema_id FROM CINEMA_HALL WHERE cinemahall_id = v_cinemahall_id; |
| | 118 | IF NOT EXISTS (SELECT 1 FROM CINEMA WHERE cinema_id = v_cinema_id) THEN |
| | 119 | RAISE EXCEPTION 'Киното со ID % не постои!', v_cinema_id; |
| | 120 | END IF; |
| | 121 | |
| | 122 | -- Провери дали воопшто има седишта во салата |
| | 123 | IF NOT EXISTS (SELECT 1 FROM SEAT WHERE cinemahall_id = v_cinemahall_id) THEN |
| | 124 | RAISE EXCEPTION 'Салата со ID % нема седишта!', v_cinemahall_id; |
| | 125 | END IF; |
| | 126 | |
| | 127 | -- За секое седиште во салата креирај тикет |
| | 128 | FOR v_seat IN |
| | 129 | SELECT seat_id FROM SEAT WHERE cinemahall_id = v_cinemahall_id |
| | 130 | LOOP |
| | 131 | IF NOT EXISTS ( |
| | 132 | SELECT 1 FROM TICKET |
| | 133 | WHERE seat_id = v_seat.seat_id |
| | 134 | AND screening_id = p_screening_id |
| | 135 | ) THEN |
| | 136 | INSERT INTO TICKET (price, user_id, reservation_id, seat_id, screening_id) |
| | 137 | VALUES (p_price, NULL, NULL, v_seat.seat_id, p_screening_id); |
| | 138 | v_count := v_count + 1; |
| | 139 | END IF; |
| | 140 | END LOOP; |
| | 141 | |
| | 142 | IF v_count = 0 THEN |
| | 143 | RAISE NOTICE 'Сите тикети за проекција % веќе се генерирани!', p_screening_id; |
| | 144 | ELSE |
| | 145 | RAISE NOTICE 'Генерирани % тикети за проекција % во сала % по цена %', |
| | 146 | v_count, p_screening_id, v_cinemahall_id, p_price; |
| | 147 | END IF; |
| | 148 | END; |
| | 149 | $$; |
| | 150 | -------------------------Процедура 2 — Купување тикет со производи----------------- |
| | 151 | CREATE OR REPLACE PROCEDURE purchase_ticket_with_products( |
| | 152 | p_ticket_id INT, |
| | 153 | p_user_id VARCHAR(13), |
| | 154 | p_reservation_id INT, |
| | 155 | p_products INT[] |
| | 156 | ) |
| | 157 | LANGUAGE plpgsql AS $$ |
| | 158 | DECLARE |
| | 159 | v_product_id INT; |
| | 160 | v_price FLOAT; |
| | 161 | v_screening_id INT; |
| | 162 | v_screening_date DATE; |
| | 163 | v_total_price FLOAT := 0; |
| | 164 | BEGIN |
| | 165 | -- Провери дали тикетот постои |
| | 166 | IF NOT EXISTS (SELECT 1 FROM TICKET WHERE ticket_id = p_ticket_id) THEN |
| | 167 | RAISE EXCEPTION 'Тикетот со ID % не постои!', p_ticket_id; |
| | 168 | END IF; |
| | 169 | |
| | 170 | -- Провери дали тикетот е слободен |
| | 171 | IF EXISTS (SELECT 1 FROM TICKET WHERE ticket_id = p_ticket_id AND user_id IS NOT NULL) THEN |
| | 172 | RAISE EXCEPTION 'Тикетот со ID % е веќе купен!', p_ticket_id; |
| | 173 | END IF; |
| | 174 | |
| | 175 | -- Провери дали корисникот постои |
| | 176 | IF NOT EXISTS (SELECT 1 FROM "USER" WHERE user_id = p_user_id) THEN |
| | 177 | RAISE EXCEPTION 'Корисникот % не постои!', p_user_id; |
| | 178 | END IF; |
| | 179 | |
| | 180 | -- Провери дали резервацијата постои |
| | 181 | IF NOT EXISTS (SELECT 1 FROM RESERVATION WHERE reservation_id = p_reservation_id) THEN |
| | 182 | RAISE EXCEPTION 'Резервацијата со ID % не постои!', p_reservation_id; |
| | 183 | END IF; |
| | 184 | |
| | 185 | -- Провери дали резервацијата не е откажана |
| | 186 | IF EXISTS (SELECT 1 FROM RESERVATION WHERE reservation_id = p_reservation_id AND status = 'CANCELLED') THEN |
| | 187 | RAISE EXCEPTION 'Резервацијата со ID % е откажана!', p_reservation_id; |
| | 188 | END IF; |
| | 189 | |
| | 190 | -- Провери дали проекцијата не е во минатото |
| | 191 | SELECT screening_id INTO v_screening_id FROM TICKET WHERE ticket_id = p_ticket_id; |
| | 192 | SELECT date INTO v_screening_date FROM SCREENING WHERE screening_id = v_screening_id; |
| | 193 | IF v_screening_date < CURRENT_DATE THEN |
| | 194 | RAISE EXCEPTION 'Не може да се купи тикет за минати проекции! Датум: %', v_screening_date; |
| | 195 | END IF; |
| | 196 | |
| | 197 | -- Провери дали производите постојат и пресметај вкупна цена |
| | 198 | FOREACH v_product_id IN ARRAY p_products |
| | 199 | LOOP |
| | 200 | IF NOT EXISTS (SELECT 1 FROM PRODUCT WHERE product_id = v_product_id) THEN |
| | 201 | RAISE EXCEPTION 'Производот со ID % не постои!', v_product_id; |
| | 202 | END IF; |
| | 203 | SELECT price INTO v_price FROM PRODUCT WHERE product_id = v_product_id; |
| | 204 | v_total_price := v_total_price + v_price; |
| | 205 | END LOOP; |
| | 206 | |
| | 207 | -- Ажурирај го тикетот |
| | 208 | UPDATE TICKET |
| | 209 | SET user_id = p_user_id, |
| | 210 | reservation_id = p_reservation_id |
| | 211 | WHERE ticket_id = p_ticket_id; |
| | 212 | |
| | 213 | -- Додај ги производите |
| | 214 | FOREACH v_product_id IN ARRAY p_products |
| | 215 | LOOP |
| | 216 | INSERT INTO PURCHASED_PRODUCT (numbers, ticket_id, product_id) |
| | 217 | VALUES (1, p_ticket_id, v_product_id); |
| | 218 | END LOOP; |
| | 219 | |
| | 220 | RAISE NOTICE 'Тикетот % е успешно купен! Корисник: %, Производи: %, Вкупна цена на производи: %', |
| | 221 | p_ticket_id, p_user_id, array_length(p_products, 1), v_total_price; |
| | 222 | |
| | 223 | EXCEPTION |
| | 224 | WHEN OTHERS THEN |
| | 225 | RAISE NOTICE 'Грешка: % — се прави ROLLBACK!', SQLERRM; |
| | 226 | ROLLBACK; |
| | 227 | RETURN; |
| | 228 | END; |
| | 229 | $$; |
| | 230 | ----------------------------Процедура 3 — Додај review за филм-------------------- |
| | 231 | CREATE OR REPLACE PROCEDURE add_movie_review( |
| | 232 | p_user_id VARCHAR(13), |
| | 233 | p_movie_id INT, |
| | 234 | p_rating INT, |
| | 235 | p_comment VARCHAR(255) |
| | 236 | ) |
| | 237 | LANGUAGE plpgsql AS $$ |
| | 238 | DECLARE |
| | 239 | v_existing_reviews INT; |
| | 240 | v_avg_rating FLOAT; |
| | 241 | BEGIN |
| | 242 | -- Провери дали корисникот постои |
| | 243 | IF NOT EXISTS (SELECT 1 FROM "USER" WHERE user_id = p_user_id) THEN |
| | 244 | RAISE EXCEPTION 'Корисникот % не постои!', p_user_id; |
| | 245 | END IF; |
| | 246 | |
| | 247 | -- Провери дали филмот постои |
| | 248 | IF NOT EXISTS (SELECT 1 FROM MOVIE WHERE movie_id = p_movie_id) THEN |
| | 249 | RAISE EXCEPTION 'Филмот со ID % не постои!', p_movie_id; |
| | 250 | END IF; |
| | 251 | |
| | 252 | -- Провери дали рејтингот е валиден |
| | 253 | IF p_rating < 1 OR p_rating > 10 THEN |
| | 254 | RAISE EXCEPTION 'Рејтингот мора да биде помеѓу 1 и 10! Внесен рејтинг: %', p_rating; |
| | 255 | END IF; |
| | 256 | |
| | 257 | -- Провери дали коментарот е празен |
| | 258 | IF p_comment IS NULL OR TRIM(p_comment) = '' THEN |
| | 259 | RAISE EXCEPTION 'Коментарот не може да биде празен!'; |
| | 260 | END IF; |
| | 261 | |
| | 262 | -- Провери дали корисникот купил тикет за овој филм |
| | 263 | IF NOT EXISTS ( |
| | 264 | SELECT 1 FROM TICKET t |
| | 265 | JOIN SCREENING s ON s.screening_id = t.screening_id |
| | 266 | WHERE t.user_id = p_user_id |
| | 267 | AND s.movie_id = p_movie_id |
| | 268 | ) THEN |
| | 269 | RAISE EXCEPTION 'Корисникот % не купил тикет за филмот %!', p_user_id, p_movie_id; |
| | 270 | END IF; |
| | 271 | |
| | 272 | -- Провери дали корисникот веќе оставил review за овој филм |
| | 273 | IF EXISTS (SELECT 1 FROM REVIEW WHERE user_id = p_user_id AND movie_id = p_movie_id) THEN |
| | 274 | RAISE EXCEPTION 'Корисникот % веќе оставил review за филмот %!', p_user_id, p_movie_id; |
| | 275 | END IF; |
| | 276 | |
| | 277 | -- Додај review |
| | 278 | INSERT INTO REVIEW (comment, date, user_id, movie_id, rating) |
| | 279 | VALUES (p_comment, CURRENT_DATE, p_user_id, p_movie_id, p_rating); |
| | 280 | |
| | 281 | -- Пресметај нов просечен рејтинг |
| | 282 | SELECT COUNT(*), ROUND(AVG(rating)::numeric, 2) |
| | 283 | INTO v_existing_reviews, v_avg_rating |
| | 284 | FROM REVIEW WHERE movie_id = p_movie_id; |
| | 285 | |
| | 286 | RAISE NOTICE 'Review за филм % додаден! Рејтинг: %, Вкупно reviews: %, Просечен рејтинг: %', |
| | 287 | p_movie_id, p_rating, v_existing_reviews, v_avg_rating; |
| | 288 | END; |
| | 289 | $$; |
| | 290 | |
| | 291 | -----------------------Тригери------------------------------------ |
| | 292 | ----------------Тригер 1 — Спречи двојно резервирање на исто седиште----------- |
| | 293 | CREATE OR REPLACE FUNCTION check_seat_availability() |
| | 294 | RETURNS TRIGGER AS $$ |
| | 295 | BEGIN |
| | 296 | IF EXISTS ( |
| | 297 | SELECT 1 FROM TICKET |
| | 298 | WHERE seat_id = NEW.seat_id |
| | 299 | AND screening_id = NEW.screening_id |
| | 300 | ) THEN |
| | 301 | RAISE EXCEPTION 'Седиштето % е веќе зафатено за оваа проекција!', NEW.seat_id; |
| | 302 | END IF; |
| | 303 | RETURN NEW; |
| | 304 | END; |
| | 305 | $$ LANGUAGE plpgsql; |
| | 306 | |
| | 307 | CREATE OR REPLACE TRIGGER trg_check_seat_availability |
| | 308 | BEFORE INSERT ON TICKET |
| | 309 | FOR EACH ROW |
| | 310 | EXECUTE FUNCTION check_seat_availability(); |
| | 311 | -----------------------Тригер 2 — Автоматска нотификација при резервација----------- |
| | 312 | CREATE OR REPLACE FUNCTION notify_on_reservation() |
| | 313 | RETURNS TRIGGER AS $$ |
| | 314 | BEGIN |
| | 315 | INSERT INTO NOTIFICATION (message, date, user_id) |
| | 316 | VALUES ( |
| | 317 | 'Вашата резервација е успешно креирана со ID: ' || NEW.reservation_id, |
| | 318 | CURRENT_DATE, |
| | 319 | NEW.user_id |
| | 320 | ); |
| | 321 | RETURN NEW; |
| | 322 | END; |
| | 323 | $$ LANGUAGE plpgsql; |
| | 324 | |
| | 325 | CREATE OR REPLACE TRIGGER trg_notify_on_reservation |
| | 326 | AFTER INSERT ON USER_RESERVATION |
| | 327 | FOR EACH ROW |
| | 328 | EXECUTE FUNCTION notify_on_reservation(); |
| | 329 | -----------------------Тригер 3 — Автоматски COMPLETED статус---------------------- |
| | 330 | CREATE OR REPLACE FUNCTION complete_past_reservations() |
| | 331 | RETURNS TRIGGER AS $$ |
| | 332 | BEGIN |
| | 333 | IF NEW.date < CURRENT_DATE THEN |
| | 334 | UPDATE RESERVATION r |
| | 335 | SET status = 'COMPLETED' |
| | 336 | FROM TICKET t |
| | 337 | WHERE t.reservation_id = r.reservation_id |
| | 338 | AND t.screening_id = NEW.screening_id |
| | 339 | AND r.status = 'CONFIRMED'; |
| | 340 | END IF; |
| | 341 | RETURN NEW; |
| | 342 | END; |
| | 343 | $$ LANGUAGE plpgsql; |
| | 344 | CREATE EXTENSION IF NOT EXISTS postgis; |
| | 345 | |
| | 346 | |
| | 347 | |
| | 348 | -- Тест процедура 1 — генерирај тикети |
| | 349 | CALL generate_tickets_for_screening(55105026, 350.00); |
| | 350 | |
| | 351 | ALTER TABLE TICKET ALTER COLUMN user_id DROP NOT NULL; |
| | 352 | ALTER TABLE TICKET ALTER COLUMN reservation_id DROP NOT NULL; |
| | 353 | |
| | 354 | SELECT column_name, is_nullable |
| | 355 | FROM information_schema.columns |
| | 356 | WHERE table_name = 'ticket' |
| | 357 | AND column_name IN ('user_id', 'reservation_id'); |
| | 358 | |
| | 359 | CALL generate_tickets_for_screening(55105026, 350.00); |
| | 360 | |
| | 361 | -- Земи слободен тикет од новата проекција |
| | 362 | SELECT ticket_id FROM TICKET |
| | 363 | WHERE screening_id = 55105026 |
| | 364 | AND user_id IS NULL |
| | 365 | LIMIT 1; |
| | 366 | |
| | 367 | -- Земи вистински user_id |
| | 368 | SELECT user_id FROM "USER" LIMIT 1; |
| | 369 | |
| | 370 | -- Земи вистински reservation_id со статус PENDING |
| | 371 | SELECT reservation_id FROM RESERVATION |
| | 372 | WHERE status = 'PENDING' |
| | 373 | LIMIT 1; |
| | 374 | |
| | 375 | -- Земи вистински product_id |
| | 376 | SELECT product_id FROM PRODUCT LIMIT 3; |
| | 377 | |
| | 378 | CALL purchase_ticket_with_products(12818005, 'admin01', 4000005, ARRAY[1, 2, 3]); |
| | 379 | CALL add_movie_review('admin01', 195, 8, 'Одличен филм, препорачувам!'); |
| | 380 | |
| | 381 | -- Земи филм за кој admin01 има тикет но нема review |
| | 382 | SELECT DISTINCT s.movie_id |
| | 383 | FROM TICKET t |
| | 384 | JOIN SCREENING s ON s.screening_id = t.screening_id |
| | 385 | WHERE t.user_id = 'admin01' |
| | 386 | AND s.movie_id NOT IN (SELECT movie_id FROM REVIEW WHERE user_id = 'admin01') |
| | 387 | LIMIT 1; |
| | 388 | |
| | 389 | SELECT DISTINCT s.movie_id |
| | 390 | FROM TICKET t |
| | 391 | JOIN SCREENING s ON s.screening_id = t.screening_id |
| | 392 | WHERE t.user_id = 'admin01' |
| | 393 | AND s.movie_id NOT IN (SELECT movie_id FROM REVIEW WHERE user_id = 'admin01') |
| | 394 | LIMIT 1; |
| | 395 | |
| | 396 | SELECT DISTINCT s.movie_id |
| | 397 | FROM TICKET t |
| | 398 | JOIN SCREENING s ON s.screening_id = t.screening_id |
| | 399 | WHERE t.user_id = 'admin01' |
| | 400 | AND s.movie_id NOT IN (SELECT movie_id FROM REVIEW WHERE user_id = 'admin01') |
| | 401 | LIMIT 1; |
| | 402 | SELECT user_id FROM "USER" WHERE user_id != 'admin01' LIMIT 1; |
| | 403 | |
| | 404 | SELECT DISTINCT s.movie_id |
| | 405 | FROM TICKET t |
| | 406 | JOIN SCREENING s ON s.screening_id = t.screening_id |
| | 407 | WHERE t.user_id = 'admin02' |
| | 408 | AND s.movie_id NOT IN (SELECT movie_id FROM REVIEW WHERE user_id = 'admin02') |
| | 409 | LIMIT 1; |
| | 410 | CALL add_movie_review('admin02', 195, 7, 'Многу добар филм!'); |