Changes between Version 4 and Version 5 of AdvancedDatabaseDevelopment
- Timestamp:
- 08/06/26 12:58:33 (10 hours ago)
Legend:
- Unmodified
- Added
- Removed
- Modified
-
AdvancedDatabaseDevelopment
v4 v5 6 6 7 7 Системот мора да обезбеди дека: 8 * Кога корисникот се запишува на курс по даден course_id, автоматски се избира и запишува моментално активната верзија на курсот (course_version.active = true) за тој курс.8 * Кога корисникот се запишува на курс по дадена верзија на курс, автоматски се пренасочува кон моментално активната верзија на тој курс (course_version.is_active = true). 9 9 * Не е дозволено запишување на курс за кој воопшто нема активна верзија. 10 10 * Секоја верзија се третира независно: корисникот може да има повеќе запишувања на различни верзии на истиот курс, без разлика дали претходните се завршени или не. … … 14 14 ==== Тригери ==== 15 15 16 BEFORE INSERT тригер на enrollment за автоматско доделување на активната верзија на curse(course version).16 BEFORE INSERT тригер на enrollment за автоматско доделување на активната верзија на курсот (course version). 17 17 {{{ 18 18 CREATE OR REPLACE FUNCTION set_active_course_version_on_enrollment() … … 20 20 AS $$ 21 21 DECLARE 22 v_course_version_id INTEGER; 23 BEGIN 24 SELECT cv.id 22 v_course_id BIGINT; 23 v_course_version_id BIGINT; 24 BEGIN 25 SELECT cv.course_id 26 INTO v_course_id 27 FROM course_version cv 28 WHERE cv.course_version_id = NEW.course_version_id; 29 30 IF v_course_id IS NULL THEN 31 RAISE EXCEPTION 'Course version % does not exist', NEW.course_version_id; 32 END IF; 33 34 SELECT cv.course_version_id 25 35 INTO v_course_version_id 26 36 FROM course_version cv 27 WHERE cv.course_id = NEW.course_id28 AND cv. active = TRUE37 WHERE cv.course_id = v_course_id 38 AND cv.is_active = TRUE 29 39 ORDER BY cv.version_number DESC 30 40 LIMIT 1; 31 41 32 42 IF v_course_version_id IS NULL THEN 33 RAISE EXCEPTION 'No active course version found for course_id=%', NEW.course_id;43 RAISE EXCEPTION 'No active course version found for course_id=%', v_course_id; 34 44 END IF; 35 45 … … 51 61 Функција за креирање на нов enrollment. При креирање се зема најновата активна верзија на курсот. 52 62 {{{ 53 CREATE OR REPLACE FUNCTION create_enrollment_for_active_version(p_user_id INTEGER, p_course_id INTEGER)54 RETURNS INTEGER55 AS $$ 56 DECLARE 57 v_course_version_id INTEGER;58 v_enrollment_id INTEGER;59 BEGIN 60 SELECT cv. id63 CREATE OR REPLACE FUNCTION create_enrollment_for_active_version(p_user_id BIGINT, p_course_id BIGINT) 64 RETURNS BIGINT 65 AS $$ 66 DECLARE 67 v_course_version_id BIGINT; 68 v_enrollment_id BIGINT; 69 BEGIN 70 SELECT cv.course_version_id 61 71 INTO v_course_version_id 62 72 FROM course_version cv 63 73 WHERE cv.course_id = p_course_id 64 AND cv. active = TRUE74 AND cv.is_active = TRUE 65 75 ORDER BY cv.version_number DESC 66 76 LIMIT 1; … … 70 80 END IF; 71 81 72 INSERT INTO enrollment (user_id, course_ id, course_version_id, enrollment_purchase_date,status)73 VALUES (p_user_id, p_course_id, v_course_version_id, NOW(), 'PENDING')74 RETURNING id INTO v_enrollment_id;82 INSERT INTO enrollment (user_id, course_version_id, purchase_date, enrollment_status) 83 VALUES (p_user_id, v_course_version_id, CURRENT_DATE, 'pending') 84 RETURNING enrollment_id INTO v_enrollment_id; 75 85 76 86 RETURN v_enrollment_id; … … 86 96 CREATE OR REPLACE VIEW enrollments_with_active_version AS 87 97 SELECT 88 e. id AS enrollment_id,89 u. id AS user_id,98 e.enrollment_id AS enrollment_id, 99 u.user_id AS user_id, 90 100 u.name AS user_name, 91 c. id AS course_id,101 c.course_id AS course_id, 92 102 ct.title_short AS course_title, 93 cv. id AS course_version_id,103 cv.course_version_id AS course_version_id, 94 104 cv.version_number AS course_version_number, 95 cv. active AS is_version_active,96 e. enrollment_purchase_date AS purchase_date,97 e. status AS enrollment_status105 cv.is_active AS is_version_active, 106 e.purchase_date AS purchase_date, 107 e.enrollment_status AS enrollment_status 98 108 FROM enrollment e 99 JOIN user u ON e.user_id = u.id 100 JOIN course c ON e.course_id = c.id 101 JOIN course_version cv ON e.course_version_id = cv.id 102 JOIN course_translate ct ON c.id = ct.course_id AND ct.language = 'mk'; 109 JOIN "user" u ON e.user_id = u.user_id 110 JOIN course_version cv ON e.course_version_id = cv.course_version_id 111 JOIN course c ON cv.course_id = c.course_id 112 JOIN course_translate ct ON c.course_id = ct.course_id 113 JOIN language l ON l.id = ct.language_id AND l.value = 'mk'; 103 114 }}} 104 115 … … 110 121 111 122 Системот мора да обезбеди дека: 112 * Само една верзија може да биде активна ( active = true) по курс во исто време123 * Само една верзија може да биде активна (is_active = true) по курс во исто време 113 124 * Кога се активира нова верзија, претходните активни верзии на истиот курс автоматски се деактивираат 114 125 * Не смее да се избрише верзија која има активни (незавршени) запишувања (enrollments) … … 118 129 ==== Тригери ==== 119 130 120 BEFORE INSERT тригер во course_version, каде само таа верзија што се креира е active, останатите не се. Дополнително се ажурираат и уште некои атрибути.131 BEFORE INSERT тригер во course_version, каде само таа верзија што се креира е is_active, останатите не се. Дополнително се ажурираат и уште некои атрибути. 121 132 {{{ 122 133 CREATE OR REPLACE FUNCTION ensure_single_active_version_when_created() … … 127 138 BEGIN 128 139 UPDATE course_version 129 SET active = FALSE 130 WHERE course_id = NEW.course_id; 140 SET is_active = FALSE 141 WHERE course_id = NEW.course_id 142 AND is_active = TRUE; 131 143 132 144 SELECT COALESCE(MAX(version_number), 0) + 1 INTO v_num_new_version … … 134 146 WHERE course_id = NEW.course_id; 135 147 136 NEW. active := TRUE;137 NEW.creat ion_date:= CURRENT_DATE;148 NEW.is_active := TRUE; 149 NEW.created_at := CURRENT_DATE; 138 150 NEW.version_number := v_num_new_version; 139 151 … … 149 161 }}} 150 162 151 BEFORE UPDATE тригер за атрибутот active во course_version, со цел осигурување дека постои само една активна верзија од курсот.163 BEFORE UPDATE тригер за атрибутот is_active во course_version, со цел осигурување дека постои само една активна верзија од курсот. 152 164 {{{ 153 165 CREATE OR REPLACE FUNCTION ensure_single_active_version() … … 155 167 AS $$ 156 168 BEGIN 157 IF NEW. active = TRUE THEN169 IF NEW.is_active = TRUE THEN 158 170 UPDATE course_version 159 SET active = FALSE171 SET is_active = FALSE 160 172 WHERE course_id = NEW.course_id 161 AND id != NEW.id162 AND active = TRUE;173 AND course_version_id != NEW.course_version_id 174 AND is_active = TRUE; 163 175 END IF; 164 176 … … 169 181 170 182 CREATE TRIGGER trg_ensure_single_active_version 171 BEFORE UPDATE OF active ON course_version172 FOR EACH ROW 173 WHEN (NEW. active = TRUE)183 BEFORE UPDATE OF is_active ON course_version 184 FOR EACH ROW 185 WHEN (NEW.is_active = TRUE) 174 186 EXECUTE FUNCTION ensure_single_active_version(); 175 187 }}} … … 185 197 SELECT COUNT(*) INTO v_active_enrollments 186 198 FROM enrollment 187 WHERE course_version_id = OLD. id;199 WHERE course_version_id = OLD.course_version_id; 188 200 189 201 IF v_active_enrollments > 0 THEN … … 208 220 CREATE OR REPLACE VIEW course_latest_versions AS 209 221 SELECT 210 c. id AS course_id,222 c.course_id AS course_id, 211 223 ct.title_short AS course_title, 212 cv. id AS version_id,224 cv.course_version_id AS version_id, 213 225 cv.version_number, 214 cv. active,215 cv. version_creation_date,216 COUNT(DISTINCT e. id) FILTER (WHERE e.completion_date IS NULL) AS active_enrollments,217 COUNT(DISTINCT e. id) AS total_enrollments226 cv.is_active, 227 cv.created_at, 228 COUNT(DISTINCT e.enrollment_id) FILTER (WHERE e.completion_date IS NULL) AS active_enrollments, 229 COUNT(DISTINCT e.enrollment_id) AS total_enrollments 218 230 FROM course c 219 JOIN course_version cv ON c.id = cv.course_id 220 JOIN course_translate ct ON c.id = ct.course_id AND ct.language = 'mk' 221 LEFT JOIN enrollment e ON cv.id = e.course_version_id 231 JOIN course_version cv ON c.course_id = cv.course_id 232 JOIN course_translate ct ON c.course_id = ct.course_id 233 JOIN language l ON l.id = ct.language_id AND l.value = 'mk' 234 LEFT JOIN enrollment e ON cv.course_version_id = e.course_version_id 222 235 WHERE cv.version_number = ( 223 236 SELECT MAX(version_number) 224 237 FROM course_version 225 WHERE course_id = c. id238 WHERE course_id = c.course_id 226 239 ) 227 GROUP BY c. id, ct.title_short, cv.id, cv.version_number, cv.active, cv.version_creation_date;240 GROUP BY c.course_id, ct.title_short, cv.course_version_id, cv.version_number, cv.is_active, cv.created_at; 228 241 }}} 229 242 … … 249 262 CHECK (VALUE >= 1 AND VALUE <= 5); 250 263 264 ALTER TABLE review DROP CONSTRAINT IF EXISTS ck_review_rating_range; 251 265 ALTER TABLE review ALTER COLUMN rating TYPE rating_scale; 252 266 }}} … … 259 273 RETURNS TRIGGER AS $$ 260 274 DECLARE 261 v_completion_date TIMESTAMP;275 v_completion_date DATE; 262 276 v_existing_review_count INTEGER; 263 277 BEGIN 264 278 SELECT completion_date INTO v_completion_date 265 279 FROM enrollment 266 WHERE id = NEW.enrollment_id;280 WHERE enrollment_id = NEW.enrollment_id; 267 281 268 282 IF v_completion_date IS NULL THEN … … 294 308 CREATE OR REPLACE VIEW course_average_ratings AS 295 309 SELECT 296 c. id AS course_id,310 c.course_id AS course_id, 297 311 ct.title_short AS course_title, 298 COUNT(r. id) AS total_reviews,312 COUNT(r.review_id) AS total_reviews, 299 313 AVG(r.rating)::NUMERIC(3,2) AS average_rating, 300 COUNT(r. id) FILTER (WHERE r.rating = 5) AS five_star_count,301 COUNT(r. id) FILTER (WHERE r.rating = 4) AS four_star_count,302 COUNT(r. id) FILTER (WHERE r.rating = 3) AS three_star_count,303 COUNT(r. id) FILTER (WHERE r.rating = 2) AS two_star_count,304 COUNT(r. id) FILTER (WHERE r.rating = 1) AS one_star_count314 COUNT(r.review_id) FILTER (WHERE r.rating = 5) AS five_star_count, 315 COUNT(r.review_id) FILTER (WHERE r.rating = 4) AS four_star_count, 316 COUNT(r.review_id) FILTER (WHERE r.rating = 3) AS three_star_count, 317 COUNT(r.review_id) FILTER (WHERE r.rating = 2) AS two_star_count, 318 COUNT(r.review_id) FILTER (WHERE r.rating = 1) AS one_star_count 305 319 FROM course c 306 JOIN course_translate ct ON c.id = ct.course_id AND ct.language = 'mk' 307 LEFT JOIN course_version cv ON c.id = cv.course_id 308 LEFT JOIN enrollment e ON cv.id = e.course_version_id 309 LEFT JOIN review r ON e.id = r.enrollment_id 310 GROUP BY c.id, ct.title_short; 320 JOIN course_translate ct ON c.course_id = ct.course_id 321 JOIN language l ON l.id = ct.language_id AND l.value = 'mk' 322 LEFT JOIN course_version cv ON c.course_id = cv.course_id 323 LEFT JOIN enrollment e ON cv.course_version_id = e.course_version_id 324 LEFT JOIN review r ON e.enrollment_id = r.enrollment_id 325 GROUP BY c.course_id, ct.title_short; 311 326 }}} 312 327 … … 319 334 Системот мора да обезбеди дека: 320 335 * Корисникот може да користи само една бесплатна консултација 321 * Може да се креира meeting_ reminder за бесплатна консултација само ако корисникот сè уште не ја искористил322 * После креирање на meeting_ reminder за бесплатна консултација, флаготused_free_consultation автоматски се поставува на TRUE336 * Може да се креира meeting_email_reminder за бесплатна консултација само ако корисникот сè уште не ја искористил 337 * После креирање на meeting_email_reminder за бесплатна консултација, флагот has_used_free_consultation автоматски се поставува на TRUE 323 338 324 339 === Имплементација === … … 326 341 ==== Тригери ==== 327 342 328 BEFORE INSERT тригер на meeting reminder, каде не смее да се креира нов митинг и потсетување по емаил доколку корисникот веќе имал бесплатна консултација.343 BEFORE INSERT тригер на meeting email reminder, каде не смее да се креира нов митинг и потсетување по емаил доколку корисникот веќе имал бесплатна консултација. 329 344 {{{ 330 345 CREATE OR REPLACE FUNCTION check_free_consultation_eligibility() 331 346 RETURNS TRIGGER AS $$ 332 347 DECLARE 333 v_used_free_consultation BOOLEAN; 334 BEGIN 335 SELECT used_free_consultation INTO v_used_free_consultation 336 FROM _user 337 WHERE id = NEW.user_id; 338 339 IF v_used_free_consultation = TRUE THEN 348 v_has_used_free_consultation BOOLEAN; 349 BEGIN 350 SELECT has_used_free_consultation INTO v_has_used_free_consultation 351 FROM "user" 352 WHERE user_id = NEW.user_id; 353 354 IF v_has_used_free_consultation IS NULL THEN 355 RAISE EXCEPTION 'User % does not exist', NEW.user_id; 356 END IF; 357 358 IF v_has_used_free_consultation = TRUE THEN 340 359 RAISE EXCEPTION 'User has already used their free consultation'; 341 360 END IF; … … 346 365 347 366 CREATE TRIGGER trg_check_free_consultation_before_meeting 348 BEFORE INSERT ON meeting_ reminder367 BEFORE INSERT ON meeting_email_reminder 349 368 FOR EACH ROW 350 369 EXECUTE FUNCTION check_free_consultation_eligibility(); 351 370 }}} 352 371 353 AFTER INSERT тригер на meeting reminder, каде одкако ќе се закаже состанокот да се маркира дека тој корисник го има искористено своето право за бесплатна консултативна сесија со експерт.372 AFTER INSERT тригер на meeting email reminder, каде одкако ќе се закаже состанокот да се маркира дека тој корисник го има искористено своето право за бесплатна консултативна сесија со експерт. 354 373 {{{ 355 374 CREATE OR REPLACE FUNCTION mark_free_consultation_as_used() 356 375 RETURNS TRIGGER AS $$ 357 376 BEGIN 358 UPDATE _user359 SET used_free_consultation = TRUE360 WHERE id = NEW.user_id361 AND used_free_consultation = FALSE;377 UPDATE "user" 378 SET has_used_free_consultation = TRUE 379 WHERE user_id = NEW.user_id 380 AND has_used_free_consultation = FALSE; 362 381 363 382 RETURN NEW; … … 366 385 367 386 CREATE TRIGGER trg_mark_free_consultation_used 368 AFTER INSERT ON meeting_ reminder387 AFTER INSERT ON meeting_email_reminder 369 388 FOR EACH ROW 370 389 EXECUTE FUNCTION mark_free_consultation_as_used(); … … 375 394 Функција која кажува дали корисникот може да закаже бесплатна консултативна сесија со експерт. 376 395 {{{ 377 CREATE OR REPLACE FUNCTION can_user_schedule_free_consultation(p_user_id INTEGER)396 CREATE OR REPLACE FUNCTION can_user_schedule_free_consultation(p_user_id BIGINT) 378 397 RETURNS TABLE( 379 398 can_schedule BOOLEAN, … … 382 401 AS $$ 383 402 DECLARE 384 v_ used_free_consultation BOOLEAN;385 BEGIN 386 SELECT used_free_consultation INTO v_used_free_consultation387 FROM _user388 WHERE id = p_user_id;389 390 IF v_ used_free_consultation = TRUETHEN391 RETURN QUERY SELECT FALSE, ' Free consultation already used';403 v_has_used_free_consultation BOOLEAN; 404 BEGIN 405 SELECT has_used_free_consultation INTO v_has_used_free_consultation 406 FROM "user" 407 WHERE user_id = p_user_id; 408 409 IF v_has_used_free_consultation IS NULL THEN 410 RETURN QUERY SELECT FALSE, 'User does not exist'::TEXT; 392 411 RETURN; 393 412 END IF; 394 395 RETURN QUERY SELECT TRUE, 'Eligible for free consultation'; 396 END; 397 $$ 398 LANGUAGE plpgsql; 399 }}} 400 413 414 IF v_has_used_free_consultation = TRUE THEN 415 RETURN QUERY SELECT FALSE, 'Free consultation already used'::TEXT; 416 RETURN; 417 END IF; 418 419 RETURN QUERY SELECT TRUE, 'Eligible for free consultation'::TEXT; 420 END; 421 $$ 422 LANGUAGE plpgsql; 423 }}}
