nav_dugme codeBlog codeBlog
  • početna Početna stranica
  • Sačuvani članci Sačuvani članci
  • Učionica
  • Saveti
  • Zanimljivosti
  • Kontakt

Uvod u relacione baze podataka i SQL

Viber
zoom_plus zoom_minus bookmark

Uvod

U teoriji, termin "baza podataka" odnosi se na bilo kakvu organizovanu kolekciju podataka koja se čuva u elektronskom obliku, * međutim godinama unazad navedeni pojam se tipično poistovećuje sa relacionim bazama podataka, budući da su upravo relacioni sistemi za skladištenje i obradu podataka (SQL Server, Oracle, Db2, MySql, PostgreSQL - da navedemo samo neke), najrasprostranjeniji i najčešće korišćeni. **

Za prosleđivanje uputa, ili jednostavnije rečeno - za poslove administracije relacionih baza - koristi se SQL (Structured Query Language), deskriptivni (neproceduralni) jezik specifično namenjen radu sa bazama podataka.

Opšte je poznato da se baze podataka koriste u većini desktop aplikacija i na skoro svim sajtovima, da je značaj baza podataka izuzetno velik - i to je više nego dobar razlog da se u nastavku upoznamo sa relacionim bazama podataka i SQL-om.

* Na primer, improvizovani telefonski imenik zapisan u Excel-u sasvim se uklapa u prvu definiciju (ali takva kolekcija podataka gotovo sigurno nije prvo što bi nam moglo pasti na pamet pri pomenu odrednice "baza podataka").

** Poslednjih godina raste popularnost i nerelacionih (NoSql) sistema (među kojima u smislu popularnosti prednjači MongoDB), pa ćemo i temi NoSql baza podataka posvetiti prostor u dogledno vreme.

Osnove relacionog modela

Relacionu bazu podataka (najpraktičnije) možemo shvatiti kao sistem međusobno povezanih tabela, u kome se teži što racionalnijem i što ekonomičnijem zapisu podataka.

Da bismo što bolje ilustrovali osnovnu ideju koja stoji iza relacionog modela (i takođe, principe dizajna i upotrebe relacionih baza podataka), kreiraćemo bazu podataka koja pamti informacije o prodavcima i prodaji artikala (u fiktivnoj prodavnici) i proći kroz sve korake u projektovanju.

Prva tabela sadržaće podatke o prodavcima i neće se mnogo (gotovo ni malo) razlikovati od tabele kakvu bismo za slične potrebe napravili u Excel-u:

baze_podataka_01
Slika 1. - Tabela sa podacima o prodavcima.

U daljem tekstu, prethodna tabela nosiće naziv prodavci i fali joj samo još samo jedan detalj - doduše, veoma bitan - da bi bila prava tabela kakvu možemo koristiti i u bazama podataka (što ćemo naravno dodati u nastavku).

Sa druge strane, dizajn tabele prodaja (u kojoj se u svakom slogu beleže podaci o prodaji jednog artikla), podrazumeva malo više promišljanja, pa ćemo pažljivije razmotriti kako treba organizovati ovakvu tabelu.

Za početak, pogledaćemo zašto određene formate zapisa (koji bi u ovom trenutku vrlo verovatno mogli da vam padnu na pamet) - ne treba koristiti.

Teoretski bismo mogli da (pored ostalih podataka o prodaji), beležimo samo ime i prezime prodavca:

baze_podataka_03
Slika 2. - Tabela sa podacima o prodaji artikala - prvi primer neoptimalne strukture.

Drugi način (koji takođe nije pravo rešenje), je da u tabeli prodaja beležimo sve podatke o prodavcu:

baze_podataka_04
Slika 3. - Tabela sa podacima o prodaji artikala - drugi primer neoptimalne strukture (u pitanju je svojevrsna "karikatura", budući da je polje prodavac krajnje "pretrpano", ali svrha ovakvih primera je da nam ukažu na to kako podatke ne treba formatirati).

Prvi format zapisa ima dva nedostatka:

  • pre svega, kombinacija imena i prezimena nije jedinstven podatak i ne upućuje obavezno na jednu osobu ("Petar Jovanović" može biti ime i prezime dva različita prodavca) *
  • ukoliko želimo da dobijemo više informacija o prodavcu:
    • u tabeli prodaja nema dodatnih podataka
    • ako ručno tražimo podatke o prodavcu u tabeli prodavci, ne možemo biti sigurni da čitamo prave podatke (iz gore navedenih razloga)

* Ukoliko nastane kritična situacija u vezi sa određenom prodajom koju je obavio "Petar Jovanović" (i pri tom u preduzeću postoji više od jedne osobe sa navedenom kombinacijom imena i prezimena), ne možemo biti sigurni o kom "Petru Jovanoviću" je reč.

Osvrnućemo se i na to da u nekim situacijama kombinacija imena i prezimena može biti podatak koji nedvosmisleno upućuje na jednu osobu (u maloj firmi sa manje od pet zaposlenih, verovatno ipak postoji samo jedan "Petar Jovanović"), međutim - takav pristup nije u skladu sa relacionim modelom, na takav pristup se ne treba oslanjati i takvim pristupom se nećemo baviti u ovom članku (a ne bavimo se ni inače). :)

Drugi format zapisa je potpun: svi podaci o prodavcu (koje inače beležimo, u tabeli prodavci) jesu zapisani - i jeste moguće razlikovati prodavce, * ali ....

U pitanju je format koji nije ni malo elegantan i pregledan (baš, baš - nimalo :)), a ako bismo "skroz preterali" i podatke upisivali ručno, neelegantnosti i nepreglednosti bismo mogli dodati i povećanu mogućnost pojave grešaka.

Doduše, to važi i za prvi format (mada je manje očigledno na prvi pogled, zbog manje količine podataka), pa zapravo - u oba slučaja - krajnje nepotrebno beležimo iste podatke na dva mesta (a jasno je i to da između istih podataka u dve tabele zapravo ne postoji veza).

Potrebno je naći rešenje koje omogućava da u tabeli prodaja jedinstveni prodavac bude naveden na jednostavan, ekonomičan i nedvosmisleno prepoznatljiv način - po mogućnosti preko samo jednog podatka (a pogotovo bez navođenja (svih) detalja putem "copy-paste" metoda), a potrebno je istovremeno i da podaci o prodavcima budu ("negde i nekako") zapisani - i po potrebi dostupni.

Pošto su podaci o prodavcima već zapisani (u zasebnoj tabeli), rešenje je da svakom prodavcu pripišemo jedinstven identifikacioni broj i da potom povežemo tabele.

* Kako zapravo možemo razlikovati osobe u tabeli prodavci koju koristimo? Kombinacija imena i prezimena ne upućuje na jednu osobu, a situacija se ne popravlja ni kada dodamo datum rođenja (pa ostaje samo e-mail adresa).

Preko verifikovane email adrese mogu se identifikovati osobe u bazama podataka (više o tome u nastavku) i upravo to je mehanizam raspoznavanja prodavaca u tabeli prodavci (a ne kombinacija ostalih podataka).

Primarni ključ

U tabeli prodavci, dodaćemo kolonu sa nazivom id, u kojoj će redom biti upisane celobrojne vrednosti od 1 do n, tako da svaki broj upućuje na jednog prodavca (uvek istog).

Jedinstveni podatak u okviru jednog sloga (reda) po kome je moguće prepoznati ceo slog naziva se primarni ključ.

U praksi se za primani ključ gotovo uvek koriste celobrojne vrednosti (redni brojevi) i kolona se tipično označava sa "id".

baze_podataka_02
Slika 4. - Tabela sa podacima o prodavcima koja sada sadrži i polje "id", koje služi kao primarni ključ (svojevrstan identifikacioni broj svakog prodavca).

U tabelama u bazama podataka, vrednosti u koloni id ne moraju obavezno biti brojevi od 1 do n (gde je n ukupan broj redova), ali, ukoliko ne dođe do uklanjanja redova (postupak uklanjanja slogova ćemo opisati pred kraj), u dobro dizajniranim tabelama, vrednosti u koloni id će (ipak) biti vrednosti "od 1 do n", o čemu softver za administraciju baze vodi računa automatski (i o tome ćemo više pisati u nastavku).

Uspostavljanje veze između tabela (sekundarni ključevi)

Pošto smo u tabeli prodavci definisali primarni ključ, potrebno je da uspostavimo vezu između tabela prodavci i prodaja.

Veza = relacija (i upravo je to ono po čemu je relacioni model dobio naziv).

Kada uspostavimo vezu između tabela (videćemo kasnije kako se to tehnički izvodi), u tabeli prodaja više nećemo navoditi ime i prezime prodavca (a pogotovo ne sve podatke), već samo identifikacioni broj (primarni ključ) iz tabele prodavci, što (naravno) znači da se polje prodavac_id * u tabeli prodaja nadalje popunjava podacima brojčanog (a ne tekstualnog) tipa, koji se u svakom slogu poklapaju sa primarnim ključem prodavca koji je prodao navedeni artikl.

