= Креирање на базата на податоци == DDL скрипти === `User`, `Admin`, `Regular_User` {{{ CREATE TABLE "User" ( user_id BIGSERIAL PRIMARY KEY, username VARCHAR(255) NOT NULL UNIQUE, email VARCHAR(255) NOT NULL UNIQUE, password VARCHAR(255) NOT NULL, registration_timestamp TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, is_active BOOLEAN NOT NULL DEFAULT TRUE, CONSTRAINT user_email_check CHECK ( email ~ '^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}$' ), CONSTRAINT user_password_check CHECK ( length(password) >= 8 AND password ~ '[A-Z]' AND password ~ '[a-z]' AND password ~ '[0-9]' AND password ~ '[^a-zA-Z0-9]' ) ); }}} {{{ CREATE TABLE "Admin" ( user_id BIGINT PRIMARY KEY, CONSTRAINT fk_admin_user FOREIGN KEY (user_id) REFERENCES "User" (user_id) ON DELETE CASCADE ON UPDATE CASCADE ); }}} {{{ CREATE TABLE "Regular_User" ( user_id BIGINT PRIMARY KEY, first_name VARCHAR(255) NOT NULL, last_name VARCHAR(255) NOT NULL, date_of_birth DATE NOT NULL, is_active BOOLEAN NOT NULL DEFAULT TRUE, CONSTRAINT fk_regular_user FOREIGN KEY (user_id) REFERENCES "User" (user_id) ON DELETE CASCADE ON UPDATE CASCADE ); }}} === `User_Card` {{{ CREATE TABLE "User_Card" ( card_id BIGSERIAL PRIMARY KEY, user_id BIGINT NOT NULL, provider_token TEXT NOT NULL, card_brand VARCHAR(255), last_four VARCHAR(4), CONSTRAINT uq_user_provider_token UNIQUE (user_id, provider_token), CONSTRAINT fk_card_user FOREIGN KEY (user_id) REFERENCES "Regular_User" (user_id) ON DELETE CASCADE ON UPDATE CASCADE ); }}} === `Performer` {{{ CREATE TABLE "Performer" ( performer_id BIGSERIAL PRIMARY KEY, name VARCHAR(255) NOT NULL ); }}} === `Event_Type`, `Event` {{{ CREATE TABLE "Event_Type" ( type_id BIGSERIAL PRIMARY KEY, name VARCHAR(255) NOT NULL ); }}} {{{ CREATE TABLE "Event" ( event_id BIGSERIAL PRIMARY KEY, name VARCHAR(255) NOT NULL, type_id BIGINT NOT NULL, description TEXT NOT NULL, min_age INTEGER NOT NULL, created_by BIGINT NOT NULL, is_active BOOLEAN NOT NULL DEFAULT TRUE, CONSTRAINT fk_event_type FOREIGN KEY (type_id) REFERENCES "Event_Type" (type_id) ON DELETE RESTRICT ON UPDATE RESTRICT, CONSTRAINT fk_event_admin FOREIGN KEY (created_by) REFERENCES "Admin" (user_id) ON DELETE RESTRICT ON UPDATE RESTRICT ); }}} === `Venue`, `Section`, `Seat` {{{ CREATE TABLE "Venue" ( venue_id BIGSERIAL PRIMARY KEY, name VARCHAR(255) NOT NULL, number_of_sections INTEGER NOT NULL, number_of_seats INTEGER NOT NULL, address_street VARCHAR(255) NOT NULL, address_city VARCHAR(255) NOT NULL, address_country VARCHAR(255) NOT NULL ); }}} {{{ CREATE TABLE "Section" ( section_id BIGSERIAL PRIMARY KEY, name VARCHAR(255) NOT NULL, number_of_seats INTEGER NOT NULL, venue_id BIGINT NOT NULL, CONSTRAINT uq_section_venue_name UNIQUE (venue_id, name), CONSTRAINT fk_section_venue FOREIGN KEY (venue_id) REFERENCES "Venue" (venue_id) ON DELETE CASCADE ON UPDATE CASCADE ); }}} {{{ CREATE TABLE "Seat" ( seat_id BIGSERIAL PRIMARY KEY, seat_number INTEGER NOT NULL, row_number INTEGER NOT NULL, section_id BIGINT NOT NULL, CONSTRAINT uq_seat_section_number UNIQUE (section_id, seat_number), CONSTRAINT fk_seat_section FOREIGN KEY (section_id) REFERENCES "Section" (section_id) ON DELETE CASCADE ON UPDATE CASCADE ); }}} === `Event_Happening`, `Event_Period` {{{ CREATE TABLE "Event_Happening" ( event_happening_id BIGSERIAL PRIMARY KEY, event_id BIGINT NOT NULL, event_time TIMESTAMP NOT NULL, venue_id BIGINT NOT NULL, duration_minutes INTEGER NOT NULL, organizers TEXT NOT NULL, sponsors TEXT, CONSTRAINT uq_happening_time_venue UNIQUE (event_time, venue_id), CONSTRAINT fk_happening_event FOREIGN KEY (event_id) REFERENCES "Event" (event_id) ON DELETE RESTRICT ON UPDATE CASCADE, CONSTRAINT fk_happening_venue FOREIGN KEY (venue_id) REFERENCES "Venue" (venue_id) ON DELETE RESTRICT ON UPDATE CASCADE ); }}} {{{ CREATE TABLE "Event_Period" ( period_id BIGSERIAL PRIMARY KEY, name VARCHAR(255) NOT NULL, start_date DATE NOT NULL, end_date DATE NOT NULL, price_discount_percent INTEGER NOT NULL, event_happening_id BIGINT NOT NULL, CONSTRAINT uq_period_happening_name UNIQUE (event_happening_id, name), CONSTRAINT event_period_dates_check CHECK (start_date <= end_date), CONSTRAINT fk_period_happening FOREIGN KEY (event_happening_id) REFERENCES "Event_Happening" (event_happening_id) ON DELETE CASCADE ON UPDATE CASCADE ); }}} === `Event_Happening_Performer` {{{ CREATE TABLE "Event_Happening_Performer" ( event_happening_id BIGINT NOT NULL, performer_id BIGINT NOT NULL, CONSTRAINT pk_ehp PRIMARY KEY (event_happening_id, performer_id), CONSTRAINT fk_ehp_happening FOREIGN KEY (event_happening_id) REFERENCES "Event_Happening" (event_happening_id) ON DELETE CASCADE ON UPDATE CASCADE, CONSTRAINT fk_ehp_performer FOREIGN KEY (performer_id) REFERENCES "Performer" (performer_id) ON DELETE CASCADE ON UPDATE CASCADE ); }}} === `Event_Happening_Rating` {{{ CREATE TABLE "Event_Happening_Rating" ( rating_id BIGSERIAL PRIMARY KEY, rating INTEGER NOT NULL CHECK (rating BETWEEN 1 AND 10), comment TEXT, timestamp TIMESTAMP NOT NULL, event_happening_id BIGINT NOT NULL, user_id BIGINT NOT NULL DEFAULT 0, CONSTRAINT uq_rating_happening_user UNIQUE (event_happening_id, user_id), CONSTRAINT fk_rating_happening FOREIGN KEY (event_happening_id) REFERENCES "Event_Happening" (event_happening_id) ON DELETE CASCADE ON UPDATE CASCADE, CONSTRAINT fk_rating_regular_user FOREIGN KEY (user_id) REFERENCES "Regular_User" (user_id) ON DELETE SET DEFAULT ON UPDATE CASCADE ); }}} === `Ticket` {{{ CREATE TABLE "Ticket" ( ticket_id BIGSERIAL PRIMARY KEY, base_price DECIMAL(10,2) NOT NULL, is_available BOOLEAN NOT NULL DEFAULT TRUE, event_happening_id BIGINT NOT NULL, seat_id BIGINT NOT NULL, CONSTRAINT uq_ticket_happening_seat UNIQUE (event_happening_id, seat_id), CONSTRAINT fk_ticket_seat FOREIGN KEY (seat_id) REFERENCES "Seat" (seat_id) ON DELETE RESTRICT ON UPDATE CASCADE, CONSTRAINT fk_ticket_happening FOREIGN KEY (event_happening_id) REFERENCES "Event_Happening" (event_happening_id) ON DELETE RESTRICT ON UPDATE CASCADE ); }}} === `Ticket_Order`, `Ticket_Order_Item` {{{ CREATE TABLE "Ticket_Order" ( order_id BIGSERIAL PRIMARY KEY, order_time TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, order_amount DECIMAL(10,2) NOT NULL DEFAULT 0.0, user_id BIGINT NOT NULL, card_id BIGINT, CONSTRAINT fk_order_regular_user FOREIGN KEY (user_id) REFERENCES "Regular_User" (user_id) ON DELETE RESTRICT ON UPDATE CASCADE, CONSTRAINT fk_order_card FOREIGN KEY (card_id) REFERENCES "User_Card" (card_id) ON DELETE RESTRICT ON UPDATE CASCADE ); }}} {{{ CREATE TABLE "Ticket_Order_Item" ( order_item_id BIGSERIAL PRIMARY KEY, qr_code VARCHAR(255), item_price DECIMAL(10,2) NOT NULL, order_id BIGINT NOT NULL, ticket_id BIGINT NOT NULL, CONSTRAINT fk_item_order FOREIGN KEY (order_id) REFERENCES "Ticket_Order" (order_id) ON DELETE CASCADE ON UPDATE CASCADE, CONSTRAINT fk_item_ticket FOREIGN KEY (ticket_id) REFERENCES "Ticket" (ticket_id) ON DELETE RESTRICT ON UPDATE CASCADE ); }}} === `Ticket_Refund`, `Ticket_Refund_Item` {{{ CREATE TABLE "Ticket_Refund" ( refund_id BIGSERIAL PRIMARY KEY, refund_time TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, refund_amount DECIMAL(10,2) NOT NULL DEFAULT 0.0, reasons TEXT, order_id BIGINT NOT NULL, CONSTRAINT fk_refund_order FOREIGN KEY (order_id) REFERENCES "Ticket_Order" (order_id) ON DELETE RESTRICT ON UPDATE CASCADE ); }}} {{{ CREATE TABLE "Ticket_Refund_Item" ( refund_item_id BIGSERIAL PRIMARY KEY, item_price DECIMAL(10,2) NOT NULL, refund_id BIGINT NOT NULL, order_item_id BIGINT NOT NULL UNIQUE, CONSTRAINT fk_ri_refund FOREIGN KEY (refund_id) REFERENCES "Ticket_Refund" (refund_id) ON DELETE CASCADE ON UPDATE CASCADE, CONSTRAINT fk_ri_order_item FOREIGN KEY (order_item_id) REFERENCES "Ticket_Order_Item" (order_item_id) ON DELETE RESTRICT ON UPDATE CASCADE ); }}} == DML скрипти - Погледи (Views) === `Venue_Layout` Овој поглед ја прикажува деталната физичка структура на секој објект, декомпонирајќи го просторот од ниво на локација до конкретно седиште. Со линеарно поврзување на хиерархијата `Venue -> Section -> Seat`, погледот овозможува брза верификација на капацитетот и распоредот, што е неопходно за логистичко планирање на настаните. {{{ CREATE OR REPLACE VIEW "Venue_Layout" AS SELECT v.venue_id, v.name AS venue_name, s.section_id, s.name AS section_name, st.seat_id, st.row_number, st.seat_number FROM "Venue" v JOIN "Section" s ON v.venue_id = s.venue_id JOIN "Seat" st ON s.section_id = st.section_id; }}} === `User_Tickets` Овој поглед обезбедува сеопфатен хронолошки приказ на сите трансакции по корисник. Покрај основните информации како настан, цена и QR-код, погледот инкорпорира податоци за рефундации (преку '''LEFT JOIN''' со табелите за рефундација), овозможувајќи моментна идентификација на статусот на секој билет. Погледот е филтриран само за активни корисници, со што се елиминираат податоците од неактивни профили. {{{ CREATE OR REPLACE VIEW "User_Tickets" AS SELECT u.user_id, u.username, toi.order_item_id, t.ticket_id, e.event_id, e.name AS event_name, eh.event_time, toi.qr_code, toi.item_price AS price_paid, tri.refund_item_id, tr.refund_time FROM "User" u JOIN "Regular_User" ru ON u.user_id = ru.user_id JOIN "Ticket_Order" o ON ru.user_id = o.user_id JOIN "Ticket_Order_Item" toi ON o.order_id = toi.order_id JOIN "Ticket" t ON toi.ticket_id = t.ticket_id JOIN "Event_Happening" eh ON t.event_happening_id = eh.event_happening_id JOIN "Event" e ON eh.event_id = e.event_id LEFT JOIN "Ticket_Refund_Item" tri ON toi.order_item_id = tri.order_item_id LEFT JOIN "Ticket_Refund" tr ON tri.refund_id = tr.refund_id WHERE u.is_active = TRUE; }}} === `Future_Events` Овој поглед служи како динамичен извор за репертоарот на платформата. Ги изолира само идните настани (`eh.event_time > CURRENT_TIMESTAMP`) кои се означени како активни, прикажувајќи ги со нивната целосна географска локација. Оптимизиран е за брзо пребарување од страна на крајните корисници при избор на термин. {{{ CREATE OR REPLACE VIEW "Future_Events" AS SELECT e.event_id, e.name AS event_name, eh.event_happening_id, eh.event_time, v.venue_id, v.name AS venue_name, v.address_street AS street, v.address_city AS city, v.address_country AS country FROM "Event" e JOIN "Event_Happening" eh ON e.event_id = eh.event_id JOIN "Venue" v ON eh.venue_id = v.venue_id WHERE eh.event_time > CURRENT_TIMESTAMP AND e.is_active = TRUE; }}} === `Available_Tickets` Овој поглед ја нуди моменталната „понуда“ на слободни седишта. Имплементира бизнис логика за динамично ценообразовање: во реално време ја пресметува крајната цена на билетот, применувајќи го соодветниот попуст (`price_discount_percent`) врз база на тековниот временски период (`Event_Period`). Со ова, корисникот секогаш добива ажурирана цена при резервација. {{{ CREATE OR REPLACE VIEW "Available_Tickets" AS SELECT t.ticket_id, ROUND(t.base_price * (1 - COALESCE(ep.price_discount_percent, 0) / 100.0), 2) AS price, e.event_id, e.name AS event_name, eh.event_happening_id, eh.event_time, v.name AS venue_name, s.name AS section_name, st.row_number, st.seat_number FROM "Ticket" t JOIN "Event_Happening" eh ON t.event_happening_id = eh.event_happening_id JOIN "Event" e ON eh.event_id = e.event_id JOIN "Venue" v ON eh.venue_id = v.venue_id JOIN "Seat" st ON t.seat_id = st.seat_id JOIN "Section" s ON st.section_id = s.section_id LEFT JOIN "Event_Period" ep ON eh.event_happening_id = ep.event_happening_id AND CURRENT_DATE BETWEEN ep.start_date AND ep.end_date WHERE t.is_available = TRUE AND e.is_active = TRUE; }}} === `Event_Overall_Ratings` Овој материјализиран поглед врши агрегациска анализа на задоволството на публиката. Со пресметување на просечните оценки ('''AVG''') и бројот на рецензии ('''COUNT''') по настан, тој обезбедува клучни перформанс индикатори (KPIs) за организаторите. Материјализирањето е избрано за да се обезбеди висока брзина на вчитување при прикажување на статистички извештаи, избегнувајќи комплексни пресметки при секој прашалник. {{{ CREATE MATERIALIZED VIEW "Event_Overall_Ratings" AS SELECT e.event_id, e.name AS event_name, eh.event_happening_id, eh.event_time, COUNT(ehr.rating_id) AS total_reviews, COALESCE(ROUND(AVG(ehr.rating), 2), 0.00) AS average_rating FROM "Event" e JOIN "Event_Happening" eh ON e.event_id = eh.event_id LEFT JOIN "Event_Happening_Rating" ehr ON eh.event_happening_id = ehr.event_happening_id WHERE e.is_active = TRUE GROUP BY e.event_id, e.name, eh.event_happening_id, eh.event_time; }}} === `User_Order_History` Овој поглед генерира финансиски профил за секој корисник, сумирајќи ги направените нарачки и повратните средства. Оптимизиран е за сметководствени цели, нудејќи јасен увид во обемот на купени и рефундирани ставки. Материјализираната природа на погледот значително го растеретува серверот при генерирање на кориснички „Dashboard“ извештаи. {{{ CREATE MATERIALIZED VIEW "User_Order_History" AS SELECT u.user_id, u.username, o.order_id, o.order_time, o.order_amount, COALESCE(items.total_items_ordered, 0) AS total_items_ordered, COALESCE(items.total_items_refunded, 0) AS total_items_refunded, COALESCE(items.total_amount_refunded, 0.00) AS total_amount_refunded FROM "User" u JOIN "Regular_User" ru ON u.user_id = ru.user_id JOIN "Ticket_Order" o ON ru.user_id = o.user_id LEFT JOIN ( SELECT toi.order_id, COUNT(toi.order_item_id) AS total_items_ordered, COUNT(tri.refund_item_id) AS total_items_refunded, SUM(COALESCE(tri.item_price, 0.00)) AS total_amount_refunded FROM "Ticket_Order_Item" toi LEFT JOIN "Ticket_Refund_Item" tri ON toi.order_item_id = tri.order_item_id GROUP BY toi.order_id ) items ON o.order_id = items.order_id WHERE u.is_active = TRUE; }}} === `Venue_Occupancy_Report` Овој аналитички поглед го мери „ефектот на продажба“ преку пресметка на процентот на пополнетост (`occupancy_percentage`) на секој термин. Тој е примарна алатка за менаџерите кои треба да го следат успехот на настанот во реално време. Имплементиран е како материјализиран поглед за да поддржи брзо аналитичко известување без дополнително оптоварување на базата со тешки '''JOIN''' операции врз табелите за нарачки. {{{ CREATE MATERIALIZED VIEW "Venue_Occupancy_Report" AS SELECT e.event_id, e.name AS event_name, eh.event_happening_id, eh.event_time, v.name AS venue_name, v.number_of_seats AS total_venue_capacity, COALESCE(stats.sold_count, 0) AS tickets_sold, ROUND((COALESCE(stats.sold_count, 0)::NUMERIC / v.number_of_seats::NUMERIC) * 100, 2) AS occupancy_percentage FROM "Event" e JOIN "Event_Happening" eh ON e.event_id = eh.event_id JOIN "Venue" v ON eh.venue_id = v.venue_id LEFT JOIN ( SELECT t.event_happening_id, COUNT(toi.order_item_id) AS sold_count FROM "Ticket_Order_Item" toi JOIN "Ticket" t ON toi.ticket_id = t.ticket_id GROUP BY t.event_happening_id ) stats ON eh.event_happening_id = stats.event_happening_id WHERE e.is_active = TRUE GROUP BY e.event_id, e.name, eh.event_happening_id, eh.event_time, v.name, v.number_of_seats, stats.sold_count; }}}