| | 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 |
| | 16 | CREATE OR REPLACE VIEW v_site_zapisnici AS |
| | 17 | SELECT id_na_zapisnik, datum, vreme, lokacija, Potpis, |
| | 18 | id_slucaj, EMBG_Prekrsuvach, Vozilo_Broj_Sasija, EMBG_Policaec |
| | 19 | FROM Zapisnik; |
| | 20 | }}} |
| | 21 | |
| | 22 | === Поглед 2: v_zapisnici_detalno === |
| | 23 | '''Целосни детали за конкретен записник: прекршител, возило, полицаец, прекршок, износ казна и статус уплата.'''[[BR]] |
| | 24 | Се пребарува по: `id_na_zapisnik` / `EMBG_Prekrsuvach`. |
| | 25 | {{{#!sql |
| | 26 | CREATE OR REPLACE VIEW v_zapisnici_detalno AS |
| | 27 | SELECT 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 |
| | 32 | FROM Zapisnik z |
| | 33 | LEFT JOIN Gragjanin g ON z.EMBG_Prekrsuvach = g.EMBG |
| | 34 | LEFT JOIN Vozilo v ON z.Vozilo_Broj_Sasija = v.broj_na_sasija |
| | 35 | LEFT JOIN Policaec pol ON z.EMBG_Policaec = pol.EMBG_P |
| | 36 | LEFT JOIN Gragjanin g_p ON pol.EMBG_P = g_p.EMBG |
| | 37 | LEFT JOIN Stavka_Zapisnik sz ON z.id_na_zapisnik = sz.id_na_zapisnik |
| | 38 | LEFT JOIN Prekrsok p ON sz.id_na_prekrsok = p.id_prekrsok |
| | 39 | LEFT JOIN Kazna k ON p.id_kazna = k.id_kazna |
| | 40 | LEFT 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 |
| | 47 | CREATE OR REPLACE VIEW v_neplateni_kazni AS |
| | 48 | SELECT 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 |
| | 52 | FROM Uplata u |
| | 53 | JOIN Zapisnik z ON u.id_zapisnik = z.id_na_zapisnik |
| | 54 | JOIN Gragjanin g ON z.EMBG_Prekrsuvach = g.EMBG |
| | 55 | JOIN Stavka_Zapisnik sz ON z.id_na_zapisnik = sz.id_na_zapisnik |
| | 56 | JOIN Prekrsok p ON sz.id_na_prekrsok = p.id_prekrsok |
| | 57 | JOIN Kazna k ON p.id_kazna = k.id_kazna |
| | 58 | WHERE u.status = 'Neplateno'; |
| | 59 | }}} |
| | 60 | |
| | 61 | === Поглед 4: v_povtoreni_prekrsoci === |
| | 62 | '''Граѓани што повторуваат ист тип прекршок (`COUNT > 1`) — колку пати, прв и последен пат, вкупен износ казни.'''[[BR]] |
| | 63 | Се пребарува по: `EMBG`. |
| | 64 | {{{#!sql |
| | 65 | CREATE OR REPLACE VIEW v_povtoreni_prekrsoci AS |
| | 66 | WITH 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 | ) |
| | 77 | SELECT 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 |
| | 79 | FROM Prekrsoci_Stats ps |
| | 80 | JOIN Gragjanin g ON ps.EMBG_Prekrsuvach = g.EMBG |
| | 81 | JOIN 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 |
| | 88 | CREATE OR REPLACE VIEW v_policaici_prosek_zapisnici AS |
| | 89 | WITH 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 | ) |
| | 95 | SELECT 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 |
| | 102 | FROM Policaec_Stats ps |
| | 103 | JOIN Policaec pol ON ps.EMBG_Policaec = pol.EMBG_P |
| | 104 | JOIN Gragjanin g ON pol.EMBG_P = g.EMBG; |
| | 105 | }}} |
| | 106 | |
| | 107 | === Поглед 6: v_istorija_gragjanin === |
| | 108 | '''Целосен профил на граѓанин: вкупно прекршоци, платено, долг, прв/последен прекршок и број различни типови.'''[[BR]] |
| | 109 | Се пребарува по: `EMBG`. |
| | 110 | {{{#!sql |
| | 111 | CREATE OR REPLACE VIEW v_istorija_gragjanin AS |
| | 112 | WITH 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 | ), |
| | 125 | Uplata_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 | ) |
| | 134 | SELECT 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 |
| | 140 | FROM Gragjanin g |
| | 141 | JOIN Zapisnik_Stats zs ON g.EMBG = zs.EMBG_Prekrsuvach |
| | 142 | LEFT 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 |
| | 153 | CREATE 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 |
| | 168 | CREATE INDEX idx_stavka_zapisnik_id_zapisnik ON Stavka_Zapisnik(id_na_zapisnik); |
| | 169 | CREATE 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 || — || материјализација || |