Changes between Initial Version and Version 1 of DatabaseProgramming


Ignore:
Timestamp:
09/13/26 21:27:34 (2 weeks ago)
Author:
231075
Comment:

--

Legend:

Unmodified
Added
Removed
Modified
  • DatabaseProgramming

    v1 v1  
     1= DatabaseProgramming =
     2
     3== Опис ==
     4
     5Покрај основната структура на базата, имплементирана е и дополнителна PostgreSQL логика со функции, процедури и тригери.
     6
     7Оваа логика се користи за:
     8
     9* проверка на валидноста на податоците
     10* извршување на почести операции над повеќе табели
     11* автоматско одржување на историја и timestamps
     12* зачувување на интегритетот на податоците
     13* спроведување на дел од бизнис правилата на системот
     14
     15Кодот е поделен во:
     16
     17* [attachment:functions.sql functions.sql]
     18* [attachment:procedures.sql procedures.sql]
     19* [attachment:triggers.sql triggers.sql]
     20
     21== Функции ==
     22
     23Функциите главно се користат како помошни операции кои потоа се повикуваат од процедурите и тригерите.
     24
     25Едноставен пример е функцијата за добивање на целосното име на корисник:
     26
     27{{{#!sql
     28CREATE OR REPLACE FUNCTION fn_get_full_name(p_user_id int4)
     29RETURNS text LANGUAGE sql STABLE AS $$
     30SELECT first_name || ' ' || last_name
     31FROM "User"
     32WHERE user_id = p_user_id;
     33
     34$$$;
     35}}}
     36
     37За добивање на vendor-от поврзан со даден проект се користи:
     38
     39{{{#!sql
     40CREATE OR REPLACE FUNCTION fn_get_vendor_id_for_project(p_project_id int4)
     41RETURNS int4 LANGUAGE sql STABLE AS $$
     42    SELECT cvc.vendor_id
     43    FROM Project p
     44    JOIN Client_Vendor_Contract cvc
     45      ON cvc.contract_id = p.contract_id
     46    WHERE p.project_id = p_project_id;
     47$$;
     48}}}
     49
     50Поголемиот дел од функциите се validation функции. На пример:
     51
     52{{{#!sql
     53CREATE OR REPLACE FUNCTION fn_assert_project_exists(p_project_id int4)
     54RETURNS void LANGUAGE plpgsql AS $$
     55BEGIN
     56    IF NOT EXISTS (
     57        SELECT 1
     58        FROM Project
     59        WHERE project_id = p_project_id
     60    ) THEN
     61        RAISE EXCEPTION 'Project % does not exist', p_project_id;
     62    END IF;
     63END;
     64$$;
     65}}}
     66
     67На истиот принцип се имплементирани проверки за `Vendor`, `Client`, `Review`, `Project_Status`, `Subscription_Tier`, корисничките типови и `Rating_Dimension`.
     68
     69== Процедури ==
     70
     71Процедурите ги имплементираат главните операции кои менуваат податоци во повеќе поврзани табели.
     72
     73=== Регистрација на корисник ===
     74
     75`sp_register_user` прво креира запис во `"User"`, а потоа го додава корисникот во соодветната subtype табела.
     76
     77{{{#!sql
     78INSERT INTO "User" (
     79    type, first_name, last_name, email, password_hash
     80)
     81VALUES (
     82    p_type, p_first_name, p_last_name,
     83    p_email, p_password_hash
     84)
     85RETURNING user_id INTO p_user_id;
     86
     87IF p_type = 'client' THEN
     88    INSERT INTO Client_User (user_id, client_id)
     89    VALUES (p_user_id, p_client_id);
     90
     91ELSIF p_type = 'vendor' THEN
     92    INSERT INTO Vendor_User (user_id, vendor_id)
     93    VALUES (p_user_id, p_vendor_id);
     94
     95ELSIF p_type = 'management' THEN
     96    INSERT INTO Management_User (user_id, role_id)
     97    VALUES (p_user_id, p_role_id);
     98END IF;
     99}}}
     100
     101Пред внесувањето се проверуваат типот на корисникот, задолжителните параметри и референците кон останатите табели.
     102
     103=== Креирање договор и проект ===
     104
     105`sp_create_contract_with_project` овозможува со една операција да се креира договор и неговиот почетен проект:
     106
     107{{{#!sql
     108INSERT INTO Client_Vendor_Contract (
     109    client_id, vendor_id, contract_title,
     110    contract_number, start_date, end_date,
     111    total_value, currency_code, terms_summary
     112)
     113VALUES (
     114    p_client_id, p_vendor_id, p_contract_title,
     115    p_contract_number, p_cvc_start_date, p_cvc_end_date,
     116    p_total_value, p_currency_code, p_terms_summary
     117)
     118RETURNING contract_id INTO p_contract_id;
     119
     120INSERT INTO Project (
     121    contract_id, status_id, project_name,
     122    start_date, end_date, budget
     123)
     124VALUES (
     125    p_contract_id, p_status_id, p_project_name,
     126    p_project_start_date, p_project_end_date, p_budget
     127)
     128RETURNING project_id INTO p_project_id;
     129}}}
     130
     131Пред креирањето се проверуваат client, vendor и project status, како и буџетот и потребните текстуални полиња.
     132
     133=== Промена на статус на проект ===
     134
     135`sp_update_project_status` го менува тековниот статус и истовремено додава запис во историјата:
     136
     137{{{#!sql
     138UPDATE Project
     139SET status_id = p_new_status_id
     140WHERE project_id = p_project_id;
     141
     142INSERT INTO Project_Status_History (
     143    project_id,
     144    status_id,
     145    vendor_user_id,
     146    management_user_id,
     147    comment
     148)
     149VALUES (
     150    p_project_id,
     151    p_new_status_id,
     152    p_vendor_user_id,
     153    p_management_user_id,
     154    p_comment
     155);
     156}}}
     157
     158Со ова секоја промена на статусот останува евидентирана.
     159
     160=== Внесување review ===
     161
     162`sp_submit_review` креира review и неговите оценки во една операција.
     163
     164Оценките се предаваат како JSONB низа:
     165
     166{{{#!sql
     167FOR v_score IN
     168    SELECT * FROM jsonb_array_elements(p_scores)
     169LOOP
     170    PERFORM fn_assert_dimension_exists(
     171        (v_score->>'dimension_id')::int4
     172    );
     173
     174    IF ((v_score->>'score_value')::int4 < 1)
     175       OR ((v_score->>'score_value')::int4 > 5) THEN
     176        RAISE EXCEPTION
     177            'Score value must be between 1 and 5';
     178    END IF;
     179
     180    INSERT INTO Review_Score (
     181        review_id,
     182        dimension_id,
     183        score_value
     184    )
     185    VALUES (
     186        p_review_id,
     187        (v_score->>'dimension_id')::int4,
     188        (v_score->>'score_value')::int4
     189    );
     190END LOOP;
     191}}}
     192
     193Процедурата дополнително проверува дали корисникот припаѓа на клиентот на проектот и дали за проектот веќе постои review.
     194
     195Останатите процедури се користат за:
     196
     197 * деактивирање на корисник
     198 * промена на project budget
     199 * отворање dispute ticket
     200 * разрешување dispute ticket
     201 * објавување review
     202 * обновување vendor subscription
     203
     204== Тригери ==
     205
     206Тригерите автоматски извршуваат проверки или дополнителни операции при `INSERT` и `UPDATE`.
     207
     208=== User subtype проверка ===
     209
     210За `Client_User`, `Vendor_User` и `Management_User` се користи заедничка trigger функција која спречува еден корисник да припаѓа на повеќе subtype табели.
     211
     212{{{#!sql
     213CREATE TRIGGER trg_client_user_subtype
     214  BEFORE INSERT ON Client_User
     215  FOR EACH ROW
     216  EXECUTE FUNCTION trg_enforce_user_subtype();
     217
     218CREATE TRIGGER trg_vendor_user_subtype
     219  BEFORE INSERT ON Vendor_User
     220  FOR EACH ROW
     221  EXECUTE FUNCTION trg_enforce_user_subtype();
     222
     223CREATE TRIGGER trg_management_user_subtype
     224  BEFORE INSERT ON Management_User
     225  FOR EACH ROW
     226  EXECUTE FUNCTION trg_enforce_user_subtype();
     227}}}
     228
     229Дополнително се проверува дали вредноста на `"User".type` одговара на subtype табелата.
     230
     231=== Автоматско ажурирање на updated_at ===
     232
     233За табелите кои имаат `updated_at` е дефинирана заедничка trigger функција:
     234
     235{{{#!sql
     236CREATE OR REPLACE FUNCTION trg_set_updated_at()
     237RETURNS TRIGGER LANGUAGE plpgsql AS $$
     238BEGIN
     239  NEW.updated_at := NOW();
     240  RETURN NEW;
     241END;
     242$$;
     243}}}
     244
     245На пример:
     246
     247{{{#!sql
     248CREATE TRIGGER trg_project_updated_at
     249  BEFORE UPDATE ON Project
     250  FOR EACH ROW
     251  EXECUTE FUNCTION trg_set_updated_at();
     252}}}
     253
     254Истиот механизам се користи и за договори, subscriptions, reviews, dispute tickets и корисници.
     255
     256=== Историја на промена на буџет ===
     257
     258При промена на буџетот на проект, автоматски се креира audit запис:
     259
     260{{{#!sql
     261CREATE OR REPLACE FUNCTION trg_capture_budget_change()
     262RETURNS TRIGGER LANGUAGE plpgsql AS $$
     263BEGIN
     264  IF NEW.budget IS DISTINCT FROM OLD.budget THEN
     265    INSERT INTO Project_Budget_Audit (
     266        project_id,
     267        old_budget,
     268        new_budget
     269    )
     270    VALUES (
     271        OLD.project_id,
     272        OLD.budget,
     273        NEW.budget
     274    );
     275  END IF;
     276
     277  RETURN NEW;
     278END;
     279$$;
     280}}}
     281
     282Тригерот се активира после промена на `Project`:
     283
     284{{{#!sql
     285CREATE TRIGGER trg_project_budget_audit
     286  AFTER UPDATE ON Project
     287  FOR EACH ROW
     288  EXECUTE FUNCTION trg_capture_budget_change();
     289}}}
     290
     291=== Проверка на dispute и review ===
     292
     293При разрешување на dispute се проверува дали е назначен management корисник. Доколку `resolved_at` не е поставен, автоматски се поставува тековното време.
     294
     295{{{#!sql
     296IF NEW.is_resolved = true AND OLD.is_resolved = false THEN
     297    IF NEW.assigned_management_user_id IS NULL THEN
     298        RAISE EXCEPTION
     299          'Dispute ticket % must have an assigned management user before it can be resolved',
     300          OLD.ticket_id;
     301    END IF;
     302
     303    IF NEW.resolved_at IS NULL THEN
     304        NEW.resolved_at := NOW();
     305    END IF;
     306END IF;
     307}}}
     308
     309Дополнително, review не може да биде објавен додека има неразрешен dispute:
     310
     311{{{#!sql
     312IF EXISTS (
     313    SELECT 1
     314    FROM Dispute_Ticket
     315    WHERE review_id = NEW.review_id
     316      AND is_resolved = false
     317) THEN
     318    RAISE EXCEPTION
     319      'Review % cannot be published while it has unresolved dispute tickets',
     320      NEW.review_id;
     321END IF;
     322}}}
     323
     324== Заклучок ==
     325
     326Функциите се користат за повторно употребливи проверки и помошни операции, процедурите ги имплементираат главните операции над податоците, а тригерите автоматски ги применуваат правилата кои треба да важат независно од начинот на кој се менуваат податоците.
     327
     328Со ова дел од апликациската и validation логиката е имплементирана директно во PostgreSQL базата.
     329$$$