| 1 | = Материјализирани погледи и индексирање = |
| 2 | |
| 3 | === 1. Број казнети според линија секоја година === |
| 4 | |
| 5 | {{{#!div |
| 6 | {{{#!sql |
| 7 | create materialized view kazni_po_linija_godisno as |
| 8 | select |
| 9 | extract(year from kz.kz_datum) as godina, |
| 10 | li.li_ime, |
| 11 | count(*) as broj_kazni |
| 12 | from linija li |
| 13 | join istanca_na_linija inl on inl.l_id = li.l_id |
| 14 | join kontroli ko on ko.inl_id = inl.inl_id |
| 15 | join kazna kz on kz.kontrola_id = ko.kontrola_id |
| 16 | group by godina, li.li_ime |
| 17 | order by godina, li.li_ime desc; |
| 18 | |
| 19 | create index idx_kazni_po_linija on kazni_po_linija_godisno(godina, li_ime); |
| 20 | }}} |
| 21 | }}} |
| 22 | |
| 23 | === 2. Најголем број возења по интервал од 1 час според линија за секоја година === |
| 24 | |
| 25 | {{{#!div |
| 26 | {{{#!sql |
| 27 | create materialized view maks_vozenja_po_saat_godisno as |
| 28 | select |
| 29 | godina, |
| 30 | li_ime, |
| 31 | interval_1h, |
| 32 | broj_vozenja |
| 33 | from ( |
| 34 | select |
| 35 | extract(year from v.vozenje_start)::int as godina, |
| 36 | li.li_ime, |
| 37 | to_char(extract(hour from v.vozenje_start), '00') || ':00 - ' || to_char(extract(hour from v.vozenje_start + interval '1 hour'), '00') || ':00' as interval_1h, |
| 38 | count(*) as broj_vozenja |
| 39 | from linija li |
| 40 | join instanca_na_linija inl on inl.li_id = li.li_id |
| 41 | join vozenje v on v.inl_id = inl.inl_id |
| 42 | group by godina, li.li_ime, interval_1h |
| 43 | ) as q1 |
| 44 | where (godina, li_ime, broj_vozenja) in ( |
| 45 | select |
| 46 | godina, |
| 47 | li_ime, |
| 48 | max(broj_vozenja) |
| 49 | from ( |
| 50 | select |
| 51 | extract(year from v.vozenje_start)::int as godina, |
| 52 | li.li_ime, |
| 53 | to_char(extract(hour from v.vozenje_start), '00') || ':00 - ' || to_char(extract(hour from v.vozenje_start + interval '1 hour'), '00') || ':00' as interval_1h, |
| 54 | count(*) as broj_vozenja |
| 55 | from linija li |
| 56 | join instanca_na_linija inl on inl.li_id = li.li_id |
| 57 | join vozenje v on v.inl_id = inl.inl_id |
| 58 | group by godina, li.li_ime, interval_1h |
| 59 | ) as q2 |
| 60 | group by godina, li_ime |
| 61 | ) |
| 62 | order by godina, li_ime; |
| 63 | |
| 64 | create index idx_vozenja_po_saat on maks_vozenja_po_saat_godisno (li_ime, godina, interval_1h); |
| 65 | }}} |
| 66 | }}} |
| 67 | |
| 68 | === 3. Број возења од линија за секоја година === |
| 69 | |
| 70 | {{{#!div |
| 71 | {{{#!sql |
| 72 | create materialized view vozenja_po_linija_godisno as |
| 73 | select |
| 74 | extract(year from v.vozenje_start) as godina, |
| 75 | li.li_ime, |
| 76 | li.li_pravec, |
| 77 | count(v.vozenje_id) as broj_vozenja |
| 78 | from linija li |
| 79 | join instanca_na_linija inl on inl.li_id = li.li_id |
| 80 | join vozenje v on v.inl_id = inl.inl_id |
| 81 | group by godina, li.li_id, li.li_ime, li.li_pravec |
| 82 | order by godina, li.li_id desc; |
| 83 | |
| 84 | create index idx_vozenja_po_linija on vozenja_po_linija_godisno (li_ime, godina); |
| 85 | }}} |
| 86 | }}} |
| 87 | |
| 88 | === 4. Број патници според постојка на дадена линија за секоја година === |
| 89 | |
| 90 | {{{#!div |
| 91 | {{{#!sql |
| 92 | create materialized view patnici_po_postojka_godisno as |
| 93 | select |
| 94 | extract(year from v.vozenje_start) as godina, |
| 95 | p.p_ime, |
| 96 | li.li_ime, |
| 97 | count(v.patnik_k_id) as broj_patnici |
| 98 | from postojka p |
| 99 | join postojka_na_linija pnl on pnl.p_id = p.p_id |
| 100 | join vozenje v on v.pnl_id = pnl.pnl_id |
| 101 | join patnik pa on pa.k_id = v.patnik_k_id |
| 102 | join linija li on li.li_id = pnl.li_id |
| 103 | group by godina, p.p_ime, li.li_ime |
| 104 | order by godina, broj_patnici desc; |
| 105 | |
| 106 | create index idx_patnici_po_postojka on patnici_po_postojka_godisno (godina, p_ime, li_ime); |
| 107 | }}} |
| 108 | }}} |
| 109 | |
| 110 | === 5. Часови во кои најмногу се купуваат часовни и дневни билети за секоја година === |
| 111 | |
| 112 | {{{#!div |
| 113 | {{{#!sql |
| 114 | create materialized view maks_kupeni_bileti_po_cas_godisno as |
| 115 | select |
| 116 | tb_ime, |
| 117 | godina, |
| 118 | interval_1h, |
| 119 | broj_bileti |
| 120 | from ( |
| 121 | select |
| 122 | tb.tb_ime, |
| 123 | extract(year from b.b_datum_na_kupuvanje)::int as godina, |
| 124 | to_char(extract(hour from b.b_datum_na_kupuvanje), '00') || ':00 - ' || to_char((extract(hour from b.b_datum_na_kupuvanje) + 1)::int, '00') || ':00' as interval_1h, |
| 125 | count(*) as broj_bileti |
| 126 | from tipbilet tb |
| 127 | join bilet b on b.tb_id = tb.tb_id |
| 128 | where tb.tb_ime in ('часовен', 'дневен') |
| 129 | group by tb.tb_ime, godina, interval_1h |
| 130 | ) q1 |
| 131 | where (q1.tb_ime, q1.godina, q1.broj_bileti) in ( |
| 132 | select |
| 133 | tb_ime, |
| 134 | godina, |
| 135 | max(broj_bileti) |
| 136 | from ( |
| 137 | select |
| 138 | tb.tb_ime, |
| 139 | extract(year from b.b_datum_na_kupuvanje)::int as godina, |
| 140 | to_char(extract(hour from b.b_datum_na_kupuvanje), '00') || ':00 - ' || to_char((extract(hour from b.b_datum_na_kupuvanje) + 1)::int, '00') || ':00' as interval_1h, |
| 141 | count(*) as broj_bileti |
| 142 | from tipbilet tb |
| 143 | join bilet b on b.tb_id = tb.tb_id |
| 144 | where tb.tb_ime in ('часовен', 'дневен') |
| 145 | group by tb.tb_ime, godina, interval_1h |
| 146 | ) q2 |
| 147 | group by godina, tb_ime |
| 148 | ) |
| 149 | order by q1.godina, q1.tb_ime; |
| 150 | |
| 151 | create index idx_bileti_kupeni_od_cas on maks_kupeni_bileti_po_cas_godisno (tb_ime, godina, interval_1h); |
| 152 | }}} |
| 153 | }}} |
| 154 | |
| 155 | === 6. Профит од казни и билети за секоја година === |
| 156 | |
| 157 | {{{#!div |
| 158 | {{{#!sql |
| 159 | create materialized view profit_kazni_i_bileti_godisno as |
| 160 | select |
| 161 | godina, |
| 162 | sum(coalesce(kz_iznos, 0)) + sum(coalesce(tb_cena, 0)) as vkupen_promet |
| 163 | from ( |
| 164 | select |
| 165 | extract(year from kz.kz_datum)::int as godina, |
| 166 | kz.kz_iznos, |
| 167 | null::numeric as tb_cena |
| 168 | from kazna kz |
| 169 | where |
| 170 | kz.kz_plateno = true |
| 171 | and kz.kz_datum is not null |
| 172 | and kz.kz_datum_plateno is not null |
| 173 | and extract(year from kz.kz_datum) = extract(year from kz.kz_datum_plateno) |
| 174 | union all |
| 175 | select |
| 176 | extract(year from b.b_datum_na_kupuvanje)::int as godina, |
| 177 | null::numeric as kz_iznos, |
| 178 | tb.tb_cena |
| 179 | from bilet b |
| 180 | join tipbilet tb on tb.tb_id = b.tb_id |
| 181 | ) as subquery |
| 182 | group by godina |
| 183 | order by godina; |
| 184 | |
| 185 | create index idx_prihod_godisen on profit_kazni_i_bileti_godisno(godina); |
| 186 | }}} |
| 187 | }}} |
| 188 | |
| 189 | = Тригери и фукниции = |
| 190 | |
| 191 | === 1. Промена на статус на билет === |
| 192 | |
| 193 | {{{#!div |
| 194 | {{{#!sql |
| 195 | create or replace function bilet_status() |
| 196 | returns trigger as |
| 197 | $$ |
| 198 | begin |
| 199 | if new.b_status = 'expired' then |
| 200 | raise exception 'билетот е веќе истечен'; |
| 201 | end if; |
| 202 | |
| 203 | if new.b_status = 'inactive' and new.b_datum_aktivacija is null then |
| 204 | new.b_datum_aktivacija = now(); |
| 205 | new.b_status = 'active'; |
| 206 | end if; |
| 207 | |
| 208 | if new.b_status = 'active' and new.b_datum_aktivacija is not null then |
| 209 | if exists ( |
| 210 | select 1 |
| 211 | from tipbilet tb |
| 212 | where tb.tb_id = new.tb_id |
| 213 | and (new.b_datum_aktivacija + (tb.tb_trajnost || ' minutes')::interval) < now() |
| 214 | ) then |
| 215 | new.b_status = 'expired'; |
| 216 | raise exception 'билетот е истечен'; |
| 217 | end if; |
| 218 | end if; |
| 219 | |
| 220 | return new; |
| 221 | end; |
| 222 | $$ language plpgsql; |
| 223 | |
| 224 | create or replace trigger proverka_bilet |
| 225 | before insert or update on bilet |
| 226 | for each row |
| 227 | execute function bilet_status(); |
| 228 | }}} |
| 229 | }}} |
| 230 | |
| 231 | === 2. Промена на статус на билет === |
| 232 | |
| 233 | {{{#!div |
| 234 | {{{#!sql |
| 235 | create or replace function vozenje_status() |
| 236 | returns trigger as $$ |
| 237 | begin |
| 238 | update vozenje |
| 239 | set vozenje_end = now(), vozenje_status = 'finished' |
| 240 | where patnik_k_id = new.patnik_k_id |
| 241 | and vozenje_start is not null |
| 242 | and vozenje_end is null |
| 243 | and vozenje_status = 'active' |
| 244 | and vozenje_id != new.vozenje_id; |
| 245 | |
| 246 | if new.vozenje_end is not null and new.vozenje_status != 'finished' then |
| 247 | new.vozenje_status = 'finished'; |
| 248 | end if; |
| 249 | |
| 250 | if new.vozenje_end is null then |
| 251 | new.vozenje_status = 'active'; |
| 252 | end if; |
| 253 | |
| 254 | return new; |
| 255 | end; |
| 256 | $$ language plpgsql; |
| 257 | |
| 258 | create or replace trigger vozenje_proverka |
| 259 | before insert or update on vozenje |
| 260 | for each row |
| 261 | execute function vozenje_status(); |
| 262 | }}} |
| 263 | }}} |
| 264 | |
| 265 | = Процеудури и трансакции = |
| 266 | |
| 267 | ==== 1. Регистрација на корисник ==== |
| 268 | |
| 269 | {{{#!div |
| 270 | {{{#!sql |
| 271 | create or replace procedure registracija_korisnik( |
| 272 | i_k_ime varchar(4000), |
| 273 | i_k_adresa varchar(4000), |
| 274 | i_k_telefon varchar(500), |
| 275 | i_k_email varchar(500), |
| 276 | i_k_embg varchar(13), |
| 277 | i_k_lozinka varchar(500), |
| 278 | i_k_is_admin boolean, |
| 279 | i_k_uloga varchar(500), |
| 280 | i_v_plata float default null, |
| 281 | i_v_datum_vrabotuvanje date default null, |
| 282 | i_v_datum_prekin_vrabotuvanje date default null |
| 283 | ) |
| 284 | language plpgsql |
| 285 | as $$ |
| 286 | declare |
| 287 | nov_k_id bigint; |
| 288 | begin |
| 289 | if i_k_ime is null or trim(i_k_ime) = '' then |
| 290 | raise exception 'името е задолжително'; |
| 291 | end if; |
| 292 | |
| 293 | if i_k_embg is null or length(i_k_embg) != 13 or i_k_embg !~ '^[0-9]{13}$' then |
| 294 | raise exception 'ембг мора да има 13 карактери'; |
| 295 | end if; |
| 296 | |
| 297 | if i_k_email is null or i_k_email !~ '^[a-za-z0-9._%+-]+@[a-za-z0-9.-]+\.[a-za-z]{2,}$' then |
| 298 | raise exception 'невалидна е-маил адреса'; |
| 299 | end if; |
| 300 | |
| 301 | if i_k_lozinka is null or trim(i_k_lozinka) = '' then |
| 302 | raise exception 'лозинка е задолжителна'; |
| 303 | end if; |
| 304 | |
| 305 | insert into korisnik( |
| 306 | k_ime, k_adresa, k_telefon, k_email, k_embg, k_lozinka, k_is_admin |
| 307 | ) values ( |
| 308 | i_k_ime, i_k_adresa, i_k_telefon, i_k_email, i_k_embg, i_k_lozinka, i_k_is_admin |
| 309 | ) returning k_id into nov_k_id; |
| 310 | |
| 311 | if i_k_uloga = 'patnik' then |
| 312 | insert into patnik(k_id) values (nov_k_id); |
| 313 | elsif i_k_uloga = 'vozac' then |
| 314 | if i_v_plata is null or i_v_datum_vrabotuvanje is null then |
| 315 | raise exception 'податоците за вработен се неопходни'; |
| 316 | end if; |
| 317 | if i_v_plata <= 0 then |
| 318 | raise exception 'платата неможе да е нула или пониска вредност'; |
| 319 | end if; |
| 320 | insert into vraboten(k_id, v_plata, v_datum_na_vrabotuvanje, v_datum_prekin_vrabotuvanje) |
| 321 | values (nov_k_id, i_v_plata, i_v_datum_vrabotuvanje, i_v_datum_prekin_vrabotuvanje); |
| 322 | insert into vozac(k_id) values (nov_k_id); |
| 323 | elsif i_k_uloga = 'konduktor' then |
| 324 | if i_v_plata is null or i_v_datum_vrabotuvanje is null then |
| 325 | raise exception 'податоците за вработен се неопходни'; |
| 326 | end if; |
| 327 | if i_v_plata <= 0 then |
| 328 | raise exception 'платата неможе да е нула или пониска вредност'; |
| 329 | end if; |
| 330 | insert into vraboten(k_id, v_plata, v_datum_na_vrabotuvanje, v_datum_prekin_vrabotuvanje) |
| 331 | values (nov_k_id, i_v_plata, i_v_datum_vrabotuvanje, i_v_datum_prekin_vrabotuvanje); |
| 332 | insert into konduktor(k_id) values (nov_k_id); |
| 333 | else |
| 334 | raise exception 'невалидна улога: %', i_k_uloga; |
| 335 | end if; |
| 336 | exception |
| 337 | when unique_violation then |
| 338 | if sqlerrm ~ 'unizue_k_embg' then |
| 339 | raise exception 'ембг-то % веќе постои', i_k_embg; |
| 340 | elsif sqlerrm ~ 'unique_k_email' then |
| 341 | raise exception 'е-маил-то % веќе постои', i_k_email; |
| 342 | else |
| 343 | raise exception 'грешка при регистрација: %', sqlerrm; |
| 344 | end if; |
| 345 | when others then |
| 346 | raise exception 'грешка при регистрација: %', sqlerrm; |
| 347 | end; |
| 348 | $$; |
| 349 | }}} |
| 350 | }}} |
| 351 | |
| 352 | ==== 2. Запишување казна ==== |
| 353 | |
| 354 | {{{#!div |
| 355 | {{{#!sql |
| 356 | create or replace procedure zapisi_kazna( |
| 357 | i_kz_iznos float, |
| 358 | i_kz_plateno boolean, |
| 359 | i_kz_datum timestamp, |
| 360 | i_kz_datum_plateno timestamp default null, |
| 361 | i_kz_dokument varchar(500), |
| 362 | i_konduktor_k_id bigint, |
| 363 | i_kontrola_id bigint, |
| 364 | kz_tip varchar(500), |
| 365 | i_patnik_k_id bigint default null, |
| 366 | i_kzn_telefon varchar(500) default null, |
| 367 | i_kzn_ime varchar(4000) default null, |
| 368 | i_kzn_adresa varchar(4000) default null |
| 369 | ) |
| 370 | language plpgsql |
| 371 | as $$ |
| 372 | declare |
| 373 | nova_kz_id bigint; |
| 374 | begin |
| 375 | if i_kz_iznos is null or i_kz_iznos <= 0 then |
| 376 | raise exception 'износот на казната мора да биде позитивен'; |
| 377 | end if; |
| 378 | |
| 379 | if i_kz_datum is null then |
| 380 | raise exception 'датумот на казната е задолжителен'; |
| 381 | end if; |
| 382 | |
| 383 | if i_kz_dokument is null or trim(i_kz_dokument) = '' then |
| 384 | raise exception 'документот за казна е задолжителен'; |
| 385 | end if; |
| 386 | |
| 387 | if i_konduktor_k_id is null or not exists ( |
| 388 | select 1 |
| 389 | from konduktor |
| 390 | where k_id = i_konduktor_k_id |
| 391 | ) then |
| 392 | raise exception 'невалиден кондуктор'; |
| 393 | end if; |
| 394 | |
| 395 | if i_kontrola_id is null or not exists ( |
| 396 | select 1 |
| 397 | from kontrola |
| 398 | where kontrola_id = i_kontrola_id |
| 399 | ) then |
| 400 | raise exception 'невалидна контрола'; |
| 401 | end if; |
| 402 | |
| 403 | insert into kazna( |
| 404 | kz_iznos, kz_plateno, kz_datum, kz_datum_plateno, kz_dokument, konduktor_k_id, kontrola_id |
| 405 | ) values ( |
| 406 | i_kz_iznos, i_kz_plateno, i_kz_datum, i_kz_datum_plateno, i_kz_dokument, i_konduktor_k_id, i_kontrola_id |
| 407 | ) returning kz_id into nova_kz_id; |
| 408 | |
| 409 | if kz_tip = 'registriran' then |
| 410 | if i_patnik_k_id is null or not exists ( |
| 411 | select 1 |
| 412 | from patnik |
| 413 | where k_id = i_patnik_k_id |
| 414 | ) then |
| 415 | raise exception 'грешка: не постои како регистриран патник'; |
| 416 | end if; |
| 417 | insert into kaznazaregistriran(kz_id, patnik_k_id) |
| 418 | values (nova_kz_id, i_patnik_k_id); |
| 419 | elsif kz_tip = 'neregistriran' then |
| 420 | if i_kzn_ime is null or trim(i_kzn_ime) = '' then |
| 421 | raise exception 'име е задолжително за нерегистриран патник'; |
| 422 | end if; |
| 423 | if i_kzn_adresa is null or trim(i_kzn_adresa) = '' then |
| 424 | raise exception 'адреса е задолжителна за нерегистриран патник'; |
| 425 | end if; |
| 426 | if i_kzn_telefon is null or trim(i_kzn_telefon) = '' then |
| 427 | raise exception 'телефонот е задолжителен за нерегистриран патник'; |
| 428 | end if; |
| 429 | if i_kzn_telefon !~ '^[0-9]{9,12}$' then |
| 430 | raise exception 'невалиден формат на телефонски број'; |
| 431 | end if; |
| 432 | insert into kaznazaneregistriran( |
| 433 | kz_id, kzn_telefon, kzn_ime, kzn_adresa |
| 434 | ) values ( |
| 435 | nova_kz_id, i_kzn_telefon, i_kzn_ime, i_kzn_adresa |
| 436 | ); |
| 437 | else |
| 438 | raise exception 'патникот може да е само регистриран или нерегистриран: %', kz_tip; |
| 439 | end if; |
| 440 | exception |
| 441 | when others then |
| 442 | raise exception 'грешка при запишување казна: %', sqlerrm; |
| 443 | end; |
| 444 | $$; |
| 445 | }}} |
| 446 | }}} |