Changes between Version 1 and Version 2 of DatabaseProgramming


Ignore:
Timestamp:
05/26/26 23:48:39 (15 hours ago)
Author:
231088
Comment:

--

Legend:

Unmodified
Added
Removed
Modified
  • DatabaseProgramming

    v1 v2  
     1= Функции, Процедури и Тригери =
     2Во оваа фаза беа имплементирани функции, процедури и тригери кои ја поддржуваат основната бизнис логика на системот за booking на артисти и бендови. Секоја компонента е одговорна за одредена операција — од пресметување статистики, преку управување со bookings, до автоматска заштита од грешки.
     3
    14== Функции ==
    2 
    3 === '''fn_artist_average_rating(p_bookable_id INT)''' ===
    4 Оваа функција ја пресметува просечната оцена за даден артист/бенд (bookable) така што ги зема сите рецензии (''Review'') за неговите букинзи и ја враќа средната вредност заокружена на две децимали.
    5 
    6 Со тоа се имплементира бизнис логиката за прикажување на рејтинг на артистите во апликацијата и за статистика/сортирање по оцена.
    7 
    8 === '''fn_artist_total_earnings(p_bookable_id INT)''' ===
    9 Функцијата ја собира сумата на сите плаќања со статус ''PAID'' за сите букинзи поврзани со даден артист или бенд.
    10 
    11 Така се имплементира бизнис логиката за пресметка на вкупната заработка на артистот, која може да се прикаже во неговиот профил или во администраторскиот дел.
    12 
    13 === '''fn_check_artist_availability(p_bookable_id INT, p_date DATE)''' ===
    14 Оваа функција проверува дали артистот има барем еден запис во ''AvailabilitySlot'' со статус ''AVAILABLE'' на дадениот датум и враќа TRUE/FALSE.
    15 
    16 Со тоа се имплементира логиката за проверка на достапност при креирање понуда или букинг – апликацијата прво проверува дали артистот е слободен на конкретниот датум.
    17 
    18 === '''fn_total_client_bookings(p_client_id INT)''' ===
    19 Функцијата го брои бројот на букинзи за даден клиент преку поврзување на ''BookingRequest'', ''Offer'' и ''Booking'' и враќа цел број.
    20 
    21 Ова ја покрива бизнис логиката за статистика за клиентот (на пример колку настани има букирано), што може да се користи во неговиот профил или за аналитика.
     5Функциите се користат за пресметување и враќање на вредности кои се потребни на повеќе места во апликацијата. Секоја функција прима параметри, извршува логика врз базата и враќа резултат.
     6
     7=== 1. fn_artist_average_rating ===
     8Функцијата {{{fn_artist_average_rating}}} го пресметува просечниот рејтинг на артист или бенд врз основа на сите reviews оставени за нивните завршени bookings.
     9
     10Ги поврзува табелите {{{Review}}}, {{{Booking}}} и {{{Offer}}} за да го пронајде просекот на рејтинзите за конкретниот артист. Ако артистот сè уште нема reviews, функцијата враќа {{{0}}} наместо {{{NULL}}} преку употреба на {{{COALESCE}}}.
     11{{{
     12CREATE OR REPLACE FUNCTION fn_artist_average_rating(
     13    p_bookable_id INT
     14)
     15RETURNS NUMERIC(3,2)
     16AS
     17$$
     18DECLARE
     19    v_average_rating NUMERIC(3,2);
     20BEGIN
     21
     22    SELECT
     23        ROUND(AVG(r.rating), 2)
     24    INTO v_average_rating
     25    FROM Review r
     26    JOIN Booking bk
     27        ON r.booking_id = bk.booking_id
     28    JOIN Offer o
     29        ON bk.offer_id = o.offer_id
     30    WHERE o.bookable_id = p_bookable_id;
     31
     32    RETURN COALESCE(v_average_rating, 0);
     33
     34END;
     35$$
     36LANGUAGE plpgsql;
     37}}}
     38
     39=== 2. fn_artist_total_earnings ===
     40Функцијата {{{fn_artist_total_earnings}}} ја пресметува вкупната заработка на артист или бенд земајќи ги предвид само успешно завршените плаќања со статус {{{PAID}}}.
     41
     42Ги поврзува табелите {{{Payment}}}, {{{Booking}}} и {{{Offer}}} и го сумира износот на сите потврдени плаќања. Ако артистот сè уште нема реализирани плаќања, функцијата враќа {{{0}}} наместо {{{NULL}}}.
     43{{{
     44CREATE OR REPLACE FUNCTION fn_artist_total_earnings(
     45    p_bookable_id INT
     46)
     47RETURNS NUMERIC(10,2)
     48AS
     49$$
     50DECLARE
     51    v_total NUMERIC(10,2);
     52BEGIN
     53
     54    SELECT
     55        SUM(p.amount)
     56    INTO v_total
     57    FROM Payment p
     58    JOIN Booking bk
     59        ON p.booking_id = bk.booking_id
     60    JOIN Offer o
     61        ON bk.offer_id = o.offer_id
     62    WHERE o.bookable_id = p_bookable_id
     63      AND p.payment_status = 'PAID';
     64
     65    RETURN COALESCE(v_total, 0);
     66
     67END;
     68$$
     69LANGUAGE plpgsql;
     70}}}
     71
     72=== 3. fn_check_artist_availability ===
     73Функцијата {{{fn_check_artist_availability}}} проверува дали артист или бенд има слободен термин на одреден датум и враќа {{{TRUE}}} или {{{FALSE}}}.
     74
     75Наместо {{{COUNT(*)}}} кој ги брои сите редици, функцијата користи {{{EXISTS}}} кој застанува веднаш при првиот пронајден слободен слот — со што е значително поефикасна.
     76{{{
     77CREATE OR REPLACE FUNCTION fn_check_artist_availability(
     78    p_bookable_id INT,
     79    p_date        DATE
     80)
     81RETURNS BOOLEAN
     82AS
     83$$
     84DECLARE
     85    v_exists BOOLEAN;
     86BEGIN
     87
     88    SELECT EXISTS(
     89        SELECT 1
     90        FROM AvailabilitySlot
     91        WHERE bookable_id = p_bookable_id
     92          AND DATE(start_datetime) = p_date
     93          AND status = 'AVAILABLE'
     94    )
     95    INTO v_exists;
     96
     97    RETURN v_exists;
     98
     99END;
     100$$
     101LANGUAGE plpgsql;
     102}}}
     103
     104=== 4. fn_total_client_bookings ===
     105Функцијата {{{fn_total_client_bookings}}} го враќа вкупниот број на bookings направени од одреден клиент.
     106
     107Ги поврзува табелите {{{BookingRequest}}}, {{{Offer}}} и {{{Booking}}} за да го пресмета бројот на сите bookings поврзани со клиентот, без разлика на нивниот статус.
     108{{{
     109CREATE OR REPLACE FUNCTION fn_total_client_bookings(
     110    p_client_id INT
     111)
     112RETURNS INT
     113AS
     114$$
     115DECLARE
     116    v_total INT;
     117BEGIN
     118
     119    SELECT COUNT(*)
     120    INTO v_total
     121    FROM BookingRequest br
     122    JOIN Offer o
     123        ON br.request_id = o.request_id
     124    JOIN Booking bk
     125        ON bk.offer_id = o.offer_id
     126    WHERE br.client_id = p_client_id;
     127
     128    RETURN v_total;
     129
     130END;
     131$$
     132LANGUAGE plpgsql;
     133}}}
    22134
    23135== Процедури ==
    24 
    25 === '''sp_create_booking_request(p_client_id, p_duration_id, p_event_type, p_event_date, p_location_id)''' ===
    26 Процедурата креира нов запис во ''BookingRequest'' со основните информации за настанот (клиент, траење, тип, датум, локација).
    27 
    28 На тој начин ја инкапсулира бизнис операцијата „клиентот испраќа барање за букинг“ и осигурува дека секогаш се пополнуваат потребните полиња на централизирано место.
    29 
    30 === '''sp_cancel_booking(p_booking_id INT)''' ===
    31 Оваа процедура го ажурира полето ''booking_status'' на избраниот букинг во вредност ''CANCELLED''.
    32 
    33 На тој начин се имплементира логиката за откажување на букинг од страна на клиент или администратор, а преку тригерот за историја автоматски се логира и промената на статусот.
    34 
    35 === '''sp_create_payment(p_booking_id INT, p_amount NUMERIC)''' ===
    36 Процедурата додава нов запис во ''Payment'' за одреден букинг, со зададена сума и статус ''PAID''.
    37 
    38 Така се имплементира делот од бизнис логиката за евиденција на успешно извршени плаќања, што потоа се користи и во функцијата за вкупна заработка на артистите.
     136Процедурите се користат за извршување на акции врз базата — за разлика од функциите, тие не враќаат вредност туку вршат промени. Секоја процедура содржи валидација пред да изврши каква било промена, со цел да се спречат невалидни состојби во базата.
     137
     138=== 1. sp_create_booking_request ===
     139Процедурата {{{sp_create_booking_request}}} креира нов барање за booking од страна на клиент.
     140
     141Пред да го изврши вметнувањето, процедурата врши три валидации:
     142 * Проверува дали клиентот постои во табелата {{{ClientProfile}}}
     143 * Проверува дали локацијата постои во табелата {{{Location}}}
     144 * Проверува дали датумот на настанот е во иднина
     145
     146Ако некоја од валидациите не помине, се фрла исклучок со јасна порака за грешка.
     147{{{
     148CREATE OR REPLACE PROCEDURE sp_create_booking_request(
     149    p_client_id   INT,
     150    p_duration_id INT,
     151    p_event_type  VARCHAR,
     152    p_event_date  DATE,
     153    p_location_id INT
     154)
     155LANGUAGE plpgsql
     156AS
     157$$
     158BEGIN
     159
     160    IF NOT EXISTS (
     161        SELECT 1 FROM ClientProfile WHERE client_id = p_client_id
     162    ) THEN
     163        RAISE EXCEPTION 'Client with ID % does not exist!', p_client_id;
     164    END IF;
     165
     166    IF NOT EXISTS (
     167        SELECT 1 FROM Location WHERE location_id = p_location_id
     168    ) THEN
     169        RAISE EXCEPTION 'Location with ID % does not exist!', p_location_id;
     170    END IF;
     171
     172    IF p_event_date <= CURRENT_DATE THEN
     173        RAISE EXCEPTION 'Event date must be in the future!';
     174    END IF;
     175
     176    INSERT INTO BookingRequest(
     177        client_id,
     178        duration_id,
     179        event_type,
     180        event_date,
     181        location_id
     182    )
     183    VALUES (
     184        p_client_id,
     185        p_duration_id,
     186        p_event_type,
     187        p_event_date,
     188        p_location_id
     189    );
     190
     191END;
     192$$;
     193}}}
     194
     195=== 2. sp_cancel_booking ===
     196Процедурата {{{sp_cancel_booking}}} го откажува постоечки booking со менување на неговиот статус во {{{CANCELLED}}}.
     197
     198Пред да го изврши откажувањето, процедурата врши три проверки:
     199 * Проверува дали booking со дадениот ID воопшто постои
     200 * Проверува дали booking не е веќе откажан
     201 * Проверува дали booking не е веќе завршен — завршените bookings не можат да се откажат
     202
     203Без овие проверки, повикот со непостоечки ID би поминал тивко без никаква промена или грешка.
     204{{{
     205CREATE OR REPLACE PROCEDURE sp_cancel_booking(
     206    p_booking_id INT
     207)
     208LANGUAGE plpgsql
     209AS
     210$$
     211DECLARE
     212    v_current_status VARCHAR(30);
     213BEGIN
     214
     215    SELECT booking_status
     216    INTO v_current_status
     217    FROM Booking
     218    WHERE booking_id = p_booking_id;
     219
     220    IF NOT FOUND THEN
     221        RAISE EXCEPTION 'Booking with ID % does not exist!', p_booking_id;
     222    END IF;
     223
     224    IF v_current_status = 'CANCELLED' THEN
     225        RAISE EXCEPTION 'Booking % is already cancelled!', p_booking_id;
     226    END IF;
     227
     228    IF v_current_status = 'COMPLETED' THEN
     229        RAISE EXCEPTION 'Booking % is already completed and cannot be cancelled!', p_booking_id;
     230    END IF;
     231
     232    UPDATE Booking
     233    SET booking_status = 'CANCELLED'
     234    WHERE booking_id = p_booking_id;
     235
     236END;
     237$$;
     238}}}
     239
     240=== 3. sp_create_payment ===
     241Процедурата {{{sp_create_payment}}} креира ново плаќање поврзано со одреден booking.
     242
     243Клучна поправка во однос на оригиналната верзија е дека плаќањето се креира со статус {{{PENDING}}} наместо веднаш {{{PAID}}}. Ова одговара на реалниот тек — плаќањето прво се иницира, па дури потоа се потврдува.
     244
     245Процедурата врши три валидации:
     246 * Проверува дали booking постои
     247 * Проверува дали износот е позитивен број
     248 * Проверува дали веќе не постои плаќање за истиот booking
     249{{{
     250CREATE OR REPLACE PROCEDURE sp_create_payment(
     251    p_booking_id INT,
     252    p_amount     NUMERIC(10,2)
     253)
     254LANGUAGE plpgsql
     255AS
     256$$
     257BEGIN
     258
     259    IF NOT EXISTS (
     260        SELECT 1 FROM Booking WHERE booking_id = p_booking_id
     261    ) THEN
     262        RAISE EXCEPTION 'Booking with ID % does not exist!', p_booking_id;
     263    END IF;
     264
     265    IF p_amount <= 0 THEN
     266        RAISE EXCEPTION 'Payment amount must be greater than 0!';
     267    END IF;
     268
     269    IF EXISTS (
     270        SELECT 1 FROM Payment WHERE booking_id = p_booking_id
     271    ) THEN
     272        RAISE EXCEPTION 'Payment for booking % already exists!', p_booking_id;
     273    END IF;
     274
     275    INSERT INTO Payment(
     276        booking_id,
     277        amount,
     278        payment_status
     279    )
     280    VALUES (
     281        p_booking_id,
     282        p_amount,
     283        'PENDING'
     284    );
     285
     286END;
     287$$;
     288}}}
    39289
    40290== Тригери ==
    41 
    42 === '''fn_update_slot_status() / trg_update_slot_status''' ===
    43 Овој тригер се активира по вметнување (AFTER INSERT) на нов букинг во ''Booking'' и преку функцијата ''fn_update_slot_status'' ги ажурира сите ''AvailabilitySlot'' записи за соодветниот артист на датумот на настанот во статус ''BOOKED''.
    44 
    45 Со тоа се имплементира бизнис правилото дека кога ќе се потврди букинг за одреден датум, слободните термини за тој датум повеќе не се достапни во календарот на артистот.
    46 
    47 === '''fn_prevent_double_booking() / trg_prevent_double_booking''' ===
    48 Тригерот се активира пред вметнување (BEFORE INSERT) на нов букинг и во функцијата ''fn_prevent_double_booking'' проверува дали веќе постои букинг со статус ''CONFIRMED'' за истиот артист (bookable_id) и истиот датум на настан (event_date).
    49 
    50 Ако постои, се крева грешка „Artist already booked on that date!“ и се спречува внесот, со што директно се имплементира бизнис правилото дека артист може да има најмногу еден потврден настап во еден ден.
    51 
    52 === '''fn_booking_status_history() / trg_booking_status_history''' ===
    53 Овој тригер се активира по ажурирање (AFTER UPDATE) на ''Booking'' кога статусот се менува и преку функцијата ''fn_booking_status_history'' внесува ред во ''BookingStatusHistory'' со новиот статус и времето на промена.
    54 
    55 Така се имплементира бизнис логиката за водење историја (audit trail) на статусите на букинзите, што е корисно за следење на целиот животен циклус на букинг (''CREATED → CONFIRMED → COMPLETED/CANCELLED'').
     291Тригерите се специјални функции кои се извршуваат автоматски при одредени настани во базата — без потреба апликацијата да ги повикува рачно. Во системот се имплементирани три тригери кои обезбедуваат конзистентност и интегритет на податоците.
     292
     293=== 1. trg_prevent_double_booking ===
     294Тригерот {{{trg_prevent_double_booking}}} спречува двоен booking — ситуација каде ист артист или бенд би бил потврден за два различни настани на ист датум.
     295
     296Се извршува автоматски '''пред''' секој нов запис во табелата {{{Booking}}} ({{{BEFORE INSERT}}}). Доколку за истиот артист веќе постои {{{CONFIRMED}}} booking на истиот датум, тригерот фрла исклучок и го спречува вметнувањето.
     297{{{
     298CREATE OR REPLACE FUNCTION fn_prevent_double_booking()
     299RETURNS TRIGGER
     300AS
     301$$
     302DECLARE
     303    v_count      INT;
     304    v_bookable   INT;
     305    v_event_date DATE;
     306BEGIN
     307
     308    SELECT o.bookable_id, br.event_date
     309    INTO v_bookable, v_event_date
     310    FROM Offer o
     311    JOIN BookingRequest br ON br.request_id = o.request_id
     312    WHERE o.offer_id = NEW.offer_id;
     313
     314    SELECT COUNT(*)
     315    INTO v_count
     316    FROM Booking b
     317    JOIN Offer o
     318        ON b.offer_id = o.offer_id
     319    JOIN BookingRequest br
     320        ON br.request_id = o.request_id
     321    WHERE o.bookable_id = v_bookable
     322      AND br.event_date = v_event_date
     323      AND b.booking_status = 'CONFIRMED';
     324
     325    IF v_count > 0 THEN
     326        RAISE EXCEPTION 'Artist/Band with ID % is already booked on %!',
     327            v_bookable, v_event_date;
     328    END IF;
     329
     330    RETURN NEW;
     331
     332END;
     333$$
     334LANGUAGE plpgsql;
     335
     336CREATE OR REPLACE TRIGGER trg_prevent_double_booking
     337BEFORE INSERT ON Booking
     338FOR EACH ROW
     339EXECUTE FUNCTION fn_prevent_double_booking();
     340}}}
     341
     342=== 2. trg_update_slot_status ===
     343Тригерот {{{trg_update_slot_status}}} автоматски го означува слободниот термин на артистот како {{{BOOKED}}} во моментот кога се креира нов booking.
     344
     345Се извршува автоматски '''по''' секој нов запис во табелата {{{Booking}}} ({{{AFTER INSERT}}}). Го пронаоѓа соодветниот {{{AvailabilitySlot}}} според артистот и датумот на настанот и го менува неговиот статус — со тоа се спречува двојна резервација на ист термин.
     346{{{
     347CREATE OR REPLACE FUNCTION fn_update_slot_status()
     348RETURNS TRIGGER
     349AS
     350$$
     351DECLARE
     352    v_bookable   INT;
     353    v_event_date DATE;
     354BEGIN
     355
     356    SELECT o.bookable_id, br.event_date
     357    INTO v_bookable, v_event_date
     358    FROM Offer o
     359    JOIN BookingRequest br ON br.request_id = o.request_id
     360    WHERE o.offer_id = NEW.offer_id;
     361
     362    UPDATE AvailabilitySlot
     363    SET status = 'BOOKED'
     364    WHERE bookable_id = v_bookable
     365      AND DATE(start_datetime) = v_event_date
     366      AND status = 'AVAILABLE';
     367
     368    RETURN NEW;
     369
     370END;
     371$$
     372LANGUAGE plpgsql;
     373
     374CREATE OR REPLACE TRIGGER trg_update_slot_status
     375AFTER INSERT ON Booking
     376FOR EACH ROW
     377EXECUTE FUNCTION fn_update_slot_status();
     378}}}
     379
     380=== 3. trg_booking_status_history ===
     381Тригерот {{{trg_booking_status_history}}} автоматски го запишува секој пат кога статусот на booking се менува, формирајќи целосен историјат на промени.
     382
     383Се извршува автоматски '''по''' секој {{{UPDATE}}} на табелата {{{Booking}}}, но само кога вредноста на {{{booking_status}}} навистина се променила — ова се постигнува со условот {{{WHEN (OLD.booking_status IS DISTINCT FROM NEW.booking_status)}}}. Со тоа се избегнува непотребно запишување при update-и кои не го менуваат статусот.
     384{{{
     385CREATE OR REPLACE FUNCTION fn_booking_status_history()
     386RETURNS TRIGGER
     387AS
     388$$
     389BEGIN
     390
     391    INSERT INTO BookingStatusHistory(
     392        booking_id,
     393        new_status,
     394        changed_at
     395    )
     396    VALUES (
     397        NEW.booking_id,
     398        NEW.booking_status,
     399        NOW()
     400    );
     401
     402    RETURN NEW;
     403
     404END;
     405$$
     406LANGUAGE plpgsql;
     407
     408CREATE OR REPLACE TRIGGER trg_booking_status_history
     409AFTER UPDATE ON Booking
     410FOR EACH ROW
     411WHEN (OLD.booking_status IS DISTINCT FROM NEW.booking_status)
     412EXECUTE FUNCTION fn_booking_status_history();
     413}}}