= Оптимизација на прашалници == Анализа и оптимизација на `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; }}} Погледот е веќе најоптимално напишан со користење на постоечките уникатни констреинти и не бара дополнителни модификации. ==== 1. Примарен филтер: Примарен филтер за овој поглед е `venue_id` (ID на објектот), бидејќи најчестото пребарување во апликацијата е насочено кон визуелизација или вчитување на комплетната мапа на седишта за еден конкретен објект кога корисникот сака да купи билет. ==== 2. Случај на употреба: Погледот се користи при интерактивниот приказ на салата или стадионот во корисничкиот интерфејс. Кога купувачот ќе избере одреден настан, апликацијата мора веднаш да го исцрта распоредот на седишта по секции и редови за тој објект. Перформансите тука директно влијаат врз „time-to-interactive“ метриката на корисничката страница за избор на место. ==== 3. Иницијално време: * '''SELECT:''' 2.190 ms (Времето е мерено во продукциска околина пред оптимизација). * '''INSERT:''' 4.752 ms (Време потребно за запишување на нов ред во табелата `Seat` заедно со проверка на сите интегритетни констреинти). * '''UPDATE:''' 0.133 ms (Инстантна операција базирана на примарен клуч). ==== 4. Анализа на планот на извршување (без индекси): При '''SELECT''' операцијата, '''PostgreSQL''' паметно ги користи веќе постоечките уникатни индекси генерирани од бизнис констреинтите (`uq_section_venue_name` на `Section` и `uq_seat_section_number` на `Seat`). Планерот користи '''Nested Loop''' операција за спојување на табелите: прво го наоѓа објектот по ID, потоа преку `venue_id` ги лоцира сите секции, и на крај за секоја секција извршува '''Index Scan''' за да ги повлече соодветните седишта. Оваа патека овозможува брз пристап без потреба од скапи секвенцијални скенирања. * '''SELECT''' {{{ EXPLAIN ANALYZE SELECT * FROM "Venue_Layout" WHERE venue_id = 1; }}} ||= QUERY PLAN = = =|| ||Nested Loop (cost\=1.01..9625.30 rows\=2264 width\=54) (actual time\=0.868..2.190 rows\=775.00 loops\=1)|| ||Buffers shared hit\=21 read\=13 dirtied\=1|| ||-> Nested Loop (cost\=0.57..25.51 rows\=6 width\=38) (actual time\=0.622..0.627 rows\=5.00 loops\=1)|| ||Buffers shared read\=6|| || -> Index Scan using ""Venue_pkey"" on ""Venue"" v (cost\=0.29..8.30 rows\=1 width\=28) (actual time\=0.071..0.072 rows\=1.00 loops\=1)|| ||Index Cond (venue_id \= 1)|| ||Index Searches 1|| ||Buffers shared read\=3|| || -> Index Scan using uq_section_venue_name on ""Section"" s (cost\=0.29..17.14 rows\=6 width\=18) (actual time\=0.548..0.550 rows\=5.00 loops\=1)|| ||Index Cond (venue_id \= 1)|| ||Index Searches 1|| ||Buffers shared read\=3|| || -> Index Scan using uq_seat_section_number on ""Seat"" st (cost\=0.44..1590.95 rows\=901 width\=24) (actual time\=0.056..0.282 rows\=155.00 loops\=5)|| ||Index Cond (section_id \= s.section_id)|| ||Index Searches 5|| ||Buffers shared hit\=21 read\=7 dirtied\=1|| ||Planning|| ||Buffers shared hit\=14 read\=22|| ||Planning Time 5.752 ms|| ||Execution Time 2.271 ms|| * '''INSERT''' {{{ EXPLAIN ANALYZE INSERT INTO "Seat" (seat_id, section_id, row_number, seat_number) VALUES (99999999, 1, 1, 99); }}} ||= QUERY PLAN = = =|| ||Insert on ""Seat"" (cost\=0.00..0.01 rows\=0 width\=0) (actual time\=4.492..4.493 rows\=0.00 loops\=1)|| ||Buffers shared hit\=5 read\=6 dirtied\=3|| ||-> Result (cost\=0.00..0.01 rows\=1 width\=24) (actual time\=0.001..0.001 rows\=1.00 loops\=1)|| ||Planning Time 0.033 ms|| ||Trigger for constraint fk_seat_section time\=0.243 calls\=1|| ||Execution Time 4.752 ms|| * '''UPDATE''' {{{ EXPLAIN ANALYZE UPDATE "Seat" SET seat_number = 100 WHERE seat_id = 99999999; }}} ||= QUERY PLAN = = =|| ||Update on ""Seat"" (cost\=0.44..8.46 rows\=0 width\=0) (actual time\=0.101..0.102 rows\=0.00 loops\=1)|| ||Buffers shared hit\=12|| || -> Index Scan using ""Seat_pkey"" on ""Seat"" (cost\=0.44..8.46 rows\=1 width\=10) (actual time\=0.031..0.032 rows\=1.00 loops\=1)|| ||Index Cond (seat_id \= 99999999)|| ||Index Searches 1|| ||Buffers shared hit\=4|| ||Planning Time 0.125 ms|| ||Execution Time 0.133 ms|| ==== 5. Оптимизација и индексирање: Бидејќи постоечките уникатни констреинти веќе идеално ги покриваат '''JOIN''' релациите, креирањето на дополнителни индекси е непотребно и би довело до залудно трошење на мемориски ресурси и забавување на запишувањето ('''INSERT'''/'''UPDATE'''). Погледот е оценет како оптимален во својата тековна форма. Доволно е само да се изврши: {{{ ANALYZE "Venue"; ANALYZE "Section"; ANALYZE "Seat"; }}} == Анализа и оптимизација на `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; }}} Погледот е веќе најоптимално напишан со користење на постоечките уникатни констреинти и не бара дополнителни модификации. ==== 1. Примарен филтер: Примарен филтер за овој поглед е `user_id` (ID на корисникот), бидејќи најчестото и критично пребарување е кога најавениот клиент ја отвора својата корисничка профилна страница („My Tickets“) за да ги види своите активни или минати билети. ==== 2. Случај на употреба: Погледот претставува јадро на апликацијата во делот за дигитални билети. Секојпат кога корисникот сака да го прикаже својот QR-код на билетот за некој настан, системот го извршува овој прашалник. Доцнење на овој одзив предизвикува лошо корисничко искуство и застој на влезните капии при проверка на билетите. ==== 3. Иницијално време: * '''SELECT:''' 375.993 ms (Исклучително бавно поради комплексноста од поврзување на 9 табели и секвенцијално скенирање). * '''INSERT:''' 0.523 ms * '''UPDATE:''' 0.118 ms ==== 4. Анализа на планот на извршување (без индекси): Со воведувањето на новите '''B-tree''' индекси, планерот го напушти паралелното секвенцијално скенирање и премина кон '''Nested Loop''' операција. Ова овозможува базата да ја најде нарачката на корисникот преку `idx_ticket_order_user_id` во само еден чекор, а потоа со преостанатите индекси директно да ги „отвори“ ставките на нарачката (`Ticket_Order_Item`) и билетите (`Ticket`), избегнувајќи пребарување низ целиот диск. * '''SELECT''' {{{ EXPLAIN ANALYZE SELECT * FROM "User_Tickets" WHERE user_id = 5; }}} ||= QUERY PLAN =|| ||Gather (cost\=110699.49..459840.66 rows\=4 width\=145) (actual time\=370.701..375.993 rows\=0.00 loops\=1)|| ||Workers Planned: 2|| ||Workers Launched: 2|| ||Buffers: shared read\=82353|| ||-> Nested Loop Left Join (cost\=109699.49..458840.26 rows\=2 width\=145) (actual time\=312.925..312.929 rows\=0.00 loops\=3)|| ||Buffers: shared read\=82353|| ||-> Nested Loop Left Join (cost\=109699.07..458836.23 rows\=2 width\=145) (actual time\=312.924..312.928 rows\=0.00 loops\=3)|| ||Buffers: shared read\=82353|| ||-> Nested Loop (cost\=109698.64..458832.18 rows\=2 width\=129) (actual time\=312.923..312.927 rows\=0.00 loops\=3)|| ||Buffers: shared read\=82353|| ||-> Nested Loop (cost\=109698.35..458828.40 rows\=2 width\=98) (actual time\=312.923..312.927 rows\=0.00 loops\=3)|| ||Buffers: shared read\=82353|| ||-> Nested Loop (cost\=109698.07..458824.71 rows\=2 width\=90) (actual time\=312.923..312.926 rows\=0.00 loops\=3)|| ||Buffers: shared read\=82353|| ||-> Nested Loop (cost\=109697.63..458819.48 rows\=2 width\=82) (actual time\=312.922..312.925 rows\=0.00 loops\=3)|| ||Buffers: shared read\=82353|| ||-> Parallel Hash Join (cost\=109696.77..458802.55 rows\=2 width\=62) (actual time\=312.921..312.924 rows\=0.00 loops\=3)|| ||Hash Cond: (toi.order_id \= o.order_id)|| ||Buffers: shared read\=82353|| ||-> Parallel Seq Scan on ""Ticket_Order_Item"" toi (cost\=0.00..326137.01 rows\=8750001 width\=62) (never executed)|| ||-> Parallel Hash (cost\=109696.76..109696.76 rows\=1 width\=16) (actual time\=312.874..312.875 rows\=0.00 loops\=3)|| ||Buckets: 1024 Batches: 1 Memory Usage: 0kB|| ||Buffers: shared read\=82353|| ||-> Parallel Seq Scan on ""Ticket_Order"" o (cost\=0.00..109696.76 rows\=1 width\=16) (actual time\=312.664..312.664 rows\=0.00 loops\=3)|| ||Filter: (user_id \= 5)|| ||Rows Removed by Filter: 1750000|| ||Buffers: shared read\=82353|| ||-> Materialize (cost\=0.86..16.91 rows\=1 width\=36) (never executed)|| ||-> Nested Loop (cost\=0.86..16.91 rows\=1 width\=36) (never executed)|| ||-> Index Scan using ""User_pkey"" on ""User"" u (cost\=0.43..8.45 rows\=1 width\=28) (never executed)|| ||Index Cond: (user_id \= 5)|| ||Filter: is_active|| ||Index Searches: 0|| ||-> Index Only Scan using ""Regular_User_pkey"" on ""Regular_User"" ru (cost\=0.43..8.45 rows\=1 width\=8) (never executed)|| ||Index Cond: (user_id \= 5)|| ||Heap Fetches: 0|| ||Index Searches: 0|| ||-> Index Scan using ""Ticket_pkey"" on ""Ticket"" t (cost\=0.44..2.61 rows\=1 width\=16) (never executed)|| ||Index Cond: (ticket_id \= toi.ticket_id)|| ||Index Searches: 0|| ||-> Index Scan using ""Event_Happening_pkey"" on ""Event_Happening"" eh (cost\=0.29..1.85 rows\=1 width\=24) (never executed)|| ||Index Cond: (event_happening_id \= t.event_happening_id)|| ||Index Searches: 0|| ||-> Index Scan using ""Event_pkey"" on ""Event"" e (cost\=0.29..1.89 rows\=1 width\=39) (never executed)|| ||Index Cond: (event_id \= eh.event_id)|| ||Index Searches: 0|| ||-> Index Scan using ""Ticket_Refund_Item_order_item_id_key"" on ""Ticket_Refund_Item"" tri (cost\=0.43..2.02 rows\=1 width\=24) (never executed)|| ||Index Cond: (order_item_id \= toi.order_item_id)|| ||Index Searches: 0|| ||-> Index Scan using ""Ticket_Refund_pkey"" on ""Ticket_Refund"" tr (cost\=0.42..2.02 rows\=1 width\=16) (never executed)|| ||Index Cond: (refund_id \= tri.refund_id)|| ||Index Searches: 0|| ||Planning|| ||Buffers: shared hit\=33 read\=44 dirtied\=2|| ||Planning Time: 13.836 ms|| ||Execution Time: 376.133 ms|| * '''INSERT''' {{{ EXPLAIN ANALYZE INSERT INTO "Ticket_Order_Item" (order_item_id, order_id, ticket_id, item_price, qr_code) VALUES (99999999, 1, 1, 1200.00, '3ebd5fa12ea8781d1e9ae4333484984a'); }}} ||= QUERY PLAN =|| ||Insert on ""Ticket_Order_Item"" (cost\=0.00..0.01 rows\=0 width\=0) (actual time\=0.049..0.049 rows\=0.00 loops\=1)|| ||Buffers: shared hit\=5 dirtied\=1|| ||-> Result (cost\=0.00..0.01 rows\=1 width\=556) (actual time\=0.001..0.001 rows\=1.00 loops\=1)|| ||Planning Time: 0.028 ms|| ||Trigger for constraint fk_item_order: time\=0.251 calls\=1|| ||Trigger for constraint fk_item_ticket: time\=0.210 calls\=1|| ||Execution Time: 0.523 ms|| * '''UPDATE''' {{{ EXPLAIN ANALYZE UPDATE "Ticket_Order_Item" SET qr_code = '3ebd5fa12ec8781d1e9ae4333484984a' WHERE order_item_id = 99999999; }}} ||= QUERY PLAN =|| ||Update on ""Ticket_Order_Item"" (cost\=0.44..8.46 rows\=0 width\=0) (actual time\=0.085..0.086 rows\=0.00 loops\=1)|| ||Buffers: shared hit\=7|| ||-> Index Scan using ""Ticket_Order_Item_pkey"" on ""Ticket_Order_Item"" (cost\=0.44..8.46 rows\=1 width\=522) (actual time\=0.039..0.040 rows\=1.00 loops\=1)|| ||Index Cond: (order_item_id \= 99999999)|| ||Index Searches: 1|| ||Buffers: shared hit\=4|| ||Planning Time: 0.173 ms|| ||Execution Time: 0.118 ms|| ==== 5. Оптимизација и индексирање: За драстично кратење на времето, воведени се следните индекси: * `idx_ticket_order_user_id`: За директно мапирање на корисник кон неговите нарачки. * `idx_toi_order_id`: За забрзување на поврзувањето на ставки од нарачка. * `idx_toi_ticket_id`: За брзо поврзување на ставка од нарачка со конкретен билет. Извршен е ANALYZE за обновување на статистиките на планерот. {{{ CREATE INDEX CONCURRENTLY idx_ticket_order_user_id ON "Ticket_Order" (user_id); CREATE INDEX CONCURRENTLY idx_toi_order_id ON "Ticket_Order_Item" (order_id); CREATE INDEX CONCURRENTLY idx_toi_ticket_id ON "Ticket_Order_Item" (ticket_id); ANALYZE "Ticket_Order"; ANALYZE "Ticket_Order_Item"; }}} ==== 6. Резултат по оптимизација: Времето за '''SELECT''' е намалено на ~1.24 ms, што претставува забрзување од преку 300 пати. Читањето од диск е сведено на минимум, а сите операции се извршуваат директно во RAM меморијата преку индексните структури. Системот сега работи со конзистентна брзина, дури и под оптоварување. * '''SELECT''' {{{ EXPLAIN ANALYZE SELECT * FROM "User_Tickets" WHERE user_id = 99; }}} ||= QUERY PLAN =|| ||Nested Loop Left Join (cost\=3.59..75.95 rows\=4 width\=145) (actual time\=1.244..1.247 rows\=0.00 loops\=1)|| ||Buffers: shared hit\=1 read\=10|| ||-> Nested Loop Left Join (cost\=3.17..67.89 rows\=4 width\=145) (actual time\=1.243..1.246 rows\=0.00 loops\=1)|| ||Buffers: shared hit\=1 read\=10|| ||-> Nested Loop (cost\=2.74..59.80 rows\=4 width\=129) (actual time\=1.242..1.245 rows\=0.00 loops\=1)|| ||Buffers: shared hit\=1 read\=10|| ||-> Nested Loop (cost\=2.46..52.24 rows\=4 width\=98) (actual time\=1.242..1.244 rows\=0.00 loops\=1)|| ||Buffers: shared hit\=1 read\=10|| ||-> Nested Loop (cost\=2.17..44.84 rows\=4 width\=90) (actual time\=1.241..1.244 rows\=0.00 loops\=1)|| ||Buffers: shared hit\=1 read\=10|| ||-> Nested Loop (cost\=1.73..34.40 rows\=4 width\=82) (actual time\=1.241..1.243 rows\=0.00 loops\=1)|| ||Buffers: shared hit\=1 read\=10|| ||-> Nested Loop (cost\=1.29..25.36 rows\=1 width\=36) (actual time\=1.240..1.242 rows\=0.00 loops\=1)|| ||Buffers: shared hit\=1 read\=10|| ||-> Nested Loop (cost\=0.86..16.91 rows\=1 width\=36) (actual time\=1.119..1.123 rows\=1.00 loops\=1)|| ||Buffers: shared hit\=1 read\=7|| ||-> Index Scan using ""User_pkey"" on ""User"" u (cost\=0.43..8.45 rows\=1 width\=28) (actual time\=0.716..0.718 rows\=1.00 loops\=1)|| ||Index Cond: (user_id \= 99)|| ||Filter: is_active|| ||Index Searches: 1|| ||Buffers: shared read\=4|| ||-> Index Only Scan using ""Regular_User_pkey"" on ""Regular_User"" ru (cost\=0.43..8.45 rows\=1 width\=8) (actual time\=0.398..0.400 rows\=1.00 loops\=1)|| ||Index Cond: (user_id \= 99)|| ||Heap Fetches: 0|| ||Index Searches: 1|| ||Buffers: shared hit\=1 read\=3|| ||-> Index Scan using idx_ticket_order_user_id on ""Ticket_Order"" o (cost\=0.43..8.44 rows\=1 width\=16) (actual time\=0.116..0.116 rows\=0.00 loops\=1)|| ||Index Cond: (user_id \= 99)|| ||Index Searches: 1|| ||Buffers: shared read\=3|| ||-> Index Scan using idx_toi_order_id on ""Ticket_Order_Item"" toi (cost\=0.44..8.82 rows\=22 width\=62) (never executed)|| ||Index Cond: (order_id \= o.order_id)|| ||Index Searches: 0|| ||-> Index Scan using ""Ticket_pkey"" on ""Ticket"" t (cost\=0.44..2.61 rows\=1 width\=16) (never executed)|| ||Index Cond: (ticket_id \= toi.ticket_id)|| ||Index Searches: 0|| ||-> Index Scan using ""Event_Happening_pkey"" on ""Event_Happening"" eh (cost\=0.29..1.85 rows\=1 width\=24) (never executed)|| ||Index Cond: (event_happening_id \= t.event_happening_id)|| ||Index Searches: 0|| ||-> Index Scan using ""Event_pkey"" on ""Event"" e (cost\=0.29..1.89 rows\=1 width\=39) (never executed)|| ||Index Cond: (event_id \= eh.event_id)|| ||Index Searches: 0|| ||-> Index Scan using ""Ticket_Refund_Item_order_item_id_key"" on ""Ticket_Refund_Item"" tri (cost\=0.43..2.02 rows\=1 width\=24) (never executed)|| ||Index Cond: (order_item_id \= toi.order_item_id)|| ||Index Searches: 0|| ||-> Index Scan using ""Ticket_Refund_pkey"" on ""Ticket_Refund"" tr (cost\=0.42..2.02 rows\=1 width\=16) (never executed)|| ||Index Cond: (refund_id \= tri.refund_id)|| ||Index Searches: 0|| ||Planning|| ||Buffers: shared hit\=264 read\=37|| ||Planning Time: 41.864 ms|| ||Execution Time: 1.392 ms|| * '''INSERT''' {{{ EXPLAIN ANALYZE INSERT INTO "Ticket_Order_Item" (order_item_id, order_id, ticket_id, item_price, qr_code) VALUES (77777777, 1, 1, 1200.00, '3ebc5fd22ec8681d1e9ae4333484984a'); }}} ||= QUERY PLAN =|| ||Insert on ""Ticket_Order_Item"" (cost\=0.00..0.01 rows\=0 width\=0) (actual time\=0.273..0.273 rows\=0.00 loops\=1)|| ||Buffers: shared hit\=11 read\=3 dirtied\=3|| ||-> Result (cost\=0.00..0.01 rows\=1 width\=556) (actual time\=0.001..0.001 rows\=1.00 loops\=1)|| ||Planning Time: 0.031 ms|| ||Trigger for constraint fk_item_order: time\=0.241 calls\=1|| ||Trigger for constraint fk_item_ticket: time\=2.938 calls\=1|| ||Execution Time: 3.470 ms|| * '''UPDATE''' {{{ EXPLAIN ANALYZE UPDATE "Ticket_Order_Item" SET qr_code = '3ebc5fd20ec8681d1e9ee5733484984a' WHERE order_item_id = 77777777; }}} ||= QUERY PLAN =|| ||Update on ""Ticket_Order_Item"" (cost\=0.44..8.46 rows\=0 width\=0) (actual time\=0.102..0.103 rows\=0.00 loops\=1)|| ||Buffers: shared hit\=7|| ||-> Index Scan using ""Ticket_Order_Item_pkey"" on ""Ticket_Order_Item"" (cost\=0.44..8.46 rows\=1 width\=522) (actual time\=0.049..0.051 rows\=1.00 loops\=1)|| ||Index Cond: (order_item_id \= 77777777)|| ||Index Searches: 1|| ||Buffers: shared hit\=4|| ||Planning Time: 0.160 ms|| ||Execution Time: 0.179 ms|| == Анализа и оптимизација на `Future_Events` Овој поглед служи за динамично генерирање на репертоарот, прикажувајќи ги исклучиво претстојните настани преку филтрирање на изминатите термини во однос на моменталното време на системот. Дополнително, тој ја прикажува комплетната географска адреса и локација на објектот каде ќе се одржи настанот. {{{ 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; }}} Погледот е веќе најоптимално напишан и не треба да се преуредува. ==== 1. Примарен филтер: Примарниот филтер е колоната `event_time` во табелата `Event_Happening`. Погледот постојано го бара само подмножеството на записи кои се во иднина. ==== 2. Случај на употреба: Овој поглед е „лицето“ на платформата. Се користи при вчитување на листата на претстојни настани за корисниците. Ефикасноста овде директно го дефинира времето на првично вчитување на апликацијата. ==== 3. Иницијално време: * '''SELECT:''' 100.080 ms (Бавно поради '''Seq Scan''' низ илјадници записи во `Event_Happening` за проверка на времето). * '''INSERT:''' 12.452 ms (Процесирањето на тригерите и запишувањето на податокот). * '''UPDATE:''' 0.104 ms (Инстантна брзина преку '''primary key'''). ==== 4. Анализа на планот на извршување (без индекси): Без индекс на времето, базата мораше да прави '''Sequential Scan''' низ целата табела `Event_Happening` (12,973 записи), што резултираше со бавно филтрирање во меморијата ('''Filter: `event_time > CURRENT_TIMESTAMP`'''). Дополнително, базата трошеше непотребни 466 читања од диск за да ја провери секоја редица. * '''SELECT''' {{{ EXPLAIN ANALYZE SELECT * FROM "Future_Events" WHERE city = 'London'; }}} ||= QUERY PLAN =|| ||Nested Loop (cost\=263.49..1377.68 rows\=332 width\=121) (actual time\=3.243..100.080 rows\=340.00 loops\=1)|| || Buffers: shared hit\=778 read\=844|| || -> Hash Join (cost\=263.20..1234.31 rows\=332 width\=90) (actual time\=2.695..18.025 rows\=340.00 loops\=1)|| || Hash Cond: (eh.venue_id \= v.venue_id)|| || Buffers: shared hit\=2 read\=600|| || -> Seq Scan on ""Event_Happening"" eh (cost\=0.00..937.09 rows\=12957 width\=32) (actual time\=0.154..12.105 rows\=12973.00 loops\=1)|| || Filter: (event_time > CURRENT_TIMESTAMP)|| || Rows Removed by Filter: 18365|| || Buffers: shared hit\=1 read\=466|| || -> Hash (cost\=260.00..260.00 rows\=256 width\=66) (actual time\=2.516..2.517 rows\=256.00 loops\=1)|| || Buckets: 1024 Batches: 1 Memory Usage: 33kB|| || Buffers: shared hit\=1 read\=134|| || -> Seq Scan on ""Venue"" v (cost\=0.00..260.00 rows\=256 width\=66) (actual time\=0.222..2.393 rows\=256.00 loops\=1)|| || Filter: ((address_city)::text \= 'London'::text)|| || Rows Removed by Filter: 9744|| || Buffers: shared hit\=1 read\=134|| || -> Index Scan using ""Event_pkey"" on ""Event"" e (cost\=0.29..0.43 rows\=1 width\=39) (actual time\=0.237..0.237 rows\=1.00 loops\=340)|| || Index Cond: (event_id \= eh.event_id)|| || Index Searches: 340|| || Buffers: shared hit\=776 read\=244|| ||Planning:|| || Buffers: shared hit\=31 read\=8|| ||Planning Time: 0.659 ms|| ||Execution Time: 100.249 ms|| * '''INSERT''' {{{ EXPLAIN ANALYZE INSERT INTO "Event_Happening" (event_happening_id, event_id, venue_id, event_time, duration_minutes, organizers) VALUES (99999999, 1, 1, '2026-12-31 20:00:00', 120, 'Avalon Production'); }}} ||= QUERY PLAN =|| ||Insert on ""Event_Happening"" (cost\=0.00..0.01 rows\=0 width\=0) (actual time\=12.004..12.005 rows\=0.00 loops\=1)|| || Buffers: shared hit\=4 read\=5 dirtied\=1|| || -> Result (cost\=0.00..0.01 rows\=1 width\=100) (actual time\=0.002..0.003 rows\=1.00 loops\=1)|| ||Planning Time: 0.047 ms|| ||Trigger for constraint fk_happening_event: time\=0.276 calls\=1|| ||Trigger for constraint fk_happening_venue: time\=0.134 calls\=1|| ||Execution Time: 12.452 ms|| * '''UPDATE''' {{{ EXPLAIN ANALYZE UPDATE "Event_Happening" SET event_time = '2027-01-01 21:00:00' WHERE event_happening_id = 99999999; }}} ||= QUERY PLAN =|| ||Update on ""Event_Happening"" (cost\=0.29..8.30 rows\=0 width\=0) (actual time\=0.072..0.072 rows\=0.00 loops\=1)|| || Buffers: shared hit\=12|| || -> Index Scan using ""Event_Happening_pkey"" on ""Event_Happening"" (cost\=0.29..8.30 rows\=1 width\=14) (actual time\=0.023..0.024 rows\=1.00 loops\=1)|| || Index Cond: (event_happening_id \= 99999999)|| || Index Searches: 1|| || Buffers: shared hit\=3|| ||Planning Time: 0.089 ms|| ||Execution Time: 0.104 ms|| ==== 5. Оптимизација и индексирање: За да се елиминира потребата од пребарување на целата табела, креиравме '''B-tree''' индекс врз колоната `event_time`. Ова му овозможува на планерот да го лоцира временскиот „праг“ моментално. {{{ CREATE INDEX CONCURRENTLY idx_event_happening_time ON "Event_Happening" (event_time); ANALYZE "Event_Happening"; }}} ==== 6. Резултат по оптимизација: Со воведувањето на индексот, базата премина на '''Bitmap Index Scan''', што значи дека ја чита само релевантната мапа на записи наместо целиот диск. * Времето за '''SELECT''' падна на 8.451 ms, што е приближно 12 пати побрзо. * Времето за '''INSERT''' падна на 0.458 ms (околу 27 пати побрзо), бидејќи индексната структура се ажурира ефикасно. * '''SELECT''' {{{ EXPLAIN ANALYZE SELECT * FROM "Future_Events" WHERE city = 'Berlin'; }}} ||= QUERY PLAN =|| ||Nested Loop (cost\=509.90..1246.42 rows\=96 width\=121) (actual time\=2.527..8.451 rows\=114.00 loops\=1)|| || Buffers: shared hit\=981|| || -> Hash Join (cost\=509.62..1204.96 rows\=96 width\=90) (actual time\=2.504..7.880 rows\=114.00 loops\=1)|| || Hash Cond: (eh.venue_id \= v.venue_id)|| || Buffers: shared hit\=639|| || -> Bitmap Heap Scan on ""Event_Happening"" eh (cost\=248.69..910.02 rows\=12955 width\=32) (actual time\=0.722..3.795 rows\=12973.00 loops\=1)|| || Recheck Cond: (event_time > CURRENT_TIMESTAMP)|| || Heap Blocks: exact\=467|| || Buffers: shared hit\=504|| || -> Bitmap Index Scan on idx_event_happening_time (cost\=0.00..245.45 rows\=12955 width\=0) (actual time\=0.656..0.656 rows\=12976.00 loops\=1)|| || Index Cond: (event_time > CURRENT_TIMESTAMP)|| || Index Searches: 1|| || Buffers: shared hit\=37|| || -> Hash (cost\=260.00..260.00 rows\=74 width\=66) (actual time\=1.770..1.771 rows\=74.00 loops\=1)|| || Buckets: 1024 Batches: 1 Memory Usage: 15kB|| || Buffers: shared hit\=135|| || -> Seq Scan on ""Venue"" v (cost\=0.00..260.00 rows\=74 width\=66) (actual time\=0.011..1.739 rows\=74.00 loops\=1)|| || Filter: ((address_city)::text \= 'Berlin'::text)|| || Rows Removed by Filter: 9926|| || Buffers: shared hit\=135|| || -> Index Scan using ""Event_pkey"" on ""Event"" e (cost\=0.29..0.43 rows\=1 width\=39) (actual time\=0.004..0.004 rows\=1.00 loops\=114)|| || Index Cond: (event_id \= eh.event_id)|| || Index Searches: 114|| || Buffers: shared hit\=342|| ||Planning:|| || Buffers: shared hit\=40|| ||Planning Time: 0.547 ms|| ||Execution Time: 8.502 ms|| * '''INSERT''' {{{ EXPLAIN ANALYZE INSERT INTO "Event_Happening" (event_happening_id, event_id, venue_id, event_time, duration_minutes, organizers) VALUES (88888888, 1, 1, '2028-05-15 19:30:00', 90, 'Berlin Concerts GmbH'); }}} ||= QUERY PLAN =|| ||Insert on ""Event_Happening"" (cost\=0.00..0.01 rows\=0 width\=0) (actual time\=0.275..0.275 rows\=0.00 loops\=1)|| || Buffers: shared hit\=12 dirtied\=2|| || -> Result (cost\=0.00..0.01 rows\=1 width\=100) (actual time\=0.001..0.001 rows\=1.00 loops\=1)|| ||Planning Time: 0.031 ms|| ||Trigger for constraint fk_happening_event: time\=0.109 calls\=1|| ||Trigger for constraint fk_happening_venue: time\=0.058 calls\=1|| ||Execution Time: 0.458 ms|| * '''UPDATE''' {{{ EXPLAIN ANALYZE UPDATE "Event_Happening" SET event_time = '2028-05-16 20:00:00' WHERE event_happening_id = 88888888; }}} ||= QUERY PLAN =|| ||Update on ""Event_Happening"" (cost\=0.29..8.30 rows\=0 width\=0) (actual time\=0.073..0.073 rows\=0.00 loops\=1)|| || Buffers: shared hit\=14|| || -> Index Scan using ""Event_Happening_pkey"" on ""Event_Happening"" (cost\=0.29..8.30 rows\=1 width\=14) (actual time\=0.022..0.023 rows\=1.00 loops\=1)|| || Index Cond: (event_happening_id \= 88888888)|| || Index Searches: 1|| || Buffers: shared hit\=3|| ||Planning Time: 0.107 ms|| ||Execution Time: 0.119 ms|| == Анализа и оптимизација на `Available_Tickets` Овој поглед овозможува моментален увид во инвентарот на достапни слободни седишта за активните настани. Во него е имплементирана комплексна логика која ја калкулира крајната продажна цена во реално време, земајќи го предвид актуелниот процент на попуст за тековниот временски период во кој се наоѓа купувачот. {{{ 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; }}} Погледот е веќе најоптимално напишан и не треба да се преуредува. ==== 1. Примарен филтер: Погледот се потпира на два филтера: `event_happening_id` (за специфичниот настани) и `is_available` (за статусот на билетот). ==== 2. Случај на употреба: Ова е клучниот поглед што се користи во моментот кога корисникот сака да купи билет. Брзината овде директно го намалува ризикот од "Race Condition" - односно, двајца корисници да се обидат да го резервираат истото место истовремено поради доцнење на податоците. ==== 3. Иницијално време: * '''SELECT:''' 79.398 ms (Релативно бавно поради '''Bitmap Heap Scan''' и '''Filter''' операција врз табела со илјадници записи). * '''INSERT:''' 18.031 ms (Високо време поради проверките на констреинтите при вметнување нови билети). * '''UPDATE:''' 0.125 ms (Инстантна брзина преку '''primary key'''). ==== 4. Анализа на планот на извршување (без индекси): Без композитен индекс, базата користеше '''Bitmap Heap Scan''' за пребарување на билетите. Иако го пронаоѓаше настанот, таа мораше да прави дополнителна проверка во меморијата ('''Filter: `is_available`''') и да отфрла огромен број записи ('''Rows Removed by Filter: 374'''). Ова трошеше 754 читања од диск. * '''SELECT''' {{{ EXPLAIN ANALYZE SELECT * FROM "Available_Tickets" WHERE event_happening_id = 1; }}} ||= QUERY PLAN =|| ||Nested Loop (cost\=2838.67..2934.30 rows\=357 width\=125) (actual time\=77.881..79.398 rows\=360.00 loops\=1)|| || Buffers: shared hit\=36 read\=754 dirtied\=1|| || -> Nested Loop Left Join (cost\=5.31..45.04 rows\=1 width\=79) (actual time\=3.244..3.250 rows\=1.00 loops\=1)|| || Buffers: shared hit\=8 read\=5|| || -> Nested Loop (cost\=0.86..24.91 rows\=1 width\=75) (actual time\=2.758..2.763 rows\=1.00 loops\=1)|| || Buffers: shared hit\=8 read\=1|| || -> Nested Loop (cost\=0.57..16.61 rows\=1 width\=63) (actual time\=0.020..0.024 rows\=1.00 loops\=1)|| || Buffers: shared hit\=6|| || -> Index Scan using ""Event_Happening_pkey"" on ""Event_Happening"" eh (cost\=0.29..8.30 rows\=1 width\=32) (actual time\=0.013..0.016 rows\=1.00 loops\=1)|| || Index Cond: (event_happening_id \= 1)|| || Index Searches: 1|| || Buffers: shared hit\=3|| || -> Index Scan using ""Event_pkey"" on ""Event"" e (cost\=0.29..8.30 rows\=1 width\=39) (actual time\=0.005..0.005 rows\=1.00 loops\=1)|| || Index Cond: (event_id \= eh.event_id)|| || Index Searches: 1|| || Buffers: shared hit\=3|| || -> Index Scan using ""Venue_pkey"" on ""Venue"" v (cost\=0.29..8.30 rows\=1 width\=28) (actual time\=2.734..2.734 rows\=1.00 loops\=1)|| || Index Cond: (venue_id \= eh.venue_id)|| || Index Searches: 1|| || Buffers: shared hit\=2 read\=1|| || -> Bitmap Heap Scan on ""Event_Period"" ep (cost\=4.45..20.12 rows\=1 width\=12) (actual time\=0.481..0.481 rows\=0.00 loops\=1)|| || Recheck Cond: (event_happening_id \= 1)|| || Filter: ((CURRENT_DATE >\= start_date) AND (CURRENT_DATE <\= end_date))|| || Rows Removed by Filter: 4|| || Heap Blocks: exact\=1|| || Buffers: shared read\=4|| || -> Bitmap Index Scan on uq_period_happening_name (cost\=0.00..4.45 rows\=4 width\=0) (actual time\=0.088..0.088 rows\=4.00 loops\=1)|| || Index Cond: (event_happening_id \= 1)|| || Index Searches: 1|| || Buffers: shared read\=3|| || -> Merge Join (cost\=2833.37..2881.23 rows\=357 width\=31) (actual time\=74.625..75.812 rows\=360.00 loops\=1)|| || Merge Cond: (st.seat_id \= t.seat_id)|| || Buffers: shared hit\=28 read\=749 dirtied\=1|| || -> Nested Loop (cost\=0.74..1196963.53 rows\=20753208 width\=18) (actual time\=0.227..1.135 rows\=734.00 loops\=1)|| || Buffers: shared hit\=23 read\=2|| || -> Index Scan using ""Seat_pkey"" on ""Seat"" st (cost\=0.44..671104.56 rows\=20753208 width\=24) (actual time\=0.024..0.343 rows\=734.00 loops\=1)|| || Index Searches: 1|| || Buffers: shared hit\=8 read\=2|| || -> Memoize (cost\=0.30..0.32 rows\=1 width\=10) (actual time\=0.001..0.001 rows\=1.00 loops\=734)|| || Cache Key: st.section_id|| || Cache Mode: logical|| || Hits: 729 Misses: 5 Evictions: 0 Overflows: 0 Memory Usage: 1kB|| || Buffers: shared hit\=15|| || -> Index Scan using ""Section_pkey"" on ""Section"" s (cost\=0.29..0.31 rows\=1 width\=10) (actual time\=0.009..0.009 rows\=1.00 loops\=5)|| || Index Cond: (section_id \= st.section_id)|| || Index Searches: 5|| || Buffers: shared hit\=15|| || -> Sort (cost\=2830.84..2831.74 rows\=357 width\=29) (actual time\=74.391..74.441 rows\=360.00 loops\=1)|| || Sort Key: t.seat_id|| || Sort Method: quicksort Memory: 41kB|| || Buffers: shared hit\=5 read\=747 dirtied\=1|| || -> Bitmap Heap Scan on ""Ticket"" t (cost\=22.15..2815.71 rows\=357 width\=29) (actual time\=1.866..73.806 rows\=360.00 loops\=1)|| || Recheck Cond: (event_happening_id \= 1)|| || Filter: is_available|| || Rows Removed by Filter: 374|| || Heap Blocks: exact\=744|| || Buffers: shared hit\=5 read\=747 dirtied\=1|| || -> Bitmap Index Scan on uq_ticket_happening_seat (cost\=0.00..22.06 rows\=733 width\=0) (actual time\=1.599..1.599 rows\=744.00 loops\=1)|| || Index Cond: (event_happening_id \= 1)|| || Index Searches: 1|| || Buffers: shared hit\=4 read\=4|| ||Planning:|| || Buffers: shared hit\=59 read\=8|| ||Planning Time: 1.571 ms|| ||Execution Time: 79.504 ms|| * '''INSERT''' {{{ EXPLAIN ANALYZE INSERT INTO "Ticket" (ticket_id, event_happening_id, seat_id, base_price, is_available) VALUES (99999999, 1, 99999, 1500.00, TRUE); }}} ||= QUERY PLAN =|| ||Insert on ""Ticket"" (cost\=0.00..0.01 rows\=0 width\=0) (actual time\=16.622..16.622 rows\=0.00 loops\=1)|| || Buffers: shared hit\=8 read\=3 dirtied\=2|| || -> Result (cost\=0.00..0.01 rows\=1 width\=41) (actual time\=0.001..0.002 rows\=1.00 loops\=1)|| ||Planning Time: 0.028 ms|| ||Trigger for constraint fk_ticket_seat: time\=1.188 calls\=1|| ||Trigger for constraint fk_ticket_happening: time\=0.202 calls\=1|| ||Execution Time: 18.031 ms|| * '''UPDATE''' {{{ EXPLAIN ANALYZE UPDATE "Ticket" SET is_available = FALSE WHERE ticket_id = 99999999; }}} ||= QUERY PLAN =|| ||Update on ""Ticket"" (cost\=0.44..8.46 rows\=0 width\=0) (actual time\=0.086..0.087 rows\=0.00 loops\=1)|| || Buffers: shared hit\=7|| || -> Index Scan using ""Ticket_pkey"" on ""Ticket"" (cost\=0.44..8.46 rows\=1 width\=7) (actual time\=0.036..0.038 rows\=1.00 loops\=1)|| || Index Cond: (ticket_id \= 99999999)|| || Index Searches: 1|| || Buffers: shared hit\=4|| ||Planning Time: 0.254 ms|| ||Execution Time: 0.125 ms|| ==== 5. Оптимизација и индексирање: За да се елиминира потребата од скенирање на сите билети (вклучително и оние што веќе се продадени), го имплементиравме овој индекс: {{{ CREATE INDEX CONCURRENTLY idx_ticket_available_happening ON "Ticket" (event_happening_id) WHERE is_available = TRUE; ANALYZE "Ticket"; }}} ==== 6. Резултат по оптимизација: Со индексот, времето на извршување драматично се подобри: * Времето за '''SELECT''' падна на 1.800 ms, што е 44 пати побрзо. Базата сега користи директна патека до податоците и нема „филтрирање“ на отфрлени редици. * Времето за '''INSERT''' падна на 0.628 ms (околу 28 пати побрзо), бидејќи констреинтите сега побрзо ги потврдуваат постојните записи. * '''SELECT''' {{{ EXPLAIN ANALYZE SELECT * FROM "Available_Tickets" WHERE event_happening_id = 2; }}} ||= QUERY PLAN =|| ||Nested Loop (cost\=1436.93..1541.30 rows\=363 width\=125) (actual time\=0.899..1.800 rows\=360.00 loops\=1)|| || Buffers: shared hit\=402|| || -> Nested Loop Left Join (cost\=5.31..45.04 rows\=1 width\=79) (actual time\=0.078..0.081 rows\=1.00 loops\=1)|| || Buffers: shared hit\=13|| || -> Nested Loop (cost\=0.86..24.91 rows\=1 width\=75) (actual time\=0.031..0.034 rows\=1.00 loops\=1)|| || Buffers: shared hit\=9|| || -> Nested Loop (cost\=0.57..16.61 rows\=1 width\=63) (actual time\=0.025..0.027 rows\=1.00 loops\=1)|| || Buffers: shared hit\=6|| || -> Index Scan using ""Event_Happening_pkey"" on ""Event_Happening"" eh (cost\=0.29..8.30 rows\=1 width\=32) (actual time\=0.016..0.017 rows\=1.00 loops\=1)|| || Index Cond: (event_happening_id \= 2)|| || Index Searches: 1|| || Buffers: shared hit\=3|| || -> Index Scan using ""Event_pkey"" on ""Event"" e (cost\=0.29..8.30 rows\=1 width\=39) (actual time\=0.005..0.005 rows\=1.00 loops\=1)|| || Index Cond: (event_id \= eh.event_id)|| || Index Searches: 1|| || Buffers: shared hit\=3|| || -> Index Scan using ""Venue_pkey"" on ""Venue"" v (cost\=0.29..8.30 rows\=1 width\=28) (actual time\=0.005..0.005 rows\=1.00 loops\=1)|| || Index Cond: (venue_id \= eh.venue_id)|| || Index Searches: 1|| || Buffers: shared hit\=3|| || -> Bitmap Heap Scan on ""Event_Period"" ep (cost\=4.45..20.12 rows\=1 width\=12) (actual time\=0.043..0.043 rows\=0.00 loops\=1)|| || Recheck Cond: (event_happening_id \= 2)|| || Filter: ((CURRENT_DATE >\= start_date) AND (CURRENT_DATE <\= end_date))|| || Rows Removed by Filter: 4|| || Heap Blocks: exact\=1|| || Buffers: shared hit\=4|| || -> Bitmap Index Scan on uq_period_happening_name (cost\=0.00..4.45 rows\=4 width\=0) (actual time\=0.008..0.008 rows\=4.00 loops\=1)|| || Index Cond: (event_happening_id \= 2)|| || Index Searches: 1|| || Buffers: shared hit\=3|| || -> Merge Join (cost\=1431.62..1488.09 rows\=363 width\=31) (actual time\=0.815..1.498 rows\=360.00 loops\=1)|| || Merge Cond: (st.seat_id \= t.seat_id)|| || Buffers: shared hit\=389|| || -> Nested Loop (cost\=0.74..1197017.90 rows\=20753360 width\=18) (actual time\=0.177..0.692 rows\=735.00 loops\=1)|| || Buffers: shared hit\=25|| || -> Index Scan using ""Seat_pkey"" on ""Seat"" st (cost\=0.44..671106.84 rows\=20753360 width\=24) (actual time\=0.009..0.188 rows\=735.00 loops\=1)|| || Index Searches: 1|| || Buffers: shared hit\=10|| || -> Memoize (cost\=0.30..0.32 rows\=1 width\=10) (actual time\=0.000..0.000 rows\=1.00 loops\=735)|| || Cache Key: st.section_id|| || Cache Mode: logical|| || Hits: 730 Misses: 5 Evictions: 0 Overflows: 0 Memory Usage: 1kB|| || Buffers: shared hit\=15|| || -> Index Scan using ""Section_pkey"" on ""Section"" s (cost\=0.29..0.31 rows\=1 width\=10) (actual time\=0.003..0.004 rows\=1.00 loops\=5)|| || Index Cond: (section_id \= st.section_id)|| || Index Searches: 5|| || Buffers: shared hit\=15|| || -> Sort (cost\=1429.07..1429.98 rows\=363 width\=29) (actual time\=0.629..0.658 rows\=360.00 loops\=1)|| || Sort Key: t.seat_id|| || Sort Method: quicksort Memory: 41kB|| || Buffers: shared hit\=364|| || -> Bitmap Heap Scan on ""Ticket"" t (cost\=7.25..1413.64 rows\=363 width\=29) (actual time\=0.088..0.525 rows\=360.00 loops\=1)|| || Recheck Cond: ((event_happening_id \= 2) AND is_available)|| || Heap Blocks: exact\=361|| || Buffers: shared hit\=364|| || -> Bitmap Index Scan on idx_ticket_available_happening (cost\=0.00..7.16 rows\=363 width\=0) (actual time\=0.038..0.038 rows\=361.00 loops\=1)|| || Index Cond: (event_happening_id \= 2)|| || Index Searches: 1|| || Buffers: shared hit\=3|| ||Planning:|| || Buffers: shared hit\=100 read\=2|| ||Planning Time: 8.763 ms|| ||Execution Time: 1.992 ms|| * '''INSERT''' {{{ EXPLAIN ANALYZE INSERT INTO "Ticket" (ticket_id, event_happening_id, seat_id, base_price, is_available) VALUES (88888888, 2, 77777, 1800.00, TRUE); }}} ||= QUERY PLAN =|| ||Insert on ""Ticket"" (cost\=0.00..0.01 rows\=0 width\=0) (actual time\=0.304..0.304 rows\=0.00 loops\=1)|| || Buffers: shared hit\=15 dirtied\=2|| || -> Result (cost\=0.00..0.01 rows\=1 width\=41) (actual time\=0.001..0.001 rows\=1.00 loops\=1)|| ||Planning Time: 0.029 ms|| ||Trigger for constraint fk_ticket_seat: time\=0.201 calls\=1|| ||Trigger for constraint fk_ticket_happening: time\=0.107 calls\=1|| ||Execution Time: 0.628 ms|| * '''UPDATE''' {{{ EXPLAIN ANALYZE UPDATE "Ticket" SET is_available = FALSE WHERE ticket_id = 88888888; }}} ||= QUERY PLAN =|| ||Update on ""Ticket"" (cost\=0.44..8.46 rows\=0 width\=0) (actual time\=0.098..0.098 rows\=0.00 loops\=1)|| || Buffers: shared hit\=14|| || -> Index Scan using ""Ticket_pkey"" on ""Ticket"" (cost\=0.44..8.46 rows\=1 width\=7) (actual time\=0.026..0.028 rows\=1.00 loops\=1)|| || Index Cond: (ticket_id \= 88888888)|| || Index Searches: 1|| || Buffers: shared hit\=4|| ||Planning Time: 0.125 ms|| ||Execution Time: 0.155 ms|| == Анализа и оптимизација на `Event_Overall_Ratings` Овој материјализиран поглед врши брза статистичка анализа на задоволството на публиката преку пресметување на просечната оцена за секој настан поединечно. Со оглед на тоа што користи агрегациски функции ('''COUNT''' и '''AVG'''), тој е зачуван како материјализиран поглед со цел да се избегне постојано пресметување и да се зачуваат перформансите на базата. {{{ 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 GROUP BY e.event_id, e.name, eh.event_happening_id, eh.event_time; }}} Погледот е веќе најоптимално напишан и не треба да се преуредува. ==== 1. Примарен филтер: `event_happening_id` (за специфични оценки по настан) или `event_id` (за севкупни оценки на серија настани). ==== 2. Случај на употреба: Прикажување на рејтингот на настанот во „Event Details“ страницата или генерирање на листа на „Најдобро оценети настани“ за администраторите. ==== 3. Оптимизација: Овој поглед е оптимално напишан со '''LEFT JOIN''' и '''GROUP BY''', што овозможува агрегација на сите оценки во еден чекор. Не бара индексирање бидејќи податоците се веќе пресметани и зачувани во материјализираната форма. == Анализа и оптимизација на `User_Order_History` Овој материјализиран поглед генерира финансиски профил и историја на нарачки за секој корисник, сумирајќи ги направените трошоци на ниво на главна нарачка. Тој нуди брз увид во точниот број на купени ставки, бројот на рефундирани билети како и вкупната сума на вратени пари по нарачка без оптоварување на трансакциските табели. {{{ 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; }}} Погледот е веќе најоптимално напишан и не треба да се преуредува. ==== 1. Примарен филтер: `user_id` (за преглед на лична историја на нарачки). ==== 2. Случај на употреба: Овозможување на корисниците да ги видат сите свои минати купувања, како и следење на статусот на рефундациите. ==== 3. Оптимизација: Користењето на под-прашалник ('''Subquery''') за агрегација на `Ticket_Order_Item` е најчист начин за пресметка на `total_items_refunded`. Бидејќи ова е аналитички поглед, тој е дизајниран да ја избегне сложената логика во реално време, па затоа е најдобро да остане непроменет. == Анализа и оптимизација на `Venue_Occupancy_Report` Овој комплексен аналитички поглед ја мери успешноста на продажбата преку споредба на бројот на продадени карти со максималниот капацитет на седишта во салата. Крајниот резултат дава прецизен процент на пополнетост за секој термин на настан, што е клучен бизнис индикатор за менаџерите и организаторите. {{{ 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 GROUP BY e.event_id, e.name, eh.event_happening_id, eh.event_time, v.name, v.number_of_seats, stats.sold_count; }}} Погледот е веќе најоптимално напишан и не треба да се преуредува. ==== 1. Примарен филтер: `event_happening_id` или `venue_name` (за известување за конкретни локации). ==== 2. Случај на употреба: Бизнис извештаи за менаџерите на објектите за следење на „Occupancy Rate“ (процент на исполнетост) на капацитетите. ==== 3. Оптимизација: Овој поглед прави комплексна математичка пресметка (`sold_count / total_venue_capacity`). Материјализацијата тука е клучна бидејќи пресметката не се извршува при секое барање, туку само кога се освежува погледот.