baze_podataka_05
Slika 5. - Uspostavljanje relacije između primarnog ključa "id" iz tabele "prodavci" i sekundarnog (spoljnjeg) ključa "prodavac_id" u tabeli "prodaja".

Kada primarni ključ iz jedne tabele upotrebimo u drugoj tabeli (radi prepoznavanja podataka iz prve), takav podatak nosi naziv sekundarni ključ.

* Za polje nećemo više koristiti naziv prodavac (kao u prvim primerima), već prodavac_id, da bi bilo što jasnije da se radi (upravo) o id-u prodavca (iz druge tabele).

Takođe, jedan "nerd alert" sa naše strane: mlađim čitaocima koji se zanimaju za baze podataka unapred se izvinjavamo zbog upotrebe "krupnih reči" i stranih termina koja predstoji u sledećih nekoliko odeljaka, sve do poglavlja "Struktura relacione baze i organizacija podataka" (naravno, trudićemo se - koliko god je moguće - da i u navedenim odeljcima koristimo što jednostavniji rečnik).

Prirodni i surogatni primarni ključevi

U prethodnim odeljcima opisali smo tipičan postupak koji podrazumeva da se za primarni ključ tabele biraju celobrojne vrednosti, međutim, iako celobrojna vrednost u polju id jeste vezana za samo jedan red (to jest, vrednosti se ne ponavljaju po redovima), broj 1 - sam po sebi - nema nikakve suštinske i prirodne veze sa osobom "Petar Jovanović" (čiji su podaci zabeleženi u prvom redu), pa možemo reći da takav podatak predstavlja surogatni (veštački) primarni ključ, a naravno - postavlja se i pitanje da li je ceo pristup optimalan.

Da li (možda) postoji podatak u okviru sloga koji na prirodan način identifikuje osobu, to jest, da li se neko od već postojećih polja javlja kao prirodni primarni ključ, koji može i formalno da se pojavi kao primarni ključ? Da li onda takvo polje (ako postoji) treba da se pojavi kao primarni ključ?

Načelno je moguće izabrati da jedan od već postojećih podataka bude primarni ključ, ali, to (razume se) može biti samo podatak koji se u okviru date kolone ne ponavlja.

U našem primeru, ime nije jedinstven podatak (više prodavaca može imati ime Petar, ili bilo koje drugo ime), prezime takođe nije jedinstven podatak (a nije ni datum rođenja).

E-mail adrese (kao što smo već naveli) zapravo jesu jedinstvene - pod uslovom da su verifikovane, međutim, u pitanju je podatak tekstualnog tipa, a tekstualni podaci se pretražuju na manje efikasan način od brojčanih.

Stoga ćemo za primarni ključ izabrati jedinstven celobrojni podatak iz kolone "id", ali - u praksi se najčešće koriste oba ključa u jednom slogu (to jest, nećemo se "odreći" prirodnog primarnog ključa zato što smo uveli veštački).

"Veštački" primarni ključ (id) je stvar prakse (većina tabela u bazama podataka ima primarni ključ celobrojnog tipa, sa nazivom "id"), a prirodni primarni ključ je tu da spreči dupliranje slogova po kolonama.

Rekli bismo da je pitanje koje se na ovom mestu "postavlja samo od sebe": zašto nismo koristili JMBG?

(Kratak odgovor je - zarad očuvanja preglednosti; malo duži odgovor sledi ispod.)

Kada su u pitanju osobe, lako možemo zamisliti da se trinaestocifreni JMBG - kao "najprirodniji" prirodni primarni ključ (ili bar "najočigledniji") - može koristiti za identifikaciju slogova.

Načelno može - podatak je brojčanog tipa i jedinstven.

Međutim (kad već zamišljamo), možemo zamisliti i (drugu) situaciju, u kojoj je u tabelu potrebno uneti slog sa podacima za osobu koja nema JMBG (na primer, strani državljanin sa drugačijim ličnim brojem koji nije u formatu JMBG-a), posle čega se "vraćamo na početak".

U teoriji, primarni ključ može biti i kombinacija dva polja (na primer: id države i lični broj koji odgovara izabranoj državi), ali, u situaciji koju razmatramo, najverovatnije "ne bismo komplikovali": implementirali bismo sistem koji dozvoljava korišćenje različitih "ličnih brojeva" (i nedvosmisleno prepoznavanje osoba bez obzira na državljanstvo) - ali bismo za primarni ključ (gotovo sigurno) izabrali id (int).

U svakom slučaju - i onda kada prepoznamo i koristimo polje koje predstavlja prirodni primarni ključ (i pri tom tabeli pridodamo veštački PK) - moramo i dalje biti pažljivi pri dodavanju slogova (to jest, svejedno je potrebno da implementiramo mehanizam koji proverava podatke).

Pri dodavanju novog prodavca u tabelu, prodavcu će id (tipično) biti dodeljen automatski (i neće imati "prirodne i suštinske" veze sa prodavcem), pa za proveru novog prodavca (u smislu toga da li prodavac sa navedenim podacima već postoji u tabeli), moramo koristiti ostale podatke.

Ako među podacima koji se koriste za upis novog prodavca postoji podatak koji predstavlja prirodan primarni ključ (JMBG, verifikovana e-mail adresa i sl), možemo proveriti da li u tabeli već postoji slog u kome je dati podatak naveden (da li već postoji prodavac sa datim JMBG-om, ili sa datom e-mail adresom), dok - ukoliko među podacima nema prirodnog primarnog ključa - zapravo nemamo načina da raspoznajemo slogove!

Entiteti i atributi

Pošto smo razumeli kako se podaci (u okviru relacionog modela) raspoređuju po tabelama, potrebno je da se upoznamo i sa nekoliko terminima koji opisuju pojave sa kojima smo se prethodno upoznali.

Entitet(i)

Pojam entiteta u bazama podataka, u opštem smislu, označava (tipično složeni) objekat, koji u datom sistemu ima značaj i koji se može nedvosmisleno odrediti i zapisati.

Entiteti iste kategorije zapisuju se u okviru jedne tabele i za svaki entitet je vezan jedinstven primarni ključ i takođe (kao što smo već videli), entiteti iz jedne tabele se mogu navoditi u drugim tabelama preko (sekundarnih/spoljnjih) ključeva.

U bazi koju projektujemo, entitet je prodavac (sledi "prevod" gornja dva pasusa):

  • svaki prodavac određen je skupom pojedinačnih podataka (ime, prezime, datum rođenja i sl), to jest "nedvosmisleno je određen i zapisan"
  • svaki prodavac u tabeli prodavci označen je jedinstvenim primarnim ključem
  • prodavci se (kao entiteti) pojavljuju u tabeli prodaja - preko spoljnjih ključeva (a mogu se naravno pojavljivati i u drugim tabelama)

Kao primer baze sa brojnijim (a verovatno i zanimljivijim) entitetima, možemo zamisliti bazu podataka "geografski_atlas", sa entitetima kao što su "države", "gradovi", "reke", "jezera", "planine" (i sl).

Atribut(i)

Pojam atributa označava pojedinačni podatak koji se koristi pri definisanju određenog entiteta, a u opštijem smislu, možemo reći i da atribut predstavlja jednu od zajedničkih osobina svih entiteta određene kategorije.

Da pojasnimo:

  • jedan entitet ima više atributa (za prodavca: atributi su ime, prezime, datum rođenja, datum stupanja u radni odnos i sl)
  • bilo koji entitet iz određene grupe entiteta (praktično, iz iste tabele) ima svaki od atributa koji su pripisani svim entitetima date kategorije: svi prodavci imaju ime i prezime (pri čemu svakog prodavca odlikuje njegovo sopstveno ime i prezime), za sve prodavce se beleži datum stupanja u radni odnos (pri čemu se za svakog prodavca beleži datum stupanja u radni odnos koji se odnosi na datog prodavca) i sl.

Tipovi relacija između entiteta

U smislu toga koliko puta se spoljni ključ iz jedne tabele može pojaviti u drugoj tabeli, razlikujemo sledeće tipove relacija:

  • relacija 1:1 (jedan prema jedan) - spoljni ključ određenog entiteta iz jedne tabele, može se u drugoj tabeli pojaviti samo u jednom slogu
  • relacija 1:n (jedan prema više) - spoljni ključ određenog entiteta iz jedne tabele, može se u drugoj tabeli pojaviti u više slogova
  • relacija n:n (više prema više) - više entiteta iz jedne tabele može se povezati sa više entiteta iz druge tabele (preko posredničke tabele)

Relacija 1:n (jedan prema više)

Tipično se među entitetima uspostavlja relacija 1:n (jedan prema više) koja (kao što je već navedeno) podrazumeva da se entitet iz tabele T1 može (preko spoljnjeg ključa), u tabeli T2 pojaviti proizvoljan broj puta:

baze_podataka_jedan_prema_vise
Slika 6. - Opšta šema relacije "jedan prema više".

