Uvod u relacione baze podataka¶
prof. dr Saša Malkov
Univerzitet u Beogradu - Matematički fakultet
2023/24
Čas 3 - Uvod u SQL¶
Na početku moramo da podesimo okruženje i povežemo se sa bazom podataka:
%run db2-jupyter-master/db2.ipynb
%sql connect to stud2020 user smalkov using ?
%sql set schema da
Db2 Extensions Loaded.
Connection successful. Command completed.
Podupiti¶
Podupit je svaki izraz koji predstavlja upit, a koristi se kao manji deo kompletnog upita. U SQL-u postoje tri vrste podupita prema složenosti rezultata koji izračunavaju:
- tabelarni podupit je najopštiji, to je svaki podupit koji izračunava neku tabelu;
- vektorski podupit je spec. slučaj tabelarnog, koji izračunava tačno jednu kolonu i
- skalarni podupit je spec. slučaj vektorskog, izračunava tačno jednu vrednost (t.j. tabelu sa jednom kolonom i jednim redom).
Podupiti su delimično obrađeni na prethodnom času. Ponovićemo primere vektorskih podupita i nastaviti dalje sa tabelarnim podupitima.
U narednom primeru ćemo da izdvojimo osnovne podatke (indeks, ime i prezime) studenata koji su položili ispit iz predmeta 2348. Podupitom izračunavamo skup brojeva indeksa studenata koji su položili ispit iz tog predmeta. Zatim izdvajamo podatke o svim studentima čiji broj indeksa je u tom skupu:
%%sql
select indeks, ime, prezime
from dosije
where indeks in (
-- podupitom računamo spisak brojeva indeksa studenata koji su položili ispit
select indeks
from ispit
where idpredmeta = 2348
and status = 'o'
and ocena > 5
)
INDEKS | IME | PREZIME | |
---|---|---|---|
0 | 20150112 | Slobodan | Mijatovic |
1 | 20150128 | David | Jovovic |
2 | 20180038 | Bogdan | Lipovac |
3 | 20150075 | Mirko | Radojkovic |
4 | 20170205 | Nikola | Erakovic |
U narednom primeru ćemo da izdvojimo podatke o položenim ispitima studenta 20190087, takvih da iz njih nema najmanju ocenu. Za svaki konkretan ispit koji je taj student polagao, proveravamo koje su različite ocene dobijene, da bismo zatim proverili da li je ocena koju je dobio taj student veća od bar jedne od tih ocena:
%%sql
select *
from ispit i
where indeks = 20190087
and ocena > any (
select ocena
from ispit i2
where idpredmeta = i.idpredmeta
and ocena > 5
)
SKGODINA | OZNAKAROKA | INDEKS | IDPREDMETA | STATUS | DATPOLAGANJA | POENI | OCENA | |
---|---|---|---|---|---|---|---|---|
0 | 2019 | jan1 | 20190087 | 2174 | o | 2020-01-14 | 88 | 9 |
1 | 2019 | jan2 | 20190087 | 1578 | o | 2020-01-31 | 72 | 8 |
2 | 2019 | jun1 | 20190087 | 2176 | o | 2020-06-24 | 89 | 9 |
Sada ćemo tražiti obrnuto, one ispite iz kojih student 20190087 ima najmanju ocenu. Ponovo ćemo za svaki konkretan ispit koji je taj student polagao podupitom izdvojiti sve različite ocene, ali ćemo zatim proveravati da li je ocena koju je dobio taj student manja ili jednaka od svih tih ocena:
%%sql
select *
from ispit i
where indeks = 20190087
and ocena >5
and ocena <= all (
select ocena
from ispit i2
where idpredmeta = i.idpredmeta
and ocena >5
)
SKGODINA | OZNAKAROKA | INDEKS | IDPREDMETA | STATUS | DATPOLAGANJA | POENI | OCENA | |
---|---|---|---|---|---|---|---|---|
0 | 2019 | jan1 | 20190087 | 2170 | o | 2020-01-12 | 55 | 6 |
1 | 2019 | jun2 | 20190087 | 1590 | o | 2020-07-20 | 52 | 6 |
2 | 2019 | sep1 | 20190087 | 1580 | o | 2020-09-09 | 51 | 6 |
3 | 2019 | sep3 | 20190087 | 2171 | o | 2020-09-29 | 55 | 6 |
4 | 2019 | sep3 | 20190087 | 2172 | o | 2020-09-27 | 55 | 6 |
Korelisani i nekorelisani podupiti¶
Podupit je korelisan ako ne može da stoji kao samostalan upit, t.j. ako se u telu podupita na neki način referišu elementi šireg upita. Nasuprot tome, nekorelisan je ako može da stoji kao samostalan upit.
Na primer, pogledajmo sledeći nekorelisani podupit:
%%sql
-- imena studenata koji su polozili ispit iz predmeta 2348
select ime, prezime
from dosije
where indeks in (
select indeks
from ispit
where idpredmeta = 2348
and status = 'o'
and ocena > 5
)
IME | PREZIME | |
---|---|---|
0 | Slobodan | Mijatovic |
1 | David | Jovovic |
2 | Bogdan | Lipovac |
3 | Mirko | Radojkovic |
4 | Nikola | Erakovic |
Semantiku prethodnog upita možemo da opišemo kao da se prvo izračunava nekorelisani podupit, koji izdvaja sve brojeve indeksa studenata koji su položili ispit, a da zatim izdvajamo imena onih studenata čiji je indeks u tom skupu.
Isti rezultat se može dobiti ako se stvari postave na molo drugačiji način - umesto da prvo potražimo skup indeksa studenata koji su položili traženi predmet, možemo da redom za svakog studenta proveravamo da li je traženi predmet u skupu onih predmeta koje je položio:
%%sql
-- imena studenata koji su polozili ispit iz predmeta 2348
select ime, prezime
from dosije
where 2348 in (
-- skup predmeta koje je položio student dosije.indeks
select idpredmeta
from ispit
where indeks = dosije.indeks
and status = 'o'
and ocena > 5
)
IME | PREZIME | |
---|---|---|
0 | Slobodan | Mijatovic |
1 | David | Jovovic |
2 | Bogdan | Lipovac |
3 | Mirko | Radojkovic |
4 | Nikola | Erakovic |
Konceptualno posmatrano:
- nekorelisani upit se izračunava jedanput i zatim se njegov rezultat upotrebljava više puta;
- korelisani upit se za svakog studenta izračunava ponovo i njegov rezultat se upotrebljava po jedanput.
Tako posmatrano, nekorelisani upiti su potencijalno efikasniji od korelisanih. U praksi će optimizator upita često ovakve upite uspešno svoditi na isti oblik, pa neće dolaziti do razlike u efikasnosti, ali u nekim slučajevima to neće uspeti pa bi razlike u efikasnosti mogle da budu značajne. Zbog toga je dobro da koristimo nekorelisane upite kada god je to moguće.
Tabelarni podupiti¶
Tabelarni podupiti se koriste sa operatorom EXISTS
ili kao deo klauzule FROM
ili složenog upita (naredba WITH
).
Predikat EXISTS
¶
Operator EXISTS( <tab> )
proverava da li je dati skup neprazan. Slično tome, operator NOT EXISTS( <tab> )
proverava da li je dati skup prazan. Skupovi se po pravilu deifinišu kao podupiti i to kao korelisani podupiti, zato što je skup čija se (ne)praznost proverava obično nekako povezan sa kontekstom u kome se navodi.
U narednom primeru upita tražimo studente koji su upisani 2019. godine i koji su položili bar jedan predmet:
%%sql
-- studenti upisani 2019. koji su položili bar jedan predmet
select * from dosije
-- biramo one studente koji su upisani 2019 godine i za koje...
where indeks / 10000 = 2019
-- postoji makar jedan predmet koji je položen
and exists (
select * from ispit
where indeks = dosije.indeks
and status = 'o'
and ocena > 5
)
INDEKS | IDPROGRAMA | IME | PREZIME | POL | MESTORODJENJA | IDSTATUSA | DATUPISA | DATDIPLOMIRANJA | |
---|---|---|---|---|---|---|---|---|---|
0 | 20191097 | 201 | Stefan | Mihajlovic | m | Beograd (Savski venac) | 1 | 2019-10-11 | <NA> |
1 | 20190178 | 101 | Jelena | Vukovic | z | Beograd (Savski venac) | 1 | 2019-07-01 | <NA> |
2 | 20190371 | 101 | Dimitrije | Vukadinovic | m | Loznica | 1 | 2019-07-01 | <NA> |
3 | 20190075 | 104 | Nikolina | Miljojkovic | z | Beograd (Savski venac) | 1 | 2019-07-01 | <NA> |
4 | 20190211 | 103 | Vukasin | Kalinic | m | Sabac | 1 | 2019-07-01 | <NA> |
... | ... | ... | ... | ... | ... | ... | ... | ... | ... |
474 | 20190108 | 101 | Kristina | Pavlovic | z | Krusevac | 1 | 2019-07-02 | <NA> |
475 | 20190202 | 104 | Milica | Brkic | z | Beograd | 2 | 2019-07-02 | <NA> |
476 | 20191105 | 201 | Mladen | Zezelj | m | Beograd (Savski venac) | 1 | 2019-10-11 | <NA> |
477 | 20190011 | 101 | Ana | Ilic | z | Vrsac | 1 | 2019-07-01 | <NA> |
478 | 20190254 | 101 | Tamara | Zejak | z | Beograd (Savski venac) | 1 | 2019-07-02 | <NA> |
479 rows × 9 columns
Primetimo da sličan rezultat (nije obavezno isti, više o tome uskoro) možemo da dobijemo i primenom unutrašnjeg spajanja. Pri tome moramo da koristimo DISTINCT
, da se ne bi ponovili studenti koji su položili više ispita:
%%sql
-- studenti upisani 2019. koji su položili bar jedan predmet
select distinct dosije.*
from dosije join ispit
on dosije.indeks = ispit.indeks
and status = 'o'
and ocena > 5
where dosije.indeks / 10000 = 2019
INDEKS | IDPROGRAMA | IME | PREZIME | POL | MESTORODJENJA | IDSTATUSA | DATUPISA | DATDIPLOMIRANJA | |
---|---|---|---|---|---|---|---|---|---|
0 | 20191097 | 201 | Stefan | Mihajlovic | m | Beograd (Savski venac) | 1 | 2019-10-11 | <NA> |
1 | 20190178 | 101 | Jelena | Vukovic | z | Beograd (Savski venac) | 1 | 2019-07-01 | <NA> |
2 | 20190371 | 101 | Dimitrije | Vukadinovic | m | Loznica | 1 | 2019-07-01 | <NA> |
3 | 20190075 | 104 | Nikolina | Miljojkovic | z | Beograd (Savski venac) | 1 | 2019-07-01 | <NA> |
4 | 20190211 | 103 | Vukasin | Kalinic | m | Sabac | 1 | 2019-07-01 | <NA> |
... | ... | ... | ... | ... | ... | ... | ... | ... | ... |
474 | 20190108 | 101 | Kristina | Pavlovic | z | Krusevac | 1 | 2019-07-02 | <NA> |
475 | 20190202 | 104 | Milica | Brkic | z | Beograd | 2 | 2019-07-02 | <NA> |
476 | 20191105 | 201 | Mladen | Zezelj | m | Beograd (Savski venac) | 1 | 2019-10-11 | <NA> |
477 | 20190011 | 101 | Ana | Ilic | z | Vrsac | 1 | 2019-07-01 | <NA> |
478 | 20190254 | 101 | Tamara | Zejak | z | Beograd (Savski venac) | 1 | 2019-07-02 | <NA> |
479 rows × 9 columns
Iako na prethodna dva upita daju isti rezultat, moramo da primetimo da primena operatora EXISTS
ima za rezultat mnogo jasniji upit. Kod spajanja često nije očigledno šta je razlog spajanja dveju tabela, već moramo da detaljno analiziramo uslove restrikcije, dok je u slučaju operatora EXISTS
jasno da želimo samo da proverimo postojanje odgovarajućih podataka.
Dodatni problem je eliminacija ponavljanja, koja može da napravi razliku u upitu. U našem slučaju upit sa spajanjem i DISTINCT
radi odlično, ali samo zato što izabrane kolone garantuju jedinstvenost rezultata u prvom primeru. Kad bismo u oba slučaja birali samo kolone dosije.ime
i dosije.prezime
, onda rezultat možda ne bi bio isti:
- prvi upit, sa
EXISTS
, možda bi izdvojio ponovljene podatke, ako imamo više studenata sa istim imenom i prezimenom, a - drugi upit, sa spajanjem i
DISTINCT
bi izdvojio samo jedinstvena imena i prezimena među pronađenim studentima.
Predikat NOT EXISTS
¶
Iako može da izgleda da se operatori EXISTS
i NOT EXISTS
vrlo malo razlikuju (kao što se malo razlikuju, na primer, predikati IN
i NOT IN
), razlika zaslužuje malo više pažnje. Semantički posmatrano, razlika je zaista lako razumljiva, zato što ćemo NOT EXISTS
da koristimo onda kada želimo da proverimo da ne postoji odgovarajući podatak. Na primer, ako nasuprot prethodnom primeru želimo da izdvojimo podatke o studentima upisanim 2019. godine, ali koji nisu položili nijedan ispit, onda ćemo samo da dodamo jedno NOT:
%%sql
-- studenti upisani 2019. koji nisu položili nijedan predmet
select * from dosije
where indeks / 10000 = 2019
and not exists (
select * from ispit
where indeks = dosije.indeks
and status = 'o'
and ocena > 5
)
INDEKS | IDPROGRAMA | IME | PREZIME | POL | MESTORODJENJA | IDSTATUSA | DATUPISA | DATDIPLOMIRANJA | |
---|---|---|---|---|---|---|---|---|---|
0 | 20190330 | 101 | Sara | Zakic | z | Smederevo | -1 | 2019-07-01 | <NA> |
1 | 20190440 | 103 | Viseslav | Cupovic | m | Beograd (Savski venac) | 1 | 2019-07-01 | <NA> |
2 | 20190104 | 103 | Milan | Joksimovic | m | Cacak | 1 | 2019-07-01 | <NA> |
3 | 20190160 | 101 | Veljko | Jelic | m | Beograd (Savski venac) | 1 | 2019-07-01 | <NA> |
4 | 20190072 | 103 | Aleksa | Mrvos | m | Smederevo | 1 | 2019-07-01 | <NA> |
... | ... | ... | ... | ... | ... | ... | ... | ... | ... |
112 | 20190158 | 101 | Teodora | Aleksic | z | Krusevac | 1 | 2019-07-01 | <NA> |
113 | 20190190 | 101 | Pavle | Vidakovic | m | Podgorica | 1 | 2019-07-02 | <NA> |
114 | 20190446 | 101 | Aleksa | Parandilovic | m | Vranje | -1 | 2019-07-04 | <NA> |
115 | 20191066 | 203 | Adil | Brocic | m | Beograd (Savski venac) | -7 | 2019-11-05 | <NA> |
116 | 20190263 | 103 | Uros | Savic | m | Valjevo | 2 | 2019-07-02 | <NA> |
117 rows × 9 columns
Izračunavanje ovog predikata ne može da se zameni spajanjem. Umesto toga, kao što smo ranije već bili najavili, predikat NOT EXISTS
je ekvivalentan "spajanju razlike".
U narednom primeru ćemo birati samo one predmete za koje ne postoji položen ispit. To je ekvivalentno ranije rađenom primeru u kome smo koristili levo spoljašnje spajanje predmeta i ispita a onda napravili restrikciju kojom smo izdvojili samo one predmete koji nisu spojeni ni sa jednim ispitom:
%%sql
-- za studenta 20180054 izdvojiti podatke o nepoloženim obaveznim predmetima
select
d.indeks, d.ime, d.prezime,
p.naziv
from dosije d
join predmetprograma pp
using (idprograma)
join predmet p
on p.id = pp.idpredmeta
where d.indeks = 20180054
and pp.vrsta = 'obavezan'
and not exists (
select * from ispit i
where i.indeks = d.indeks
and i.idpredmeta = p.id
and i.status = 'o'
and i.ocena > 5
)
INDEKS | IME | PREZIME | NAZIV | |
---|---|---|---|---|
0 | 20180054 | Milos | Djurisic | Relacione baze podataka |
1 | 20180054 | Milos | Djurisic | Prevodjenje programskih jezika |
2 | 20180054 | Milos | Djurisic | Vestacka inteligencija |
3 | 20180054 | Milos | Djurisic | Uvod u numericku matematiku |
4 | 20180054 | Milos | Djurisic | Programske paradigme |
... | ... | ... | ... | ... |
7 | 20180054 | Milos | Djurisic | Verovatnoca |
8 | 20180054 | Milos | Djurisic | Istrazivanje podataka 1 |
9 | 20180054 | Milos | Djurisic | Statistika |
10 | 20180054 | Milos | Djurisic | Racunarske mreze |
11 | 20180054 | Milos | Djurisic | Projektovanje baza podataka |
12 rows × 4 columns
Sada ćemo isto da uradimo pomoću spajanja razlike:
%%sql
-- za studenta 20180054 izdvojiti podatke o nepoloženim obaveznim predmetima
select
d.indeks, d.ime, d.prezime,
p.naziv
from dosije d
join predmetprograma pp
using (idprograma)
join predmet p
on p.id = pp.idpredmeta
left outer join ispit i
on i.indeks = d.indeks
and i.idpredmeta = p.id
and i.status = 'o'
and i.ocena > 5
where d.indeks = 20180054
and pp.vrsta = 'obavezan'
and i.indeks is null
INDEKS | IME | PREZIME | NAZIV | |
---|---|---|---|---|
0 | 20180054 | Milos | Djurisic | Relacione baze podataka |
1 | 20180054 | Milos | Djurisic | Prevodjenje programskih jezika |
2 | 20180054 | Milos | Djurisic | Vestacka inteligencija |
3 | 20180054 | Milos | Djurisic | Uvod u numericku matematiku |
4 | 20180054 | Milos | Djurisic | Programske paradigme |
... | ... | ... | ... | ... |
7 | 20180054 | Milos | Djurisic | Verovatnoca |
8 | 20180054 | Milos | Djurisic | Istrazivanje podataka 1 |
9 | 20180054 | Milos | Djurisic | Statistika |
10 | 20180054 | Milos | Djurisic | Racunarske mreze |
11 | 20180054 | Milos | Djurisic | Projektovanje baza podataka |
12 rows × 4 columns
Tabelarni podupiti u klauzuli FROM
¶
Drugi oblik u kome se često koriste tabelarni upiti jeste navođenje podupita umesto tabele u okviru klauzule FROM
. Koriste se tako što se upiti navode između zagrada i zatim se obavezno uvodi njihovo ime: (<podupit>) [AS] <ime>
:
%%sql -a
-- izdvojiti za dva studenta 20170054, 20170059 uporedne rezultate na svim polozenim predmetima
select
p.naziv,
prvi.indeks, prvi.poeni, prvi.ocena,
drugi.indeks, drugi.poeni, drugi.ocena
from (
select *
from ispit i
where i.indeks = '20170054'
and status = 'o'
and ocena > 5
) as prvi
full outer join (
select *
from ispit i
where i.indeks = '20170059'
and status = 'o'
and ocena > 5
) as drugi
on prvi.idpredmeta = drugi.idpredmeta
join predmet p
on p.id = coalesce(prvi.idpredmeta,drugi.idpredmeta)
NAZIV | INDEKS | POENI | OCENA | INDEKS | POENI | OCENA | |
---|---|---|---|---|---|---|---|
0 | Strani jezik | 20170054 | 89 | 9 | 20170059 | 100 | 10 |
1 | Programiranje 1 | 20170054 | 84 | 9 | 20170059 | 61 | 7 |
2 | Linearna algebra | 20170054 | 85 | 9 | 20170059 | 85 | 9 |
3 | Uvod u matematicku logiku | 20170054 | 82 | 9 | 20170059 | 95 | 10 |
4 | Geometrija 1 | 20170054 | 83 | 9 | 20170059 | 88 | 9 |
5 | Programiranje 2 | 20170054 | 95 | 10 | 20170059 | 78 | 8 |
6 | Analiza 1 | 20170054 | 65 | 7 | 20170059 | 75 | 8 |
7 | Diskretna matematika | <NA> | <NA> | <NA> | 20170059 | 95 | 10 |
8 | Geometrija 2 | <NA> | <NA> | <NA> | 20170059 | 91 | 10 |
9 | Algebra 1 | <NA> | <NA> | <NA> | 20170059 | 91 | 10 |
10 | Programski paketi u matematici | <NA> | <NA> | <NA> | 20170059 | 95 | 10 |
11 | Uvod u organizaciju i arhitekturu racunara 1 | <NA> | <NA> | <NA> | 20170059 | 81 | 9 |
12 | Uvod u numericku matematiku | <NA> | <NA> | <NA> | 20170059 | 96 | 10 |
13 | Objektno-orijentisano programiranje | 20170054 | 78 | 8 | 20170059 | 93 | 10 |
14 | Geometrija 3 | <NA> | <NA> | <NA> | 20170059 | 86 | 9 |
15 | Analiza 2 | <NA> | <NA> | <NA> | 20170059 | 61 | 7 |
16 | Uvod u veb i internet tehnologije | 20170054 | 83 | 9 | <NA> | <NA> | <NA> |
17 | Uvod u organizaciju i arhitekturu racunara 2 | 20170054 | 92 | 10 | <NA> | <NA> | <NA> |
18 | Algebra 1 | 20170054 | 61 | 7 | <NA> | <NA> | <NA> |
19 | Diskretne strukture 2 | 20170054 | 76 | 8 | <NA> | <NA> | <NA> |
20 | Uvod u organizaciju i arhitekturu racunara 1 | 20170054 | 85 | 9 | <NA> | <NA> | <NA> |
Ovakva primena je veoma značajna, ali može da bude vrlo nepregledna, pa zato umesto toga obično koristimo upitnu naredbu WITH
, koju ćemo usoro da upoznamo.
Univerzalni kvantifikator¶
U SQL-u ne postoji ekvivalent univerzalnog kvantifikatora. Zbog toga se slučajevi u kojima je on potreban moraju rešavati primenom ekvivalenta egzistencijalnog kvantifikatora, tj. predikata EXISTS
.
Na primer, ako želimo da izdvojimo podatke o studentima koji su položili sve obavezne predmete, u odsustvu univerzalnog kvantifikatora moraćemo da uslov transformišemo tako da koristi egzitencijalne: "položio sve predmete" = "ne postoji predmet koji nije položio" = "ne postoji predmet za koji ne postoji podatak da je položen":
%%sql
select *
from dosije d
where not exists (
select *
from predmetprograma pp
where pp.idprograma = d.idprograma
and pp.vrsta = 'obavezan'
and not exists (
select * from ispit i
where i.indeks = d.indeks
and i.idpredmeta = pp.idpredmeta
and i.status = 'o'
and i.ocena > 5
)
)
INDEKS | IDPROGRAMA | IME | PREZIME | POL | MESTORODJENJA | IDSTATUSA | DATUPISA | DATDIPLOMIRANJA | |
---|---|---|---|---|---|---|---|---|---|
0 | 20150090 | 103 | Andrijana | Beara | z | Sabac | -2 | 2015-07-06 | 2019-09-06 |
1 | 20150154 | 103 | Zarija | Milanovic | m | Beograd (Savski venac) | -2 | 2015-07-06 | 2019-09-28 |
2 | 20150300 | 103 | Filip | Selakovic | m | Beograd (Savski venac) | 1 | 2015-07-06 | <NA> |
3 | 20150030 | 103 | Mladen | Panic | m | Pancevo | -2 | 2015-07-06 | 2019-07-10 |
4 | 20150011 | 103 | Ana | Pantovic | z | Smederevo | -2 | 2015-07-06 | 2019-07-17 |
... | ... | ... | ... | ... | ... | ... | ... | ... | ... |
98 | 20161071 | 202 | Petar | Perin | m | Beograd (Savski venac) | -2 | 2016-10-12 | 2018-09-28 |
99 | 20171042 | 202 | Pavle | Miloradovic | m | Smederevo | -2 | 2017-10-11 | 2019-09-30 |
100 | 20160191 | 103 | Lidija | Blagojevic | z | Lazarevac | -2 | 2016-07-06 | 2020-09-15 |
101 | 20160097 | 103 | Ana | Stevanovic | z | Ruma | -2 | 2016-07-07 | 2020-09-29 |
102 | 20161013 | 202 | Milica | Kostic | z | Krusevac | 2 | 2016-10-12 | <NA> |
103 rows × 9 columns
Kolonske funkcije¶
Za razliku od običnih skalarnih funkcija, koje se izračunavaju nad datim skalarnim vrednostima, kolonske funkcije (ili tzv. agregatne funkcije) su funkcije koje se izračunavaju nad svim ili izabranim vrednostima jedne kolone (ili više kolona) koje se izdvoje upitom. Argument kolonske funkcije može da bude i izraz, u kom slučaju se za svaki pročitani red izračunava izraz i onda se nad tako dobijenim vrednostima izraza izračunava kolonska funkcija.
Opšti oblik upotrebe kolonskih funkcija je: <funkcija>([DISTINCT] <izraz> [,<izraz>]* )
.
Kolonska funkcija COUNT
¶
Služi za brojanje elemenata skupa, odnosno za brojanje redova u rezultatu upita. Zbog toga što za brojanje često nije značajno koja kolona se posmatra, umesto izraza ili naziva kolone može da se navede i *
:
COUNT(*)
- broji koliko redova ima rezultat upita;COUNT(<izraz>)
- broji u koliko redova dati izraz ima definisanu vrednost;COUNT(DISTINCT <izraz>)
- broji koliko različitih definisanih vrednosti ima dati izraz.
Kolonska funkcija COUNT
izračunava vrednost 32-bitnog tipa. Ako ima više od 2 milijarde redova, onda može da se koristi 64-bitna funkcija COUNT_BIG
.
%%sql
-- Ukupan broj evidentiranih prijavljenih ispita.
select count(*)
from ispit
1 | |
---|---|
0 | 119143 |
%%sql
-- Ukupan broj ispita sa upisanom ocenom, tj. sa ocenom koja nije `NULL`.
select count(ocena)
from ispit
1 | |
---|---|
0 | 52874 |
%%sql
-- Ukupan broj ispita sa upisanom ocenom, tj. sa ocenom koja je bar 5.
-- Efektivno isto kao prethodni upit.
select count(*)
from ispit
where ocena >= 5
1 | |
---|---|
0 | 52874 |
%%sql
-- Ukupan broj različitih ocena.
select count(distinct ocena)
from ispit
1 | |
---|---|
0 | 6 |
Kolonske funkcije MIN
i MAX
¶
Kolonske funkcije MIN
i MAX
izračunavaju najmanju i najveću definisanu vrednost izraza. Ključna reč DISTINCT
nema uticaja na rezultat.
%%sql
select min(datpolaganja), max(datpolaganja)
from ispit
1 | 2 | |
---|---|---|
0 | 2016-01-15 | 2020-09-30 |
%%sql
select min(ocena), max(ocena)
from ispit
1 | 2 | |
---|---|---|
0 | 5 | 10 |
Kolonske funkcije SUM
, AVG
i MEDIAN
¶
Kolonske funkcije SUM
, AVG
i MEDIAN
izračunavaju redom, sumu, srednju vrednost i medijanu definisanih vrednosti. Ključna reč DISTINCT
utiče na rezultat funkcija SUM
i AVG
ali ne utiče na rezultat funkcije MEDIAN
.
%%sql
select
sum( ocena ) s,
sum( distinct ocena ) sd,
avg (ocena + 0.0 ) a,
avg( distinct ocena + 0.0 ) ad,
median( ocena ) m,
median( distinct ocena ) md,
count( ocena ) c,
count( distinct ocena ) cd
from ispit
where status = 'o'
and ocena > 5
S | SD | A | AD | M | MD | C | CD | |
---|---|---|---|---|---|---|---|---|
0 | 263440 | 40 | 8.026079 | 8.0 | 8.0 | 8.0 | 32823 | 5 |
Kolonske funkcije na više skupova redova¶
Ako želimo da jednim upitom prebrojimo podatke po više različitih kriterijuma, onda možemo da napravimo preslikavanje odgovarajućih uslova u ne-NULL
vrednosti a neispunjenih uslova u NULL
vrednosti:
%%sql
-- koliko je kojih ocena dobijeno na ispitima
select
count(ocena) as ocene,
count(case when ocena=5 then 1 else null end) as ocena_5,
count(case when ocena=6 then 1 else null end) as ocena_6,
count(case when ocena=7 then 1 else null end) as ocena_7,
count(case when ocena=8 then 1 else null end) as ocena_8,
count(case when ocena=9 then 1 else null end) as ocena_9,
count(case when ocena=10 then 1 else null end) as ocena_10
from ispit
where status = 'o'
OCENE | OCENA_5 | OCENA_6 | OCENA_7 | OCENA_8 | OCENA_9 | OCENA_10 | |
---|---|---|---|---|---|---|---|
0 | 51227 | 18404 | 6608 | 6638 | 6268 | 5908 | 7401 |
Alternativa je da preslikavamo tačne uslove u 1 a netačne u 0 i da primenimo funkciju SUM
:
%%sql
-- koliko je kojih ocena dobijeno na ispitima
select
count(ocena) as ocene,
sum(case when ocena=5 then 1 else 0 end) as ocena_5,
sum(case when ocena=6 then 1 else 0 end) as ocena_6,
sum(case when ocena=7 then 1 else 0 end) as ocena_7,
sum(case when ocena=8 then 1 else 0 end) as ocena_8,
sum(case when ocena=9 then 1 else 0 end) as ocena_9,
sum(case when ocena=10 then 1 else 0 end) as ocena_10
from ispit
where status = 'o'
OCENE | OCENA_5 | OCENA_6 | OCENA_7 | OCENA_8 | OCENA_9 | OCENA_10 | |
---|---|---|---|---|---|---|---|
0 | 51227 | 18404 | 6608 | 6638 | 6268 | 5908 | 7401 |
Na sličan način pomoću funkcije AVG
možemo da izračunamo zastupljenost nekog slučaja u celom skupu:
%%sql
-- zastupljenost svake od ocena
select
avg(case when ocena=5 then 1. else 0 end) * 100 as procenat_5,
avg(case when ocena=6 then 1. else 0 end) * 100 as procenat_6,
avg(case when ocena=7 then 1. else 0 end) * 100 as procenat_7,
avg(case when ocena=8 then 1. else 0 end) * 100 as procenat_8,
avg(case when ocena=9 then 1. else 0 end) * 100 as procenat_9,
avg(case when ocena=10 then 1. else 0 end) * 100 as procenat_10
from ispit
where status = 'o'
PROCENAT_5 | PROCENAT_6 | PROCENAT_7 | PROCENAT_8 | PROCENAT_9 | PROCENAT_10 | |
---|---|---|---|---|---|---|
0 | 35.926367 | 12.899448 | 12.95801 | 12.235735 | 11.532981 | 14.447459 |
Druge kolonske funkcije¶
Ima još mnogo kolonskih funkcija:
- za statistička izračunavanja:
STDDEV
,VARIANCE
,CORRELATON
,STDDEV_SAMP
,VARIANCE_SAMP
; - regresione funkcije, koje izračunavaju različite parametre regresione funkcije;
- i druge.
%%sql
select listagg( naziv, ', ')
from da.nivokvalifikacije
1 | |
---|---|
0 | Osnovne akademske studije, Doktorske akademske... |
print(_['1'][0])
Osnovne akademske studije, Doktorske akademske studije, Master akademske studije
Grupisanje redova¶
Veoma često je potrebno da se neka kolonska funkcija primeni više puta, za različite izabrane skupove redova. Na primer, ako hoćemo da izbrojimo koliko je često neko ime u bazi podataka, mogli bismo da izvršimo upit poput:
select count(*) from dosije
where ime='...'
To bismo mogli da ponovimo za svako ime koje nas zanima, ali to je dosadan i zamoran posao. Bilo bi lakše kada bi u upitu moglo da se napiše da je kolonsku funkciju potrebno izvršiti posebno na svakom od skupova redova koji se dobijaju grupisanjem po nekom kriterijumu. Na primer, u prethodnom slučaju, kriterijum grupisanja je vrednost kolone Ime.
Upravo tome služi klauzula GROUP BY
:
GROUP BY <exp> [, <exp>]*
Na primer, u našem slučaju je potrebno da se grupiše po imenu:
GROUP BY IME
Zbog toga što će se rezultat izračunavati posebno za svaku grupu, u okviru klauzule SELECT
mogu da se navode samo izrazi koji su konstantni za sve rednove unutar jedne grupe. Drugim rečima, u okviru klauzule SELECT
mogu da se pojavljuju na proizvoljan način samo one kolone (ili izrazi) koje su navedene u klauzuli grupisanja. Sve ostale kolone mogu da se navode isključivo kao argumenti kolonskih funkcija.
%%sql
-- imena i broj ponavljanja
select ime, count(*) as n
from dosije
group by ime
order by n desc
IME | N | |
---|---|---|
0 | Nikola | 155 |
1 | Milica | 99 |
2 | Jelena | 90 |
3 | Aleksandar | 81 |
4 | Marija | 81 |
... | ... | ... |
445 | Zeljana | 1 |
446 | Zlata | 1 |
447 | Zona | 1 |
448 | Zorka | 1 |
449 | Zudana | 1 |
450 rows × 2 columns
Naredni upit izračunava broj polaganja ispita i prosečnu ocenu u svakom od održanih ispitnih rokova. Uslov grupisanja je identifikacija ispitnog roka, tj. par atributa skgodina
i oznakaroka
:
%%sql
select skgodina, oznakaroka, avg(ocena+0.0) ocena, count(*) n
from ispit
where status = 'o'
and skgodina between 2016 and 2017
group by skgodina, oznakaroka
SKGODINA | OZNAKAROKA | OCENA | N | |
---|---|---|---|---|
0 | 2016 | jan1 | 7.575136 | 1657 |
1 | 2017 | jan1 | 7.468765 | 2129 |
2 | 2016 | jan2 | 6.810833 | 1200 |
3 | 2017 | jan2 | 6.787234 | 1598 |
4 | 2016 | janps | 8.100000 | 10 |
... | ... | ... | ... | ... |
10 | 2016 | kom | 8.750000 | 12 |
11 | 2016 | sep1 | 6.514360 | 1149 |
12 | 2017 | sep1 | 6.510858 | 1842 |
13 | 2016 | sep2 | 6.803430 | 758 |
14 | 2017 | sep2 | 6.572222 | 1080 |
15 rows × 4 columns
Odabir grupa¶
Nalik na restrikciju redova, često je potrebna i restrikcija grupa. Primetimo da klauzula WHERE
bira redove i to pre grupisanja. Zbog toga, ako pre samog grupisanja izbacimo sve redove koji bi činili neku grupu, time smo izbacili i grupu. Međutim, ako je potrebno da se grupe biraju nakon restrikcije po redovima, t.j. nakon grupisanja i izračunavanja kolonskih funkcija, onda to ne možemo da uradimo pomoću klauzule WHERE
. Tome služi klauzula HAVING
.
U klauzuli HAVING
možemo da navodimo samo uslove restrikcije koji imaju smisla na nivou grupe. Kao i u slučaju klauzule SELECT
, smemo u slobodnom obliku da navodimo samo kolone i izraze po kojima je grupisano, dok ostale kolone možemo da navodimo samo kao argumente kolonskih funkcija. Kao kriterijum restrikcije se mogu navoditi ne samo kolonske funkcije koje se navode i u klauzuli SELECT
več i druge kolonske funkcije.
U narednom upitu izdvajamo imena i njhov broj ponavljanja, ali samo za imena koja se ponavljaju bar 50 puta:
%%sql
select ime, count(*) as n
from dosije
group by ime
having count(*) >= 50
order by n desc
IME | N | |
---|---|---|
0 | Nikola | 155 |
1 | Milica | 99 |
2 | Jelena | 90 |
3 | Aleksandar | 81 |
4 | Marija | 81 |
... | ... | ... |
12 | Aleksandra | 59 |
13 | Jovana | 57 |
14 | Aleksa | 53 |
15 | Djordje | 50 |
16 | Lazar | 50 |
17 rows × 2 columns
Alternativno grupisanje¶
Umesto da se eksplicitno grupišu redovi, sličan efekat može da se ostvari navođenjem uslova particionisanja neposredno iza kolonske funkcije, u obliku:
<fun>(...) OVER ( PARTITION BY <lista kolona> )
Ova tehnika je slična grupisanju, ali ima i nekih bitnih razlika:
- u okviru klauzule
SELECT
mogu da se navedu i kolone ili izrazi koji nisu navedeni u uslovu grupisanja; - upit se ne izračunava po grupama već po redovima, tako da rezultat ima isti broj redova kao odgovarajući upit bez takvih kolonskih funkcija;
- različite kolonske funkcije mogu da koriste različite uslove particionisanja.
%%sql
-- imena i broj ponavljanja
select ime, count(*) over ( partition by ime ) as n
from dosije
order by n desc
IME | N | |
---|---|---|
0 | Nikola | 155 |
1 | Nikola | 155 |
2 | Nikola | 155 |
3 | Nikola | 155 |
4 | Nikola | 155 |
... | ... | ... |
3491 | Zeljana | 1 |
3492 | Zlata | 1 |
3493 | Zona | 1 |
3494 | Zorka | 1 |
3495 | Zudana | 1 |
3496 rows × 2 columns
Ako se ne iskoristi nijedna od navedenih specifičnosti, a navede se i klauzula DISTINCT
, onda je upit ekvivalentan odgovarajućem upitu sa grupisanjem.
%%sql
-- imena i broj ponavljanja
select distinct ime, count(*) over ( partition by ime ) as n
from dosije
order by n desc
IME | N | |
---|---|---|
0 | Nikola | 155 |
1 | Milica | 99 |
2 | Jelena | 90 |
3 | Aleksandar | 81 |
4 | Marija | 81 |
... | ... | ... |
445 | Zeljana | 1 |
446 | Zlata | 1 |
447 | Zona | 1 |
448 | Zorka | 1 |
449 | Zudana | 1 |
450 rows × 2 columns
Naredni upit izdvaja podatke o studentima, uređene po imenima i prezimenima. Za svakog studenta se izdvaja koliko ukupno studenata ima isto ime, koji je indeks tog studenta po redu (uređeno leksikografski) i koje je ime tog studenta po redu (takođe leksikografski):
%%sql
select
ime,
prezime,
count(*) over ( partition by ime ) as broj_imena,
rownumber() over (partition by ime order by indeks) as indeks_rbr,
rank() over (order by ime) as ime_rank
from dosije
order by ime, broj_imena desc
IME | PREZIME | BROJ_IMENA | INDEKS_RBR | IME_RANK | |
---|---|---|---|---|---|
0 | Aco | Krsmanovic | 1 | 1 | 1 |
1 | Ada | Ilic | 1 | 1 | 2 |
2 | Adam | Dimitrijevic | 3 | 1 | 3 |
3 | Adam | Rankovic | 3 | 2 | 3 |
4 | Adam | Kolcic | 3 | 3 | 3 |
... | ... | ... | ... | ... | ... |
3491 | Zorana | Ljujic | 11 | 9 | 3484 |
3492 | Zorana | Karasicevic | 11 | 10 | 3484 |
3493 | Zorana | Kuzmanovic | 11 | 11 | 3484 |
3494 | Zorka | Stamenkovic | 1 | 1 | 3495 |
3495 | Zudana | Vlasic | 1 | 1 | 3496 |
3496 rows × 5 columns
Operatori grupisanja¶
Pored postojećeg osnovnog grupisanja, postoje i napredni oblici grupisanja, koji omogućavaju da se jednim upitom izvrše analize za veliki različit broj grupisanja. Nazivaju se i OLAP grupisanja ili operatori grupisanja. Koriste se u obliku GROUP BY <OLAP grupisanje>
, i postoje u tri oblika:
GROUPING SETS ( <set list> )
ROLLUP ( <list> )
CUBE ( <list> )
GROUPING SETS
¶
U klauzuli GROUPING SETS
se navodi jedan ili više skupova atributa po kojima se grupiše. Rezultat upita će biti unija upita sa pojedinačnim navedenim grupisanjima. Znači, ako se navede 5 skupova atributa, onda će rezultat biti unija 5 upita u kojima se primenjuje odgovarajuće grupisanje. Ako se navede samo jedan skup onda se to ne razlikuje od upita sa uobičajenom klauzulom GROUP BY
u kojoj su navedeni upravo elementi tok skupa.
U klauzuli SELECT
mogu da se nađu sve kolone ili izrazi koji se pojavljuju u bilo kom od skupova po kojima se grupiše. Ako neki izraz ili kolona ne pripadaju konkretnom grupisanju, imaće nedefinisanu vrednost.
Pored kolona, unutar skupova može da se navede i ROLLUP
ili CUBE
.
%%sql
select
skgodina,
oznakaroka,
count(*) brojprijava,
avg(ocena+0.0) prosek
from ispit
group by grouping sets( (skgodina, oznakaroka) )
-- ovaj upit je isti kao da je napisano:
-- group by skgodina, oznakaroka
SKGODINA | OZNAKAROKA | BROJPRIJAVA | PROSEK | |
---|---|---|---|---|
0 | 2015 | jan1 | 1640 | 7.746206 |
1 | 2015 | jan2 | 1182 | 6.741030 |
2 | 2015 | jun1 | 1848 | 7.127389 |
3 | 2015 | jun2 | 1550 | 6.644717 |
4 | 2015 | kom | 88 | 8.409091 |
... | ... | ... | ... | ... |
33 | 2019 | jun2 | 4995 | 6.665320 |
34 | 2019 | sep1 | 5198 | 6.635577 |
35 | 2019 | sep2 | 5808 | 6.422013 |
36 | 2019 | sep3 | 5739 | 6.521135 |
37 | 2019 | sep4 | 435 | 7.447257 |
38 rows × 4 columns
%%sql -a
-- sada grupisemo po tri skupa, treći je skup svih studenata
select
skgodina,
oznakaroka,
count(*) brojprijava,
avg(ocena+0.0) prosek
from ispit
group by grouping sets( (skgodina, oznakaroka), (skgodina), () )
SKGODINA | OZNAKAROKA | BROJPRIJAVA | PROSEK | |
---|---|---|---|---|
0 | <NA> | <NA> | 119143 | 6.882135 |
1 | 2015 | <NA> | 8251 | 7.046416 |
2 | 2016 | <NA> | 16325 | 7.007736 |
3 | 2017 | <NA> | 23383 | 6.875428 |
4 | 2018 | <NA> | 30901 | 6.857216 |
5 | 2019 | <NA> | 40283 | 6.796705 |
6 | 2015 | jan1 | 1640 | 7.746206 |
7 | 2015 | jan2 | 1182 | 6.741030 |
8 | 2015 | jun1 | 1848 | 7.127389 |
9 | 2015 | jun2 | 1550 | 6.644717 |
10 | 2015 | kom | 88 | 8.409091 |
11 | 2015 | sep1 | 1131 | 6.600318 |
12 | 2015 | sep2 | 812 | 6.154303 |
13 | 2016 | jan1 | 2948 | 7.501459 |
14 | 2016 | jan2 | 2795 | 6.781863 |
15 | 2016 | janps | 11 | 8.100000 |
16 | 2016 | jun1 | 3729 | 7.212394 |
17 | 2016 | jun2 | 2825 | 6.777102 |
18 | 2016 | kom | 12 | 8.750000 |
19 | 2016 | sep1 | 2385 | 6.477929 |
20 | 2016 | sep2 | 1620 | 6.798684 |
21 | 2017 | jan1 | 4270 | 7.400091 |
22 | 2017 | jan2 | 3709 | 6.751990 |
23 | 2017 | janps | 12 | 8.666667 |
24 | 2017 | jun1 | 5346 | 7.017414 |
25 | 2017 | jun2 | 3836 | 6.725879 |
26 | 2017 | sep1 | 3700 | 6.487179 |
27 | 2017 | sep2 | 2510 | 6.521505 |
28 | 2018 | jan1 | 5333 | 7.321097 |
29 | 2018 | jan2 | 5154 | 6.701074 |
30 | 2018 | janps | 25 | 7.450000 |
31 | 2018 | jun1 | 7102 | 7.007231 |
32 | 2018 | jun2 | 5014 | 6.736741 |
33 | 2018 | sep1 | 5001 | 6.544355 |
34 | 2018 | sep2 | 3272 | 6.631142 |
35 | 2019 | jan1 | 5623 | 7.322780 |
36 | 2019 | jan2 | 5616 | 6.825956 |
37 | 2019 | janps | 481 | 6.000000 |
38 | 2019 | jun1 | 6388 | 6.902992 |
39 | 2019 | jun2 | 4995 | 6.665320 |
40 | 2019 | sep1 | 5198 | 6.635577 |
41 | 2019 | sep2 | 5808 | 6.422013 |
42 | 2019 | sep3 | 5739 | 6.521135 |
43 | 2019 | sep4 | 435 | 7.447257 |
Primetimo da su mogući određeni problemi usled postojanja nedefinisanih vrednosti. Na primer, u narednom upitu se prvi red dobija kao rezultat grupisanja po drugom uslovu (konkretnu grupu čine svi ispiti iz 2015. godine), a drugi red se dobija kao rezultat grupisanja po prvom uslovu, ali gde je ocena nedefinisana (grupu čine ispiti iz 2015. godine bez upisane ocene). Nije očigledno koji od tih redova predstavlja koji podatak, sve dok se ne analiziraju konkretni podaci:
%%sql
-- potencijalni problemi sa NULL
select ocena, skgodina, count(*) n
from ispit
group by grouping sets ((ocena, skgodina), (skgodina), ())
order by ocena nulls first, skgodina
OCENA | SKGODINA | N | |
---|---|---|---|
0 | <NA> | 2015 | 8251 |
1 | <NA> | 2015 | 3619 |
2 | <NA> | 2016 | 16325 |
3 | <NA> | 2016 | 8440 |
4 | <NA> | 2017 | 23383 |
... | ... | ... | ... |
36 | 10 | 2015 | 762 |
37 | 10 | 2016 | 1235 |
38 | 10 | 2017 | 1475 |
39 | 10 | 2018 | 1855 |
40 | 10 | 2019 | 2077 |
41 rows × 3 columns
ROLLUP
¶
Operator ROLLUP(<lista>)
izračunava skupove grupisanja koji predstavljaju prefikse date liste. Znači:
ROLLUP( c1, c2,..., cn-1, cn )
je potpuno isto kao da je navedeno:
GROUPING SETS(
( c1, c2,..., cn-1, cn ),
( c1, c2,..., cn-1 ),
...
( c1, c2 ),
( c1 ),
()
)
%%sql
-- sada grupisemo po dva skupa
select
skgodina,
oznakaroka,
count(*) brojprijava,
avg(ocena+0.0) prosek
from ispit
group by rollup( skgodina, oznakaroka )
SKGODINA | OZNAKAROKA | BROJPRIJAVA | PROSEK | |
---|---|---|---|---|
0 | <NA> | <NA> | 119143 | 6.882135 |
1 | 2015 | <NA> | 8251 | 7.046416 |
2 | 2016 | <NA> | 16325 | 7.007736 |
3 | 2017 | <NA> | 23383 | 6.875428 |
4 | 2018 | <NA> | 30901 | 6.857216 |
... | ... | ... | ... | ... |
39 | 2019 | jun2 | 4995 | 6.665320 |
40 | 2019 | sep1 | 5198 | 6.635577 |
41 | 2019 | sep2 | 5808 | 6.422013 |
42 | 2019 | sep3 | 5739 | 6.521135 |
43 | 2019 | sep4 | 435 | 7.447257 |
44 rows × 4 columns
Dopuštene su i kombinacije konkretnih kolona i operatora ROLLUP, pa bi izraz:
GROUPING SETS ( (c1, ROLLUP( c2, c3 ), c4 ) )
bio ekvivalentan sa
GROUPING SETS (
( c1, c2, c3, c4 ),
( c1, c2, c4 ),
( c1, c4 )
)
%%sql
select
skgodina,
oznakaroka,
status,
count(*) brojprijava,
avg(ocena+0.0) prosek
from ispit
group by grouping sets(( status, rollup(skgodina), oznakaroka ))
SKGODINA | OZNAKAROKA | STATUS | BROJPRIJAVA | PROSEK | |
---|---|---|---|---|---|
0 | <NA> | jan1 | d | 1 | 5.00 |
1 | <NA> | jan2 | d | 3 | 5.00 |
2 | <NA> | jun1 | d | 1 | 5.00 |
3 | <NA> | jun2 | d | 3 | 5.00 |
4 | <NA> | sep1 | d | 1 | 5.00 |
... | ... | ... | ... | ... | ... |
194 | 2016 | sep1 | x | 1 | 6.00 |
195 | 2017 | sep1 | x | 1 | 6.00 |
196 | 2018 | sep1 | x | 4 | 8.75 |
197 | 2019 | sep1 | x | 1 | 8.00 |
198 | 2019 | sep3 | x | 2 | 8.00 |
199 rows × 5 columns
CUBE
¶
Operator CUBE(<lista>)
izračunava sve skupove grupisanja koji se dobijaju kao kombinacije elemenata date liste. Znači:
CUBE( c1, c2, c3 )
je potpuno isto kao da je navedeno:
GROUPING SETS(
( c1, c2, c3 ),
( c1, c2 ),
( c1, c3 ),
( c2, c3 ),
( c1 ),
( c2 ),
( c3 ),
()
)
%%sql -a
-- pregled upisivanja studenata po sk.godinama, programu i polu
select
sp.naziv,
pol,
year(datupisa) as godupisa,
count(*) n
from dosije d
join studijskiprogram sp
on d.idprograma = sp.id
and sp.idnivoa = 1
group by cube( sp.naziv, pol, year(datupisa))
order by 3 nulls first, 2 nulls first, 1 nulls first
NAZIV | POL | GODUPISA | N | |
---|---|---|---|---|
0 | <NA> | <NA> | <NA> | 2690 |
1 | Astronomija i astrofizika | <NA> | <NA> | 159 |
2 | Informatika | <NA> | <NA> | 1061 |
3 | Matematika | <NA> | <NA> | 1470 |
4 | <NA> | m | <NA> | 1504 |
5 | Astronomija i astrofizika | m | <NA> | 94 |
6 | Informatika | m | <NA> | 699 |
7 | Matematika | m | <NA> | 711 |
8 | <NA> | z | <NA> | 1186 |
9 | Astronomija i astrofizika | z | <NA> | 65 |
10 | Informatika | z | <NA> | 362 |
11 | Matematika | z | <NA> | 759 |
12 | <NA> | <NA> | 2015 | 435 |
13 | Astronomija i astrofizika | <NA> | 2015 | 26 |
14 | Informatika | <NA> | 2015 | 139 |
15 | Matematika | <NA> | 2015 | 270 |
16 | <NA> | m | 2015 | 234 |
17 | Astronomija i astrofizika | m | 2015 | 15 |
18 | Informatika | m | 2015 | 103 |
19 | Matematika | m | 2015 | 116 |
20 | <NA> | z | 2015 | 201 |
21 | Astronomija i astrofizika | z | 2015 | 11 |
22 | Informatika | z | 2015 | 36 |
23 | Matematika | z | 2015 | 154 |
24 | <NA> | <NA> | 2016 | 450 |
25 | Astronomija i astrofizika | <NA> | 2016 | 27 |
26 | Informatika | <NA> | 2016 | 162 |
27 | Matematika | <NA> | 2016 | 261 |
28 | <NA> | m | 2016 | 245 |
29 | Astronomija i astrofizika | m | 2016 | 16 |
30 | Informatika | m | 2016 | 103 |
31 | Matematika | m | 2016 | 126 |
32 | <NA> | z | 2016 | 205 |
33 | Astronomija i astrofizika | z | 2016 | 11 |
34 | Informatika | z | 2016 | 59 |
35 | Matematika | z | 2016 | 135 |
36 | <NA> | <NA> | 2017 | 509 |
37 | Astronomija i astrofizika | <NA> | 2017 | 26 |
38 | Informatika | <NA> | 2017 | 200 |
39 | Matematika | <NA> | 2017 | 283 |
40 | <NA> | m | 2017 | 294 |
41 | Astronomija i astrofizika | m | 2017 | 16 |
42 | Informatika | m | 2017 | 135 |
43 | Matematika | m | 2017 | 143 |
44 | <NA> | z | 2017 | 215 |
45 | Astronomija i astrofizika | z | 2017 | 10 |
46 | Informatika | z | 2017 | 65 |
47 | Matematika | z | 2017 | 140 |
48 | <NA> | <NA> | 2018 | 500 |
49 | Astronomija i astrofizika | <NA> | 2018 | 27 |
50 | Informatika | <NA> | 2018 | 203 |
51 | Matematika | <NA> | 2018 | 270 |
52 | <NA> | m | 2018 | 285 |
53 | Astronomija i astrofizika | m | 2018 | 13 |
54 | Informatika | m | 2018 | 136 |
55 | Matematika | m | 2018 | 136 |
56 | <NA> | z | 2018 | 215 |
57 | Astronomija i astrofizika | z | 2018 | 14 |
58 | Informatika | z | 2018 | 67 |
59 | Matematika | z | 2018 | 134 |
60 | <NA> | <NA> | 2019 | 448 |
61 | Astronomija i astrofizika | <NA> | 2019 | 26 |
62 | Informatika | <NA> | 2019 | 193 |
63 | Matematika | <NA> | 2019 | 229 |
64 | <NA> | m | 2019 | 240 |
65 | Astronomija i astrofizika | m | 2019 | 16 |
66 | Informatika | m | 2019 | 113 |
67 | Matematika | m | 2019 | 111 |
68 | <NA> | z | 2019 | 208 |
69 | Astronomija i astrofizika | z | 2019 | 10 |
70 | Informatika | z | 2019 | 80 |
71 | Matematika | z | 2019 | 118 |
72 | <NA> | <NA> | 2020 | 348 |
73 | Astronomija i astrofizika | <NA> | 2020 | 27 |
74 | Informatika | <NA> | 2020 | 164 |
75 | Matematika | <NA> | 2020 | 157 |
76 | <NA> | m | 2020 | 206 |
77 | Astronomija i astrofizika | m | 2020 | 18 |
78 | Informatika | m | 2020 | 109 |
79 | Matematika | m | 2020 | 79 |
80 | <NA> | z | 2020 | 142 |
81 | Astronomija i astrofizika | z | 2020 | 9 |
82 | Informatika | z | 2020 | 55 |
83 | Matematika | z | 2020 | 78 |
%%sql -a
-- prolaznost po ispitnim rokovima
with
data as (
select
skgodina,
oznakaroka,
count(*) prijavljeno,
sum(case when status='p' then 1 else 0 end) neodrzano,
count(*) - sum(case when status='n' then 1 else 0 end) izaslo,
sum(case when status='o' and ocena>5 then 1 else 0 end) polozilo
from ispit
group by cube( skgodina, oznakaroka )
)
select
data.*,
(izaslo+0.0)/prijavljeno as izlaznost,
(polozilo+0.0)/prijavljeno as prolaznost,
(polozilo+0.0)/izaslo as prolaznost_uk
from data
order by skgodina, oznakaroka
SKGODINA | OZNAKAROKA | PRIJAVLJENO | NEODRZANO | IZASLO | POLOZILO | IZLAZNOST | PROLAZNOST | PROLAZNOST_UK | |
---|---|---|---|---|---|---|---|---|---|
0 | 2015 | jan1 | 1640 | 0 | 1186 | 884 | 0.723171 | 0.539024 | 0.745363 |
1 | 2015 | jan2 | 1182 | 0 | 641 | 409 | 0.542301 | 0.346024 | 0.638066 |
2 | 2015 | jun1 | 1848 | 0 | 1099 | 722 | 0.594697 | 0.390693 | 0.656961 |
3 | 2015 | jun2 | 1550 | 0 | 653 | 360 | 0.421290 | 0.232258 | 0.551302 |
4 | 2015 | kom | 88 | 0 | 88 | 88 | 1.000000 | 1.000000 | 1.000000 |
5 | 2015 | sep1 | 1131 | 0 | 628 | 369 | 0.555261 | 0.326260 | 0.587580 |
6 | 2015 | sep2 | 812 | 0 | 337 | 174 | 0.415025 | 0.214286 | 0.516320 |
7 | 2015 | <NA> | 8251 | 0 | 4632 | 3006 | 0.561386 | 0.364319 | 0.648964 |
8 | 2016 | jan1 | 2948 | 0 | 1713 | 1234 | 0.581072 | 0.418589 | 0.720374 |
9 | 2016 | jan2 | 2795 | 0 | 1224 | 805 | 0.437925 | 0.288014 | 0.657680 |
10 | 2016 | janps | 11 | 0 | 10 | 10 | 0.909091 | 0.909091 | 1.000000 |
11 | 2016 | jun1 | 3729 | 1 | 2002 | 1361 | 0.536873 | 0.364977 | 0.679820 |
12 | 2016 | jun2 | 2825 | 1 | 988 | 597 | 0.349735 | 0.211327 | 0.604251 |
13 | 2016 | kom | 12 | 0 | 12 | 12 | 1.000000 | 1.000000 | 1.000000 |
14 | 2016 | sep1 | 2385 | 0 | 1178 | 648 | 0.493920 | 0.271698 | 0.550085 |
15 | 2016 | sep2 | 1620 | 0 | 760 | 513 | 0.469136 | 0.316667 | 0.675000 |
16 | 2016 | <NA> | 16325 | 2 | 7887 | 5180 | 0.483124 | 0.317305 | 0.656777 |
17 | 2017 | jan1 | 4270 | 2 | 2199 | 1513 | 0.514988 | 0.354333 | 0.688040 |
18 | 2017 | jan2 | 3709 | 1 | 1634 | 1022 | 0.440550 | 0.275546 | 0.625459 |
19 | 2017 | janps | 12 | 0 | 12 | 12 | 1.000000 | 1.000000 | 1.000000 |
20 | 2017 | jun1 | 5346 | 0 | 2699 | 1752 | 0.504863 | 0.327722 | 0.649129 |
21 | 2017 | jun2 | 3836 | 0 | 1565 | 886 | 0.407977 | 0.230970 | 0.566134 |
22 | 2017 | sep1 | 3700 | 1 | 1873 | 1041 | 0.506216 | 0.281351 | 0.555793 |
23 | 2017 | sep2 | 2510 | 1 | 1117 | 641 | 0.445020 | 0.255378 | 0.573859 |
24 | 2017 | <NA> | 23383 | 5 | 11099 | 6867 | 0.474661 | 0.293675 | 0.618704 |
25 | 2018 | jan1 | 5333 | 0 | 2370 | 1581 | 0.444403 | 0.296456 | 0.667089 |
26 | 2018 | jan2 | 5154 | 0 | 2141 | 1276 | 0.415406 | 0.247575 | 0.595983 |
27 | 2018 | janps | 25 | 0 | 20 | 16 | 0.800000 | 0.640000 | 0.800000 |
28 | 2018 | jun1 | 7102 | 0 | 3319 | 2135 | 0.467333 | 0.300620 | 0.643266 |
29 | 2018 | jun2 | 5014 | 0 | 2074 | 1227 | 0.413642 | 0.244715 | 0.591610 |
30 | 2018 | sep1 | 5001 | 1 | 2233 | 1295 | 0.446511 | 0.258948 | 0.579937 |
31 | 2018 | sep2 | 3272 | 0 | 1445 | 918 | 0.441626 | 0.280562 | 0.635294 |
32 | 2018 | <NA> | 30901 | 1 | 13602 | 8448 | 0.440180 | 0.273389 | 0.621085 |
33 | 2019 | jan1 | 5623 | 2 | 2434 | 1589 | 0.432865 | 0.282589 | 0.652835 |
34 | 2019 | jan2 | 5616 | 10 | 2337 | 1435 | 0.416132 | 0.255520 | 0.614035 |
35 | 2019 | janps | 481 | 3 | 184 | 90 | 0.382536 | 0.187110 | 0.489130 |
36 | 2019 | jun1 | 6388 | 13 | 3188 | 1841 | 0.499061 | 0.288197 | 0.577478 |
37 | 2019 | jun2 | 4995 | 9 | 1742 | 969 | 0.348749 | 0.193994 | 0.556257 |
38 | 2019 | sep1 | 5198 | 8 | 2088 | 1188 | 0.401693 | 0.228549 | 0.568966 |
39 | 2019 | sep2 | 5808 | 13 | 1712 | 913 | 0.294766 | 0.157197 | 0.533294 |
40 | 2019 | sep3 | 5739 | 8 | 1806 | 1118 | 0.314689 | 0.194807 | 0.619048 |
41 | 2019 | sep4 | 435 | 0 | 237 | 179 | 0.544828 | 0.411494 | 0.755274 |
42 | 2019 | <NA> | 40283 | 66 | 15728 | 9322 | 0.390438 | 0.231413 | 0.592701 |
43 | <NA> | jan1 | 19814 | 4 | 9902 | 6801 | 0.499748 | 0.343242 | 0.686831 |
44 | <NA> | jan2 | 18456 | 11 | 7977 | 4947 | 0.432217 | 0.268043 | 0.620158 |
45 | <NA> | janps | 529 | 3 | 226 | 128 | 0.427221 | 0.241966 | 0.566372 |
46 | <NA> | jun1 | 24413 | 14 | 12307 | 7811 | 0.504117 | 0.319952 | 0.634679 |
47 | <NA> | jun2 | 18220 | 10 | 7022 | 4039 | 0.385401 | 0.221679 | 0.575192 |
48 | <NA> | kom | 100 | 0 | 100 | 100 | 1.000000 | 1.000000 | 1.000000 |
49 | <NA> | sep1 | 17415 | 10 | 8000 | 4541 | 0.459374 | 0.260752 | 0.567625 |
50 | <NA> | sep2 | 14022 | 14 | 5371 | 3159 | 0.383041 | 0.225289 | 0.588159 |
51 | <NA> | sep3 | 5739 | 8 | 1806 | 1118 | 0.314689 | 0.194807 | 0.619048 |
52 | <NA> | sep4 | 435 | 0 | 237 | 179 | 0.544828 | 0.411494 | 0.755274 |
53 | <NA> | <NA> | 119143 | 74 | 52948 | 32823 | 0.444407 | 0.275492 | 0.619910 |
Skupovni operatori¶
SQL podržava osnovne skupovne operatore nad tabelama. Svaki od operatora ima po dva oblika - sa i bez ponavljanja:
UNION
,UNION ALL
- unija bez ponavljanja i sa ponavljanjem;EXCEPT
,EXCEPT ALL
- razlika bez ponavljanja i sa ponavljanjem iINTERSECT
,INTERSECT ALL
- presek bez ponavljanja i sa ponavljanjem;
Ako se skupovni operator koristi bez opcije ponavljanja, onda je to ekvivalentno kao da je iz svake tabele izdvojen skup različitih redova i zatim primenjena uobičajena matematička skupovna operacija.
%%sql -a
values 1,1,1,2,2,2,3,4,4,5
union
values 1,1,3,3,3,3,4
1 | |
---|---|
0 | 1 |
1 | 2 |
2 | 3 |
3 | 4 |
4 | 5 |
%%sql -a
values 1,1,1,2,2,2,3,4,4,5
except
values 1,1,3,3,3,3,4
1 | |
---|---|
0 | 2 |
1 | 5 |
%%sql -a
values 1,1,1,2,2,2,3,4,4,5
intersect
values 1,1,3,3,3,3,4
1 | |
---|---|
0 | 1 |
1 | 3 |
2 | 4 |
Ako se skupovni operator koristi sa opcijom ponavljanja, onda semantika zavisi od operacije. U slučaju unije, svi redovi se smatraju za različite. Rezultat će sadržati sve redove iz obeju tabela - ako prva ima N a druga N redova, rezultat će imati M+N redova:
%%sql -a
values 1,1,1,2,2,2,3,4,4,5
union all
values 1,1,3,3,3,3,4
1 | |
---|---|
0 | 1 |
1 | 1 |
2 | 3 |
3 | 3 |
4 | 3 |
5 | 3 |
6 | 4 |
7 | 1 |
8 | 1 |
9 | 1 |
10 | 2 |
11 | 2 |
12 | 2 |
13 | 3 |
14 | 4 |
15 | 4 |
16 | 5 |
U slučaju preseka i razlike, semantika je kao da je (1) svaka tabela uređena i zatim (2) proširena dodatnom kolonom u kojoj su svi redovi svake od grupa označeni rednim brojevima od 1 nadalje, (3) pa tek onda na tako prošorenim tabelama primenjena skupovna operacija i na kraju (4) obrisana dodatna kolona. Efekat toga je da se identični redovi u jednoj tabeli smatraju različitim, a između dve tabele može da bude poklapanja:
%%sql -a
values 1,1,1,2,2,2,3,4,4,5
except all
values 1,1,3,3,3,3,4
1 | |
---|---|
0 | 1 |
1 | 2 |
2 | 2 |
3 | 2 |
4 | 4 |
5 | 5 |
%%sql -a
values 1,1,1,2,2,2,3,4,4,5
intersect all
values 1,1,3,3,3,3,4
1 | |
---|---|
0 | 1 |
1 | 1 |
2 | 3 |
3 | 4 |
Naredba WITH¶
Naredba WITH
služi sa zapisivanje složenijih upita sa podupitima. Sintaksa naredbe WITH
je:
WITH <ime> [AS] ( <upit> )
[, <ime> [AS] ( <upit> )]*
<upit>
U osnovnom obliku je ekvivalentna glavnom upitu u kome su u klauzuli FROM
zapisani i imenovani odgovarajući podupiti. Njen zapis je čitljiviji i omogućava lakše pisanje i održavanje složenih upita.
Naredba WITH
ima i značajne dodatne mogućnosti:
- U telu svakog od imenovanih podupita mogu da se referišu svi prethodno definisani upiti. Na taj način se omogućava da se korak po korak grade veoma složeni upiti.
- U telu jednog podupita može da se referiše čak i taj isti podupit, čime se omogućava pisanje rekurzivnih upita.
%%sql -a
-- izdvojiti za dva studenta 20170054, 20170059 uporedne rezultate na svim polozenim obaveznim predmetima
with
prvi as (
select *
from ispit i
where i.indeks = '20170054'
and status = 'o'
and ocena > 5
),
drugi as (
select *
from ispit i
where i.indeks = '20170059'
and status = 'o'
and ocena > 5
)
select
p.naziv,
prvi.indeks, prvi.poeni, prvi.ocena,
drugi.indeks, drugi.poeni, drugi.ocena
from prvi
full outer join drugi
on prvi.idpredmeta = drugi.idpredmeta
join predmet p
on p.id = coalesce(prvi.idpredmeta,drugi.idpredmeta)
NAZIV | INDEKS | POENI | OCENA | INDEKS | POENI | OCENA | |
---|---|---|---|---|---|---|---|
0 | Strani jezik | 20170054 | 89 | 9 | 20170059 | 100 | 10 |
1 | Programiranje 1 | 20170054 | 84 | 9 | 20170059 | 61 | 7 |
2 | Linearna algebra | 20170054 | 85 | 9 | 20170059 | 85 | 9 |
3 | Uvod u matematicku logiku | 20170054 | 82 | 9 | 20170059 | 95 | 10 |
4 | Geometrija 1 | 20170054 | 83 | 9 | 20170059 | 88 | 9 |
5 | Programiranje 2 | 20170054 | 95 | 10 | 20170059 | 78 | 8 |
6 | Analiza 1 | 20170054 | 65 | 7 | 20170059 | 75 | 8 |
7 | Diskretna matematika | <NA> | <NA> | <NA> | 20170059 | 95 | 10 |
8 | Geometrija 2 | <NA> | <NA> | <NA> | 20170059 | 91 | 10 |
9 | Algebra 1 | <NA> | <NA> | <NA> | 20170059 | 91 | 10 |
10 | Programski paketi u matematici | <NA> | <NA> | <NA> | 20170059 | 95 | 10 |
11 | Uvod u organizaciju i arhitekturu racunara 1 | <NA> | <NA> | <NA> | 20170059 | 81 | 9 |
12 | Uvod u numericku matematiku | <NA> | <NA> | <NA> | 20170059 | 96 | 10 |
13 | Objektno-orijentisano programiranje | 20170054 | 78 | 8 | 20170059 | 93 | 10 |
14 | Geometrija 3 | <NA> | <NA> | <NA> | 20170059 | 86 | 9 |
15 | Analiza 2 | <NA> | <NA> | <NA> | 20170059 | 61 | 7 |
16 | Uvod u veb i internet tehnologije | 20170054 | 83 | 9 | <NA> | <NA> | <NA> |
17 | Uvod u organizaciju i arhitekturu racunara 2 | 20170054 | 92 | 10 | <NA> | <NA> | <NA> |
18 | Algebra 1 | 20170054 | 61 | 7 | <NA> | <NA> | <NA> |
19 | Diskretne strukture 2 | 20170054 | 76 | 8 | <NA> | <NA> | <NA> |
20 | Uvod u organizaciju i arhitekturu racunara 1 | 20170054 | 85 | 9 | <NA> | <NA> | <NA> |
%%sql -a
-- rangirati studente upisane u 2020. na izborni predmet 'Programiranje za veb'
-- na osnovu broja polozenih ESPB i ostvarene prosecne ocene
with
studenti as (
select
i.indeks,
sum( p.espb ) as espb,
avg( ocena + 0. ) as ocena
from ispit i
join predmet p
on i.idpredmeta = p.id
where i.status = 'o'
and ocena > 5
group by i.indeks
)
select
p.id, p.oznaka, p.naziv,
d.indeks, d.ime, d.prezime,
s.espb, s.ocena
from predmet p
join upisankurs uk
on p.id = uk.idpredmeta
and uk.skgodina = 2020
join dosije d
using( indeks )
join studenti s
using( indeks )
where p.naziv like 'Pro% za _eb'
and d.datdiplomiranja is null
order by 1, 7 desc, 8 desc
ID | OZNAKA | NAZIV | INDEKS | IME | PREZIME | ESPB | OCENA | |
---|---|---|---|---|---|---|---|---|
0 | 2343 | R291 | Programiranje za veb | 20160086 | Natalija | Nikolic | 216 | 7.615385 |
1 | 2343 | R291 | Programiranje za veb | 20150429 | Rada | Djoric | 205 | 7.222222 |
2 | 2343 | R291 | Programiranje za veb | 20160423 | Mina | Zivanovic | 193 | 8.142857 |
3 | 2343 | R291 | Programiranje za veb | 20150012 | Kristina | Savatovic | 193 | 7.057143 |
4 | 2343 | R291 | Programiranje za veb | 20160301 | Jana | Albijanic | 186 | 8.750000 |
... | ... | ... | ... | ... | ... | ... | ... | ... |
104 | 2343 | R291 | Programiranje za veb | 20190047 | Milena | Vidojevic | 49 | 7.250000 |
105 | 2343 | R291 | Programiranje za veb | 20180312 | Lazar | Keneski | 49 | 6.777778 |
106 | 2343 | R291 | Programiranje za veb | 20190051 | Teodor | Stojkovic | 48 | 9.250000 |
107 | 2343 | R291 | Programiranje za veb | 20180073 | Milica | Curkovic | 46 | 7.636364 |
108 | 2343 | R291 | Programiranje za veb | 20180008 | Milica | Gruzanic | 42 | 6.857143 |
109 rows × 8 columns
%%sql -a
-- rangirati izborne predmete upisane u 2020. prema broju studenata
-- i prosecnoj oceni studenata koji su ih upisali
-- za predmete sa bar 25 upisanih studenata
with
studenti as (
select
indeks,
avg( ocena + 0. ) as prosek
from ispit
where status = 'o'
and ocena > 5
group by indeks
),
predmeti as (
select
pp.idpredmeta,
count(*) n,
avg(prosek) prosek
from predmetprograma pp
join upisankurs uk
on pp.idpredmeta = uk.idpredmeta
and uk.skgodina = 2020
join dosije d
on d.indeks = uk.indeks
and d.idprograma = pp.idprograma
join studenti s
on s.indeks = d.indeks
where vrsta = 'izborni'
group by pp.idpredmeta
having count(*) >= 25
)
select p.*, n, prosek
from predmet p
join predmeti ps
on p.id = ps.idpredmeta
order by n desc, prosek desc
ID | OZNAKA | NAZIV | ESPB | N | PROSEK | |
---|---|---|---|---|---|---|
0 | 1894 | M3.04 | Geometrija 4 | 5 | 123 | 7.850414 |
1 | 2343 | R291 | Programiranje za veb | 6 | 109 | 7.683580 |
2 | 2180 | O62 | Osnovi mehanike | 6 | 104 | 7.939383 |
3 | 1907 | AM02 | Odabrana poglavlja astronomije | 5 | 99 | 8.030590 |
4 | 2341 | R269 | Racunarska inteligencija | 6 | 92 | 7.745577 |
5 | 2337 | R246 | Funkcionalno programiranje | 6 | 91 | 7.726705 |
6 | 2026 | R272 | Programiranje baza podataka | 6 | 87 | 7.600703 |
7 | 2492 | R292 | Alati za razvoj softvera | 6 | 82 | 7.768618 |
8 | 2350 | M133 | Primena projektivne geometrije u racunarstvu | 5 | 80 | 7.702715 |
9 | 1884 | S1.01 | Uvod u finansijsku matematiku | 5 | 79 | 7.583424 |
10 | 1945 | RM13 | Uvod u relacione baze podataka | 5 | 68 | 7.900713 |
11 | 2377 | RM14 | Operativni sistemi | 5 | 66 | 7.903647 |
12 | 1949 | RM16 | Programiranje baza podataka | 5 | 61 | 7.986134 |
13 | 2482 | RK2.NN5 | Razvoj video igara u C++-u | 2 | 59 | 7.728885 |
14 | 2023 | R241 | Konstrukcija kompilatora | 6 | 56 | 7.863567 |
15 | 2340 | R265 | Uvod u interaktivno dokazivanje teorema | 6 | 51 | 7.960585 |
16 | 2459 | RK2.PBI | Primenjena bioinformatika | 2 | 45 | 7.617126 |
17 | 1883 | M1.05 | Diskretna matematika | 5 | 44 | 8.346708 |
18 | 1886 | M4.02 | Programski paketi u matematici | 5 | 42 | 8.414853 |
19 | 2356 | M180 | Osnove matematickog modeliranja | 5 | 39 | 7.907354 |
20 | 1922 | M3.07 | Geometrija 5 | 5 | 35 | 8.781165 |
21 | 2179 | O61 | Osnovi astronomije | 6 | 33 | 8.003336 |
22 | 1885 | S1.02 | Uvod u filozofiju | 5 | 29 | 8.047319 |
23 | 2501 | RK2.UITP | Upravljanje IT projektima - Agile metodologije | 2 | 27 | 7.709460 |
24 | 1978 | M2.30 | Analiza 3B | 5 | 25 | 8.057831 |