Changes between Version 73 and Version 74 of QueryOptimization


Ignore:
Timestamp:
06/29/26 23:27:47 (6 days ago)
Author:
231027
Comment:

--

Legend:

Unmodified
Added
Removed
Modified
  • QueryOptimization

    v73 v74  
    193193||Trigger for constraint fk_ehp_performer: time\=0.271 calls\=1||
    194194||Execution Time: 0.763 ms||
    195 
    196 == Анализа и оптимизација на `Venue_Layout`
    197 
    198 Овој поглед ја прикажува целосната структура на секој објект (сала), поврзувајќи ги поединечните седишта со соодветните сектори и името на објектот.
    199 
    200 {{{
    201 
    202 CREATE VIEW "Venue_Layout" AS
    203 SELECT v.venue_id,
    204        v.name AS venue_name,
    205        s.section_id,
    206        s.name AS section_name,
    207        st.seat_id,
    208        st.seat_number
    209 FROM "Venue" v
    210 JOIN "Section" s ON v.venue_id = s.venue_id
    211 JOIN "Seat" st ON s.section_id = st.section_id;
    212 
    213 }}}
    214 
    215 ==== 1. Примарен филтер:
    216 
    217 Примарен филтер за овој поглед е `venue_id`. Ова е логично бидејќи корисникот или администраторот најчесто бараат преглед на седиштата за една специфична сала. Секундарен филтер е `seat_number` при проверка или промена на конкретно седиште.
    218 
    219 ==== 2. Случај на употреба:
    220 
    221 Погледот се користи за прикажување на графичката мапа на седишта при процесот на резервација. Корисникот избира настан, па сала, и системот мора моментално да ја вчита структурата на салата. Бавноста тука директно влијае на продажбата на билети - ако мапата се вчитува со секунди, корисникот може да се откаже.
    222 
    223 ==== 3. Иницијално време:
    224 
    225 '''SELECT''': 2.164 s (2164 ms)
    226 
    227 '''UPDATE''': 312.239 s (околу 5.2 минути)
    228 Времето за '''UPDATE''' е критично. Ова значи дека ако системот треба да ажурира статус на седиште, целата база би била под огромен притисок.
    229 
    230 ==== 4. Анализа на планот на извршување (без индекси):
    231 
    232 Кај '''SELECT''' операцијата, базата користи '''Parallel Index Scan''' врз табелата `Section` и троши многу време на филтрирање на редови кои не одговараат (Rows Removed by Filter: 27502).
    233 
    234 Кај '''UPDATE''' операцијата се случува најлошото сценарио: '''Seq Scan'''. Базата мора да ги прочита сите 20,753,209 редови во табелата `Seat` за да го најде седиштето со број 999999. Тоа е причината за времето од 312 s.
    235 
    236  * '''SELECT'''
    237 
    238 {{{
    239 
    240 EXPLAIN ANALYZE
    241     SELECT * FROM "Venue_Layout" WHERE venue_id = 1;
    242 
    243 }}}
    244 
    245 ||= QUERY PLAN =||
    246 ||Nested Loop  (cost\=1001.14..10398.19 rows\=2264 width\=55) (actual time\=447.576..2164.258 rows\=775 loops\=1)||
    247 ||  ->  Index Scan using "Venue_pkey" on "Venue" v  (cost\=0.29..8.30 rows\=1 width\=28) (actual time\=68.551..68.555 rows\=1 loops\=1)||
    248 ||        Index Cond: (venue_id \= 1)||
    249 ||  ->  Gather  (cost\=1000.85..10367.25 rows\=2264 width\=35) (actual time\=379.017..2095.610 rows\=775 loops\=1)||
    250 ||        Workers Planned: 1||
    251 ||        Workers Launched: 1||
    252 ||        ->  Nested Loop  (cost\=0.85..9140.85 rows\=1332 width\=35) (actual time\=932.279..1787.769 rows\=388 loops\=2)||
    253 ||              ->  Parallel Index Scan using "Section_pkey" on "Section" s  (cost\=0.29..1691.80 rows\=4 width\=23) (actual time\=771.383..1395.534 rows\=2 loops\=2)||
    254 ||                    Filter: (venue_id \= 1)||
    255 ||                    Rows Removed by Filter: 27502||
    256 ||              ->  Index Scan using uq_seat_section_number on "Seat" st  (cost\=0.56..1853.21 rows\=905 width\=20) (actual time\=64.373..156.866 rows\=155 loops\=5)||
    257 ||                    Index Cond: (section_id \= s.section_id)||
    258 ||Planning Time: 1874.048 ms||
    259 ||Execution Time: 2164.361 ms||
    260 
    261  * '''INSERT'''
    262 
    263 {{{
    264 
    265 EXPLAIN ANALYZE
    266     INSERT INTO "Seat" (seat_id, section_id, seat_number)
    267     SELECT COALESCE(MAX(seat_id), 0) + 1, 1, 999999 FROM "Seat";
    268 
    269 }}}
    270 
    271 ||= QUERY PLAN =||
    272 ||Insert on "Seat"  (cost\=0.67..0.69 rows\=0 width\=0) (actual time\=745.461..745.464 rows\=0 loops\=1)||
    273 ||  ->  Subquery Scan on "*SELECT*"  (cost\=0.67..0.69 rows\=1 width\=20) (actual time\=481.921..481.925 rows\=1 loops\=1)||
    274 ||        ->  Result  (cost\=0.67..0.69 rows\=1 width\=16) (actual time\=481.918..481.920 rows\=1 loops\=1)||
    275 ||              InitPlan 1||
    276 ||                ->  Limit  (cost\=0.56..0.67 rows\=1 width\=8) (actual time\=481.909..481.910 rows\=1 loops\=1)||
    277 ||                      ->  Index Only Scan Backward using "Seat_pkey" on "Seat" "Seat_1"  (cost\=0.56..2330912.81 rows\=20753208 width\=8) (actual time\=481.907..481.908 rows\=1 loops\=1)||
    278 ||                            Heap Fetches: 0||
    279 ||Planning Time: 0.339 ms||
    280 ||Trigger for constraint fk_seat_section: time\=0.533 calls\=1||
    281 ||Execution Time: 746.039 ms||
    282 
    283  * '''UPDATE'''
    284 
    285 {{{
    286 
    287 EXPLAIN ANALYZE
    288     UPDATE "Seat"
    289     SET seat_number = 888888
    290     WHERE seat_number = 999999;
    291 
    292 }}}
    293 
    294 ||= QUERY PLAN =||
    295 ||Update on "Seat"  (cost\=0.00..391602.10 rows\=0 width\=0) (actual time\=308799.033..308799.035 rows\=0 loops\=1)||
    296 ||  ->  Seq Scan on "Seat"  (cost\=0.00..391602.10 rows\=11492 width\=10) (actual time\=308792.032..308792.034 rows\=1 loops\=1)||
    297 ||        Filter: (seat_number \= 999999)||
    298 ||        Rows Removed by Filter: 20753209||
    299 ||Planning Time: 0.114 ms||
    300 ||JIT:||
    301 ||  Functions: 4||
    302 ||  Options: Inlining false, Optimization false, Expressions true, Deforming true||
    303 ||  Timing: Generation 0.405 ms (Deform 0.071 ms), Inlining 0.000 ms, Optimization 0.595 ms, Emission 6.566 ms, Total 7.565 ms||
    304 ||Execution Time: 312239.424 ms||
    305 
    306 ==== 5. Оптимизација и индексирање:
    307 
    308 За да се елиминира целосното скенирање на табелите, се воведуваат:
    309 
    310  * `idx_section_venue_id`: Овозможува веднаш да се најдат само секциите што му припаѓаат на соодветната сала.
    311 
    312  * `idx_seat_section_id`: Го забрзува поврзувањето на седиштата со нивните секции.
    313 
    314  * `idx_seat_number`: Клучен индекс кој го претвора петминутното пребарување во инстантно пронаоѓање на седиштето.
    315 
    316 {{{
    317 
    318 -- index for linking seats to their respective sections
    319 CREATE INDEX idx_seat_section_id ON "Seat"(section_id);
    320 
    321 -- index for linking sections to venues
    322 CREATE INDEX idx_section_venue_id ON "Section"(venue_id);
    323 
    324 -- index for optimizing search and update operations on specific seat numbers
    325 CREATE INDEX idx_seat_number ON "Seat"(seat_number);
    326 
    327 }}}
    328 
    329 ==== 6. Резултат по оптимизација:
    330 
    331 '''SELECT''': Намалено на 0.572 ms.
    332 
    333 '''UPDATE''': Намалено на 0.461 ms.
    334 
    335  * '''SELECT'''
    336 
    337 {{{
    338 
    339 EXPLAIN ANALYZE
    340     SELECT * FROM "Venue_Layout" WHERE venue_id = 1;
    341 
    342 }}}
    343 
    344 ||= QUERY PLAN =||
    345 ||Nested Loop  (cost\=1.01..3060.48 rows\=2264 width\=55) (actual time\=0.184..0.512 rows\=776 loops\=1)||
    346 ||  ->  Nested Loop  (cost\=0.57..16.76 rows\=6 width\=43) (actual time\=0.140..0.144 rows\=5 loops\=1)||
    347 ||        ->  Index Scan using "Venue_pkey" on "Venue" v  (cost\=0.29..8.30 rows\=1 width\=28) (actual time\=0.077..0.078 rows\=1 loops\=1)||
    348 ||              Index Cond: (venue_id \= 1)||
    349 ||        ->  Index Scan using idx_section_venue_id on "Section" s  (cost\=0.29..8.39 rows\=6 width\=23) (actual time\=0.058..0.060 rows\=5 loops\=1)||
    350 ||              Index Cond: (venue_id \= 1)||
    351 ||  ->  Index Scan using idx_seat_section_id on "Seat" st  (cost\=0.44..498.24 rows\=905 width\=20) (actual time\=0.012..0.053 rows\=155 loops\=5)||
    352 ||        Index Cond: (section_id \= s.section_id)||
    353 ||Planning Time: 1.235 ms||
    354 ||Execution Time: 0.572 ms||
    355 
    356  * '''INSERT'''
    357 
    358 {{{
    359 
    360 EXPLAIN ANALYZE
    361     INSERT INTO "Seat" (seat_id, section_id, seat_number)
    362     SELECT COALESCE(MAX(seat_id), 0) + 1, 1, 111222 FROM "Seat";
    363 
    364 }}}
    365 
    366 ||= QUERY PLAN =||
    367 ||Insert on "Seat"  (cost\=0.67..0.69 rows\=0 width\=0) (actual time\=0.493..0.494 rows\=0 loops\=1)||
    368 ||  ->  Subquery Scan on "*SELECT*"  (cost\=0.67..0.69 rows\=1 width\=20) (actual time\=0.169..0.171 rows\=1 loops\=1)||
    369 ||        ->  Result  (cost\=0.67..0.69 rows\=1 width\=16) (actual time\=0.167..0.168 rows\=1 loops\=1)||
    370 ||              InitPlan 1||
    371 ||                ->  Limit  (cost\=0.56..0.67 rows\=1 width\=8) (actual time\=0.162..0.163 rows\=1 loops\=1)||
    372 ||                      ->  Index Only Scan Backward using "Seat_pkey" on "Seat" "Seat_1"  (cost\=0.56..2330912.84 rows\=20753210 width\=8) (actual time\=0.161..0.161 rows\=1 loops\=1)||
    373 ||                            Heap Fetches: 1||
    374 ||Planning Time: 0.219 ms||
    375 ||Trigger for constraint fk_seat_section: time\=0.300 calls\=1||
    376 ||Execution Time: 0.839 ms||
    377 
    378  * '''UPDATE'''
    379 
    380 {{{
    381 
    382 EXPLAIN ANALYZE
    383     UPDATE "Seat"
    384     SET seat_number = 333444
    385     WHERE seat_number = 111222;
    386 
    387 }}}
    388 
    389 ||= QUERY PLAN =||
    390 ||Update on "Seat"  (cost\=0.44..44297.09 rows\=0 width\=0) (actual time\=0.395..0.396 rows\=0 loops\=1)||
    391 ||  ->  Index Scan using idx_seat_number on "Seat"  (cost\=0.44..44297.09 rows\=11492 width\=10) (actual time\=0.193..0.195 rows\=1 loops\=1)||
    392 ||        Index Cond: (seat_number \= 111222)||
    393 ||Planning Time: 0.141 ms||
    394 ||Execution Time: 0.461 ms||
    395 
    396 == Анализа и оптимизација на `User_Tickets`
    397 
    398 Овој поглед дава детален преглед на сите купени билети по корисник, вклучувајќи го соодветниот настан, QR-кодот за влез и информациите за евентуална рефундација.
    399 
    400 {{{
    401 
    402 CREATE VIEW "User_Tickets" AS
    403 SELECT u.user_id,
    404        u.username,
    405        t.ticket_id,
    406        e.event_id,
    407        e.name AS event_name,
    408        tp.purchase_id,
    409        tp.qr_code,
    410        tr.refund_id,
    411        tr.refund_time
    412 FROM "User" u
    413 JOIN "Ticket_Purchase" tp ON u.user_id = tp.user_id
    414 JOIN "Ticket" t ON tp.ticket_id = t.ticket_id
    415 JOIN "Event_Happening" eh ON t.event_happening_id = eh.event_happening_id
    416 JOIN "Event" e ON eh.event_id = e.event_id
    417 LEFT JOIN "Ticket_Refund" tr ON tp.purchase_id = tr.purchase_id;
    418 
    419 }}}
    420 
    421 ==== 1. Примарен филтер:
    422 
    423 Примарен филтер за овој поглед е `user_id`. Ова е најчестото сценарио бидејќи погледот служи за корисникот да ја види сопствената историја на купени билети и нивниот статус (дали се рефундирани или не).
    424 
    425 ==== 2. Случај на употреба:
    426 
    427 Погледот се користи во делот '''My Tickets''' на профилот на секој регистриран корисник. Ова е критична точка на интеракција; корисникот очекува веднаш да ги добие своите билети за да може да го прикаже QR-кодот при влез на настан. Секое доцнење тука предизвикува директен застој на влезните капии.
    428 
    429 ==== 3. Иницијално време:
    430 
    431 Иницијалното време за извршување изнесува 443.784 ms. Иако ова изгледа брзо во споредба со претходните погледи, треба да се земе предвид дека во реална околина со илјадници истовремени корисници, ова време ќе ескалира и ќе ја преоптовари базата.
    432 
    433 ==== Анализа на планот на извршување (без индекси):
    434 
    435 Главниот проблем е идентификуван кај табелата `Ticket_Purchase`:
    436 
    437  * Се извршува '''Parallel Seq Scan''' врз 3.2 милиони записи.
    438 
    439  * Базата троши 389 ms само за да ги прелиста сите трансакции и да ги најде оние што му припаѓаат на `user_id = 1`.
    440 
    441  * '''SELECT'''
    442 
    443 {{{
    444 
    445 EXPLAIN ANALYZE
    446     SELECT * FROM "User_Tickets" WHERE user_id = 1;
    447 
    448 }}}
    449 
    450 ||= QUERY PLAN =||
    451 ||Nested Loop Left Join  (cost\=1002.00..232798.26 rows\=1 width\=115) (actual time\=431.345..441.975 rows\=1 loops\=1)||
    452 ||  ->  Nested Loop  (cost\=1001.56..232789.81 rows\=1 width\=99) (actual time\=431.312..441.941 rows\=1 loops\=1)||
    453 ||        ->  Nested Loop  (cost\=1001.28..232789.45 rows\=1 width\=68) (actual time\=431.253..441.878 rows\=1 loops\=1)||
    454 ||              ->  Nested Loop  (cost\=1000.99..232789.15 rows\=1 width\=68) (actual time\=431.200..441.822 rows\=1 loops\=1)||
    455 ||                    ->  Nested Loop  (cost\=1000.43..232780.57 rows\=1 width\=60) (actual time\=431.038..441.657 rows\=1 loops\=1)||
    456 ||                          ->  Index Scan using "User_pkey" on "User" u  (cost\=0.43..8.45 rows\=1 width\=26) (actual time\=0.114..0.118 rows\=1 loops\=1)||
    457 ||                                Index Cond: (user_id \= 1)||
    458 ||                          ->  Gather  (cost\=1000.00..232772.11 rows\=1 width\=42) (actual time\=430.826..441.440 rows\=1 loops\=1)||
    459 ||                                Workers Planned: 4||
    460 ||                                Workers Launched: 4||
    461 ||                                ->  Parallel Seq Scan on "Ticket_Purchase" tp  (cost\=0.00..231772.01 rows\=1 width\=42) (actual time\=389.356..389.357 rows\=0 loops\=5)||
    462 ||                                      Filter: (user_id \= 1)||
    463 ||                                      Rows Removed by Filter: 3200000||
    464 ||                    ->  Index Scan using "Ticket_pkey" on "Ticket" t  (cost\=0.56..8.58 rows\=1 width\=16) (actual time\=0.104..0.105 rows\=1 loops\=1)||
    465 ||                          Index Cond: (ticket_id \= tp.ticket_id)||
    466 ||              ->  Index Scan using "Event_Happening_pkey" on "Event_Happening" eh  (cost\=0.29..0.31 rows\=1 width\=16) (actual time\=0.033..0.034 rows\=1 loops\=1)||
    467 ||                    Index Cond: (event_happening_id \= t.event_happening_id)||
    468 ||        ->  Index Scan using "Event_pkey" on "Event" e  (cost\=0.29..0.35 rows\=1 width\=39) (actual time\=0.039..0.040 rows\=1 loops\=1)||
    469 ||              Index Cond: (event_id \= eh.event_id)||
    470 ||  ->  Index Scan using "Ticket_Refund_purchase_id_key" on "Ticket_Refund" tr  (cost\=0.43..8.45 rows\=1 width\=24) (actual time\=0.015..0.016 rows\=0 loops\=1)||
    471 ||        Index Cond: (purchase_id \= tp.purchase_id)||
    472 ||Planning Time: 15.360 ms||
    473 ||JIT:||
    474 ||  Functions: 44||
    475 ||  Options: Inlining false, Optimization false, Expressions true, Deforming true||
    476 ||  Timing: Generation 3.367 ms (Deform 1.376 ms), Inlining 0.000 ms, Optimization 2.363 ms, Emission 42.753 ms, Total 48.484 ms||
    477 ||Execution Time: 443.784 ms||
    478 
    479  * '''INSERT'''
    480 
    481 {{{
    482 
    483 EXPLAIN ANALYZE
    484     INSERT INTO "Ticket_Purchase" (purchase_id, ticket_id, user_id, qr_code, purchase_amount)
    485     SELECT COALESCE(MAX(purchase_id), 0) + 1, 1, 1, 'QR-TEST-CODE-001', 1200.00
    486     FROM "Ticket_Purchase";
    487 
    488 }}}
    489 
    490 ||= QUERY PLAN =||
    491 ||Insert on "Ticket_Purchase"  (cost\=0.47..0.50 rows\=0 width\=0) (actual time\=0.217..0.218 rows\=0 loops\=1)||
    492 ||  ->  Subquery Scan on "*SELECT*"  (cost\=0.47..0.50 rows\=1 width\=552) (actual time\=0.055..0.057 rows\=1 loops\=1)||
    493 ||        ->  Result  (cost\=0.47..0.48 rows\=1 width\=80) (actual time\=0.043..0.044 rows\=1 loops\=1)||
    494 ||              InitPlan 1||
    495 ||                ->  Limit  (cost\=0.43..0.47 rows\=1 width\=8) (actual time\=0.038..0.039 rows\=1 loops\=1)||
    496 ||                      ->  Index Only Scan Backward using "Ticket_Purchase_pkey" on "Ticket_Purchase" "Ticket_Purchase_1"  (cost\=0.43..484152.95 rows\=16000004 width\=8) (actual time\=0.037..0.038 rows\=1 loops\=1)||
    497 ||                            Heap Fetches: 3||
    498 ||Planning Time: 0.207 ms||
    499 ||Trigger for constraint fk_purchase_ticket: time\=0.190 calls\=1||
    500 ||Trigger for constraint fk_purchase_user: time\=0.136 calls\=1||
    501 ||Execution Time: 0.600 ms||
    502 
    503  * '''UPDATE'''
    504 
    505 {{{
    506 
    507 EXPLAIN ANALYZE
    508     UPDATE "Ticket_Purchase"
    509     SET purchase_amount = 1500.00
    510     WHERE user_id = 1 AND qr_code = 'QR-TEST-CODE-001';
    511 
    512 }}}
    513 
    514 ||= QUERY PLAN =||
    515 ||Update on "Ticket_Purchase"  (cost\=0.56..8.58 rows\=0 width\=0) (actual time\=0.121..0.122 rows\=0 loops\=1)||
    516 ||  ->  Index Scan using "Ticket_Purchase_qr_code_key" on "Ticket_Purchase"  (cost\=0.56..8.58 rows\=1 width\=10) (actual time\=0.048..0.050 rows\=1 loops\=1)||
    517 ||        Index Cond: ((qr_code)::text \= 'QR-TEST-CODE-001'::text)||
    518 ||        Filter: (user_id \= 1)||
    519 ||Planning Time: 0.181 ms||
    520 ||Execution Time: 0.157 ms||
    521 
    522 ==== 5. Оптимизација и индексирање:
    523 
    524 Со цел да се избегне целосното скенирање, се предлагаат следните индекси:
    525 
    526  * `idx_ticket_purchase_user_id`: За директен пристап до билетите на корисникот.
    527 
    528  * `idx_ticket_refund_purchase_id`: За побрзо извршување на '''LEFT JOIN''' операцијата со табелата за рефундации.
    529 
    530  * `idx_ticket_purchase_ticket_id` и `idx_event_happening_event_id`: За побрзо поврзување на релациите.
    531 
    532 {{{
    533 
    534 -- index for linking ticket purchases to the specific tickets
    535 CREATE INDEX idx_ticket_purchase_ticket_id ON "Ticket_Purchase"(ticket_id);
    536 
    537 -- index for linking purchases to users
    538 CREATE INDEX idx_ticket_purchase_user_id ON "Ticket_Purchase"(user_id);
    539 
    540 -- index for the LEFT JOIN with refunds
    541 CREATE INDEX idx_ticket_refund_purchase_id ON "Ticket_Refund"(purchase_id);
    542 
    543 -- index for optimizing event lookups within scheduled event happenings
    544 CREATE INDEX idx_event_happening_event_id ON "Event_Happening"(event_id);
    545 
    546 }}}
    547 
    548 ==== 6. Резултат по оптимизација:
    549 
    550 Времето по оптимизација остана речиси идентично (450.880 ms).
    551 
    552 Планот покажува дека базата се уште избира '''Parallel Seq Scan''' наместо новокреираниот индекс.
    553 
    554 Сепак, кај '''INSERT''' и '''UPDATE''' операциите се гледа стабилност и екстремна брзина (под 1 ms), што потврдува дека индексите се правилно поставени за интегритетот на податоците.
    555 
    556  * '''SELECT'''
    557 
    558 {{{
    559 
    560 EXPLAIN ANALYZE
    561     SELECT * FROM "User_Tickets" WHERE user_id = 1;
    562 
    563 }}}
    564 
    565 ||= QUERY PLAN =||
    566 ||Nested Loop Left Join  (cost\=1002.00..232798.25 rows\=1 width\=115) (actual time\=438.622..449.169 rows\=2 loops\=1)||
    567 ||  ->  Nested Loop  (cost\=1001.56..232789.80 rows\=1 width\=99) (actual time\=438.588..449.128 rows\=2 loops\=1)||
    568 ||        ->  Gather  (cost\=1001.13..232781.35 rows\=1 width\=81) (actual time\=438.383..448.900 rows\=2 loops\=1)||
    569 ||              Workers Planned: 4||
    570 ||              Workers Launched: 4||
    571 ||              ->  Nested Loop  (cost\=1.14..231781.25 rows\=1 width\=81) (actual time\=397.225..397.235 rows\=0 loops\=5)||
    572 ||                    ->  Nested Loop  (cost\=0.85..231780.89 rows\=1 width\=50) (actual time\=397.203..397.211 rows\=0 loops\=5)||
    573 ||                          ->  Nested Loop  (cost\=0.56..231780.59 rows\=1 width\=50) (actual time\=397.176..397.182 rows\=0 loops\=5)||
    574 ||                                ->  Parallel Seq Scan on "Ticket_Purchase" tp  (cost\=0.00..231772.01 rows\=1 width\=42) (actual time\=397.114..397.115 rows\=0 loops\=5)||
    575 ||                                      Filter: (user_id \= 1)||
    576 ||                                      Rows Removed by Filter: 3200000||
    577 ||                                ->  Index Scan using "Ticket_pkey" on "Ticket" t  (cost\=0.56..8.58 rows\=1 width\=16) (actual time\=0.107..0.107 rows\=1 loops\=2)||
    578 ||                                      Index Cond: (ticket_id \= tp.ticket_id)||
    579 ||                          ->  Index Scan using "Event_Happening_pkey" on "Event_Happening" eh  (cost\=0.29..0.31 rows\=1 width\=16) (actual time\=0.055..0.055 rows\=1 loops\=2)||
    580 ||                                Index Cond: (event_happening_id \= t.event_happening_id)||
    581 ||                    ->  Index Scan using "Event_pkey" on "Event" e  (cost\=0.29..0.35 rows\=1 width\=39) (actual time\=0.044..0.044 rows\=1 loops\=2)||
    582 ||                          Index Cond: (event_id \= eh.event_id)||
    583 ||        ->  Index Scan using "User_pkey" on "User" u  (cost\=0.43..8.45 rows\=1 width\=26) (actual time\=0.086..0.087 rows\=1 loops\=2)||
    584 ||              Index Cond: (user_id \= 1)||
    585 ||  ->  Index Scan using idx_ticket_refund_purchase_id on "Ticket_Refund" tr  (cost\=0.43..8.45 rows\=1 width\=24) (actual time\=0.010..0.010 rows\=0 loops\=2)||
    586 ||        Index Cond: (purchase_id \= tp.purchase_id)||
    587 ||Planning Time: 2.459 ms||
    588 ||JIT:||
    589 ||  Functions: 99||
    590 ||  Options: Inlining false, Optimization false, Expressions true, Deforming true||
    591 ||  Timing: Generation 7.544 ms (Deform 3.336 ms), Inlining 0.000 ms, Optimization 3.874 ms, Emission 71.829 ms, Total 83.247 ms||
    592 ||Execution Time: 450.880 ms||
    593 
    594  * '''INSERT'''
    595 
    596 {{{
    597 
    598 EXPLAIN ANALYZE
    599     INSERT INTO "Ticket_Purchase" (purchase_id, ticket_id, user_id, qr_code, purchase_amount)
    600     SELECT COALESCE(MAX(purchase_id), 0) + 1, 1, 1, 'QR-TEST-CODE-002', 1200.00
    601     FROM "Ticket_Purchase";
    602 
    603 }}}
    604 
    605 ||= QUERY PLAN =||
    606 ||Insert on "Ticket_Purchase"  (cost\=0.47..0.50 rows\=0 width\=0) (actual time\=0.452..0.453 rows\=0 loops\=1)||
    607 ||  ->  Subquery Scan on "*SELECT*"  (cost\=0.47..0.50 rows\=1 width\=552) (actual time\=0.049..0.051 rows\=1 loops\=1)||
    608 ||        ->  Result  (cost\=0.47..0.48 rows\=1 width\=80) (actual time\=0.038..0.039 rows\=1 loops\=1)||
    609 ||              InitPlan 1||
    610 ||                ->  Limit  (cost\=0.43..0.47 rows\=1 width\=8) (actual time\=0.033..0.033 rows\=1 loops\=1)||
    611 ||                      ->  Index Only Scan Backward using "Ticket_Purchase_pkey" on "Ticket_Purchase" "Ticket_Purchase_1"  (cost\=0.43..484152.93 rows\=16000003 width\=8) (actual time\=0.032..0.032 rows\=1 loops\=1)||
    612 ||                            Heap Fetches: 2||
    613 ||Planning Time: 0.207 ms||
    614 ||Trigger for constraint fk_purchase_ticket: time\=0.194 calls\=1||
    615 ||Trigger for constraint fk_purchase_user: time\=0.130 calls\=1||
    616 ||Execution Time: 0.828 ms||
    617 
    618  * '''UPDATE'''
    619 
    620 {{{
    621 
    622 EXPLAIN ANALYZE
    623     UPDATE "Ticket_Purchase"
    624     SET purchase_amount = 1350.00
    625     WHERE user_id = 1 AND qr_code = 'QR-TEST-CODE-002';
    626 
    627 }}}
    628 
    629 ||= QUERY PLAN =||
    630 ||Update on "Ticket_Purchase"  (cost\=0.56..8.58 rows\=0 width\=0) (actual time\=0.207..0.207 rows\=0 loops\=1)||
    631 ||  ->  Index Scan using "Ticket_Purchase_qr_code_key" on "Ticket_Purchase"  (cost\=0.56..8.58 rows\=1 width\=10) (actual time\=0.124..0.126 rows\=1 loops\=1)||
    632 ||        Index Cond: ((qr_code)::text \= 'QR-TEST-CODE-002'::text)||
    633 ||        Filter: (user_id \= 1)||
    634 ||Planning Time: 0.150 ms||
    635 ||Execution Time: 0.267 ms||
    636 
    637 == Анализа и оптимизација на `Event_User_Ratings`
    638 
    639 Овој поглед овозможува детален пристап до поединечните коментари и оценки што секој корисник ги оставил за одреден термин на настан.
    640 
    641 {{{
    642 
    643 CREATE VIEW "Event_User_Ratings" AS
    644 SELECT eh.event_happening_id,
    645        e.event_id,
    646        e.name AS event_name,
    647        u.user_id,
    648        u.username,
    649        ehr.rating_id,
    650        ehr.rating,
    651        ehr.comment
    652 FROM "Event" e
    653 JOIN "Event_Happening" eh ON e.event_id = eh.event_id
    654 JOIN "Event_Happening_Rating" ehr ON eh.event_happening_id = ehr.event_happening_id
    655 JOIN "User" u ON ehr.user_id = u.user_id;
    656 
    657 }}}
    658 
    659 ==== 1. Примарен филтер:
    660 
    661 Примарен филтер за овој поглед е `user_id`, бидејќи најчесто корисниците сакаат да ги видат сопствените претходни оценки и коментари. Секундарен филтер е `event_happening_id`, кога администраторот сака да ги види сите рецензии за конкретен термин на настан.
    662 
    663 ==== 2. Случај на употреба:
    664 
    665 Погледот се користи во делот на корисничкиот профил (историја на рецензии) и на страната на самиот настан за приказ на рецензии од други корисници. Ова влијае на довербата на потенцијалните купувачи и на транспарентноста на платформата.
    666 
    667 ==== 3. Иницијално време:
    668 
    669 Иницијалното време за извршување е многу ниско (0.187 ms). Ова се должи на фактот што табелата `Event_Happening_Rating` е релативно мала (околу 17,800 редови), па базата може многу брзо да ја процесира дури и без екстремна оптимизација.
    670 
    671 ==== 4. Анализа на планот на извршување (без индекси):
    672 
    673 Иако времето е мало, планот без индекси покажува дека базата веќе користи некои постоечки индекси (`idx_ehr_user_id`).
    674 
    675 При '''UPDATE''' операцијата, базата користи '''Index Scan''', што значи дека веќе постои ефикасна патека за пронаоѓање на записот.
    676 
    677  * '''SELECT'''
    678 
    679 {{{
    680 
    681 EXPLAIN ANALYZE
    682     SELECT * FROM "Event_User_Ratings"
    683     WHERE user_id = 1;
    684 
    685 }}}
    686 
    687 ||= QUERY PLAN =||
    688 ||Nested Loop  (cost\=1.29..21.42 rows\=1 width\=131) (actual time\=0.104..0.105 rows\=0 loops\=1)||
    689 ||  ->  Nested Loop  (cost\=0.86..12.96 rows\=1 width\=113) (actual time\=0.103..0.105 rows\=0 loops\=1)||
    690 ||        ->  Nested Loop  (cost\=0.57..12.61 rows\=1 width\=82) (actual time\=0.103..0.104 rows\=0 loops\=1)||
    691 ||              ->  Index Scan using idx_ehr_user_id on "Event_Happening_Rating" ehr  (cost\=0.29..4.30 rows\=1 width\=74) (actual time\=0.103..0.103 rows\=0 loops\=1)||
    692 ||                    Index Cond: (user_id \= 1)||
    693 ||              ->  Index Scan using "Event_Happening_pkey" on "Event_Happening" eh  (cost\=0.29..8.30 rows\=1 width\=16) (never executed)||
    694 ||                    Index Cond: (event_happening_id \= ehr.event_happening_id)||
    695 ||        ->  Index Scan using "Event_pkey" on "Event" e  (cost\=0.29..0.35 rows\=1 width\=39) (never executed)||
    696 ||              Index Cond: (event_id \= eh.event_id)||
    697 ||  ->  Index Scan using "User_pkey" on "User" u  (cost\=0.43..8.45 rows\=1 width\=26) (never executed)||
    698 ||        Index Cond: (user_id \= 1)||
    699 ||Planning Time: 464.594 ms||
    700 ||Execution Time: 0.187 ms||
    701 
    702  * '''INSERT'''
    703 
    704 {{{
    705 
    706 EXPLAIN ANALYZE
    707     INSERT INTO "Event_Happening_Rating" (rating_id, event_happening_id, user_id, rating, comment)
    708     SELECT COALESCE(MAX(rating_id), 0) + 1, 1, 1, 5, 'Test rating'
    709     FROM "Event_Happening_Rating";
    710 
    711 }}}
    712 
    713 ||= QUERY PLAN =||
    714 ||Insert on "Event_Happening_Rating"  (cost\=0.31..0.34 rows\=0 width\=0) (actual time\=0.521..0.522 rows\=0 loops\=1)||
    715 ||  ->  Subquery Scan on "*SELECT*"  (cost\=0.31..0.34 rows\=1 width\=60) (actual time\=0.122..0.123 rows\=1 loops\=1)||
    716 ||        ->  Result  (cost\=0.31..0.33 rows\=1 width\=52) (actual time\=0.119..0.120 rows\=1 loops\=1)||
    717 ||              InitPlan 1||
    718 ||                ->  Limit  (cost\=0.29..0.31 rows\=1 width\=8) (actual time\=0.114..0.115 rows\=1 loops\=1)||
    719 ||                      ->  Index Only Scan Backward using "Event_Happening_Rating_pkey" on "Event_Happening_Rating" "Event_Happening_Rating_1"  (cost\=0.29..478.37 rows\=17872 width\=8) (actual time\=0.113..0.113 rows\=1 loops\=1)||
    720 ||                            Heap Fetches: 6||
    721 ||Planning Time: 0.297 ms||
    722 ||Trigger for constraint fk_event_happening_rating_event_happening: time\=0.490 calls\=1||
    723 ||Trigger for constraint fk_event_happening_rating_user: time\=0.370 calls\=1||
    724 ||Execution Time: 1.444 ms||
    725 
    726  * '''UPDATE'''
    727 
    728 {{{
    729 
    730 EXPLAIN ANALYZE
    731     UPDATE "Event_Happening_Rating"
    732     SET rating = 4, comment = 'New test rating'
    733     WHERE event_happening_id = 1 AND user_id = 1;
    734 
    735 }}}
    736 
    737 ||= QUERY PLAN =||
    738 ||Update on "Event_Happening_Rating"  (cost\=0.29..4.31 rows\=0 width\=0) (actual time\=0.176..0.177 rows\=0 loops\=1)||
    739 ||  ->  Index Scan using idx_ehr_user_id on "Event_Happening_Rating"  (cost\=0.29..4.31 rows\=1 width\=42) (actual time\=0.039..0.042 rows\=1 loops\=1)||
    740 ||        Index Cond: (user_id \= 1)||
    741 ||        Filter: (event_happening_id \= 1)||
    742 ||Planning Time: 0.153 ms||
    743 ||Execution Time: 0.218 ms||
    744 
    745 ==== 5. Оптимизација и индексирање:
    746 
    747 За да се осигураме дека погледот ќе остане брз и кога бројот на рецензии ќе порасне на милиони, се додаваат:
    748 
    749  * `idx_ehr_happening_id`: За брзо филтрирање на рецензии по настан.
    750 
    751  * `idx_ehr_user_id`: За брзо филтрирање на рецензии по корисник.
    752 
    753 {{{
    754 
    755 -- index for linking ratings to event happenings
    756 CREATE INDEX idx_ehr_happening_id ON "Event_Happening_Rating"(event_happening_id);
    757 
    758 -- index for linking ratings to users
    759 CREATE INDEX idx_ehr_user_id ON "Event_Happening_Rating"(user_id);
    760 
    761 }}}
    762 
    763 ==== 6. Резултат по оптимизација:
    764 
    765 По „оптимизацијата“, времето на '''SELECT''' се зголемило на 359.600 ms.
    766 
    767 Анализа на аномалијата: Планот покажува дека базата во овој случај одбрала '''Seq Scan''' наместо '''Index Scan'''.
    768 
    769  * '''SELECT'''
    770 
    771 {{{
    772 
    773 EXPLAIN ANALYZE
    774     SELECT * FROM "Event_User_Ratings" WHERE user_id = 1;
    775 
    776 }}}
    777 
    778 ||= QUERY PLAN =||
    779 ||Nested Loop  (cost\=1.00..476.51 rows\=1 width\=131) (actual time\=359.514..359.524 rows\=1 loops\=1)||
    780 ||  ->  Nested Loop  (cost\=0.57..468.06 rows\=1 width\=113) (actual time\=359.389..359.396 rows\=1 loops\=1)||
    781 ||        ->  Nested Loop  (cost\=0.29..467.71 rows\=1 width\=82) (actual time\=2.104..2.110 rows\=1 loops\=1)||
    782 ||              ->  Seq Scan on "Event_Happening_Rating" ehr  (cost\=0.00..459.40 rows\=1 width\=74) (actual time\=2.038..2.041 rows\=1 loops\=1)||
    783 ||                    Filter: (user_id \= 1)||
    784 ||                    Rows Removed by Filter: 17871||
    785 ||              ->  Index Scan using "Event_Happening_pkey" on "Event_Happening" eh  (cost\=0.29..8.30 rows\=1 width\=16) (actual time\=0.061..0.062 rows\=1 loops\=1)||
    786 ||                    Index Cond: (event_happening_id \= ehr.event_happening_id)||
    787 ||        ->  Index Scan using "Event_pkey" on "Event" e  (cost\=0.29..0.35 rows\=1 width\=39) (actual time\=357.279..357.280 rows\=1 loops\=1)||
    788 ||              Index Cond: (event_id \= eh.event_id)||
    789 ||  ->  Index Scan using "User_pkey" on "User" u  (cost\=0.43..8.45 rows\=1 width\=26) (actual time\=0.120..0.121 rows\=1 loops\=1)||
    790 ||        Index Cond: (user_id \= 1)||
    791 ||Planning Time: 31.049 ms||
    792 ||Execution Time: 359.600 ms||
    793 
    794  * '''INSERT'''
    795 
    796 {{{
    797 
    798 EXPLAIN ANALYZE
    799     INSERT INTO "Event_Happening_Rating" (rating_id, event_happening_id, user_id, rating, comment)
    800     SELECT COALESCE(MAX(rating_id), 0) + 1, 2, 1, 5, 'Test rating'
    801     FROM "Event_Happening_Rating";
    802 
    803 }}}
    804 
    805 ||= QUERY PLAN =||
    806 ||Insert on "Event_Happening_Rating"  (cost\=0.31..0.34 rows\=0 width\=0) (actual time\=0.482..0.483 rows\=0 loops\=1)||
    807 ||  ->  Subquery Scan on "*SELECT*"  (cost\=0.31..0.34 rows\=1 width\=60) (actual time\=0.117..0.119 rows\=1 loops\=1)||
    808 ||        ->  Result  (cost\=0.31..0.33 rows\=1 width\=52) (actual time\=0.115..0.115 rows\=1 loops\=1)||
    809 ||              InitPlan 1||
    810 ||                ->  Limit  (cost\=0.29..0.31 rows\=1 width\=8) (actual time\=0.110..0.111 rows\=1 loops\=1)||
    811 ||                      ->  Index Only Scan Backward using "Event_Happening_Rating_pkey" on "Event_Happening_Rating" "Event_Happening_Rating_1"  (cost\=0.29..484.37 rows\=17872 width\=8) (actual time\=0.108..0.108 rows\=1 loops\=1)||
    812 ||                            Heap Fetches: 4||
    813 ||Planning Time: 0.203 ms||
    814 ||Trigger for constraint fk_event_happening_rating_event_happening: time\=0.197 calls\=1||
    815 ||Trigger for constraint fk_event_happening_rating_user: time\=0.114 calls\=1||
    816 ||Execution Time: 0.842 ms||
    817 
    818  * '''UPDATE'''
    819 
    820 {{{
    821 
    822 EXPLAIN ANALYZE
    823     UPDATE "Event_Happening_Rating"
    824     SET rating = 6, comment = 'New test rating'
    825     WHERE event_happening_id = 1 AND user_id = 2;
    826 
    827 }}}
    828 
    829 ||= QUERY PLAN =||
    830 ||Update on "Event_Happening_Rating"  (cost\=0.29..8.31 rows\=0 width\=0) (actual time\=0.021..0.022 rows\=0 loops\=1)||
    831 ||  ->  Index Scan using uq_rating_happening_user on "Event_Happening_Rating"  (cost\=0.29..8.31 rows\=1 width\=42) (actual time\=0.020..0.021 rows\=0 loops\=1)||
    832 ||        Index Cond: ((event_happening_id \= 1) AND (user_id \= 2))||
    833 ||Planning Time: 0.132 ms||
    834 ||Execution Time: 0.081 ms||
    835 
    836 == Анализа и оптимизација на `Event_Overall_Ratings`
    837 
    838 Овој поглед врши статистичка анализа на задоволството на публиката преку пресметување на просечната оцена и вкупниот број на рецензии за секој поединечен настан.
    839 
    840 {{{
    841 
    842 CREATE VIEW "Event_Overall_Ratings" AS
    843 SELECT
    844     e.event_id,
    845     e.name AS event_name,
    846     eh.event_happening_id,
    847     eh.event_time,
    848     COUNT(ehr.rating_id) AS total_reviews,
    849     AVG(ehr.rating) AS average_rating
    850 FROM "Event" e
    851 JOIN "Event_Happening" eh ON e.event_id = eh.event_id
    852 JOIN "Event_Happening_Rating" ehr ON eh.event_happening_id = ehr.event_happening_id
    853 GROUP BY e.event_id, e.name, eh.event_happening_id, eh.event_time;
    854 
    855 }}}
    856 
    857 ==== 1. Примарен филтер:
    858 
    859 Примарен филтер за овој поглед е `event_id`. Најчесто пребаруваме сумарна статистика за еден конкретен настан за да ги прикажеме ѕвездичките (рејтингот) на неговата насловна страна.
    860 
    861 ==== 2. Случај на употреба:
    862 
    863 Овој поглед е клучен за почетната страна на апликацијата и пребарувањето на настани. Корисниците често ги филтрираат настаните според нивната популарност и просечна оценка. Бидејќи овие податоци се агрегираат ('''COUNT''' и '''AVG''') од илјадници рецензии, пресметката може да стане тешка операција.
    864 
    865 ==== 3. Иницијално време:
    866 
    867 Иницијалното време за извршување е 1.029 s (1029 ms). Ова е на самата граница на прифатливост. Ако имаме илјадници настани со милиони рецензии, ова време би пораснало експоненцијално.
    868 
    869 ==== 4. Анализа на планот на извршување (без индекси):
    870 
    871 Базата троши најмногу време на '''Bitmap Heap Scan''' врз `Event_Happening` и '''Index Scan''' врз рецензиите за да ги собере сите потребни редови за агрегација.
    872 
    873 Потоа користи '''GroupAggregate''' операција која мора да ги процесира сите вчитани редови за да ги пресмета просекот и вкупниот број.
    874 
    875  * '''SELECT'''
    876 
    877 {{{
    878 
    879 EXPLAIN ANALYZE
    880     SELECT * FROM "Event_Overall_Ratings" WHERE event_id = 1;
    881 
    882 }}}
    883 
    884 ||= QUERY PLAN =||
    885 ||GroupAggregate  (cost\=72.25..72.32 rows\=3 width\=95) (actual time\=1029.642..1029.647 rows\=1 loops\=1)||
    886 ||  Group Key: eh.event_happening_id||
    887 ||  ->  Sort  (cost\=72.25..72.26 rows\=3 width\=67) (actual time\=1029.557..1029.604 rows\=1 loops\=1)||
    888 ||        Sort Key: eh.event_happening_id||
    889 ||        Sort Method: quicksort  Memory: 25kB||
    890 ||        ->  Nested Loop  (cost\=4.90..72.22 rows\=3 width\=67) (actual time\=223.616..1029.548 rows\=1 loops\=1)||
    891 ||              ->  Index Scan using "Event_pkey" on "Event" e  (cost\=0.29..8.30 rows\=1 width\=39) (actual time\=0.046..0.053 rows\=1 loops\=1)||
    892 ||                    Index Cond: (event_id \= 1)||
    893 ||              ->  Nested Loop  (cost\=4.61..63.89 rows\=3 width\=36) (actual time\=223.566..1029.488 rows\=1 loops\=1)||
    894 ||                    ->  Bitmap Heap Scan on "Event_Happening" eh  (cost\=4.33..22.32 rows\=5 width\=24) (actual time\=0.054..805.890 rows\=5 loops\=1)||
    895 ||                          Recheck Cond: (event_id \= 1)||
    896 ||                          Heap Blocks: exact\=5||
    897 ||                          ->  Bitmap Index Scan on idx_event_happening_event_id  (cost\=0.00..4.32 rows\=5 width\=0) (actual time\=0.029..0.029 rows\=5 loops\=1)||
    898 ||                                Index Cond: (event_id \= 1)||
    899 ||                    ->  Index Scan using uq_rating_happening_user on "Event_Happening_Rating" ehr  (cost\=0.29..8.30 rows\=1 width\=20) (actual time\=44.710..44.711 rows\=0 loops\=5)||
    900 ||                          Index Cond: (event_happening_id \= eh.event_happening_id)||
    901 ||Planning Time: 1.025 ms||
    902 ||Execution Time: 1029.756 ms||
    903 
    904  * '''INSERT'''
    905 
    906 {{{
    907 
    908 EXPLAIN ANALYZE
    909     INSERT INTO "Event_Happening_Rating" (rating_id, event_happening_id, user_id, rating, comment)
    910     SELECT COALESCE(MAX(rating_id), 0) + 1, 1, 15, 9, 'Test rating'
    911     FROM "Event_Happening_Rating";
    912 
    913 }}}
    914 
    915 ||= QUERY PLAN =||
    916 ||Insert on "Event_Happening_Rating"  (cost\=0.31..0.34 rows\=0 width\=0) (actual time\=0.405..0.407 rows\=0 loops\=1)||
    917 ||  ->  Subquery Scan on "*SELECT*"  (cost\=0.31..0.34 rows\=1 width\=60) (actual time\=0.156..0.158 rows\=1 loops\=1)||
    918 ||        ->  Result  (cost\=0.31..0.33 rows\=1 width\=52) (actual time\=0.154..0.155 rows\=1 loops\=1)||
    919 ||              InitPlan 1||
    920 ||                ->  Limit  (cost\=0.29..0.31 rows\=1 width\=8) (actual time\=0.149..0.149 rows\=1 loops\=1)||
    921 ||                      ->  Index Only Scan Backward using "Event_Happening_Rating_pkey" on "Event_Happening_Rating" "Event_Happening_Rating_1"  (cost\=0.29..484.37 rows\=17872 width\=8) (actual time\=0.148..0.148 rows\=1 loops\=1)||
    922 ||                            Heap Fetches: 2||
    923 ||Planning Time: 0.199 ms||
    924 ||Trigger for constraint fk_event_happening_rating_event_happening: time\=0.232 calls\=1||
    925 ||Trigger for constraint fk_event_happening_rating_user: time\=0.234 calls\=1||
    926 ||Execution Time: 0.918 ms||
    927 
    928  * '''UPDATE'''
    929 
    930 {{{
    931 
    932 EXPLAIN ANALYZE
    933     UPDATE "Event_Happening_Rating"
    934     SET rating = 8, comment = 'New test rating'
    935     WHERE event_happening_id = 1 AND user_id = 15;
    936 
    937 }}}
    938 
    939 ||= QUERY PLAN =||
    940 ||Update on "Event_Happening_Rating"  (cost\=0.29..8.31 rows\=0 width\=0) (actual time\=0.458..0.459 rows\=0 loops\=1)||
    941 ||  ->  Index Scan using uq_rating_happening_user on "Event_Happening_Rating"  (cost\=0.29..8.31 rows\=1 width\=42) (actual time\=0.131..0.133 rows\=1 loops\=1)||
    942 ||        Index Cond: ((event_happening_id \= 1) AND (user_id \= 15))||
    943 ||Planning Time: 0.136 ms||
    944 ||Execution Time: 0.500 ms||
    945 
    946 ==== 5. Оптимизација и индексирање:
    947 
    948 За да се забрза пресметката, воведуваме композитен индекс:
    949 
    950  * idx_ehr_happening_id_rating: Овој индекс ги содржи и `event_happening_id` и самата оценка (rating). Ова овозможува базата да ги пресмета '''AVG''' и '''COUNT''' директно од структурата на индексот, без да мора да ги чита редовите од самата табела.
    951 
    952 {{{
    953 
    954 -- composite index to speed up grouping and aggregate calculations (AVG, COUNT)
    955 CREATE INDEX idx_ehr_happening_id_rating ON "Event_Happening_Rating"(event_happening_id, rating);
    956 
    957 }}}
    958 
    959 ==== 6. Резултат по оптимизација:
    960 
    961 Времето на извршување се намали на 0.459 ms.
    962 
    963 Подобрување: Забрзување од над 2200 пати.
    964 
    965 Операциите за внесување и ажурирање остануваат под 1 ms, што значи дека композитниот индекс не додава голем товар при запишување.
    966 
    967  * '''SELECT'''
    968 
    969 {{{
    970 
    971 EXPLAIN ANALYZE
    972     SELECT * FROM "Event_Overall_Ratings" WHERE event_id = 1;
    973 
    974 }}}
    975 
    976 ||= QUERY PLAN =||
    977 ||GroupAggregate  (cost\=72.25..72.32 rows\=3 width\=95) (actual time\=0.387..0.389 rows\=1 loops\=1)||
    978 ||  Group Key: eh.event_happening_id||
    979 ||  ->  Sort  (cost\=72.25..72.26 rows\=3 width\=67) (actual time\=0.374..0.375 rows\=1 loops\=1)||
    980 ||        Sort Key: eh.event_happening_id||
    981 ||        Sort Method: quicksort  Memory: 25kB||
    982 ||        ->  Nested Loop  (cost\=4.90..72.22 rows\=3 width\=67) (actual time\=0.271..0.367 rows\=1 loops\=1)||
    983 ||              ->  Index Scan using "Event_pkey" on "Event" e  (cost\=0.29..8.30 rows\=1 width\=39) (actual time\=0.126..0.128 rows\=1 loops\=1)||
    984 ||                    Index Cond: (event_id \= 1)||
    985 ||              ->  Nested Loop  (cost\=4.61..63.89 rows\=3 width\=36) (actual time\=0.142..0.235 rows\=1 loops\=1)||
    986 ||                    ->  Bitmap Heap Scan on "Event_Happening" eh  (cost\=4.33..22.32 rows\=5 width\=24) (actual time\=0.067..0.144 rows\=5 loops\=1)||
    987 ||                          Recheck Cond: (event_id \= 1)||
    988 ||                          Heap Blocks: exact\=5||
    989 ||                          ->  Bitmap Index Scan on idx_event_happening_event_id  (cost\=0.00..4.32 rows\=5 width\=0) (actual time\=0.038..0.038 rows\=5 loops\=1)||
    990 ||                                Index Cond: (event_id \= 1)||
    991 ||                    ->  Index Scan using uq_rating_happening_user on "Event_Happening_Rating" ehr  (cost\=0.29..8.30 rows\=1 width\=20) (actual time\=0.016..0.016 rows\=0 loops\=5)||
    992 ||                          Index Cond: (event_happening_id \= eh.event_happening_id)||
    993 ||Planning Time: 1.134 ms||
    994 ||Execution Time: 0.459 ms||
    995 
    996  * '''INSERT'''
    997 
    998 {{{
    999 
    1000 EXPLAIN ANALYZE
    1001     INSERT INTO "Event_Happening_Rating" (rating_id, event_happening_id, user_id, rating, comment)
    1002     SELECT COALESCE(MAX(rating_id), 0) + 1, 1, 20, 7, 'Test rating'
    1003     FROM "Event_Happening_Rating";
    1004 
    1005 }}}
    1006 
    1007 ||= QUERY PLAN =||
    1008 ||Insert on "Event_Happening_Rating"  (cost\=0.31..0.34 rows\=0 width\=0) (actual time\=0.538..0.539 rows\=0 loops\=1)||
    1009 ||  ->  Subquery Scan on "*SELECT*"  (cost\=0.31..0.34 rows\=1 width\=60) (actual time\=0.111..0.112 rows\=1 loops\=1)||
    1010 ||        ->  Result  (cost\=0.31..0.33 rows\=1 width\=52) (actual time\=0.109..0.109 rows\=1 loops\=1)||
    1011 ||              InitPlan 1||
    1012 ||                ->  Limit  (cost\=0.29..0.31 rows\=1 width\=8) (actual time\=0.104..0.105 rows\=1 loops\=1)||
    1013 ||                      ->  Index Only Scan Backward using "Event_Happening_Rating_pkey" on "Event_Happening_Rating" "Event_Happening_Rating_1"  (cost\=0.29..485.40 rows\=17874 width\=8) (actual time\=0.102..0.103 rows\=1 loops\=1)||
    1014 ||                            Heap Fetches: 1||
    1015 ||Planning Time: 0.195 ms||
    1016 ||Trigger for constraint fk_event_happening_rating_event_happening: time\=0.223 calls\=1||
    1017 ||Trigger for constraint fk_event_happening_rating_user: time\=0.172 calls\=1||
    1018 ||Execution Time: 0.980 ms||
    1019 
    1020  * '''UPDATE'''
    1021 
    1022 {{{
    1023 
    1024 EXPLAIN ANALYZE
    1025     UPDATE "Event_Happening_Rating"
    1026     SET rating = 8, comment = 'New test rating'
    1027     WHERE event_happening_id = 1 AND user_id = 15;
    1028 
    1029 }}}
    1030 
    1031 ||= QUERY PLAN =||
    1032 ||Update on "Event_Happening_Rating"  (cost\=0.29..8.31 rows\=0 width\=0) (actual time\=0.482..0.483 rows\=0 loops\=1)||
    1033 ||  ->  Index Scan using uq_rating_happening_user on "Event_Happening_Rating"  (cost\=0.29..8.31 rows\=1 width\=42) (actual time\=0.096..0.098 rows\=1 loops\=1)||
    1034 ||        Index Cond: ((event_happening_id \= 1) AND (user_id \= 20))||
    1035 ||Planning Time: 0.163 ms||
    1036 ||Execution Time: 0.548 ms||
    1037 
    1038 == Анализа и оптимизација на `Event_Financial_Summary`
    1039 
    1040 Овој поглед ги сумира финансиските резултати за секој настан, прикажувајќи го вкупниот број на продадени билети, нето приходот по одбивање на рефундациите и посебно издвоената заработка од административните такси при враќање на влезниците.
    1041 
    1042 {{{
    1043 
    1044 CREATE VIEW "Event_Financial_Summary" AS
    1045 SELECT
    1046     e.event_id,
    1047     e.name AS event_name,
    1048     eh.event_happening_id,
    1049     eh.event_time,
    1050     COUNT(tp.purchase_id) AS total_tickets_sold,
    1051    
    1052     -- total revenue
    1053     SUM(tp.purchase_amount) - SUM(CASE WHEN tr.refund_amount IS NOT NULL THEN tr.refund_amount ELSE 0 END) AS net_revenue,
    1054    
    1055     -- refund taxes
    1056     SUM(CASE WHEN tr.refund_id IS NOT NULL THEN tp.purchase_amount - tr.refund_amount ELSE 0 END) AS refund_tax_profit
    1057 
    1058 FROM "Event" e
    1059 JOIN "Event_Happening" eh ON e.event_id = eh.event_id
    1060 JOIN "Ticket" t ON eh.event_happening_id = t.event_happening_id
    1061 JOIN "Ticket_Purchase" tp ON t.ticket_id = tp.ticket_id
    1062 LEFT JOIN "Ticket_Refund" tr ON tp.purchase_id = tr.purchase_id
    1063 GROUP BY e.event_id, e.name, eh.event_happening_id, eh.event_time;
    1064 
    1065 }}}
    1066 
    1067 ==== 1. Примарен филтер:
    1068 
    1069 Примарен филтер за овој поглед е `event_id`. Овој поглед е наменет за организаторите на настани кои сакаат да видат прецизен финансиски извештај за одреден настан во реално време.
    1070 
    1071 ==== 2. Случај на употреба:
    1072 
    1073 Овој поглед се користи за пресметка на чистата заработка откако ќе се одбијат сите рефундирани средства, како и за следење на профитот остварен од административните такси при рефундација. Поради комплексноста на агрегациите врз милиони редови, оптимизацијата тука е критична за да не се блокира работата на финансискиот сектор.
    1074 
    1075 ==== 3. Иницијално време:
    1076 
    1077 Иницијалното време за извршување изнесува 319.320 s (околу 5.3 минути). Ова е најбавното време во целиот проект, што го прави погледот неупотреблив во реални услови без индексирање.
    1078 
    1079 ==== 4. Анализа на планот на извршување (без индекси):
    1080 
    1081 Најголемиот товар паѓа на '''Parallel Seq Scan''' врз табелата `Ticket_Purchase` (над 315 s). Базата мора да ги помине сите 16 милиони записи за да ги пресмета сумите.
    1082 
    1083 Се користи '''Parallel Hash Join''' кој бара огромна количина на меморија и процесорска моќ за да ги поврзе трансакциите со билетите и нивните соодветни настани.
    1084 
    1085  * '''SELECT'''
    1086 
    1087 {{{
    1088 
    1089 EXPLAIN ANALYZE
    1090     SELECT * FROM "Event_Financial_Summary" WHERE event_id = 1;
    1091 
    1092 }}}
    1093 
    1094 ||= QUERY PLAN =||
    1095 ||Finalize GroupAggregate  (cost\=300491.02..300511.03 rows\=5 width\=71) (actual time\=319308.288..319318.376 rows\=1 loops\=1)||
    1096 ||  Group Key: eh.event_happening_id||
    1097 ||  ->  Gather Merge  (cost\=300491.02..300510.84 rows\=10 width\=75) (actual time\=319308.204..319318.298 rows\=1 loops\=1)||
    1098 ||        Workers Planned: 2||
    1099 ||        Workers Launched: 2||
    1100 ||        ->  Partial GroupAggregate  (cost\=299491.00..299509.67 rows\=5 width\=75) (actual time\=319135.885..319135.894 rows\=0 loops\=3)||
    1101 ||              Group Key: eh.event_happening_id||
    1102 ||              ->  Sort  (cost\=299491.00..299493.66 rows\=1064 width\=79) (actual time\=319135.736..319135.767 rows\=388 loops\=3)||
    1103 ||                    Sort Key: eh.event_happening_id||
    1104 ||                    Sort Method: quicksort  Memory: 25kB||
    1105 ||                    Worker 0:  Sort Method: quicksort  Memory: 25kB||
    1106 ||                    Worker 1:  Sort Method: quicksort  Memory: 160kB||
    1107 ||                    ->  Nested Loop Left Join  (cost\=17569.44..299437.50 rows\=1064 width\=79) (actual time\=257215.363..319135.341 rows\=388 loops\=3)||
    1108 ||                          ->  Parallel Hash Join  (cost\=17569.00..291013.94 rows\=1064 width\=67) (actual time\=257187.356..319052.018 rows\=388 loops\=3)||
    1109 ||                                Hash Cond: (tp.ticket_id \= t.ticket_id)||
    1110 ||                                ->  Parallel Seq Scan on "Ticket_Purchase" tp  (cost\=0.00..248438.67 rows\=6666668 width\=20) (actual time\=39.675..315154.157 rows\=5333334 loops\=3)||
    1111 ||                                ->  Parallel Hash  (cost\=17536.03..17536.03 rows\=2638 width\=63) (actual time\=2787.389..2787.393 rows\=1442 loops\=3)||
    1112 ||                                      Buckets: 8192  Batches: 1  Memory Usage: 576kB||
    1113 ||                                      ->  Nested Loop  (cost\=1.14..17536.03 rows\=2638 width\=63) (actual time\=1483.997..2465.151 rows\=1442 loops\=3)||
    1114 ||                                            ->  Nested Loop  (cost\=0.57..1029.33 rows\=3 width\=55) (actual time\=632.603..753.210 rows\=2 loops\=3)||
    1115 ||                                                  ->  Parallel Index Scan using "Event_Happening_pkey" on "Event_Happening" eh  (cost\=0.29..1004.39 rows\=3 width\=24) (actual time\=632.410..752.992 rows\=2 loops\=3)||
    1116 ||                                                        Filter: (event_id \= 1)||
    1117 ||                                                        Rows Removed by Filter: 10444||
    1118 ||                                                  ->  Index Scan using "Event_pkey" on "Event" e  (cost\=0.29..8.30 rows\=1 width\=39) (actual time\=0.094..0.100 rows\=1 loops\=5)||
    1119 ||                                                        Index Cond: (event_id \= 1)||
    1120 ||                                            ->  Index Scan using uq_ticket_event_happening_seat on "Ticket" t  (cost\=0.56..5486.81 rows\=1542 width\=16) (actual time\=652.636..1027.008 rows\=865 loops\=5)||
    1121 ||                                                  Index Cond: (event_happening_id \= eh.event_happening_id)||
    1122 ||                          ->  Index Scan using idx_ticket_refund_purchase_id on "Ticket_Refund" tr  (cost\=0.43..7.92 rows\=1 width\=20) (actual time\=0.214..0.214 rows\=0 loops\=1163)||
    1123 ||                                Index Cond: (purchase_id \= tp.purchase_id)||
    1124 ||Planning Time: 2770.897 ms||
    1125 ||JIT:||
    1126 ||  Functions: 96||
    1127 ||  Options: Inlining false, Optimization false, Expressions true, Deforming true||
    1128 ||  Timing: Generation 9.017 ms (Deform 4.245 ms), Inlining 0.000 ms, Optimization 4.730 ms, Emission 79.393 ms, Total 93.140 ms||
    1129 ||Execution Time: 319320.792 ms||
    1130 
    1131  * '''INSERT'''
    1132 
    1133 {{{
    1134 
    1135 EXPLAIN ANALYZE
    1136     INSERT INTO "Ticket_Purchase" (purchase_id, ticket_id, user_id, qr_code, purchase_time, purchase_amount)
    1137     SELECT COALESCE(MAX(purchase_id), 0) + 1, 1, 1, 'QR-TEST-CODE-003', CURRENT_TIMESTAMP, 1500.00
    1138     FROM "Ticket_Purchase";
    1139 
    1140 }}}
    1141 
    1142 ||= QUERY PLAN =||
    1143 ||Insert on "Ticket_Purchase"  (cost\=0.70..0.73 rows\=0 width\=0) (actual time\=1391.459..1391.461 rows\=0 loops\=1)||
    1144 ||  ->  Subquery Scan on "*SELECT*"  (cost\=0.70..0.73 rows\=1 width\=552) (actual time\=0.132..0.137 rows\=1 loops\=1)||
    1145 ||        ->  Result  (cost\=0.70..0.71 rows\=1 width\=88) (actual time\=0.106..0.109 rows\=1 loops\=1)||
    1146 ||              InitPlan 1||
    1147 ||                ->  Limit  (cost\=0.43..0.70 rows\=1 width\=8) (actual time\=0.101..0.102 rows\=1 loops\=1)||
    1148 ||                      ->  Index Only Scan Backward using "Ticket_Purchase_pkey" on "Ticket_Purchase" "Ticket_Purchase_1"  (cost\=0.43..4162966.51 rows\=16000002 width\=8) (actual time\=0.099..0.099 rows\=1 loops\=1)||
    1149 ||                            Heap Fetches: 1||
    1150 ||Planning Time: 0.232 ms||
    1151 ||Trigger for constraint fk_purchase_ticket: time\=1.278 calls\=1||
    1152 ||Trigger for constraint fk_purchase_user: time\=0.358 calls\=1||
    1153 ||Execution Time: 1393.154 ms||
    1154 
    1155  * '''UPDATE'''
    1156 
    1157 {{{
    1158 
    1159 EXPLAIN ANALYZE
    1160     UPDATE "Ticket_Purchase"
    1161     SET purchase_amount = 1800.00
    1162     WHERE qr_code = 'QR-TEST-CODE-003';
    1163 
    1164 }}}
    1165 
    1166 ||= QUERY PLAN =||
    1167 ||Update on "Ticket_Purchase"  (cost\=0.56..8.58 rows\=0 width\=0) (actual time\=0.266..0.267 rows\=0 loops\=1)||
    1168 ||  ->  Index Scan using "Ticket_Purchase_qr_code_key" on "Ticket_Purchase"  (cost\=0.56..8.58 rows\=1 width\=10) (actual time\=0.117..0.119 rows\=1 loops\=1)||
    1169 ||        Index Cond: ((qr_code)::text \= 'QR-TEST-CODE-003'::text)||
    1170 ||Planning Time: 0.188 ms||
    1171 ||Execution Time: 0.364 ms||
    1172 
    1173 ==== 5. Оптимизација и индексирање:
    1174 
    1175 За да се овозможи побрзо филтрирање и поврзување на трансакциите, воведени се:
    1176 
    1177  * `idx_ticket_purchase_ticket_id`: Клучен за брзо поврзување на трансакциите со табелата на билети.
    1178 
    1179  * `idx_ticket_event_happening_id`: Овозможува базата веднаш да ги групира билетите според термините на настанот.
    1180 
    1181  * `idx_ticket_refund_purchase_id`: Го забрзува '''LEFT JOIN''' делот каде што се проверува дали за одредена трансакција постои рефундација.
    1182 
    1183 {{{
    1184 
    1185 -- index for linking ticket purchases to the specific tickets
    1186 CREATE INDEX idx_ticket_purchase_ticket_id ON "Ticket_Purchase"(ticket_id);
    1187 
    1188 -- index for the LEFT JOIN with refunds to calculate net revenue accurately
    1189 CREATE INDEX idx_ticket_refund_purchase_id ON "Ticket_Refund"(purchase_id);
    1190 
    1191 -- index for linking tickets to scheduled events
    1192 CREATE INDEX idx_ticket_event_happening_id ON "Ticket"(event_happening_id);
    1193 
    1194 }}}
    1195 
    1196 ==== 6. Резултат по оптимизација:
    1197 
    1198 Времето на извршување е намалено на 1.510 s (1510 ms).
    1199 
    1200 Подобрување: Ова е забрзување од 211 пати.
    1201 
    1202 Иако 1.5 s се уште изгледа како „многу“ во споредба со претходните погледи, за анализа на 16 милиони трансакции со комплексни '''SUM''' и '''CASE''' операции, ова е извонреден резултат.
    1203 
    1204  * '''SELECT'''
    1205 
    1206 {{{
    1207 
    1208 EXPLAIN ANALYZE
    1209     SELECT * FROM "Event_Financial_Summary" WHERE event_id = 1;
    1210 
    1211 }}}
    1212 
    1213 ||= QUERY PLAN =||
    1214 ||Finalize GroupAggregate  (cost\=298955.06..298975.08 rows\=5 width\=71) (actual time\=1499.323..1508.016 rows\=1 loops\=1)||
    1215 ||  Group Key: eh.event_happening_id||
    1216 ||  ->  Gather Merge  (cost\=298955.06..298974.89 rows\=10 width\=75) (actual time\=1499.269..1507.962 rows\=1 loops\=1)||
    1217 ||        Workers Planned: 2||
    1218 ||        Workers Launched: 2||
    1219 ||        ->  Partial GroupAggregate  (cost\=297955.04..297973.71 rows\=5 width\=75) (actual time\=1463.528..1463.535 rows\=0 loops\=3)||
    1220 ||              Group Key: eh.event_happening_id||
    1221 ||              ->  Sort  (cost\=297955.04..297957.70 rows\=1064 width\=79) (actual time\=1463.377..1463.407 rows\=388 loops\=3)||
    1222 ||                    Sort Key: eh.event_happening_id||
    1223 ||                    Sort Method: quicksort  Memory: 25kB||
    1224 ||                    Worker 0:  Sort Method: quicksort  Memory: 25kB||
    1225 ||                    Worker 1:  Sort Method: quicksort  Memory: 160kB||
    1226 ||                    ->  Nested Loop Left Join  (cost\=16033.48..297901.55 rows\=1064 width\=79) (actual time\=1201.975..1463.060 rows\=388 loops\=3)||
    1227 ||                          ->  Parallel Hash Join  (cost\=16033.05..289477.99 rows\=1064 width\=67) (actual time\=1201.913..1460.100 rows\=388 loops\=3)||
    1228 ||                                Hash Cond: (tp.ticket_id \= t.ticket_id)||
    1229 ||                                ->  Parallel Seq Scan on "Ticket_Purchase" tp  (cost\=0.00..248438.67 rows\=6666668 width\=20) (actual time\=0.091..663.287 rows\=5333334 loops\=3)||
    1230 ||                                ->  Parallel Hash  (cost\=16000.08..16000.08 rows\=2638 width\=63) (actual time\=13.806..13.809 rows\=1442 loops\=3)||
    1231 ||                                      Buckets: 8192  Batches: 1  Memory Usage: 512kB||
    1232 ||                                      ->  Nested Loop  (cost\=1.01..16000.08 rows\=2638 width\=63) (actual time\=30.720..39.526 rows\=4325 loops\=1)||
    1233 ||                                            ->  Nested Loop  (cost\=0.57..1029.33 rows\=3 width\=55) (actual time\=30.641..37.392 rows\=5 loops\=1)||
    1234 ||                                                  ->  Parallel Index Scan using "Event_Happening_pkey" on "Event_Happening" eh  (cost\=0.29..1004.39 rows\=3 width\=24) (actual time\=30.536..37.242 rows\=5 loops\=1)||
    1235 ||                                                        Filter: (event_id \= 1)||
    1236 ||                                                        Rows Removed by Filter: 31332||
    1237 ||                                                  ->  Index Scan using "Event_pkey" on "Event" e  (cost\=0.29..8.30 rows\=1 width\=39) (actual time\=0.021..0.023 rows\=1 loops\=5)||
    1238 ||                                                        Index Cond: (event_id \= 1)||
    1239 ||                                            ->  Index Scan using idx_ticket_event_happening_id on "Ticket" t  (cost\=0.44..4974.83 rows\=1542 width\=16) (actual time\=0.052..0.324 rows\=865 loops\=5)||
    1240 ||                                                  Index Cond: (event_happening_id \= eh.event_happening_id)||
    1241 ||                          ->  Index Scan using idx_ticket_refund_purchase_id on "Ticket_Refund" tr  (cost\=0.43..7.92 rows\=1 width\=20) (actual time\=0.007..0.007 rows\=0 loops\=1163)||
    1242 ||                                Index Cond: (purchase_id \= tp.purchase_id)||
    1243 ||Planning Time: 1.604 ms||
    1244 ||JIT:||
    1245 ||  Functions: 96||
    1246 ||  Options: Inlining false, Optimization false, Expressions true, Deforming true||
    1247 ||  Timing: Generation 8.629 ms (Deform 3.959 ms), Inlining 0.000 ms, Optimization 3.603 ms, Emission 79.354 ms, Total 91.585 ms||
    1248 ||Execution Time: 1510.555 ms||
    1249 
    1250  * '''INSERT'''
    1251 
    1252 {{{
    1253 
    1254 EXPLAIN ANALYZE
    1255     INSERT INTO "Ticket_Purchase" (purchase_id, ticket_id, user_id, qr_code, purchase_time, purchase_amount)
    1256     SELECT COALESCE(MAX(purchase_id), 0) + 1, 1, 2, 'QR-TEST-CODE-004', CURRENT_TIMESTAMP, 1400.00
    1257     FROM "Ticket_Purchase";
    1258 
    1259 }}}
    1260 
    1261 ||= QUERY PLAN =||
    1262 ||Insert on "Ticket_Purchase"  (cost\=0.70..0.73 rows\=0 width\=0) (actual time\=0.351..0.352 rows\=0 loops\=1)||
    1263 ||  ->  Subquery Scan on "*SELECT*"  (cost\=0.70..0.73 rows\=1 width\=552) (actual time\=0.145..0.147 rows\=1 loops\=1)||
    1264 ||        ->  Result  (cost\=0.70..0.71 rows\=1 width\=88) (actual time\=0.132..0.133 rows\=1 loops\=1)||
    1265 ||              InitPlan 1||
    1266 ||                ->  Limit  (cost\=0.43..0.70 rows\=1 width\=8) (actual time\=0.126..0.127 rows\=1 loops\=1)||
    1267 ||                      ->  Index Only Scan Backward using "Ticket_Purchase_pkey" on "Ticket_Purchase" "Ticket_Purchase_1"  (cost\=0.43..4162966.51 rows\=16000002 width\=8) (actual time\=0.124..0.124 rows\=1 loops\=1)||
    1268 ||                            Heap Fetches: 2||
    1269 ||Planning Time: 0.208 ms||
    1270 ||Trigger for constraint fk_purchase_ticket: time\=0.225 calls\=1||
    1271 ||Trigger for constraint fk_purchase_user: time\=0.176 calls\=1||
    1272 ||Execution Time: 0.802 ms||
    1273 
    1274  * '''UPDATE'''
    1275 
    1276 {{{
    1277 
    1278 EXPLAIN ANALYZE
    1279     UPDATE "Ticket_Purchase"
    1280     SET purchase_amount = 1700.00
    1281     WHERE qr_code = 'QR-TEST-CODE-004';
    1282 
    1283 }}}
    1284 
    1285 ||= QUERY PLAN =||
    1286 ||Update on "Ticket_Purchase"  (cost\=0.56..8.58 rows\=0 width\=0) (actual time\=0.161..0.162 rows\=0 loops\=1)||
    1287 ||  ->  Index Scan using "Ticket_Purchase_qr_code_key" on "Ticket_Purchase"  (cost\=0.56..8.58 rows\=1 width\=10) (actual time\=0.115..0.116 rows\=1 loops\=1)||
    1288 ||        Index Cond: ((qr_code)::text \= 'QR-TEST-CODE-004'::text)||
    1289 ||Planning Time: 0.134 ms||
    1290 ||Execution Time: 0.195 ms||
    1291 
    1292 == Анализа и оптимизација на `Future_Events`
    1293 
    1294 Овој поглед служи за динамично генерирање на репертоарот, прикажувајќи ги исклучиво претстојните настани во реално време преку филтрирање на изминатите термини.
    1295 
    1296 {{{
    1297 
    1298 CREATE VIEW "Future_Events" AS
    1299 SELECT
    1300     e.event_id,
    1301     e.name AS event_name,
    1302     eh.event_happening_id,
    1303     eh.event_time,
    1304     v.venue_id,
    1305     v.name AS venue_name,
    1306     v.address_city AS city
    1307 FROM "Event" e
    1308 JOIN "Event_Happening" eh ON e.event_id = eh.event_id
    1309 JOIN "Venue" v ON eh.venue_id = v.venue_id
    1310 WHERE eh.event_time > CURRENT_TIMESTAMP;
    1311 
    1312 }}}
    1313 
    1314 ==== 1. Примарен филтер:
    1315 
    1316 Примарен филтер за овој поглед е `event_time > CURRENT_TIMESTAMP` во комбинација со `venue_id`. Корисниците најчесто сакаат да видат што се случува во нивниот град или во конкретен објект во блиска иднина.
    1317 
    1318 ==== 2. Случај на употреба:
    1319 
    1320 Овој поглед е „срцето“ на насловната страница. Секој пат кога корисникот ќе ја отвори апликацијата или ќе филтрира настани по локација, овој прашалник се извршува. Тука перформансите се клучни за корисничкото искуство - ако репертоарот се вчита побавно од 2 s, корисникот веројатно ќе ја напушти страницата.
    1321 
    1322 ==== 3. Иницијално време:
    1323 
    1324 Иницијалното време за извршување е многу ниско (0.193 ms), но забележливо е дека базата веќе користи постоечки уникатен индекс (`uq_happening_time_venue`). Проблемот тука не е брзината на еден прашалник, туку времето на планирање кое при комплексни филтри може да биде поголемо од самото извршување.
    1325 
    1326 ==== 4. Анализа на планот на извршување (без индекси):
    1327 
    1328 Базата користи '''Index Scan''' врз постоечко уникатно ограничување.
    1329 
    1330 Сепак, при '''INSERT''' операцијата се појавува време од 573.660 ms, што е релативно високо за едноставно додавање на термин. Ова укажува на тоа дека базата троши време на проверка на интегритетот и пресметка на екстремните вредности ('''MAX''').
    1331 
    1332  * '''SELECT'''
    1333 
    1334 {{{
    1335 
    1336 EXPLAIN ANALYZE
    1337     SELECT * FROM "Future_Events"
    1338     WHERE venue_id = 1;
    1339 
    1340 }}}
    1341 
    1342 ||= QUERY PLAN =||
    1343 ||Nested Loop  (cost\=0.86..26.26 rows\=1 width\=92) (actual time\=0.055..0.056 rows\=0 loops\=1)||
    1344 ||  ->  Nested Loop  (cost\=0.57..17.94 rows\=1 width\=63) (actual time\=0.054..0.055 rows\=0 loops\=1)||
    1345 ||        ->  Index Scan using uq_happening_time_venue on "Event_Happening" eh  (cost\=0.29..9.64 rows\=1 width\=32) (actual time\=0.054..0.054 rows\=0 loops\=1)||
    1346 ||              Index Cond: ((event_time > CURRENT_TIMESTAMP) AND (venue_id \= 1))||
    1347 ||        ->  Index Scan using "Event_pkey" on "Event" e  (cost\=0.29..8.30 rows\=1 width\=39) (never executed)||
    1348 ||              Index Cond: (event_id \= eh.event_id)||
    1349 ||  ->  Index Scan using "Venue_pkey" on "Venue" v  (cost\=0.29..8.30 rows\=1 width\=37) (never executed)||
    1350 ||        Index Cond: (venue_id \= 1)||
    1351 ||Planning Time: 4.258 ms||
    1352 ||Execution Time: 0.193 ms||
    1353 
    1354  * '''INSERT'''
    1355 
    1356 {{{
    1357 
    1358 EXPLAIN ANALYZE
    1359     INSERT INTO "Event_Happening" (event_happening_id, event_id, venue_id, event_time, duration)
    1360     VALUES ((SELECT MAX(event_happening_id) + 1 FROM "Event_Happening"), 1, 1, '2026-12-31 20:00:00', 120);
    1361 
    1362 }}}
    1363 
    1364 ||= QUERY PLAN =||
    1365 ||Insert on "Event_Happening"  (cost\=0.33..0.34 rows\=0 width\=0) (actual time\=572.079..572.083 rows\=0 loops\=1)||
    1366 ||  InitPlan 2||
    1367 ||    ->  Result  (cost\=0.31..0.33 rows\=1 width\=8) (actual time\=0.260..0.262 rows\=1 loops\=1)||
    1368 ||          InitPlan 1||
    1369 ||            ->  Limit  (cost\=0.29..0.31 rows\=1 width\=8) (actual time\=0.256..0.258 rows\=1 loops\=1)||
    1370 ||                  ->  Index Only Scan Backward using "Event_Happening_pkey" on "Event_Happening" "Event_Happening_1"  (cost\=0.29..822.34 rows\=31337 width\=8) (actual time\=0.255..0.256 rows\=1 loops\=1)||
    1371 ||                        Heap Fetches: 0||
    1372 ||  ->  Result  (cost\=0.00..0.01 rows\=1 width\=36) (actual time\=0.263..0.264 rows\=1 loops\=1)||
    1373 ||Planning Time: 0.199 ms||
    1374 ||Trigger for constraint fk_event_happening_event: time\=1.080 calls\=1||
    1375 ||Trigger for constraint fk_event_happening_venue: time\=0.448 calls\=1||
    1376 ||Execution Time: 573.660 ms||
    1377 
    1378  * '''UPDATE'''
    1379 
    1380 {{{
    1381 
    1382 EXPLAIN ANALYZE
    1383     UPDATE "Event_Happening"
    1384     SET event_time = '2027-01-01 21:00:00'
    1385     WHERE event_happening_id = (SELECT MAX(event_happening_id) FROM "Event_Happening");
    1386 
    1387 }}}
    1388 
    1389 ||= QUERY PLAN =||
    1390 ||Update on "Event_Happening"  (cost\=0.61..8.63 rows\=0 width\=0) (actual time\=0.569..0.570 rows\=0 loops\=1)||
    1391 ||  InitPlan 2||
    1392 ||    ->  Result  (cost\=0.31..0.32 rows\=1 width\=8) (actual time\=0.292..0.293 rows\=1 loops\=1)||
    1393 ||          InitPlan 1||
    1394 ||            ->  Limit  (cost\=0.29..0.31 rows\=1 width\=8) (actual time\=0.288..0.288 rows\=1 loops\=1)||
    1395 ||                  ->  Index Only Scan Backward using "Event_Happening_pkey" on "Event_Happening" "Event_Happening_1"  (cost\=0.29..822.34 rows\=31337 width\=8) (actual time\=0.287..0.287 rows\=1 loops\=1)||
    1396 ||                        Heap Fetches: 1||
    1397 ||  ->  Index Scan using "Event_Happening_pkey" on "Event_Happening"  (cost\=0.29..8.30 rows\=1 width\=14) (actual time\=0.310..0.312 rows\=1 loops\=1)||
    1398 ||        Index Cond: (event_happening_id \= (InitPlan 2).col1)||
    1399 ||Planning Time: 0.240 ms||
    1400 ||Execution Time: 0.662 ms||
    1401 
    1402 ==== 5. Оптимизација и индексирање:
    1403 
    1404 За да се овозможи максимална ефикасност при различни комбинации на филтрирање (само по време, само по локација или двете заедно), се предлагаат:
    1405 
    1406  * `idx_event_happening_time`: За брзо хронолошко подредување.
    1407 
    1408  * `idx_event_happening_venue_id`: За брзо филтрирање по објект.
    1409 
    1410  * `idx_event_happening_venue_time`: Композитен индекс кој е идеален за прашалници кои бараат „претстојни настани во конкретна сала“.
    1411 
    1412 {{{
    1413 
    1414 -- index for optimizing chronological filtering of future events
    1415 CREATE INDEX idx_event_happening_time ON "Event_Happening"(event_time);
    1416 
    1417 -- index for accelerating lookups of events scheduled at specific venues
    1418 CREATE INDEX idx_event_happening_venue_id ON "Event_Happening"(venue_id);
    1419 
    1420 -- composite index to optimize queries filtering both by venue and upcoming time slots
    1421 CREATE INDEX idx_event_happening_venue_time ON "Event_Happening"(venue_id, event_time);
    1422 
    1423 }}}
    1424 
    1425 ==== 6. Резултат по оптимизација:
    1426 
    1427 Времето на извршување останува стабилно ниско (0.215 ms).
    1428 
    1429 Најголемото подобрување се гледа кај '''INSERT''' операцијата, каде времето се намали од 573 ms на 1.278 ms. Ова е клучно бидејќи администраторите често додаваат нови термини во пакети, па ова забрзување значително го олеснува нивниот процес.
    1430 
    1431  * '''SELECT'''
    1432 
    1433 {{{
    1434 
    1435 EXPLAIN ANALYZE
    1436     SELECT * FROM "Future_Events"
    1437     WHERE venue_id = 1;
    1438 
    1439 }}}
    1440 
    1441 ||= QUERY PLAN =||
    1442 ||Nested Loop  (cost\=0.86..24.93 rows\=1 width\=92) (actual time\=0.153..0.157 rows\=1 loops\=1)||
    1443 ||  ->  Nested Loop  (cost\=0.57..16.61 rows\=1 width\=63) (actual time\=0.081..0.083 rows\=1 loops\=1)||
    1444 ||        ->  Index Scan using idx_event_happening_venue_time on "Event_Happening" eh  (cost\=0.29..8.31 rows\=1 width\=32) (actual time\=0.067..0.068 rows\=1 loops\=1)||
    1445 ||              Index Cond: ((venue_id \= 1) AND (event_time > CURRENT_TIMESTAMP))||
    1446 ||        ->  Index Scan using "Event_pkey" on "Event" e  (cost\=0.29..8.30 rows\=1 width\=39) (actual time\=0.009..0.009 rows\=1 loops\=1)||
    1447 ||              Index Cond: (event_id \= eh.event_id)||
    1448 ||  ->  Index Scan using "Venue_pkey" on "Venue" v  (cost\=0.29..8.30 rows\=1 width\=37) (actual time\=0.070..0.071 rows\=1 loops\=1)||
    1449 ||        Index Cond: (venue_id \= 1)||
    1450 ||Planning Time: 203.237 ms||
    1451 ||Execution Time: 0.215 ms||
    1452 
    1453  * '''INSERT'''
    1454 
    1455 {{{
    1456 
    1457 EXPLAIN ANALYZE
    1458     INSERT INTO "Event_Happening" (event_happening_id, event_id, venue_id, event_time, duration)
    1459     VALUES ((SELECT MAX(event_happening_id) + 1 FROM "Event_Happening"), 1, 1, '2026-11-15 19:00:00', 90);
    1460 
    1461 }}}
    1462 
    1463 ||= QUERY PLAN =||
    1464 ||Insert on "Event_Happening"  (cost\=0.33..0.34 rows\=0 width\=0) (actual time\=0.726..0.728 rows\=0 loops\=1)||
    1465 ||  InitPlan 2||
    1466 ||    ->  Result  (cost\=0.31..0.33 rows\=1 width\=8) (actual time\=0.281..0.282 rows\=1 loops\=1)||
    1467 ||          InitPlan 1||
    1468 ||            ->  Limit  (cost\=0.29..0.31 rows\=1 width\=8) (actual time\=0.278..0.279 rows\=1 loops\=1)||
    1469 ||                  ->  Index Only Scan Backward using "Event_Happening_pkey" on "Event_Happening" "Event_Happening_1"  (cost\=0.29..826.36 rows\=31338 width\=8) (actual time\=0.276..0.277 rows\=1 loops\=1)||
    1470 ||                        Heap Fetches: 1||
    1471 ||  ->  Result  (cost\=0.00..0.01 rows\=1 width\=36) (actual time\=0.284..0.284 rows\=1 loops\=1)||
    1472 ||Planning Time: 0.235 ms||
    1473 ||Trigger for constraint fk_event_happening_event: time\=0.252 calls\=1||
    1474 ||Trigger for constraint fk_event_happening_venue: time\=0.255 calls\=1||
    1475 ||Execution Time: 1.278 ms||
    1476 
    1477  * '''UPDATE'''
    1478 
    1479 {{{
    1480 
    1481 EXPLAIN ANALYZE
    1482     UPDATE "Event_Happening"
    1483     SET event_time = '2026-11-20 20:00:00'
    1484     WHERE event_happening_id = (SELECT MAX(event_happening_id) FROM "Event_Happening");
    1485 
    1486 }}}
    1487 
    1488 ||= QUERY PLAN =||
    1489 ||Update on "Event_Happening"  (cost\=0.61..8.63 rows\=0 width\=0) (actual time\=0.543..0.544 rows\=0 loops\=1)||
    1490 ||  InitPlan 2||
    1491 ||    ->  Result  (cost\=0.31..0.32 rows\=1 width\=8) (actual time\=0.169..0.170 rows\=1 loops\=1)||
    1492 ||          InitPlan 1||
    1493 ||            ->  Limit  (cost\=0.29..0.31 rows\=1 width\=8) (actual time\=0.166..0.166 rows\=1 loops\=1)||
    1494 ||                  ->  Index Only Scan Backward using "Event_Happening_pkey" on "Event_Happening" "Event_Happening_1"  (cost\=0.29..826.36 rows\=31338 width\=8) (actual time\=0.165..0.165 rows\=1 loops\=1)||
    1495 ||                        Heap Fetches: 1||
    1496 ||  ->  Index Scan using "Event_Happening_pkey" on "Event_Happening"  (cost\=0.29..8.30 rows\=1 width\=14) (actual time\=0.183..0.185 rows\=1 loops\=1)||
    1497 ||        Index Cond: (event_happening_id \= (InitPlan 2).col1)||
    1498 ||Planning Time: 0.283 ms||
    1499 ||Execution Time: 0.616 ms||
    1500 
    1501 == Анализа и оптимизација на `Available_Tickets`
    1502 
    1503 Овој поглед овозможува моментален увид во инвентарот на достапни седишта и автоматско враќање на рефундираните билети во понуда.
    1504 
    1505 {{{
    1506 
    1507 CREATE VIEW "Available_Tickets" AS
    1508 SELECT
    1509     t.ticket_id,
    1510     t.ticket_type,
    1511     t.base_price,
    1512     e.event_id,
    1513     e.name AS event_name,
    1514     eh.event_happening_id,
    1515     eh.event_time,
    1516     s.name AS section_name,
    1517     st.seat_number
    1518 FROM "Ticket" t
    1519 JOIN "Event_Happening" eh ON t.event_happening_id = eh.event_happening_id
    1520 JOIN "Event" e ON eh.event_id = e.event_id
    1521 JOIN "Seat" st ON t.seat_id = st.seat_id
    1522 JOIN "Section" s ON st.section_id = s.section_id
    1523 WHERE t.is_available = TRUE;
    1524 
    1525 }}}
    1526 
    1527 ==== 1. Примарен филтер:
    1528 
    1529 Примарен филтер за овој поглед е `is_available = TRUE` во комбинација со `event_id`. Ова е најфреквентниот прашалник на платформата, бидејќи секој корисник што прегледува настан сака да знае кои седишта се слободни за продажба.
    1530 
    1531 ==== 2. Случај на употреба:
    1532 
    1533 Овој поглед го претставува инвентарот во реално време. Поради големиот волумен на податоци во табелата `Ticket` (околу 30 милиони записи), секое доцнење тука може да доведе до Race Condition (двајца корисници да го видат истото седиште како слободно). Ефикасноста тука не е само прашање на UX, туку и на интегритет на продажбата.
    1534 
    1535 ==== 3. Иницијално време:
    1536 
    1537 Иницијалното време за извршување изнесува 948.253 ms. Иако е под една секунда, ова е премногу бавно за систем со голем сообраќај каде илјадници корисници истовремено го повикуваат овој поглед.
    1538 
    1539 ==== 4. Анализа на планот на извршување (без индекси):
    1540 
    1541 Најголем проблем е '''Merge Join''' операцијата која мора да ги спои табелите `Ticket` и `Seat`. Бидејќи базата нема соодветен индекс за достапните карти, таа троши многу ресурси на сортирање и пребарување низ милиони редови.
    1542 
    1543 Се појавуваат високи трошоци за читање од диск бидејќи базата не може веднаш да ги „отфрли“ веќе продадените билети.
    1544 
    1545  * '''SELECT'''
    1546 
    1547 {{{
    1548 
    1549 EXPLAIN ANALYZE
    1550     SELECT * FROM "Available_Tickets"
    1551     WHERE event_id = 1;
    1552 
    1553 }}}
    1554 
    1555 ||= QUERY PLAN =||
    1556 ||Merge Join  (cost\=27256.32..27455.75 rows\=811 width\=85) (actual time\=299.567..948.130 rows\=347 loops\=1)||
    1557 ||  Merge Cond: (st.seat_id \= t.seat_id)||
    1558 ||  ->  Nested Loop  (cost\=0.86..2908890.51 rows\=20753360 width\=19) (actual time\=19.526..667.820 rows\=1163 loops\=1)||
    1559 ||        ->  Index Scan using "Seat_pkey" on "Seat" st  (cost\=0.56..2383051.22 rows\=20753360 width\=20) (actual time\=0.716..648.333 rows\=1163 loops\=1)||
    1560 ||        ->  Memoize  (cost\=0.30..0.32 rows\=1 width\=15) (actual time\=0.016..0.016 rows\=1 loops\=1163)||
    1561 ||              Cache Key: st.section_id||
    1562 ||              Cache Mode: logical||
    1563 ||              Hits: 1156  Misses: 7  Evictions: 0  Overflows: 0  Memory Usage: 1kB||
    1564 ||              ->  Index Scan using "Section_pkey" on "Section" s  (cost\=0.29..0.31 rows\=1 width\=15) (actual time\=2.600..2.600 rows\=1 loops\=7)||
    1565 ||                    Index Cond: (section_id \= st.section_id)||
    1566 ||  ->  Sort  (cost\=27253.12..27255.15 rows\=811 width\=82) (actual time\=280.018..280.056 rows\=347 loops\=1)||
    1567 ||        Sort Key: t.seat_id||
    1568 ||        Sort Method: quicksort  Memory: 62kB||
    1569 ||        ->  Nested Loop  (cost\=5.05..27213.93 rows\=811 width\=82) (actual time\=0.827..279.855 rows\=347 loops\=1)||
    1570 ||              ->  Nested Loop  (cost\=4.61..30.67 rows\=5 width\=55) (actual time\=0.243..0.408 rows\=7 loops\=1)||
    1571 ||                    ->  Index Scan using "Event_pkey" on "Event" e  (cost\=0.29..8.30 rows\=1 width\=39) (actual time\=0.141..0.153 rows\=1 loops\=1)||
    1572 ||                          Index Cond: (event_id \= 1)||
    1573 ||                    ->  Bitmap Heap Scan on "Event_Happening" eh  (cost\=4.33..22.32 rows\=5 width\=24) (actual time\=0.095..0.239 rows\=7 loops\=1)||
    1574 ||                          Recheck Cond: (event_id \= 1)||
    1575 ||                          Heap Blocks: exact\=6||
    1576 ||                          ->  Bitmap Index Scan on idx_event_happening_event_id  (cost\=0.00..4.32 rows\=5 width\=0) (actual time\=0.066..0.066 rows\=11 loops\=1)||
    1577 ||                                Index Cond: (event_id \= 1)||
    1578 ||              ->  Index Scan using idx_ticket_event_happening_id on "Ticket" t  (cost\=0.44..5433.49 rows\=316 width\=35) (actual time\=39.884..39.910 rows\=50 loops\=7)||
    1579 ||                    Index Cond: (event_happening_id \= eh.event_happening_id)||
    1580 ||                    Filter: is_available||
    1581 ||                    Rows Removed by Filter: 568||
    1582 ||Planning Time: 3.990 ms||
    1583 ||Execution Time: 948.253 ms||
    1584 
    1585  * '''INSERT'''
    1586 
    1587 {{{
    1588 
    1589 EXPLAIN ANALYZE
    1590     INSERT INTO "Ticket" (ticket_id, ticket_type, base_price, is_available, event_happening_id, seat_id)
    1591     VALUES ((SELECT MAX(ticket_id) + 1 FROM "Ticket"), 'Standard', 500.00, TRUE, 1, 999999);
    1592 
    1593 }}}
    1594 
    1595 ||= QUERY PLAN =||
    1596 ||Insert on "Ticket"  (cost\=0.61..0.62 rows\=0 width\=0) (actual time\=0.429..0.430 rows\=0 loops\=1)||
    1597 ||  InitPlan 2||
    1598 ||    ->  Result  (cost\=0.59..0.61 rows\=1 width\=8) (actual time\=0.215..0.216 rows\=1 loops\=1)||
    1599 ||          InitPlan 1||
    1600 ||            ->  Limit  (cost\=0.56..0.59 rows\=1 width\=8) (actual time\=0.212..0.213 rows\=1 loops\=1)||
    1601 ||                  ->  Index Only Scan Backward using "Ticket_pkey" on "Ticket" "Ticket_1"  (cost\=0.56..892367.26 rows\=28748980 width\=8) (actual time\=0.211..0.212 rows\=1 loops\=1)||
    1602 ||                        Heap Fetches: 1||
    1603 ||  ->  Result  (cost\=0.00..0.01 rows\=1 width\=147) (actual time\=0.218..0.218 rows\=1 loops\=1)||
    1604 ||Planning Time: 0.211 ms||
    1605 ||Trigger for constraint fk_ticket_event_happening: time\=0.325 calls\=1||
    1606 ||Trigger for constraint fk_ticket_seat: time\=0.243 calls\=1||
    1607 ||Execution Time: 1.045 ms||
    1608 
    1609  * '''UPDATE'''
    1610 
    1611 {{{
    1612 
    1613 EXPLAIN ANALYZE
    1614     UPDATE "Ticket"
    1615     SET base_price = 600.00
    1616     WHERE ticket_id = (SELECT MAX(ticket_id) FROM "Ticket");
    1617 
    1618 }}}
    1619 
    1620 ||= QUERY PLAN =||
    1621 ||Update on "Ticket"  (cost\=1.17..9.18 rows\=0 width\=0) (actual time\=0.340..0.341 rows\=0 loops\=1)||
    1622 ||  InitPlan 2||
    1623 ||    ->  Result  (cost\=0.59..0.60 rows\=1 width\=8) (actual time\=0.270..0.271 rows\=1 loops\=1)||
    1624 ||          InitPlan 1||
    1625 ||            ->  Limit  (cost\=0.56..0.59 rows\=1 width\=8) (actual time\=0.267..0.268 rows\=1 loops\=1)||
    1626 ||                  ->  Index Only Scan Backward using "Ticket_pkey" on "Ticket" "Ticket_1"  (cost\=0.56..892367.26 rows\=28748980 width\=8) (actual time\=0.266..0.266 rows\=1 loops\=1)||
    1627 ||                        Heap Fetches: 1||
    1628 ||  ->  Index Scan using "Ticket_pkey" on "Ticket"  (cost\=0.56..8.58 rows\=1 width\=10) (actual time\=0.291..0.292 rows\=1 loops\=1)||
    1629 ||        Index Cond: (ticket_id \= (InitPlan 2).col1)||
    1630 ||Planning Time: 0.244 ms||
    1631 ||Execution Time: 0.393 ms||
    1632 
    1633 ==== 5. Оптимизација и индексирање:
    1634 
    1635 За овој поглед го воведуваме специјализираниот делумен индекс:
    1636 
    1637  * `idx_ticket_is_available_true`: Овој индекс ги содржи само редовите каде `is_available` е TRUE. Наместо да пребарува низ 30 милиони записи, базата сега гледа само во мал подмножество (на пр. 50,000 слободни билети).
    1638 
    1639  * idx_ticket_event_happening_id: Овозможува моментално поврзување на билетите со конкретниот настан.
    1640 
    1641  * idx_ticket_seat_id: Го забрзува поврзувањето со табелата `Seat` за да се прикажат броевите на седиштата и секторите.
    1642 
    1643 {{{
    1644 
    1645 -- partial index for rapid retrieval of only unsold tickets by filtering out sold ones
    1646 CREATE INDEX idx_ticket_is_available_true ON "Ticket"(is_available) WHERE is_available = TRUE;
    1647 
    1648 -- index for linking tickets to scheduled events to accelerate event-based filtering
    1649 CREATE INDEX idx_ticket_event_happening_id ON "Ticket"(event_happening_id);
    1650 
    1651 -- index for optimizing seat-based joins to retrieve venue layout details for each ticket
    1652 CREATE INDEX idx_ticket_seat_id ON "Ticket"(seat_id);
    1653 
    1654 }}}
    1655 
    1656 ==== 6. Резултат по оптимизација:
    1657 
    1658 Времето на извршување падна на 3.882 ms.
    1659 
    1660 Подобрување: Забрзување од околу 245 пати.
    1661 
    1662 Времето на '''INSERT''' и '''UPDATE''' операциите останува минимално, што потврдува дека делумните индекси се многу „лесни“ за одржување бидејќи не се ажурираат при секоја промена, туку само кога се менува статусот на достапност.
    1663 
    1664  * '''SELECT'''
    1665 
    1666 {{{
    1667 
    1668 EXPLAIN ANALYZE
    1669     SELECT * FROM "Available_Tickets"
    1670     WHERE event_id = 1;
    1671 
    1672 }}}
    1673 
    1674 ||= QUERY PLAN =||
    1675 ||Merge Join  (cost\=26670.90..26870.11 rows\=793 width\=85) (actual time\=2.496..3.734 rows\=347 loops\=1)||
    1676 ||  Merge Cond: (st.seat_id \= t.seat_id)||
    1677 ||  ->  Nested Loop  (cost\=0.86..2908890.51 rows\=20753360 width\=19) (actual time\=0.270..1.289 rows\=1163 loops\=1)||
    1678 ||        ->  Index Scan using "Seat_pkey" on "Seat" st  (cost\=0.56..2383051.22 rows\=20753360 width\=20) (actual time\=0.049..0.516 rows\=1163 loops\=1)||
    1679 ||        ->  Memoize  (cost\=0.30..0.32 rows\=1 width\=15) (actual time\=0.000..0.000 rows\=1 loops\=1163)||
    1680 ||              Cache Key: st.section_id||
    1681 ||              Cache Mode: logical||
    1682 ||              Hits: 1156  Misses: 7  Evictions: 0  Overflows: 0  Memory Usage: 1kB||
    1683 ||              ->  Index Scan using "Section_pkey" on "Section" s  (cost\=0.29..0.31 rows\=1 width\=15) (actual time\=0.009..0.009 rows\=1 loops\=7)||
    1684 ||                    Index Cond: (section_id \= st.section_id)||
    1685 ||  ->  Sort  (cost\=26667.76..26669.74 rows\=793 width\=82) (actual time\=2.213..2.236 rows\=347 loops\=1)||
    1686 ||        Sort Key: t.seat_id||
    1687 ||        Sort Method: quicksort  Memory: 62kB||
    1688 ||        ->  Nested Loop  (cost\=5.05..26629.57 rows\=793 width\=82) (actual time\=0.566..2.095 rows\=347 loops\=1)||
    1689 ||              ->  Nested Loop  (cost\=4.61..30.67 rows\=5 width\=55) (actual time\=0.153..0.260 rows\=7 loops\=1)||
    1690 ||                    ->  Index Scan using "Event_pkey" on "Event" e  (cost\=0.29..8.30 rows\=1 width\=39) (actual time\=0.077..0.083 rows\=1 loops\=1)||
    1691 ||                          Index Cond: (event_id \= 1)||
    1692 ||                    ->  Bitmap Heap Scan on "Event_Happening" eh  (cost\=4.33..22.32 rows\=5 width\=24) (actual time\=0.072..0.168 rows\=7 loops\=1)||
    1693 ||                          Recheck Cond: (event_id \= 1)||
    1694 ||                          Heap Blocks: exact\=6||
    1695 ||                          ->  Bitmap Index Scan on idx_event_happening_event_id  (cost\=0.00..4.32 rows\=5 width\=0) (actual time\=0.047..0.047 rows\=11 loops\=1)||
    1696 ||                                Index Cond: (event_id \= 1)||
    1697 ||              ->  Index Scan using idx_ticket_event_happening_id on "Ticket" t  (cost\=0.44..5316.69 rows\=309 width\=35) (actual time\=0.234..0.253 rows\=50 loops\=7)||
    1698 ||                    Index Cond: (event_happening_id \= eh.event_happening_id)||
    1699 ||                    Filter: is_available||
    1700 ||                    Rows Removed by Filter: 568||
    1701 ||Planning Time: 4.840 ms||
    1702 ||Execution Time: 3.882 ms||
    1703 
    1704  * '''INSERT'''
    1705 
    1706 {{{
    1707 
    1708 EXPLAIN ANALYZE
    1709     INSERT INTO "Ticket" (ticket_id, ticket_type, base_price, is_available, event_happening_id, seat_id)
    1710     VALUES ((SELECT MAX(ticket_id) + 1 FROM "Ticket"), 'Standard', 500.00, TRUE, 1, 888888);
    1711 
    1712 }}}
    1713 
    1714 ||= QUERY PLAN =||
    1715 ||Insert on "Ticket"  (cost\=0.61..0.62 rows\=0 width\=0) (actual time\=0.545..0.546 rows\=0 loops\=1)||
    1716 ||  InitPlan 2||
    1717 ||    ->  Result  (cost\=0.59..0.61 rows\=1 width\=8) (actual time\=0.092..0.093 rows\=1 loops\=1)||
    1718 ||          InitPlan 1||
    1719 ||            ->  Limit  (cost\=0.56..0.59 rows\=1 width\=8) (actual time\=0.089..0.090 rows\=1 loops\=1)||
    1720 ||                  ->  Index Only Scan Backward using "Ticket_pkey" on "Ticket" "Ticket_1"  (cost\=0.56..882762.56 rows\=28108400 width\=8) (actual time\=0.088..0.089 rows\=1 loops\=1)||
    1721 ||                        Heap Fetches: 1||
    1722 ||  ->  Result  (cost\=0.00..0.01 rows\=1 width\=147) (actual time\=0.095..0.095 rows\=1 loops\=1)||
    1723 ||Planning Time: 0.250 ms||
    1724 ||Trigger for constraint fk_ticket_event_happening: time\=0.473 calls\=1||
    1725 ||Trigger for constraint fk_ticket_seat: time\=207.063 calls\=1||
    1726 ||Execution Time: 208.133 ms||
    1727 
    1728  * '''UPDATE'''
    1729 
    1730 {{{
    1731 
    1732 EXPLAIN ANALYZE
    1733     UPDATE "Ticket"
    1734     SET base_price = 700.00
    1735     WHERE ticket_id = (SELECT MAX(ticket_id) FROM "Ticket");
    1736 
    1737 }}}
    1738 
    1739 ||= QUERY PLAN =||
    1740 ||Update on "Ticket"  (cost\=1.17..9.18 rows\=0 width\=0) (actual time\=0.168..0.169 rows\=0 loops\=1)||
    1741 ||  InitPlan 2||
    1742 ||    ->  Result  (cost\=0.59..0.60 rows\=1 width\=8) (actual time\=0.033..0.034 rows\=1 loops\=1)||
    1743 ||          InitPlan 1||
    1744 ||            ->  Limit  (cost\=0.56..0.59 rows\=1 width\=8) (actual time\=0.031..0.031 rows\=1 loops\=1)||
    1745 ||                  ->  Index Only Scan Backward using "Ticket_pkey" on "Ticket" "Ticket_1"  (cost\=0.56..882762.56 rows\=28108400 width\=8) (actual time\=0.030..0.030 rows\=1 loops\=1)||
    1746 ||                        Heap Fetches: 1||
    1747 ||  ->  Index Scan using "Ticket_pkey" on "Ticket"  (cost\=0.56..8.58 rows\=1 width\=10) (actual time\=0.045..0.046 rows\=1 loops\=1)||
    1748 ||        Index Cond: (ticket_id \= (InitPlan 2).col1)||
    1749 ||Planning Time: 0.242 ms||
    1750 ||Execution Time: 0.397 ms||