Kao konkretan primer možemo uzeti tabelu prodaja: jedan prodavac (entitet iz tabele prodavci) može se pojaviti u više slogova u tabeli prodaja.

Relacija 1:1 (jedan prema jedan)

Relacija 1:1 nije uobičajena kao 1:n, ali se svakako pojavljuje (dovoljno često). Ovoga puta, svaki entitet iz tabele T1 može se u tabeli T2 pojaviti samo jednom:

baze_podataka_jedan_prema_jedan
Slika 7. - Opšta šema relacije "jedan prema jedan".

Kako konkretan primer, zamislićemo bazu podataka u kojoj se beleže podaci u okviru određene sportske lige: ako, na primer, zamislimo da svaki klub (entitet iz tabele klubovi) može imati samo jednog glavnog trenera i da svaki glavni trener (entitet iz tabele osoblje) ima pravo da trenira samo jedan klub, možemo zamisliti i tabelu treneri (preko koje je zabeleženo koji trener trenira koji klub); tabela treneri je sa druge dve navedene tabele povezana relacijama 1:1.

Razmotrimo i naizgled sličan primer u kome bi (ponovo naizgled) takođe trebalo da se pojave dve relacije 1:1 (ali se zapravo pojavljuju relacije 1:1 i 1:n).

Ako za primer entiteta uzmemo grad i državu, a kao primer tabele koja spaja dva navedena entiteta, uzmemo tabelu glavni_grad, jasno je da se bilo koji grad u takvoj tabeli može pojaviti samo jednom (svaki grad može biti glavni grad samo jedne države), pa stoga između tabela grad i glavni_grad postoji relacija 1:1.

Međutim, budući da postoje države sa dva (pa čak i tri) glavna grada, jasno je da mora postojati mehanizam koji omogućava da se određena država u tabeli pojavi više puta, pa stoga između tabele države i tabele glavni_grad zapravo stoji veza 1:n (a ne 1:1).

Relacija n:n (više prema više)

Što se tiče relacije n:n (više prema više), razmotrićemo verovatno najtipičniji primer ovakve veze: povezivanje glumaca sa filmovima u kojima su se pojavljivali.

Ako kao entitete zamislimo glumce i filmove (kojima odgovaraju tabele glumci i filmovi), ostaje pitanje gde i kako (u kom formatu) zabeležiti podatke o tome u kojim filmovima se određeni glumac pojavljivao (ili - koji glumci su se pojavili u određenom filmu).

Intuitivno možete zaključiti da bi pokušaj da (na primer), u tabeli glumci, u jednom slogu navedemo sve filmove koji se vezuju za glumca, veoma podsećao na "pokušaj" (koji smo videli ranije) da u tabeli prodaja navedemo sve podatke o prodavcu.

Za sada je najbitnije znati da se podaci ne mogu na efikasan način povezati preko jedne tabele, to jest, da je potrebna posrednička tabela. Međutim, budući da zadatak nije skroz trivijalan, detaljniju diskusiju o implementaciji relacije n:n ćemo ostaviti za kasnije, jer smatramo da je potrebno da se prvo detaljnije upoznamo sa ostalim osnovnim odrednicama baza podataka (i takođe sa SQL sintaksom), da bismo što bolje razumeli kako (i pre svega - zašto) se n:n relacije implementiraju na specifičan način koji ćemo opisati (ali bez brige, nije ni iz daleka komplikovano).

Struktura relacione baze i organizacija podataka

Da bi baza podataka bila u stanju da korisnicima na brz i efikasan način ponudi korisne informacije (a svakako je upravo to svrha relacionih i drugih baza podataka), potrebno je da tabele budu pravilno koncipirane i pravilno strukturirane i takođe - potrebno je koristiti efikasne upite.

Za početak, pozabavićemo se strukturom tabela.

U daljem tekstu ćemo povremeno upotrebljavati i skraćenicu RDBMS (Relational Database Management System), koja označava sistem za upravljanje bazama podataka - zaokružen softverski paket koji obuhvata sistem za smeštaj podataka i manipulaciju podacima, kao i pomoćne programe za administraciju.

U popularne (R)DBMS sisteme spadaju (već pominjani) DB2, SQL Server i Oracle (kao tradicionalni sistemi sistemi sa vlasničkom licencom) i MySql i PostgreSQL (kao open-source rešenja).

Tabela, polje, slog

Tabela je sistem međusobno povezanih ćelija (raspoređenih u redove i kolone), u koje se upisuju podaci (o određenoj kategoriji entiteta).

Pojedinačna ćelija naziva se "polje":

baze_podataka_polje
Slika 8. - Polje u tabeli.

Polje je odrednica koja se koristi i za celu kolonu (budući da cela kolona sadrži polja istog tipa):

baze_podataka_polje_kolona
Slika 9. - Kolona u tabeli (koja se, u opštem smislu, takođe naziva polje).

Ceo red naziva se "slog":

baze_podataka_slog
Slika 10. - Slog u tabeli.

Zašto baš "polje" (verujemo da je naziv malo čudan, pri prvom susretu)? U vreme kada su se baze podataka pojavile, bilo je uobičajeno da unosu podataka u računarsku memoriju prethodi unos podataka preko papirnih formulara, a na papirnim formularima koji služe unosu podataka, pojedinačni podatak upisuje se u odštampani okvir koji se tipično naziva (pogađate) - polje ("polje za unos podataka").

U svakoj tabeli, slog predstavlja jedan složeni podatak (jedan entitet), pa tako u tabeli prodavci svaki red sadrži sve podatke koje beležimo o jednoj osobi (što znači da cela tabela sadrži podatke o osobama); podaci o osobama koje učestvuju u prodaji se (nadalje) ne zapisuju direktno u drugim tabelama (što bi samo povećalo memorijsko zauzeće i mogućnost pojave grešaka), već se po potrebi koriste podaci iz tabele prodavci (preko ključa) - kao što smo već ustanovili.

Svaki od podataka u jednom slogu pripada određenom (prigodno odabranom) tipu podataka: primarni ključ je (gotovo uvek) celobrojna vrednost, a ostali podaci se mogu zapisivati kao: brojevi, tekst, datumi (u posebnom formatu), boolean vrednosti (true i false) i sl.

Deluje da smo u tabelama prodavci i prodaja izabrali optimalan format za pojedinačne podatke, ali .... možda bi neko od čitalaca bio sklon da se zapita da li smo (možda) mogli izabrati i neki sažetiji format?!

Prodiskutovaćemo ukratko o optimalnom formatu slogova.

Atomski podaci

Budući da je tabela u bazi podataka namenjena zapisivanju kolekcija složenih podataka (tako da se u svakom slogu pojavljuje jedan entitet), jedno od osnovnih pitanja je: kako (tačno) treba organizovati (odnosno zapisati) podatke u okviru jednog sloga.

Ako bismo u tabeli prodavci za zapis podataka (u svakom slogu) odredili svega dva polja: id (int) i osoba (text), prva kolona bi sadržala redni broj, dok bismo u drugu upisivali "sve ostale" podatke, što bi (na primeru jednog sloga) imalo sledeći oblik:

		
id:    1
osoba: "Jovan Petrović, Ruzveltova 25, 41 godina, Mašinski inženjer, jocapet79@gmail.com"
		
	
Slika 11. - Neoptimalna organizacija slogova u tabeli sa podacima o osobama (koja podrazumeva da su svi podaci o osobi "nagurani" u jednu ćeliju).

Nije teško intuitivno zaključiti da u gornjem zapisu "nešto ne štima", iako deluje da je u tehničkom smislu sve u redu.

Tehnički, jeste sve "u redu", ali - samo u smislu najosnovnije korektnosti zapisa podataka.

U smislu efikasne obrade, prethodni format zapisa ne pomaže ni najmanje: ukoliko je potrebno doći do nekog od "unutrašnjih" podataka, moramo (umesto da podatak pročitamo neposredno), proći kroz nisku znakova (polje osoba je samo obična niska) i izdvojiti delove koji nas zanimaju (a ukoliko izdvojeni podatak nije tekstualnog tipa, izvesna količina procesorskog vremena biće utrošena i na konverziju podatka iz tekstualnog u brojčani zapis).

Umesto zapisa preko (jedne) niske, složeni podaci se dele na "atome" (podatke koji se ne mogu dalje usitnjavati) i za svaki podatak se (kao što smo već nagovestili) koristi odgovarajući tip.

U tabeli koju projektujemo, za id treba izabrati celobrojni tip, a za ime, prezime i e-mail - niske. Za datum ćemo koristiti specijalizovani tip podatka (datetime), koji nije običan tekst, već podatak brojčanog tipa.

Sada možemo definisati mnogo praktičniji format tabele prodavci:

		
polje           | vrednost            | tip podatka
-----------------------------------------------------
id:             | 1                   | celobrojni
ime:            | Jovan               | niska znakova  
prezime:        | Petrović            | niska znakova
ulica:          | Ruzveltova          | niska znakova
broj:           | 25                  | celobrojni
datum_rodjenja: | 18.08.1979.         | datum
zanimanje:      | Mašinski inženjer   | niska znakova
email:          | jocapet79@gmail.com | niska znakova
		
	
Slika 12. - Optimalna organizacija slogova u tabeli sa podacima o osobama: svaki podatak ima odgovarajući tip i smešten je u zasebno polje.

