Changes between Initial Version and Version 1 of QueryOptimization


Ignore:
Timestamp:
06/12/26 19:54:29 (9 days ago)
Author:
231025
Comment:

--

Legend:

Unmodified
Added
Removed
Modified
  • QueryOptimization

    v1 v1  
     1= Фаза 3: Индекси и оптимизација на прашалници =
     2
     3''' Членови на тимот '''
     4* Матеа Ѓоргоска 231069 (координатор)
     5* Ана Марија Кукулева 231025
     6
     7
     8[[BR]]
     9== 1. Погледи ==
     10Во оваа фаза дефиниравме 6 погледи кои покриваат реални сценарија за пребарување во апликацијата.
     11
     12=== Поглед 1: v_site_zapisnici ===
     13'''Брз преглед на сите записници по лице, без join-ови.'''[[BR]]
     14Се пребарува по: `EMBG_Prekrsuvach` (или `EMBG_Policaec`).
     15{{{#!sql
     16CREATE OR REPLACE VIEW v_site_zapisnici AS
     17SELECT id_na_zapisnik, datum, vreme, lokacija, Potpis,
     18       id_slucaj, EMBG_Prekrsuvach, Vozilo_Broj_Sasija, EMBG_Policaec
     19FROM Zapisnik;
     20}}}
     21
     22=== Поглед 2: v_zapisnici_detalno ===
     23'''Целосни детали за конкретен записник: прекршител, возило, полицаец, прекршок, износ казна и статус уплата.'''[[BR]]
     24Се пребарува по: `id_na_zapisnik` / `EMBG_Prekrsuvach`.
     25{{{#!sql
     26CREATE OR REPLACE VIEW v_zapisnici_detalno AS
     27SELECT z.id_na_zapisnik, z.datum, z.vreme, z.lokacija, z.id_slucaj,
     28       g.EMBG AS embg_prekrsitel, g.ime AS prekrsitel_ime, g.prezime AS prekrsitel_prezime,
     29       v.broj_na_sasija, v.model AS vozilo_model,
     30       pol.EMBG_P AS embg_policaec, g_p.ime AS policaec_ime, g_p.prezime AS policaec_prezime,
     31       p.ime AS prekrsok, k.iznos_kazna, u.iznos AS iznos_uplata, u.status AS status_uplata
     32FROM Zapisnik z
     33LEFT JOIN Gragjanin g        ON z.EMBG_Prekrsuvach = g.EMBG
     34LEFT JOIN Vozilo v           ON z.Vozilo_Broj_Sasija = v.broj_na_sasija
     35LEFT JOIN Policaec pol       ON z.EMBG_Policaec = pol.EMBG_P
     36LEFT JOIN Gragjanin g_p      ON pol.EMBG_P = g_p.EMBG
     37LEFT JOIN Stavka_Zapisnik sz ON z.id_na_zapisnik = sz.id_na_zapisnik
     38LEFT JOIN Prekrsok p         ON sz.id_na_prekrsok = p.id_prekrsok
     39LEFT JOIN Kazna k            ON p.id_kazna = k.id_kazna
     40LEFT JOIN Uplata u           ON z.id_na_zapisnik = u.id_zapisnik;
     41}}}
     42
     43=== Поглед 3: v_neplateni_kazni ===
     44'''Сите неплатени казни на даден граѓанин, со износ за наплата и колку денови се веќе неплатени.'''[[BR]]
     45Се пребарува по: `EMBG_prekrsitel`.
     46{{{#!sql
     47CREATE OR REPLACE VIEW v_neplateni_kazni AS
     48SELECT z.id_na_zapisnik, z.datum AS datum_prekrsok, z.lokacija,
     49       g.EMBG AS embg_prekrsitel, g.ime AS ime_prekrsitel, g.prezime AS prezime_prekrsitel,
     50       p.ime AS prekrsok, k.iznos_kazna, u.iznos AS iznos_za_naplata,
     51       (CURRENT_DATE - z.datum) AS denovi_neplateno
     52FROM Uplata u
     53JOIN Zapisnik z         ON u.id_zapisnik = z.id_na_zapisnik
     54JOIN Gragjanin g        ON z.EMBG_Prekrsuvach = g.EMBG
     55JOIN Stavka_Zapisnik sz ON z.id_na_zapisnik = sz.id_na_zapisnik
     56JOIN Prekrsok p         ON sz.id_na_prekrsok = p.id_prekrsok
     57JOIN Kazna k            ON p.id_kazna = k.id_kazna
     58WHERE u.status = 'Neplateno';
     59}}}
     60
     61=== Поглед 4: v_povtoreni_prekrsoci ===
     62'''Граѓани што повторуваат ист тип прекршок (`COUNT > 1`) — колку пати, прв и последен пат, вкупен износ казни.'''[[BR]]
     63Се пребарува по: `EMBG`.
     64{{{#!sql
     65CREATE OR REPLACE VIEW v_povtoreni_prekrsoci AS
     66WITH Prekrsoci_Stats AS (
     67    SELECT z.EMBG_Prekrsuvach, sz.id_na_prekrsok,
     68           COUNT(*) AS broj_povtori, MIN(z.datum) AS prv_pat, MAX(z.datum) AS posleden_pat,
     69           SUM(k.iznos_kazna) AS vkupen_iznos_kazni
     70    FROM Zapisnik z
     71    JOIN Stavka_Zapisnik sz ON z.id_na_zapisnik = sz.id_na_zapisnik
     72    JOIN Prekrsok p         ON sz.id_na_prekrsok = p.id_prekrsok
     73    JOIN Kazna k            ON p.id_kazna = k.id_kazna
     74    GROUP BY z.EMBG_Prekrsuvach, sz.id_na_prekrsok
     75    HAVING COUNT(*) > 1
     76)
     77SELECT g.EMBG, g.ime, g.prezime, p.id_prekrsok, p.ime AS prekrsok,
     78       ps.broj_povtori, ps.prv_pat, ps.posleden_pat, ps.vkupen_iznos_kazni
     79FROM Prekrsoci_Stats ps
     80JOIN Gragjanin g ON ps.EMBG_Prekrsuvach = g.EMBG
     81JOIN Prekrsok p  ON ps.id_na_prekrsok = p.id_prekrsok;
     82}}}
     83
     84=== Поглед 5: v_policaici_prosek_zapisnici ===
     85'''Статистики по полицаец: вкупно записници, прв/последен, активни месеци и просечен број записници по месец.'''[[BR]]
     86Се пребарува по: `EMBG_P` (дополнително по име на станица).
     87{{{#!sql
     88CREATE OR REPLACE VIEW v_policaici_prosek_zapisnici AS
     89WITH Policaec_Stats AS (
     90    SELECT EMBG_Policaec, COUNT(id_na_zapisnik) AS vkupno_zapisnici,
     91           MIN(datum) AS prv_zapisnik, MAX(datum) AS posleden_zapisnik
     92    FROM Zapisnik
     93    GROUP BY EMBG_Policaec
     94)
     95SELECT pol.EMBG_P, pol.broj_na_znacka, g.ime AS policaec_ime, g.prezime AS policaec_prezime,
     96       ps.vkupno_zapisnici, ps.prv_zapisnik, ps.posleden_zapisnik,
     97       GREATEST(DATE_PART('year', AGE(ps.posleden_zapisnik, ps.prv_zapisnik)) * 12 +
     98                DATE_PART('month', AGE(ps.posleden_zapisnik, ps.prv_zapisnik)), 1) AS aktivni_meseci,
     99       ROUND(ps.vkupno_zapisnici::numeric /
     100             GREATEST(DATE_PART('year', AGE(ps.posleden_zapisnik, ps.prv_zapisnik)) * 12 +
     101                      DATE_PART('month', AGE(ps.posleden_zapisnik, ps.prv_zapisnik)), 1)::numeric, 2) AS prosek_zapisnici_po_mesec
     102FROM Policaec_Stats ps
     103JOIN Policaec pol ON ps.EMBG_Policaec = pol.EMBG_P
     104JOIN Gragjanin g  ON pol.EMBG_P = g.EMBG;
     105}}}
     106
     107=== Поглед 6: v_istorija_gragjanin ===
     108'''Целосен профил на граѓанин: вкупно прекршоци, платено, долг, прв/последен прекршок и број различни типови.'''[[BR]]
     109Се пребарува по: `EMBG`.
     110{{{#!sql
     111CREATE OR REPLACE VIEW v_istorija_gragjanin AS
     112WITH Zapisnik_Stats AS (
     113    SELECT EMBG_Prekrsuvach,
     114           COUNT(DISTINCT z.id_na_zapisnik) AS vkupno_zapisnici,
     115           MIN(z.datum) AS prv_prekrsok, MAX(z.datum) AS posleden_prekrsok,
     116           COUNT(sz.id_stavka) AS vkupno_prekrshoci,
     117           COUNT(DISTINCT sz.id_na_prekrsok) AS razlichni_tipovi,
     118           COALESCE(SUM(k.iznos_kazna), 0) AS vkupen_iznos_kazni
     119    FROM Zapisnik z
     120    LEFT JOIN Stavka_Zapisnik sz ON z.id_na_zapisnik = sz.id_na_zapisnik
     121    LEFT JOIN Prekrsok p         ON sz.id_na_prekrsok = p.id_prekrsok
     122    LEFT JOIN Kazna k            ON p.id_kazna = k.id_kazna
     123    GROUP BY EMBG_Prekrsuvach
     124),
     125Uplata_Stats AS (
     126    SELECT z.EMBG_Prekrsuvach,
     127           COALESCE(SUM(CASE WHEN u.status = 'Plateno'  THEN u.iznos END), 0) AS vkupno_plateno,
     128           COALESCE(SUM(CASE WHEN u.status = 'Neplateno' THEN u.iznos END), 0) AS vkupen_dolg,
     129           COUNT(CASE WHEN u.status = 'Neplateno' THEN u.id_uplata END) AS broj_neplateni
     130    FROM Uplata u
     131    JOIN Zapisnik z ON u.id_zapisnik = z.id_na_zapisnik
     132    GROUP BY z.EMBG_Prekrsuvach
     133)
     134SELECT g.EMBG, g.ime, g.prezime, g.datum_ragjanje,
     135       zs.vkupno_zapisnici, zs.vkupno_prekrshoci, zs.razlichni_tipovi, zs.vkupen_iznos_kazni,
     136       COALESCE(us.vkupno_plateno, 0) AS vkupno_plateno,
     137       COALESCE(us.vkupen_dolg, 0)    AS vkupen_dolg,
     138       COALESCE(us.broj_neplateni, 0) AS broj_neplateni,
     139       zs.prv_prekrsok, zs.posleden_prekrsok
     140FROM Gragjanin g
     141JOIN Zapisnik_Stats zs ON g.EMBG = zs.EMBG_Prekrsuvach
     142LEFT JOIN Uplata_Stats us ON g.EMBG = us.EMBG_Prekrsuvach;
     143}}}
     144
     145
     146[[BR]]
     147== 2. Оптимизација ==
     148За секој поглед го измеривме почетното време преку `EXPLAIN ANALYZE`, ја идентификувавме најбавната операција и применивме оптимизација. Кај пребарувањата по клуч тоа е индекс, а кај аналитичките погледи — материјализација.
     149
     150=== Поглед 1 — v_site_zapisnici ===
     151Најбавна операција: `Seq Scan on zapisnik` (се чита целата табела). Решение — индекс на колоната по која се филтрира:
     152{{{#!sql
     153CREATE INDEX idx_zapisnik_prekrsuvach ON Zapisnik(EMBG_Prekrsuvach);
     154}}}
     155
     156'''Explain план (пред / по индекс):'''
     157[[Image(view1.png, 700px)]]
     158[[Image(vew1-index.png, 700px)]]
     159
     160'''Времиња на извршување:'''
     161* SELECT: 6m4s → '''~133ms'''
     162* INSERT: 1,699s → '''0,066s'''
     163* UPDATE: 0,081s → '''0,055s'''
     164
     165=== Поглед 2 — v_zapisnici_detalno ===
     166Најбавна операција: `Parallel Seq Scan on uplata` (~2.000.000 редови). Решение — индекси на join-колоните:
     167{{{#!sql
     168CREATE INDEX idx_stavka_zapisnik_id_zapisnik ON Stavka_Zapisnik(id_na_zapisnik);
     169CREATE INDEX idx_uplata_id_zapisnik          ON Uplata(id_zapisnik);
     170}}}
     171
     172'''Explain план (пред / по индекс):'''
     173[[Image(view2.png, 700px)]]
     174[[Image(view2-index.png, 700px)]]
     175
     176'''Времиња на извршување:'''
     177* SELECT: 5m59s → '''~154ms'''
     178* INSERT: 3,962s → '''0,230s'''
     179* UPDATE: 3,950s → '''0,053s'''
     180
     181=== Поглед 3 — v_neplateni_kazni ===
     182Најбавна операција: `Seq Scan on uplata`. Join-условот (`u.id_zapisnik = z.id_na_zapisnik`) е ист како кај Поглед 2, '''па не е потребен нов индекс''' — се реискористува постоечкиот `idx_uplata_id_zapisnik`.
     183
     184'''Explain план (пред / по индекс):'''
     185[[Image(view3.png, 700px)]]
     186[[Image(view3-index.png, 700px)]]
     187
     188'''Времиња на извршување:'''
     189* SELECT: 6,922s → '''~121ms'''
     190* INSERT: 0,153s → '''0,134s'''
     191* UPDATE: 1,990s → '''0,031s'''
     192
     193=== Поглед 4 — v_povtoreni_prekrsoci ===
     194Аналитички поглед. План: `GROUP BY` / `HashAggregate` над цела `Zapisnik` — агрегацијата ги поминува сите редови, индекс не помага. Решение — '''материјализиран поглед'''; INSERT/UPDATE на основните табели остануваат исти.
     195
     196'''Explain план:'''
     197[[Image(view4.png, 700px)]]
     198
     199'''Време на извршување:''' ~4,917s
     200
     201=== Поглед 5 — v_policaici_prosek_zapisnici ===
     202Аналитички поглед. План: агрегација (`COUNT`, `MIN`, `MAX`) групирана по полицаец над цела `Zapisnik` — индекс не носи добивка. Решение — '''материјализиран поглед''' со периодично освежување.
     203
     204'''Explain план:'''
     205[[Image(view5.png, 700px)]]
     206
     207'''Време на извршување:''' ~3,234s
     208
     209=== Поглед 6 — v_istorija_gragjanin ===
     210Аналитички поглед. План: две одделни агрегации (над `Zapisnik`/`Stavka_Zapisnik` и над `Uplata`) па join — двете страни се скенираат во целост. Решение — '''материјализиран поглед''' со периодично освежување.
     211
     212'''Explain план:'''
     213[[Image(view6.png, 700px)]]
     214
     215'''Време на извршување:''' ~9,076s
     216
     217
     218[[BR]]
     219== 3. Резиме ==
     220||='''Поглед'''=||='''Пред'''=||='''По'''=||='''Решение'''=||
     221|| v_site_zapisnici || 6m4s || ~133ms || индекс ||
     222|| v_zapisnici_detalno || 5m59s || ~154ms || индекс ||
     223|| v_neplateni_kazni || 6,922s || ~121ms || постоечки индекс ||
     224|| v_povtoreni_prekrsoci || ~4,917s || — || материјализација ||
     225|| v_policaici_prosek_zapisnici || ~3,234s || — || материјализација ||
     226|| v_istorija_gragjanin || ~9,076s || — || материјализација ||