| Version 5 (modified by , 12 days ago) ( diff ) |
|---|
DatabaseCreation
Опис
Релационата база на податоци е имплементирана во PostgreSQL според претходно дефинираниот ER и релационен модел.
DDL скриптата ги дефинира сите потребни табели, примарни и странски клучеви, ограничувања, проверки и default вредности.
Целосната скрипта е достапна во:
Основни табели
Во почетниот дел од скриптата се креираат помошните табели кои понатаму се користат од останатите ентитети.
Пример за Role:
CREATE TABLE Role ( role_id SERIAL NOT NULL PRIMARY KEY, role_name text NOT NULL UNIQUE, description text );
На сличен начин се дефинирани Permission, Industry, Project_Status, Technology, Rating_Dimension и Subscription_Tier.
Корисници
Сите типови на корисници ги содржат заедничките податоци во табелата "User":
CREATE TABLE "User" (
user_id SERIAL NOT NULL PRIMARY KEY,
type text NOT NULL
CHECK (type IN ('client', 'vendor', 'management')),
first_name text NOT NULL,
last_name text NOT NULL,
email text NOT NULL UNIQUE,
password_hash text NOT NULL,
is_active bool NOT NULL DEFAULT false,
last_login_at timestamp,
created_at timestamp NOT NULL DEFAULT NOW(),
updated_at timestamp NOT NULL DEFAULT NOW()
);
Полето type е ограничено со CHECK, така што може да има само една од трите дозволени вредности.
За конкретните типови на корисници се користат дополнителни табели. На пример:
CREATE TABLE Client_User ( user_id int4 NOT NULL PRIMARY KEY, client_id int4 NOT NULL, CONSTRAINT fk_clientuser_user FOREIGN KEY (user_id) REFERENCES "User" (user_id) ON DELETE CASCADE ON UPDATE CASCADE, CONSTRAINT fk_clientuser_client FOREIGN KEY (client_id) REFERENCES Client (client_id) ON DELETE RESTRICT ON UPDATE CASCADE );
На истиот принцип се дефинирани и Vendor_User и Management_User.
Клиенти, продавачи и договори
Клиентот е поврзан со индустријата преку странски клуч:
CREATE TABLE Client ( client_id SERIAL NOT NULL PRIMARY KEY, industry_id int4 NOT NULL, company_name text NOT NULL, contact_email text NOT NULL, CONSTRAINT fk_client_industry FOREIGN KEY (industry_id) REFERENCES Industry (industry_id) ON DELETE RESTRICT ON UPDATE CASCADE );
Врската помеѓу клиент и vendor е претставена преку договор:
CREATE TABLE Client_Vendor_Contract ( contract_id SERIAL NOT NULL PRIMARY KEY, client_id int4 NOT NULL, vendor_id int4 NOT NULL, contract_number text UNIQUE, contract_title text NOT NULL, start_date date NOT NULL DEFAULT CURRENT_DATE, end_date date, total_value numeric(10,2), currency_code text, terms_summary text, is_active bool NOT NULL DEFAULT true, created_at timestamp NOT NULL DEFAULT NOW(), updated_at timestamp NOT NULL DEFAULT NOW(), CONSTRAINT chk_cvc_dates CHECK (end_date IS NULL OR end_date > start_date), CONSTRAINT fk_cvc_client FOREIGN KEY (client_id) REFERENCES Client (client_id), CONSTRAINT fk_cvc_vendor FOREIGN KEY (vendor_id) REFERENCES Vendor (vendor_id) );
Со CHECK ограничувањето се спречува крајниот датум на договорот да биде пред почетниот.
Проекти
Секој проект е поврзан со договор и со тековен статус:
CREATE TABLE Project ( project_id SERIAL NOT NULL PRIMARY KEY, contract_id int4 NOT NULL, status_id int4 NOT NULL, project_name text NOT NULL, start_date date NOT NULL DEFAULT CURRENT_DATE, end_date date, budget numeric(10,2) NOT NULL, created_at timestamp NOT NULL DEFAULT NOW(), updated_at timestamp NOT NULL DEFAULT NOW(), CONSTRAINT chk_project_dates CHECK (end_date IS NULL OR end_date > start_date), CONSTRAINT fk_project_contract FOREIGN KEY (contract_id) REFERENCES Client_Vendor_Contract (contract_id), CONSTRAINT fk_project_status FOREIGN KEY (status_id) REFERENCES Project_Status (status_id) );
Технологиите користени на проектите се моделирани со many-to-many релација:
CREATE TABLE Project_Technology ( project_id int4 NOT NULL, technology_id int4 NOT NULL, PRIMARY KEY (project_id, technology_id), CONSTRAINT fk_projtec_project FOREIGN KEY (project_id) REFERENCES Project (project_id) ON DELETE CASCADE, CONSTRAINT fk_projtec_technology FOREIGN KEY (technology_id) REFERENCES Technology (technology_id) ON DELETE RESTRICT );
Историја на проектите
За промените на буџетот се користи посебна audit табела:
CREATE TABLE Project_Budget_Audit ( audit_id SERIAL NOT NULL PRIMARY KEY, project_id int4 NOT NULL, old_budget numeric(10,2) NOT NULL, new_budget numeric(10,2) NOT NULL, created_at timestamp NOT NULL DEFAULT NOW(), updated_at timestamp NOT NULL DEFAULT NOW(), CONSTRAINT fk_budgetaudit_project FOREIGN KEY (project_id) REFERENCES Project (project_id) );
Промените на статусот се зачувуваат во Project_Status_History, каде покрај проектот и статусот се чува и корисникот кој ја направил промената.
Reviews и оценки
За секој проект може да постои една рецензија:
CREATE TABLE Review ( review_id SERIAL NOT NULL PRIMARY KEY, project_id int4 NOT NULL UNIQUE, client_user_id int4 NOT NULL, review_date date NOT NULL DEFAULT CURRENT_DATE, summary_text text NOT NULL, is_published bool NOT NULL DEFAULT false, CONSTRAINT fk_review_project FOREIGN KEY (project_id) REFERENCES Project (project_id), CONSTRAINT fk_review_clientuser FOREIGN KEY (client_user_id) REFERENCES Client_User (user_id) );
Оценките се чуваат одделно за секоја димензија:
CREATE TABLE Review_Score ( review_id int4 NOT NULL, dimension_id int4 NOT NULL, score_value int4 NOT NULL, PRIMARY KEY (review_id, dimension_id), CONSTRAINT fk_reviewscore_review FOREIGN KEY (review_id) REFERENCES Review (review_id) ON DELETE CASCADE, CONSTRAINT fk_reviewscore_dimension FOREIGN KEY (dimension_id) REFERENCES Rating_Dimension (dimension_id) );
Dispute систем
При оспорување на review се креира запис во Dispute_Ticket:
CREATE TABLE Dispute_Ticket ( ticket_id SERIAL NOT NULL PRIMARY KEY, assigned_management_user_id int4 DEFAULT NULL, review_id int4 NOT NULL, vendor_user_id int4 NOT NULL, reason text NOT NULL, is_resolved bool NOT NULL DEFAULT false, filed_at date NOT NULL DEFAULT CURRENT_DATE, resolved_at timestamp, resolution_note text );
Табелата е дополнително поврзана со Management_User, Review и Vendor_User преку странски клучеви.
Views
За почесто користените прикази и аналитички податоци се дефинирани PostgreSQL views. Тие ги обединуваат податоците од повеќе поврзани табели и овозможуваат поедноставен пристап до информации за проекти, договори, буџети, оценки, спорови и претплати.
Целосната скрипта е достапна во:
Проекти по Vendor и Client
Погледот vw_projects_per_vendor ги прикажува сите проекти поврзани со одреден vendor, заедно со статусот, периодот и буџетот:
CREATE OR REPLACE VIEW vw_projects_per_vendor AS SELECT v.vendor_id, v.agency_name, p.project_id, p.project_name, ps.status_name, p.start_date, p.end_date, p.budget, cvc.currency_code FROM Project p JOIN Client_Vendor_Contract cvc ON cvc.contract_id = p.contract_id JOIN Vendor v ON v.vendor_id = cvc.vendor_id JOIN Project_Status ps ON ps.status_id = p.status_id ORDER BY v.agency_name, ps.status_name, p.project_name;
Соодветниот поглед за клиентите е vw_projects_per_client:
CREATE OR REPLACE VIEW vw_projects_per_client AS SELECT c.client_id, c.company_name, p.project_id, p.project_name, ps.status_name, p.start_date, p.end_date, p.budget, cvc.currency_code FROM Project p JOIN Client_Vendor_Contract cvc ON cvc.contract_id = p.contract_id JOIN Client c ON c.client_id = cvc.client_id JOIN Project_Status ps ON ps.status_id = p.status_id ORDER BY c.company_name, ps.status_name, p.project_name;
Буџети по Vendor и Client
За аналитички приказ на вкупниот број на проекти и нивниот буџет се користат vw_budget_per_vendor и vw_budget_per_client.
CREATE OR REPLACE VIEW vw_budget_per_vendor AS SELECT v.vendor_id, v.agency_name, cvc.currency_code, COUNT(p.project_id) AS project_count, SUM(p.budget) AS total_budget FROM Project p JOIN Client_Vendor_Contract cvc ON cvc.contract_id = p.contract_id JOIN Vendor v ON v.vendor_id = cvc.vendor_id GROUP BY v.vendor_id, v.agency_name, cvc.currency_code ORDER BY v.agency_name, cvc.currency_code;
CREATE OR REPLACE VIEW vw_budget_per_client AS SELECT c.client_id, c.company_name, cvc.currency_code, COUNT(p.project_id) AS project_count, SUM(p.budget) AS total_budget FROM Project p JOIN Client_Vendor_Contract cvc ON cvc.contract_id = p.contract_id JOIN Client c ON c.client_id = cvc.client_id GROUP BY c.client_id, c.company_name, cvc.currency_code ORDER BY c.company_name, cvc.currency_code;
Групирањето се врши и според currency_code, со што буџетите во различни валути не се собираат во една вредност.
Клиенти по индустрија
vw_clients_per_industry овозможува приказ на клиентските компании групирани според нивната индустрија:
CREATE OR REPLACE VIEW vw_clients_per_industry AS SELECT i.industry_id, i.industry_name, c.client_id, c.company_name, c.contact_email FROM Client c JOIN Industry i ON i.industry_id = c.industry_id ORDER BY i.industry_name, c.company_name;
Просечна оценка по Vendor
За секој vendor се пресметува просечна оценка од сите Review_Score записи за неговите проекти:
CREATE OR REPLACE VIEW vw_avg_rating_per_vendor AS SELECT v.vendor_id, v.agency_name, COUNT(DISTINCT r.review_id) AS review_count, ROUND(AVG(rs.score_value), 2) AS avg_rating FROM Review_Score rs JOIN Review r ON r.review_id = rs.review_id JOIN Project p ON p.project_id = r.project_id JOIN Client_Vendor_Contract cvc ON cvc.contract_id = p.contract_id JOIN Vendor v ON v.vendor_id = cvc.vendor_id GROUP BY v.vendor_id, v.agency_name ORDER BY avg_rating DESC NULLS LAST;
COUNT(DISTINCT r.review_id) го прикажува бројот на рецензии, додека AVG ја пресметува просечната вредност од сите оценети димензии.
Нерешени Dispute Tickets
За потребите на dispute системот е креиран vw_unresolved_dispute_tickets, кој ги прикажува само тикетите што сè уште не се решени:
CREATE OR REPLACE VIEW vw_unresolved_dispute_tickets AS SELECT dt.ticket_id, dt.filed_at, dt.reason, r.review_id, p.project_id, p.project_name, vu.user_id AS filed_by_vendor_user_id, vu_u.first_name || ' ' || vu_u.last_name AS filed_by_vendor_user, mu.user_id AS assigned_management_user_id, mu_u.first_name || ' ' || mu_u.last_name AS assigned_to_management_user, dt.created_at, dt.updated_at FROM Dispute_Ticket dt JOIN Review r ON r.review_id = dt.review_id JOIN Project p ON p.project_id = r.project_id JOIN Vendor_User vu ON vu.user_id = dt.vendor_user_id JOIN "User" vu_u ON vu_u.user_id = vu.user_id LEFT JOIN Management_User mu ON mu.user_id = dt.assigned_management_user_id LEFT JOIN "User" mu_u ON mu_u.user_id = mu.user_id WHERE dt.is_resolved = false ORDER BY dt.filed_at;
За management корисникот се користи LEFT JOIN, бидејќи нерешен dispute ticket може сè уште да нема доделен management корисник.
Број на проекти по статус
За статистички приказ на состојбата на проектите се користат два погледи кои го пресметуваат бројот на проекти по статус.
За vendor:
CREATE OR REPLACE VIEW vw_project_count_by_status_per_vendor AS SELECT v.vendor_id, v.agency_name, ps.status_id, ps.status_name, COUNT(p.project_id) AS project_count FROM Project p JOIN Client_Vendor_Contract cvc ON cvc.contract_id = p.contract_id JOIN Vendor v ON v.vendor_id = cvc.vendor_id JOIN Project_Status ps ON ps.status_id = p.status_id GROUP BY v.vendor_id, v.agency_name, ps.status_id, ps.status_name ORDER BY v.agency_name, ps.status_name;
За client:
CREATE OR REPLACE VIEW vw_project_count_by_status_per_client AS SELECT c.client_id, c.company_name, ps.status_id, ps.status_name, COUNT(p.project_id) AS project_count FROM Project p JOIN Client_Vendor_Contract cvc ON cvc.contract_id = p.contract_id JOIN Client c ON c.client_id = cvc.client_id JOIN Project_Status ps ON ps.status_id = p.status_id GROUP BY c.client_id, c.company_name, ps.status_id, ps.status_name ORDER BY c.company_name, ps.status_name;
Овие погледи овозможуваат брзо добивање на бројот на проекти во статуси како Draft, In Progress, Completed и останатите дефинирани статуси.
Договори по Vendor и Client
За приказ на договорите од двете перспективи се користат vw_contracts_per_vendor и vw_contracts_per_client.
CREATE OR REPLACE VIEW vw_contracts_per_vendor AS SELECT v.vendor_id, v.agency_name, cvc.contract_id, cvc.contract_number, cvc.contract_title, c.client_id, c.company_name AS client_name, cvc.start_date, cvc.end_date, cvc.total_value, cvc.currency_code, cvc.is_active FROM Client_Vendor_Contract cvc JOIN Vendor v ON v.vendor_id = cvc.vendor_id JOIN Client c ON c.client_id = cvc.client_id ORDER BY v.agency_name, cvc.is_active DESC, cvc.start_date DESC;
CREATE OR REPLACE VIEW vw_contracts_per_client AS SELECT c.client_id, c.company_name, cvc.contract_id, cvc.contract_number, cvc.contract_title, v.vendor_id, v.agency_name AS vendor_name, cvc.start_date, cvc.end_date, cvc.total_value, cvc.currency_code, cvc.is_active FROM Client_Vendor_Contract cvc JOIN Client c ON c.client_id = cvc.client_id JOIN Vendor v ON v.vendor_id = cvc.vendor_id ORDER BY c.company_name, cvc.is_active DESC, cvc.start_date DESC;
Со сортирање на is_active DESC, активните договори се прикажуваат пред историските договори.
Vendor претплати
За приказ на активните и историските претплати на агенциите е дефиниран vw_vendor_subscriptions:
CREATE OR REPLACE VIEW vw_vendor_subscriptions AS SELECT v.vendor_id, v.agency_name, st.tier_id, st.tier_name, COALESCE( vs.negotiated_price, st.list_price, 0 ) AS effective_price, vs.start_date, vs.end_date, vs.is_active FROM Vendor_Subscription vs JOIN Vendor v ON v.vendor_id = vs.vendor_id JOIN Subscription_Tier st ON st.tier_id = vs.tier_id ORDER BY v.agency_name, vs.is_active DESC, vs.start_date DESC;
Полето effective_price ја користи договорената цена доколку постои. Во спротивно се користи стандардната цена од Subscription_Tier.
На овој начин views обезбедуваат готови прикази за најчестите оперативни и аналитички потреби на системот, без истите JOIN, GROUP BY и агрегатни операции да се повторуваат во секој прашалник.
Полнење со податоци
За тестирање на базата се користи seed_generator.py, кој генерира реалистични податоци и CSV датотеки за табелите.
Генерирањето се извршува со:
Податоците се генерираат според зависностите помеѓу табелите, така што се почитуваат дефинираните foreign key ограничувања.
Attachments (4)
- ddl.sql (13.1 KB ) - added by 11 days ago.
- seed_generator.py (37.4 KB ) - added by 11 days ago.
- load_seed.sql (7.5 KB ) - added by 11 days ago.
- views.sql (11.5 KB ) - added by 11 days ago.
Download all attachments as: .zip