Setićemo se na ovom mestu izjave Alberta Ajnštajna koji je rekao da "stvari treba pojednostaviti koliko je god moguće, ali ne više od toga" i osvrnuti na to da navedeni princip važi i za ("atomske") podatke u bazama podataka: sasvim je izvodljivo da se ime osobe rastavi na pojedinačna slova, ali - to više nije ime ("atom"), već kolekcija pojedinačnih znakova koja nema pravo značenje (isto važi i za rastavljanje datuma na dan, mesec, godinu i sl).

Obrada podataka - Upiti i osnove SQL-a

Rad sa bazama podataka podrazumeva korišćenje tekstualnih komandi, bilo da bazama pristupamo preko programa koji dolaze uz RDBMS (konzolni program, web aplikacija), ili preko programskih jezika, pa je stoga neophodno dobro se upoznati sa sa SQL sintaksom.

U praksi, SQL sintaksa nije univerzalna i svaki RDBMS ima svoju sintaksu, * ali srećom, međusobne razlike u većini situacija nisu velike i suštinske, već je svaka konkretna SQL implementacija svojevrsna "varijacija na temu" i u svemu ima mnogo više sličnosti nego razlika.

* Za primere u članku, koristićemo MySql.

Pojam upita u bazama podataka

Upit je niz tekstualnih komandi (praktično, omanja skripta), preko kojih se od baze zahteva određeni vid obrade podataka: kreiranje nove baze ili nove tabele u postojećoj bazi, unos podataka, čitanje, izmena, brisanje, dodavanje ili uklanjanje kolona (a postoje i određene komande koje prevazilaze okvire ovog članka, te im nećemo posvećivati pažnju).

Upiti se pokreću upisivanjem (unapred definisanih) komandi programskog jezika SQL.

Jedna usputna napomena ....

Neki programi i sajtovi "deluju kao da koriste baze podataka", pa tako kada otvorimo stranicu sa informacijama o određenom filmu na sajtu IMDB, znamo da sajt "čita podatke iz baze". Sa druge strane, nedovoljno upućeni korisnici često nisu svesni da iza mnogih manje očiglednih primera (kao što su na primer chat aplikacije i društvene mreže uopšte), takođe stoje baze podataka.

Još se manje razmišlja o tome da se HTML stranice (u slučaju bilo kog iole ozbiljnije dizajniranog sajta), ne čuvaju na serveru u obliku u kome se prosleđuju browserima, već u obliku šablona koji se (kada se pozove određena stranica) popunjavaju podacima koji se preuzimaju iz (već znate šta ćemo reći) - baza podataka.

SQL (Structured Query Language) - osnovne postavke

SQL (Structured Query Language) je deskriptivni programski jezik koji se, u sistemima za upravljanje bazama podataka, koristi za prosleđivanje upita i pošto je u pitanju pristup koji se razlikuje od proceduralnih jezika, napravićemo malu digresiju i objasniti razliku.

Jezici u kojima je potrebno detaljno opisati postupak za rešavanje određenog problema (primer takvog jezika je C), pripadaju kategoriji proceduralnih programskih jezika.

Nasuprot tome, deskriptivni jezici (deskripcija = opis) koriste se za opisivanje (prirode) problema, korišćenjem unapred definisane sintakse - pri čemu se ne opisuje procedura za rešavanje problema.

Problem se rešava po unapred definisanoj proceduri na koju korisnik (DBMS-a) nema direktnog uticaja.

Na primer: U SQL-u, pri pozivanje upita za čitanje tabele, korisnik navodi tip upita (čitanje), naziv tabele i po potrebi ograničenja i uslove ("učitati i prikazati podatke iz tabele 'xyz', ali, samo podatke koji se odnose na jun 2020."), posle čega sistem pristupa pronalaženju podataka i prikazuje rezultat korisniku.

U nastavku ćemo prikazati upite preko kojih se može kreirati baza koju smo u uvodnim poglavljima koristili kao primer, s tim da ćemo se prvo upoznati (ukratko) sa konkretnim DBMS sistemom koji ćemo koristiti (a to je, kao što smo već nagovestili - MySql).

Kratak uvod u MySql

MySql je open-source sistem za upravljanje bazama podataka koji se može instalirati samostalno, a dolazi i uz XAMPP, paket aplikacija o kome smo pisali u članku o pokretanju lokalnog web servera.

U pitanju je kvalitetan i jednostavan * DBMS koji se koristi na brojnim sajtovima i omogućava veliku brzinu i efikasnost u obradi podataka.

* Sistem je jednostavan (što je samo po sebi dobro), ali istovremeno prilično moćan i bogat opcijama (što je - još bolje).

Za isprobavanje primera iz članka, pokrenite XAMPP (pokrenite servise Apache i MySql) i u browseru unesite adresu:

		
localhost/phpmyadmin
		
	
Slika 13. - Adresa za pokretanje web aplikacije phpmyadmin (koja je deo paketa XAMPP), preko koje se može pristupati MySql bazama

.... čime se pokreće web aplikacija preko koje se može pristupati bazama podataka na sistemu.

Kreiranje baze preko upita (CREATE DATABASE)

Da bismo mogli da prosleđujemo upite, potrebno je pokrenuti prozor za upis SQL upita (uz MySql dolazi i "pravi" konzolni program, ali, uvažićemo da je rad u grafičkom okruženju ipak udobniji za početno upoznavanje) ....

baze_podataka_01
Slika 14. - Otvaranje konzole za upis SQL upita u okviru web aplikacije phpmyadmin.

Novu bazu možemo kreirati preko sledećeg upita:

		
CREATE DATABASE prodaja;
		
	
Slika 15. - SQL upit za kreiranje nove baze podataka.

Prethodni upit poslužiće sasvim dobro, ali, ako želimo da budemo skroz "zvanični", možemo koristiti nešto potpuniji upit ....

		
CREATE DATABASE IF NOT EXISTS prodaja
	CHARACTER SET utf8
	COLLATE utf8_general_ci;
		
	
Slika 16. - SQL upit za kreiranje nove baze podataka.

.... koji će biti izvršen samo ukoliko baza sa navedenim imenom ne postoji (a usput smo definisali i metod enkodiranja znakova koji dozvoljava unos ćiriličnih i latiničnih znakova).

Naziv baze se poklapa sa nazivom tabele koju ćemo kreirati u nastavku, ali, to ne predstavlja problem i nije retka pojava, pogotovo u manjim bazama podataka.

Kreiranje tabela preko upita (CREATE TABLE)

Pošto smo kreirali novu bazu (i prethodno osmislili strukturu tabela), možemo napisati upite za kreiranje tabela:

		
USE prodaja;

CREATE TABLE prodavci
(
	id int NOT NULL AUTO_INCREMENT PRIMARY KEY,
	ime varchar(255) NOT NULL,
	prezime varchar(255) NOT NULL,
	datum_rodjenja datetime NOT NULL,
	adresa varchar(255) NOT NULL,
	broj int NOT NULL,
	email varchar(255) NOT NULL
);

CREATE TABLE prodaja
(
	id int NOT NULL AUTO_INCREMENT PRIMARY KEY,
	datum datetime NOT NULL,
	artikl varchar(255) NOT NULL,
	cena double NOT NULL,
	kolicina double NOT NULL,
	prodavac_id int NOT NULL,
	FOREIGN KEY (prodavac_id) REFERENCES prodaja(id)
)
		
	
Slika 17. - SQL upiti za kreiranje tabela "prodavci" i "prodaja". Više SQL upita možemo proslediti odjednom, pod uslovom da ih razdvojimo znakom ";" (napomena: takođe je bitno da poslednja instrukcija u svakom upitu bude zapisana bez zareza na kraju).

Kao što vidite, birali smo odgovarajuće tipove podataka za svako polje, polje id smo proglasili za primarni ključ u obe tabele, a dodali smo i direktivu AUTO_INCREMENT, koja će omogućiti da se (u obe tabele) id automatski uvećava za 1 pri unosu svakog novog sloga.

Na taj način smo sprečili pojavu dupliranih id-ova (do koje bi moglo doći ukoliko se od korisnika zahteva da sami unose id-ove), a korisnike smo rasteretili nepotrebne brige.

Takođe, primetimo da su dva nezavisna upita za kreiranje tabela odvojena znakom ; (tačka-zarez), što moramo raditi ukoliko želimo da dva ili više upita prosledimo odjednom.

Budući da se u tabeli prodaja pozivamo na id-ove prodavaca iz tabele prodavci (FOREIGN KEY (prodavac_id) REFERENCES prodaja(id)) i da je unos podataka u tabele naš sledeći korak, razmotrićemo mehanizam koji proverava da li prodavci koje navodimo u tabeli prodaja zapravo postoje.

Referencijalni integritet

Pretpostavljamo da mnogi čitaoci unapred razumeju šta bi moglo da se desi ukoliko pokušamo da u tabelu prodaja unesemo nepostojeći id prodavca, ali, prodiskutujmo ipak malo više o celoj problematici (uz pretpostavku da za početak ne vodimo računa o "referencijalnom integritetu").

Ukoliko u tabelu prodaja pokušamo da unesemo novi slog (na primer: prodaju artikla koju je obavio prodavac sa rednim brojem 17), podatak će biti uredno unet i tabela prodaja će sama po sebi biti korektna (još jednom - pretpostavka je da za sada ne vodimo računa o referencijalnom integritetu), međutim, ako nakon unosa prosledimo (drugi) upit, kojim se zahtevaju podaci o "fantomskom" prodavcu #17 - čiji id ne postoji u tabeli prodavci - sistem će (opravdano) prijaviti grešku.

Da se to ne bi dešavalo, potrebno je da baza podataka pri svakom upisu u određenu tabelu, vodi računa o tome da li spoljnji ključ koji se upisuje (zapravo) postoji u tabeli za koju je vezan, a pojam referencijalnog integriteta odnosi se upravo na mere koje DBMS preduzima da onemogući korisnike da se pozivaju na podatke (entitete) koji ne postoje u bazi.

U tabeli koju smo kreirali, budući da jeste podešeno da baza podataka vodi računa o referencijalnom integritetu, pri pokušaju unosa prodaje koju je obavio prodavac sa id-om 17, bila bi prijavljena greška i slog ne bi bio unet.

Upis podataka u tabele (INSERT)

Sada kada znamo da će nas MySql sprečiti da se "upucamo u nogu" (po pitanju referenciranja nepostojećih prodavaca), pogledajmo kako izgleda upit kojim se podaci unose:

		
INSERT INTO prodavci
	(ime, prezime, datum_rodjenja, email)
VALUES
	("Petar", "Jovanović", "1979-06-15", "petar_jovanovic@str.co.rs"),
	("Ivan", "Kovačević", "1993-04-27", "ivan_kovacevic@str.co.rs"),
	("Jelena", "Marković", "1984-09-09", "jelena_markovic@str.co.rs"),
	("Ana", "Stanković", "1991-08-17", "ana_stankovic@str.co.rs");

INSERT INTO prodaja
	(datum, artikl, cena, kolicina, prodavac_id)
VALUES
	("2020-06-15", "Sveska", 80.00, 4, 1),
	("2020-06-15", "Olovka", 56.57, 2, 1),
	("2020-06-15", "Gumica", 34.96, 2, 2),
	("2020-06-15", "Selotejp", 134.57, 1, 2);
		
	
Slika 18. - SQL upiti za upis podataka u tabele koje smo prethodno kreirali (valja primetiti da se podaci tekstualnog tipa zapisuju pod navodnicima, dok se podaci brojčanog tipa zapisuju bez navodnika).

Po pokretanju gornja dva upita, podaci su uredno uneti u obe tabele (nismo pokušali da "prevarimo" MySql) i možemo ih dalje čitati, menjati, brisati (ili obrađivati na neki drugi način).

Čitanje podataka preko upita (SELECT)

Osnovni upit za čitanje podataka iz određene tabele zadaje se preko komande SELECT i ima sledeću formu:

		
SELECT * FROM prodaja
		
	
Slika 19. - SQL upit koji vraća celokupan sadržaj tabele "prodaja" (sve slogove i sva polja).

Ovakav upit vratiće sve slogove iz tabele prodaja (odnosno: sva polja - svih slogova).

		
ime    | prezime   | datum_rodjenja | email
---------------------------------------------------------------
Petar  | Jovanović | 1979-06-15     | petar_jovanovic@str.co.rs
Ivan   | Kovačević | 1993-04-27     | ivan_kovacevic@str.co.rs
Jelena | Marković  | 1984-09-09     | jelena_markovic@str.co.rs
Ana    | Stanković | 1991-08-17     | ana_stankovic@str.co.rs
		
	
Slika 20. - Rezultat izvršavanja upita sa slike #13. - prikaz sadržaja tabele "prodavci".

Ukoliko želimo da (zarad preglednosti) prikažemo samo određena polja (recimo, id u prikazu često ne predstavlja bitnu informaciju), ili da upitu dodamo polja koja podrazumevaju obradu postojećih polja (da budemo jasni: takva polja vide se u pregledu koji dobijamo kao rezultat izvršavanja upita i nije u pitanje upis u tabelu), možemo navesti polja pojedinačno, preko sledeće sintakse:

		
SELECT
	datum,
	artikl,
	cena AS 'Cena po artiklu',
	kolicina AS 'Količina',
	cena * kolicina AS 'Ukupna cena po artiklu',
FROM
	prodaja
		
	
Slika 21. - SQL upit preko koga sadržaj tabele prodaja možemo prikazati na pregledniji način (podatke iz kolona cena i količina smo množili, a određenim poljima smo pripisali i takozvane alijase, alternativne nazive).

Prethodni upit vratiće tabelu sledeće sadržine:

		
datum      | artikl    | Cena po artiklu | Količina | Ukupna cena po artiklu
----------------------------------------------------------------------------
2020-06-15 | Sveska    | 80              | 4        | 320
2020-06-15 | Olovka    | 56.57           | 2        | 113.14
2020-06-15 | Gumica    | 34.96           | 2        | 69.92
2020-06-15 | Selotejp  | 134.57          | 1        | 134.57
		
	
Slika 22. - Rezultat izvršavanja upita sa slike 15. - pregled tabele prodaja, sa podešavanjima koje smo sami definisali.

Kao što vidimo, preko upita je moguće dobiti (privremenu) tabelu sa poljima koja predstavljaju rezultat obrade polja tabele (množili smo vrednosti polja cena i kolicina u okviru svakog sloga) i takođe je moguće za polja koristiti preglednije nazive ("alijase").

Da bismo na najbolji način zaokružili priču o SELECT upitima, prikazaćemo kako se podaci mogu filtrirati i sortirati.

  • za filtriranje se koristi klauza WHERE (uz precizno navođenje uslova)
  • za sortiranje se koristi naredba ORDER BY (uz navođenje konkretnog kriterijuma za sortiranje)

Sledeći upit:

		
SELECT
	datum,
	artikl,
	cena AS 'Cena po artiklu',
	kolicina AS 'Količina',
	cena * kolicina AS 'Ukupna cena po artiklu'
FROM
	prodaja
WHERE
	cena * kolicina > 300
ORDER BY
	cena * kolicina DESC
		
	
Slika 23. - SQL upit sa filtriranjem i sortiranjem slogova.

.... vratiće samo slogove kod kojih je ukupna vrednost (jednog) prodatog artikla veća od 300 Din (za razliku od upita SELECT bez klauze WHERE - koji vraća sve slogove), a tabela će biti sortirana u nerastući poredak po kriterijumu najveće cene.

Upit za ažuriranje slogova (UPDATE)

Za izmenu već unetih podataka, koristi se komanda UPDATE:

		
UPDATE
	prodavci
SET
	email='petar_m_jovanovic@str.co.rs'
WHERE
	id=1
		
	
Slika 24. - SQL upit za ažuriranje slogova: moramo voditi računa da navedemo uslov za pristup određenim slogovima ("WHERE id=1"), jer ćemo u suprotnom navedeni podatak ("petar_m_jovanovic@str.co.rs") upisati u polje "email" SVIH slogova!

Komanda UPDATE sama po sebi nije destruktivna (u tom smislu da ne briše slogove), ali, pri korišćenju ove komande moramo biti jako pažljivi jer će, ukoliko se izostavi WHERE klauza, koja precizira koje slogove je potrebno ažurirati - biti ažurirani svi slogovi!

(Uz klauzu WHERE id=1, upit se odnosi samo na slog u kome polje id ima vrednost 1.)

U prethodnom primeru, ako se izostavi WHERE klauza, polje email će u svakom slogu dobiti vrednost "petar_m_jovanovic@str.co.rs".

Upit za uklanjanje slogova (DELETE)

Za uklanjanje slogova, koristi se komanda DELETE:

		
DELETE FROM
	prodavci
WHERE
	id=3
		
	
Slika 25. - SQL upit za uklanjanje slogova: ovoga puta moramo biti još i pažljiviji nego u slučaju upita za ažuriranje i nikako ne smemo izostaviti uslov, jer će u suprotnom biti obrisani SVI SLOGOVI.

Naveli smo da pri pozivanju upita za ažuriranje moramo biti veoma pažljivi i da ne smemo izostaviti uslov (osim naravno ukoliko nam jeste namera da polje u svim redovima dobije istu vrednost). *

Sa komandom DELETE moramo biti još pažljiviji, jer je upit za brisanje destruktivan: pri pozivu komande uz uslov, dolazi do nepovratnog brisanja jednog reda (što je slučaj sa gornjim primerom; inače uslov može obuhvatiti i više redova)

Ako se uslov izostavi, biće obrisani SVI slogovi !

Stoga - budite jako pažljivi. :)

* Upit UPDATE bez WHERE klauze, kojim zapravo želimo da ažuriramo celu kolonu je krajnje legitimna pojava u radu sa bazama podataka (iako možda nije nešto sa čime se srećemo često, pogotovo ne svakodnevno)

Primer: posle dodavanja polja P3 u tabelu T, potrebno je polju P3 (u svim slogovima) dodeliti vrednost P1 * P2.

Međutim, ponovo apel sa naše strane: uvek pazite da se "ne pređete" sa komandama UPDATE i DELETE.

Izmena strukture tabele (ALTER TABLE, DROP)

Za izmenu strukture tabele možemo koristiti komande ALTER TABLE i DROP. Komanda ALTER TABLE (preko prigodne sintakse) umeće nove kolone u postojeće tabele, dok se preko komande DROP uklanjaju postojeće kolone (naravno, sa pripadajućim sadržajem).

Ako je u tabelu prodavci potrebno dodati (na primer) kolonu sa kućnim adresama prodavaca, možemo koristiti sledeći kod:

		
ALTER TABLE
	prodavci
ADD
	kucna_adresa varchar(255) NOT NULL
AFTER
	email;
		
	
Slika 26. - SQL upit za dodavanje nove kolone.

Ako je iz tabele potrebno ukloniti određenu kolonu (za svaki slučaj ćemo ukloniti samo kolonu sa kućnim adresama, koju smo dodali samo zarad primera i koja nam ne treba za prave), možemo koristiti komandu DROP (u kombinaciji sa komandom ALTER TABLE):

		
ALTER TABLE
	prodavci
DROP
	kucna_adresa
		
	
Slika 27. - SQL upit za uklanjanje postojeće kolone.

Komanda DROP može se koristiti i za uklanjanje celokupnih tabela (kao i same baze), tako da ovoga puta još izričitije upozorenje:

Budite veoma, VEOMA! pažljivi pri korišćenju komande DROP (i pročitajte ovu rečenicu još koji put). :)

Implementacija relacija u MySql-u

Iako ćemo se u većini svakodnevnih situacija sretati sa relacijom 1:n, potrebno je biti upoznat i sa načinima za implementiranje relacija 1:1 (sa kojom ćete se sretati bar ponekad) i pogotovo n:n (koja se sreće prilično često).

Implementacija relacije 1:n

Pri implementaciji relacije 1:n, ne moramo preduzimati dodatne korake (budući da je u pitanju podrazumevana relacija).

Za primer (i podsećanje) uzećemo tabele prodavci i prodaja (koje smo već koristili) i ukoliko pri definisanju polja ne navedemo dodatna ograničenja:

		
CREATE TABLE prodaja
(
	--ostala polja,
	prodavac_id int NOT NULL,
	FOREIGN KEY (prodavac_id) REFERENCES prodavci(id)
)
		
	
Slika 28. - SQL kod za definisanje novog polja, ako ne navedemo da se podatak u koloni može pojaviti samo jednom, važiće relacija "jedan prema više".

.... bilo koji od entiteta koji se referenciraju preko spoljnjeg ključa moći će u tabeli da se pojavi više puta (u gornjem primeru - prodavac, predstavljen preko spoljnjeg ključa prodavac_id, moći će da se pojavi u proizvoljnom broju slogova u tabeli prodaja).

Implementacija relacije 1:1

Relacija 1:1 takođe se definiše na jednostavan način, tako što se uz odgovarajuće polje navodi rezervisana reč UNIQUE.

Uzmimo (ponovo) za primer gradove, države i glavne gradove:

baze_podataka_erm_01
Slika 29. - Dijagram baze podataka "glavni_gradovi".

Tabele sa prethodnog dijagrama mogu se kreirati i povezati preko sledećeg SQL koda:

		
CREATE DATABASE IF NOT EXISTS mini_geografija
	CHARACTER SET utf8,
	COLLATE utf8_general_ci;

USE mini_geografija;

CREATE TABLE grad
(
	id int NOT NULL AUTO_INCREMENT PRIMARY KEY,
	naziv varchar(255) NOT NULL,
	broj_stanovnika int NOT NULL
);

CREATE TABLE drzava
(
	id int NOT NULL AUTO_INCREMENT PRIMARY KEY,
	naziv varchar(255) NOT NULL,
	broj_stanovnika int NOT NULL
);

CREATE TABLE glavni_gradovi
(
	id int NOT NULL AUTO_INCREMENT PRIMARY KEY,

	-- uz polje grad_id mora stajati odrednica UNIQUE
	grad_id int NOT NULL UNIQUE,
	
	-- uz polje drzava_id (praktično) NE SME
	-- stajati odrednica UNIQUE
	drzava_id NOT NULL,
	
	FOREIGN KEY (grad_id) REFERENCES gradovi(id),
	FOREIGN KEY (drzava_id) REFERENCES drzave(id)
)
		
	
Slika 30. - SQL kod kojim se definiše struktura baze podataka "glavni_gradovi".

Ranije smo razmotrili da se grad u tabeli glavni_gradovi može pojaviti jednom, a država više puta, pa stoga, pri definiciji polja tabele glavni_gradovi: uz grad (grad_id) stoji odrednica UNIQUE, dok uz državu (drzava_id) ne stoje nikakve dodatne odrednice.

Implementacija relacije n:n

Videli smo da se relacije 1:1 i (pogotovo) 1:n, implementiraju na vrlo jednostavan način, međutim, za implementaciju relacije n:n je potrebno malo više pažnje i dodatna tabela.

Ako se vratimo na primer sa povezivanjem filmova i glumaca, vidimo da sledeći format sloga u tabeli glumci (kao što smo već nagovestili):

		
id:      1,
ime:     "Danilo",
nadimak: "Bata",
prezime: "Stojković",
filmovi: "Varljivo Leto '68; Ko to tamo peva; Maratonci trče počasni krug;"
		
	
Slika 31. - Primer (veoma) lošeg pokušaja definisanja relacije "više prema više".

.... umnogome podseća na neodgovarajuće formate za povezivanje entiteta (sa kojima smo se već sretali u članku) - i da stoga ne predstavlja optimalno rešenje za implementaciju relacije n:n.

Budući da zapis podataka preko jedne tabele nije optimalno rešenje, relacija n:n se u praksi tipično implementira preko posredničke tabele (preko koje se spajaju id-ovi odgovarajućih entiteta):

baze_podataka_erm_02
Slika 32. - Dijagram baze podataka "glumci_i_filmovi".

Jedna od osnovnih tabela (u konkretnom primeru), sadrži podatke o glumcima:

		
CREATE TABLE glumci
(
	id int NOT NULL AUTO_INCREMENT PRIMARY KEY,
	ime varchar(255) NOT NULL,
	prezime varchar(255) NOT NULL,
	datum_rodjenja datetime NOT NULL
)
		
	
Slika 33. - SQL kod za kreiranje tabele "glumci".

Druga osnovna tabela sadrži podatke o filmovima:

		
CREATE TABLE filmovi
(
	id int NOT NULL AUTO_INCREMENT PRIMARY KEY,
	naziv varchar(255) NOT NULL,
	godina_proizvodnje int NOT NULL
)
		
	
Slika 34. - SQL kod za kreiranje tabele "filmovi".

.... a preko dodatne (posredničke) tabele se entiteti iz prve tabele (glumci) spajaju sa entitetima iz druge tabele (filmovima):

		
CREATE TABLE povezivanje
(
	id int NOT NULL AUTO_INCREMENT PRIMARY KEY,
	glumac_id int NOT NULL,
	film_id int NOT NULL,

	FOREIGN KEY (glumac_id) REFERENCES glumci(id),
	FOREIGN KEY (film_id) REFERENCES filmovi(id)
)
		
	
Slika 35. - SQL kod za kreiranje posredničke tabele "povezivanje".

Na ovaj način smo omogućili povezivanje proizvoljnog broja glumaca sa proizvoljnim brojem filmova (a budući da smo uključili proveru referencijalnog integriteta, možemo se osloniti na to da će sam DBMS voditi računa da se u tabeli povezivanje ne pojave glumci koji ne postoje u bazi, niti filmovi koji ne postoje u bazi).

Ugnežđeni upiti

Za kraj smo ostavili nešto složenije upite.

Ugnežđeni upiti (prva kategorija kompleksnijih upita o kojima ćemo diskutovati u uvodnom članku) podrazumevaju korišćenje određenog upita unutar drugog upita.

Za primer ćemo uzeti operaciju koju smo nagovestili na početku: potrebno je pronaći (sve) podatke o prodavcu koji je obavio određenu prodaju.

Ako se pitate "u čemu je problem", podsetićemo se organizacije podataka koju smo (takođe) uveli na početku:

  • u tabeli prodaja upisani su samo id-ovi prodavaca (iz tabele prodavci)
  • u tabeli prodavci (naravno) ne postoje podaci o prodaji artikala (to jest, podaci o tome ko je od prodavaca prodao koji artikl)

Problem se može rešiti preko sledećeg upita ....

		
SELECT
	ime, prezime, email
FROM
	prodavci
WHERE
	prodavci.id = (SELECT prodaja.prodavac_id FROM prodaja WHERE prodaja.id=3)
		
	
Slika 36. - Ugnežđeni SQL upit za pronalaženje podataka o prodavcu, preko id-a iz tabele prodaja.

.... koji prvo pronalazi id prodavca, posle čega se nađeni id koristi u upitu koji pretražuje tabelu prodavci.

Da pojasnimo:

  • prvo se izvršava unutrašnji upit (koji je obuhvaćen zagradama)
  • navedeni upit se svodi na jedan (brojčani) podatak
  • navedeni podatak koristi se kao kriterijum za pretragu u WHERE klauzi glavnog upita

Po koracima:

"Unutrašnji" SELECT upit ....

		
SELECT prodaja.prodavac_id FROM prodaja WHERE prodaja.id=3
		
	
Slika 37. - Deo upita sa prethodne slike ("unutrašnji upit").

.... praktično pronalazi (samo) id prodavca koji je u tabeli prodaja - obavio određenu prodaju.

Rezultat izvršavanja unutrašnjeg upita je jedna celobrojna vrednost, posle čega "glavni" upit praktično postaje:

		
SELECT
	ime, prezime, email
FROM
	prodavci
WHERE
	prodavci.id = 2
		
	
Slika 38. - Posle izvršavanja "unutrašnjeg" upita, "glavni" upit se svodi na pronalaženje prodavca preko id-a.

.... što (samo po sebi) predstavlja vrlo jednostavan upit sa kakvim smo se susreli već na početku.

Još jedan "nerd alert" (u vezi sa sledećim poglavljem): upiti za pridruživanje preko komande JOIN umeju da "zadaju muke" pri početnom upoznavanju sa bazama podataka (i naravno da smo ih zato ostavili za sam kraj).

Ali - samo hrabro! :)

Pridruživanje podataka (JOIN)

Za sam kraj, sagledaćemo još jedan primer koji smo nagovestili na početku (i upoznati se sa 'tehnikalijama' koje stoje iza svega).

Zadatak je: napraviti pregled prodaje u kome su prodavci predstavljeni imenom i prezimenom.

Preko komande JOIN moguće je spojiti (i prikazati) podatke iz dve ili više tabela, pri čemu se kao kriterijum za spajanje koriste podaci koji se pojavljuju u svim tabelama koje učestvuju u spajanju (naravno, kriterijumi se moraju precizno navesti).

Izvršavanjem sledećeg upita:

		
SELECT
	prodaja.datum,
	prodaja.artikl,
	prodaja.cena,
	prodaja.kolicina,
	prodavci.ime,
	prodavci.prezime
FROM
	prodavci INNER JOIN prodaja
ON
	prodavci.id = prodaja.prodavac_id
		
	
Slika 39. - Primer upotrebe komande INNER JOIN, preko koje se objedinjuju podaci iz više tabela (u ovom slučaju dve), preko određenog kriterijuma (podataka koji se pojavljuju u obe tabele).

.... u pregledu se pojavljuju sve pojedinačne prodaje artikala uz koje stoji ime i prezime prodavca, a rezultat izvršavanja je sledeća tabela:

		
datum      | artikl   | cena   | kolicina | ime   | prezime
-------------------------------------------------------------
2020-06-15 | Sveska   | 80     | 4        | Petar | Jovanović
2020-06-15 | Olovka   | 56.57  | 2        | Petar | Jovanović
2020-06-15 | Gumica   | 34.96  | 2        | Ivan  | Kovačević
2020-06-15 | Selotejp | 134.57 | 1        | Ivan  | Kovačević
		
	
Slika 40. - Rezultat izvršavanja upita sa slike #38.

Na ovom mestu se (rekli bismo sasvim prirodno) postavlja i pitanje: kako se tačno podaci izdvajaju i spajaju i kako će (tačno) slogovi biti sortirani?

Kako se podaci spajaju i izdvajaju?

Glavni primer koji koristimo u članku (baza podataka o prodaji artikala) nije u stanju da obuhvati sve situacije do kojih može doći pri spajanju podataka preko komande JOIN, pa ćemo se stoga (privremeno) poslužiti drugim primerom.

Dakle: malo poduža digresija, posle čega se vraćamo na bazu prodaja.

Dodatni primer biće baza podataka koja beleži upis studenata na kurseve (na početku akademske godine), a implementiraćemo je preko sledećih tabela:

  • studenti - podaci o studentima
  • kursevi - podaci o kursevima
  • upis - pomoćna tabela preko koje se povezuju podaci iz prethodne dve tabele
baze_podataka_erm_03
Slika 41. - Dijagram baze podataka "upis_studenata".

Za kreiranje i popunjavanje tabela, možete iskoristiti SQL kod sa sledećeg linka: Primer - baza: studenti_upis

Pre nego što se osvrnemo na različite varijacije komande JOIN, osvrnimo se (pre svega) na različite situacije opšteg tipa koje se daju zamisliti tokom upisa studenata na kurseve. *

Dok upis (još uvek) traje, lako je zamisliti da u određenom trenutku:

  • među studentima ima onih koji se još nisu prijavili ni za jedan kurs
  • postoje kursevi za koje nema prijava (i naravno)
  • postoje kursevi za koje je prijavljen određen broj studenata

Različite varijante komande JOIN mogu nam pomoći da dođemo do navedenih informacija.

Razmatraćemo šematski prikaz u kome su tabele popunjene, tako da je određen broj studenata (s1-s4) prijavljen je za određen broj kurseva (k1-k3, k5), preko tabele upis:

baze_podataka_join_01
Slika 42. - Šematski prikaz popunjenih tabela u bazi podataka "upis_studenata".

* Postoje i druge situacije koje inače mogu biti od interesa (na primer, mogli bismo proveriti da li je svaki student prijavljen za najmanje tri kursa i sl), ali budući da se radi o situacijama u kojima komanda JOIN nije od prevelike pomoći, prepustićemo čitaocima da takve upite probaju samostalno.

INNER JOIN

Ako za spajanje pozivamo komandu INNER JOIN, upit će izdvojiti samo one slogove iz tabele studenti koji su se pojavili u tabeli upis i samo one slogove iz tabele kursevi koji su se pojavili u tabeli upis.

		
SELECT
	CONCAT(studenti.ime, " ", studenti.prezime) AS "Student",
	studenti.br_indeksa,
	kursevi.naziv AS "Kurs"
FROM
	(studenti INNER JOIN upis ON studenti.id=upis.student_id)
	INNER JOIN kursevi ON kursevi.id=upis.kurs_id
ORDER BY
	studenti.id, kursevi.id
		
	
Slika 43. - Primer izvršavanja komande INNER JOIN nad tabelama u bazi podataka "upis_studenata".

U praktičnom smislu: u pregledu će se pojaviti samo studenti koji su se prijavili za kurseve i samo oni kursevi za koje postoje prijave.

baze_podataka_join_02
Slika 44. - Šematski prikaz rezultata upita sa slike #43.

Takođe možemo reći i da se u upitu neće pojaviti studenti koji se nisu prijavili za kurseve, niti kursevi za koje ne postoje prijave.

LEFT JOIN

Ako za spajanje pozivamo komandu LEFT JOIN, upit će izdvojiti sve slogove iz tabele studenti i samo one slogove iz tabele kursevi koji su se pojavili u tabeli upis.

		
SELECT
	CONCAT(studenti.ime, " ", studenti.prezime) AS "Student",
	studenti.br_indeksa,
	kursevi.naziv AS "Kurs"
FROM
	(studenti LEFT JOIN upis ON studenti.id=upis.student_id)
	LEFT JOIN kursevi ON kursevi.id=upis.kurs_id
ORDER BY
	studenti.id, kursevi.id
		
	
Slika 45. - Primer izvršavanja komande LEFT JOIN nad tabelama u bazi podataka "upis_studenata".

U praktičnom smislu: u pregledu će se pojaviti svi studenti i samo oni kursevi za koje postoje prijave.

baze_podataka_join_03
Slika 46. - Šematski prikaz rezultata upita sa slike #45.

U prethodnom slučaju (INNER JOIN), po izvršavanju upita je pored podataka svakog studenta bio je naveden jedan od kurseva (to nismo prikazali na "plavo-zelenim" šemama, ali, možete primetiti ako pokrenete upite).

U slučaju komande LEFT JOIN, pored podataka o studentu koji nije prijavljen za kurs, stajaće vrednost NULL (na slici simbolično označeno tamnijom nijansom plave).

Na ovaj način (i s obzirom na metodu sortiranja koju smo izabrali), lako u rezultujućoj tabeli možemo primetiti studente koji nisu prijavljeni za kurseve.

.... a za vežbu možete prepraviti upit tako da se u rezultujućoj tabeli pojave samo studenti koji nisu prijavljeni za kurseve.

RIGHT JOIN

Ako za spajanje pozivamo komandu RIGHT JOIN, upit će izdvojiti samo one slogove iz tabele studenti koji su se pojavili u tabeli upis i (ovoga puta) sve slogove iz tabele kursevi.

		
SELECT
	CONCAT(studenti.ime, " ", studenti.prezime) AS "Student",
	studenti.br_indeksa,
	kursevi.naziv AS "Kurs"
FROM
	(studenti RIGHT JOIN upis ON studenti.id=upis.student_id)
	RIGHT JOIN kursevi ON kursevi.id=upis.kurs_id
ORDER BY
	studenti.id, kursevi.id
		
	
Slika 47. - Primer izvršavanja komande RIGHT JOIN nad tabelama u bazi podataka "upis_studenata".

U praktičnom smislu: u pregledu će se pojaviti prijavljeni studenti i svi kursevi, pa će ovoga puta, s obzirom na izabranu metodu sortiranja, biti lako uočiti kurseve za koje ne postoje prijave (na slici označeno tamnijom nijansom zelene).

baze_podataka_join_04
Slika 48. - Šematski prikaz rezultata upita sa slike #46.

Takođe, i ovoga puta možete (za vežbu) prepraviti upit tako da se u rezultujućoj tabeli pojave samo kursevi za koje nije bilo prijava.

OUTER JOIN

Ako za spajanje koristimo komandu OUTER JOIN, upit će prikazati sve studente i sve kurseve.

		
SELECT
	CONCAT(studenti.ime, " ", studenti.prezime) AS "Student",
	studenti.br_indeksa,
	kursevi.naziv AS "Kurs"
FROM
	(studenti FULL OUTER JOIN upis ON studenti.id=upis.student_id)
	FULL OUTER JOIN kursevi ON kursevi.id=upis.kurs_id
ORDER BY
	studenti.id, kursevi.id
		
	
Slika 49. - Primer izvršavanja komande OUTER JOIN nad tabelama u bazi podataka "upis_studenata".

Ukoliko je količina podataka umerena, ovakav pregled može (na primer) biti od koristi ako se rezultujuća tabela odštampa (pa odokativnom metodom možemo primetiti informacije koje nas zanimaju).

baze_podataka_join_05
Slika 50. - Šematski prikaz rezultata upita sa slike #48.

Kako god da smo izdvojili podatke, ostaje pitanje kako će slogovi u rezultujućoj tabeli biti sortirani.

Na ovom mestu, vraćamo se na prvobitni primer sa tabelom prodaja (koji ćemo koristiti do kraja).

Sortiranje podataka

Ako pri prosleđivanju upita ne navedemo kriterijum za sortiranje, DBMS će samostalno izabrati način sortiranja (prema rasporedu podataka u memoriji i sl), pa je stoga - ukoliko je potrebno da podaci budu precizno sortirani prema određenom kriterijumu - svakako najbolje da kriterijum navedemo sami.

U primerima sa upisom studenata smo "prećutno" birali odgovarajući metod sortiranja, a za primer iz baze prodaja smo izabrali da podaci budu sortirani po datumu prodaje: ORDER BY prodaja.datum:

		
SELECT
	prodaja.datum,
	prodaja.artikl,
	prodaja.cena,
	prodaja.kolicina,
	prodavci.ime,
	prodavci.prezime
FROM
	prodavci INNER JOIN prodaja
ON
	prodavci.id = prodaja.prodavac_id
ORDER BY
	prodaja.datum
		
	
Slika 51. - Primer upotrebe komande INNER JOIN - sa preciziranim kriterijumom za sortiranje slogova.

Uprošćena sintaksa za INNER JOIN i još jedan praktičan primer

SQL dozvoljava da se upit za spajanje podataka, za šta bismo inače koristili INNER JOIN, pozove preko drugačije (i reklo bi se - elegantnije) sintakse:

		
SELECT
	prodaja.datum,
	prodaja.artikl,
	prodaja.cena,
	prodaja.kolicina,
	prodavci.ime,
	prodavci.prezime
FROM
	prodavci, prodaja
WHERE
	prodavci.id = prodaja.prodavac_id
ORDER BY
	prodaja.datum
		
	
Slika 52. - Alternativni način pozivanja komande INNER JOIN.

.... što deluje "manje zvanično" (budući da ne koristimo komandu INNER JOIN) - ali je rezultat isti.

Preostaje i da se osvrnemo na još jedan primer (koji možete isprobati i preko uprošćene sintakse koju smo maločas prikazali).

Primećujemo da polja cena i kolicina ne upućuju baš najbolje na prirodu podataka koji se pojavljuju u kolonama: za cenu nije naznačeno da li je u pitanju cena po jedinici količine (ili ukupna cena za artikl, koja uzima u obzir i količinu), nije precizirano da li je u cenu uračunat porez (ili nije), za količinu nije naznačena jedinica mere (a mogli bismo navesti i još detalja).

Takođe, što se tiče prodavaca, izdvajanje imena i prezimena u zasebne kolone deluje previše "zvanično" u ovakvom upitu.

Shodno svemu navedenom, upit možemo sažeti i upotpuniti na sledeći način:

		
SELECT
	prodaja.artikl,
	CONCAT(prodaja.cena * prodaja.kolicina, "din.")
		AS "Cena po artiklu (bez PDV-a)",
	CONCAT(prodavci.ime, " ", prodavci.prezime)
		AS "Prodavac"
FROM
	prodavci INNER JOIN prodaja
ON
	prodavci.id = prodaja.prodavac_id
ORDER BY
	prodaja.datum
		
	
Slika 53. - Praktičan primer upita koji koristi INNER JOIN, obradu podataka iz više kolona (cena * količina; konkatenacija niski ime i prezime) i takođe, alijase.
  • polja cena i kolicina, iz tabele prodaja množimo, a rezultat množenja predstavljamo preko aliasa "Cena po artiklu (bez PDV-a)"
  • podatke o prodavcu spajamo preko funkcije CONCAT *

* "Konkatenacija" je zvaničan (i u opštem smislu retko upotrebljavan) naziv za operaciju spajanja niski.

Pokretanjem gornjeg upita, dobijamo tabelu sledećeg sadržaja:

		
artikl   | Cena po artiklu (bez PDV-a) | Prodavac
--------------------------------------------------------
Sveska   | 320                         | Petar Jovanović
Olovka   | 113.14                      | Petar Jovanović
Gumica   | 69.92                       | Ivan Kovačević
Selotejp | 134.57                      | Ivan Kovačević
		
	
Slika 54. - Rezultat izvršavanja upita sa slike #52.

Sledeći korak u vezi sa bazama podataka (u smislu članaka koje ćemo objaviti - čemu ćemo se posvetiti u (vrlo) doglednoj budućnosti), biće korišćenje baza podataka u programskim jezicima (konkretno: MySql i PHP).

Autor članka Nikola Vukićević Za web portal www.codeblog.rs
Napomena: Tekstovi, slike, web aplikacije i svi ostali sadržaji na sajtu www.codeblog.rs (osim u slučajevima gde je drugačije navedeno) predstavljaju intelektualnu svojinu autora sajta www.codeblog.rs i zabranjeno je njihovo korišćenje na drugim sajtovima i štampanim medijima, kao i bilo kakvo drugo korišćenje u komercijalne svrhe, bez eksplicitnog pismenog odobrenja autora.
© 2020-2023. Sva prava zadržana.
Viber
početna Početna > Članci > Uvod u relacione baze podataka i SQL

Info & povezani članci Info o članku - dugme

Info

trejler_sat Datum objave: 26.10.2020.

trejler_sat Poslednja izmena: ----

trejler_dokument Jezici: SQL

trejler_teg_crveni Težina: 8/10

Povezani članci

Strukture podataka Objektno orijentisano programiranje Regularni izrazi - napredna pretraga teksta Uvod u Javascript i DOM JSON - tekstualni format za predstavljanje objekata Operacije sa bitovima u progamskom jeziku C Uvod u web dizajn - 1. deo - Početni koraci HTTP - Statusni kodovi Napredna Google pretraga Kako napraviti dobru lozinku Ostali članci
When you see a good move, look for a better one
Emanuel Lasker (šahovski velemajstor)
codeBlog codeBlog
Projekat posvećen popularizaciji kulture i veštine programiranja.
Napomena: Tekstovi i slike na sajtu www.codeblog.rs (osim u slučajevima, gde je drugačije navedeno) predstavljaju intelektualnu svojinu autora sajta www.codeblog.rs i zabranjeno je njihovo korišćenje na drugim sajtovima i štampanim medijima, kao i bilo kakvo drugo korišćenje u komercijalne svrhe, bez eksplicitnog odobrenja autora.
© 2020-2023. Sva prava zadržana.
Facebook - logo
Instagram - logo
LinkedIn - logo
Twitter - logo
E-mail
Naslovna
   •
Uslovi korišćenja
   •
Obaveštenja
   •
FAQ
   •
Kontakt