Dio 1 – ER model i tabele
Dio 2 – Normalizacija
Dio 3 – SQL, indeksi, transakcije
Praksa – SQLite: tabele, upiti, transakcija
Materijale priredio Erkan Islamović
erkan.islamovic@eubd.edu.ba · erkan.islamovic@gmail.com
Petnaest sedmica, svaka s tri časa teorije i tri časa prakse. Danas: poglavlje 8 (Sedmica 8, označeno). Isti primjer kroz knjigu: DeliveryApp, narudžba N-1001 (24,50 €).
| Sed. | Poglavlje | Sed. | Poglavlje |
|---|---|---|---|
| 1 | P1 Zašto projektujemo | 9 | P9 Arhitektura |
| 2 | P2 Životni ciklus | 10 | P10 Integracija i API |
| 3 | P3 Zahtjevi | 11 | P11 Korisnički interfejs |
| 4 | P4 Poslovni procesi | 12 | P12 Sigurnost i privatnost |
| 5 | P5 UML i slučajevi korištenja | 13 | P13 Realno vrijeme i IoT |
| 6 | P6 Klase i objekti | 14 | P14 Kvalitet i isporuka |
| 7 | P7 Sekvence i stanja | 15 | P15 AI i završni projekat |
| 8 | P8 Baza podataka |
Šezdesetih godina podaci velikih firmi čuvali su se u bazama koje su ličile na lavirint: da bi program našao narudžbu jednog kupca, morao je znati tačan put kroz pokazivače od zapisa do zapisa. Kad bi se struktura promijenila, svi programi su se morali prepisati. Matematičar Edgar F. Codd, koji je radio u istraživačkom centru firme IBM u Kaliforniji, predložio je 1970. godine nešto sasvim drugačije, u radu „A Relational Model of Data for Large Shared Data Banks” („Relacioni model podataka za velike zajedničke banke podataka”). Podaci se čuvaju u običnim tabelama, a veza između tabela nije skriveni pokazivač nego ista vrijednost u dvije kolone. Program ne pita „kojim putem do podatka”, nego samo „šta želim”, a baza sama nađe put. U početku ni sam IBM nije žurio s tom idejom, jer je zarađivao na starim bazama. Ali ideja je bila prejaka: krajem sedamdesetih pojavile su se prve komercijalne relacione baze s jezikom SQL, a danas skoro svaka banka, bolnica, prodavnica i aplikacija za dostavu čuva podatke upravo u Coddovim tabelama. Codd je 1981. dobio Turingovu nagradu.
U poglavlju 6 nacrtali smo domenski model DeliveryApp: klase Kupac, Restoran, Jelo, Narudžba, Stavka i ostale, s vezama i višestrukošću. Klase žive u memoriji dok program radi; kad se server ugasi, nestanu. Podaci moraju preživjeti: narudžba N-1001 mora postojati i sutra, i za pet godina u izvještaju. Ovo poglavlje pretvara domenski model u relacionu bazu podataka. Prvo učimo tabele, ključeve i ER dijagram, i pravila kako se klase pretvaraju u tabele. Zatim normalizaciju, koja sprečava da isti podatak bude zapisan na dva mjesta. Na kraju pišemo SQL: pravimo tabele, punimo ih, postavljamo upite i čuvamo narudžbu u transakciji. Na praktičnoj nastavi sve radimo u SQLiteu i provjeravamo da baza za N-1001 daje 24,50 €.
Nakon poglavlja možete:
objasniti zašto se podaci DeliveryApp čuvaju u bazi, a ne u tabeli Excela; opisati tabelu, red, kolonu, primarni i strani ključ; nacrtati ER dijagram s vrstama veza; pretvoriti klasni dijagram u tabele po pravilima (1:N, M:N, kompozicija); prepoznati anomalije u nenormalizovanoj tabeli; dovesti tabelu do treće normalne forme; napisati CREATE TABLE s ključevima i ograničenjima; napisati INSERT, SELECT s JOIN i GROUP BY; izračunati u SQL-u zbir artikala N-1001 (2150 centi); objasniti čemu služi indeks; objasniti transakciju i osobine ACID na primjeru narudžbe i plaćanja.
| Čas | Sadržaj | Na času |
|---|---|---|
| Teorija 1 | 8.1–8.3 Zašto baza; tabele i ključevi; od klasa do tabela | ER dijagram DeliveryApp |
| Teorija 2 | 8.4–8.6 Anomalije i normalizacija | Jedna velika tabela → pet malih |
| Teorija 3 | 8.7–8.9 SQL, upiti, indeksi, transakcije | SUM za N-1001 = 2150; ACID |
| Praksa 1–3 | SQLite: schema.sql, podaci.sql, upiti, transakcija | baza.py ispisuje 2450 |
Prvi DeliveryApp bi se mogao voditi u Excelu: jedna tabela, red po narudžbi. Zašto to ne valja čim sistem počne raditi?
| Problem | U Excel tabeli | U bazi |
|---|---|---|
| Istovremeni rad | dva konobara otvore isti fajl; jedan pregazi drugog | baza pušta stotine korisnika istovremeno, bez gaženja |
| Ispravnost | u ćeliju za količinu može se upisati „dva” ili −3 | ograničenje CHECK (kolicina > 0) odbije pogrešan unos |
| Veze | ime restorana se prepisuje u svaki red | restoran je zapisan jednom, narudžbe pokazuju na njega |
| Pretraga | filter radi dobro do nekoliko hiljada redova | indeks nađe jedan red među milionima za djelić sekunde |
| Greška usred posla | struja nestane dok se upisuje; fajl je napola sačuvan | transakcija: sve ili ništa |
| Pristup | ko ima fajl, vidi sve | svaka uloga vidi samo svoje (poglavlje 12) |
Excel je odličan alat za računanje i analizu (izvještaj na kraju mjeseca). Baza je alat za trajno, ispravno i zajedničko čuvanje podataka. Mnogi sistemi koriste oboje: podaci žive u bazi, a izvještaji se izvoze u tabelu.
Program koji vodi bazu zove se sistem za upravljanje bazom podataka (SUBP; engleski database management system, DBMS). U ovoj knjizi koristimo SQLite, koji dolazi uz Python i čuva cijelu bazu u jednom fajlu. U velikim sistemima češći su PostgreSQL, MySQL, SQL Server i Oracle; jezik SQL je u svima skoro isti.
U relacionoj bazi sve je u tabelama. Tabela čuva podatke o jednoj vrsti stvari: restorane, jela, narudžbe. Svaki red je jedna stvar (jedan restoran), a svaka kolona jedno svojstvo (naziv, je li otvoren).
Tabela restoran:
| oznaka | naziv | otvoren |
|---|---|---|
| R-1 | Picerija | 1 |
| R-2 | Roštilj | 1 |
| R-3 | Salatarnica | 1 |
Primarni ključ je kolona čija vrijednost jedinstveno određuje red. Za restoran je to oznaka: ne mogu postojati dva restorana s oznakom R-1. Slika iz života: broj lične karte. Dvije osobe mogu se zvati isto i živjeti u istoj ulici, ali broj lične karte je samo jedan. Primarni ključ ne smije biti prazan i ne smije se ponavljati; baza to sama provjerava.
Strani ključ je kolona koja čuva primarni ključ reda iz druge tabele. Tako se pravi veza. Tabela jelo ima kolonu restoran u kojoj piše „R-1”; to znači „ovo jelo pripada restoranu R-1”.
| id | restoran | naziv | cijena |
|---|---|---|---|
| 1 | R-1 | Pizza Margarita | 950 |
| 3 | R-1 | Sok | 250 |
| 4 | R-2 | Ćevapi (10 komada) | 800 |
Baza čuva referencijalni integritet: ne dozvoljava da strani ključ pokazuje na nešto čega nema. Ako neko pokuša upisati jelo za restoran „R-9”, baza odbije upis. Ako neko pokuša obrisati restoran R-1 dok postoje njegova jela, baza ili odbije brisanje ili obriše i jela, zavisno od toga kako smo vezu definisali (8.3).
Primijetite da je cijena u centima (950), kao u Python kodu iz poglavlja 6. Novac se u bazama čuva kao cijeli broj centi ili kao poseban decimalni tip, nikad kao obični decimalni broj s pomičnim zarezom, jer bi se sabiranjem hiljada stavki skupile greške zaokruživanja.
ER dijagram (od engleskog entity–relationship, entitet–veza) crta tabele i veze između njih. Danas se najčešće crta u obliku „vranine noge” (crow's foot): na kraju linije oznaka pokazuje koliko redova može biti povezano. Mermaid ga crta kao erDiagram:
| Oznaka u Mermaidu | Značenje | ||
|---|---|---|---|
| `\ | \ | ` | tačno jedan |
| `o\ | ` | nula ili jedan | |
| `\ | {` | jedan ili više | |
o{ | nula ili više |
ER dijagram DeliveryApp:
erDiagram
RESTORAN ||--|{ JELO : nudi
KUPAC ||--|{ ADRESA : ima
KUPAC ||--o{ NARUDZBA : pravi
RESTORAN ||--o{ NARUDZBA : prima
ADRESA ||--o{ NARUDZBA : "dostava na"
DOSTAVLJAC |o--o{ NARUDZBA : nosi
NARUDZBA ||--|{ STAVKA : sadrzi
JELO ||--o{ STAVKA : "naručeno u"
NARUDZBA ||--o| PLACANJE : "plaćena"Linija KUPAC ||--o{ NARUDZBA : pravi čita se isto kao asocijacija u poglavlju 6: „jedan kupac pravi nula ili više narudžbi; svaka narudžba ima tačno jednog kupca”. Linija DOSTAVLJAC |o--o{ NARUDZBA kaže da narudžba ima nula ili jednog dostavljača.

Domenski model iz poglavlja 6 pretvara se u tabele po nekoliko pravila:
narudzba.udaljenost_km: float postaje kolona udaljenost_km REAL.id INTEGER PRIMARY KEY).narudzba dobija kolonu restoran. Nikad obrnuto: restoran ne može imati kolonu za „sve svoje narudžbe”.stavka sa dva strana ključa (narudzba, jelo) i svojim kolonama (kolicina, cijena_u_trenutku). Kod nas je to već bila klasa Stavka.ON DELETE CASCADE: kad se obriše narudžba, brišu se i njene stavke.ukupno() se ne čuva kao kolona, nego se računa upitom (8.8).Za nasljeđivanje (Korisnik → Kupac, Dostavljač) postoje tri uobičajena rješenja: jedna tabela za sve s kolonom „vrsta”; posebna tabela za svaku podklasu; ili zajednička tabela plus tabela za dodatne kolone svake podklase. Za DeliveryApp biramo najjednostavnije: posebne tabele kupac i dostavljac.
Odgovori: tipka R ili klik na zamućeno polje.
Zamislimo da je neko, bez projektovanja, sve narudžbe zapisao u jednu veliku tabelu:
| broj | kupac | telefon kupca | restoran | naziv restorana | jela | dostava |
|---|---|---|---|---|---|---|
| N-1001 | K-01 | 061 111 | R-1 | Picerija | 2 × Margarita 9,50; 1 × Sok 2,50 | 3,00 |
| N-1003 | K-01 | 061 111 | R-3 | Salatarnica | 1 × Voćna salata 4,50 | – |
| N-1007 | K-01 | 061 111 | R-1 | Picerija | 1 × Margarita 9,50 | 2,50 |
Izgleda pregledno, ali krije tri vrste grešaka, koje se zovu anomalije:
Uz to, kolona „jela” čuva više vrijednosti u jednoj ćeliji („2 × Margarita 9,50; 1 × Sok 2,50”). Takav tekst baza ne može sabrati; da bi se izračunao promet Margarita, program bi morao rastavljati tekst, s greškama.
Uzrok svih anomalija je isti: jedna činjenica je zapisana na više mjesta, ili su različite činjenice zapisane zajedno. Lijek je normalizacija.
Normalizacija je postupak raspoređivanja podataka u tabele tako da se svaka činjenica čuva na jednom mjestu. Radi se u koracima koji se zovu normalne forme. Za skoro sve praktične potrebe dovoljne su prve tri.
Prva normalna forma (1NF): jedna ćelija, jedna vrijednost. Nema lista u ćeliji i nema ponavljajućih kolona (jelo1, jelo2, jelo3). Kolonu „jela” rastavljamo tako da svaka stavka bude svoj red:
| broj | kupac | telefon | restoran | naziv restorana | jelo | količina | cijena |
|---|---|---|---|---|---|---|---|
| N-1001 | K-01 | 061 111 | R-1 | Picerija | Margarita | 2 | 950 |
| N-1001 | K-01 | 061 111 | R-1 | Picerija | Sok | 1 | 250 |
Ključ ove tabele je sada par (broj, jelo), jer tek oba zajedno određuju red.
Druga normalna forma (2NF): sve zavisi od cijelog ključa. U tabeli iznad, kupac, telefon i restoran zavise samo od broj (pola ključa), a ne od para (broj, jelo). Zato ih izdvajamo u tabelu narudzba s ključem broj. U tabeli stavka ostaju samo kolone koje zavise od cijelog para: kolicina i cijena_u_trenutku.
Treća normalna forma (3NF): ništa ne zavisi od nečega što nije ključ. U tabeli narudzba, telefon zavisi od kupac, a ne od broja narudžbe; naziv restorana zavisi od restoran. To su tranzitivne zavisnosti (broj → kupac → telefon). Izdvajamo ih u tabele kupac i restoran, a u narudžbi ostaju samo strani ključevi.
Rezultat su upravo tabele iz 8.3: kupac, restoran, jelo, narudzba, stavka. Sada se telefon kupca mijenja na jednom mjestu, novi restoran se upisuje bez narudžbe, a brisanje narudžbe ne briše restoran.
Postoji poznata rečenica koja pamti sve tri forme: svaka kolona koja nije ključ mora zavisiti „od ključa, od cijelog ključa, i ni od čega osim ključa”.

Normalizovana baza je ispravna, ali ponekad nije najbrža za čitanje: izvještaj „promet po restoranu za godinu” mora spojiti hiljade narudžbi i stavki. Zato se ponekad svjesno denormalizuje: dodaje se izvedena kolona ili posebna tabela za izvještaje, koja se puni noću. To je odluka arhitekte, zapisana i obrazložena (poglavlje 9), a ne slučajnost.
Važno je razlikovati denormalizaciju od istorijskog podatka. Kolona cijena_u_trenutku u tabeli stavka izgleda kao ponavljanje cijene iz tabele jelo, ali nije. To su dvije različite činjenice: koliko jelo košta danas i koliko je koštalo kad je kupac platio. Isto važi za kolone dostava i popust u tabeli narudžbe: one pamte iznos obračunat u trenutku plaćanja, po pravilima koja su tada važila. Kad udruženje sutra promijeni PP-2, stare narudžbe ne smiju promijeniti iznos. Ovo smo otkrili u poglavlju 6, vježba 5.
„Što više normalizacije, to bolje.” Normalne forme iznad treće postoje, ali u praksi se rijetko koriste. A pretjerivanje, npr. posebna tabela za svaku riječ adrese, otežava rad bez stvarne koristi. Cilj nije što više tabela, nego da svaka činjenica ima jedno mjesto.
Odgovori: tipka R ili klik na zamućeno polje.
SQL (Structured Query Language, čita se „es-kju-el” ili „sikvel”) je jezik kojim se razgovara s relacionom bazom. Ima dva glavna dijela: naredbe koje prave strukturu (CREATE TABLE, engleski data definition) i naredbe koje rade s podacima (INSERT, SELECT, UPDATE, DELETE).
Evo kako izgleda tabela narudžbe iz primjeri/deliveryapp/schema.sql:
CREATE TABLE narudzba (
broj TEXT PRIMARY KEY,
kupac TEXT NOT NULL REFERENCES kupac(oznaka),
restoran TEXT NOT NULL REFERENCES restoran(oznaka),
adresa INTEGER NOT NULL REFERENCES adresa(id),
dostavljac TEXT REFERENCES dostavljac(oznaka),
udaljenost_km REAL NOT NULL CHECK (udaljenost_km BETWEEN 0 AND 10),
kod TEXT,
popust INTEGER NOT NULL DEFAULT 0,
dostava INTEGER NOT NULL,
stanje TEXT NOT NULL DEFAULT 'NOVA' CHECK (stanje IN
('NOVA','PRIHVAĆENA','ODBIJENA','U_PRIPREMI','NA_PUTU','ISPORUČENA','OTKAZANA')),
vrijeme TEXT NOT NULL
);
CREATE TABLE stavka (
narudzba TEXT NOT NULL REFERENCES narudzba(broj) ON DELETE CASCADE,
jelo INTEGER NOT NULL REFERENCES jelo(id),
kolicina INTEGER NOT NULL CHECK (kolicina > 0),
cijena_u_trenutku INTEGER NOT NULL,
PRIMARY KEY (narudzba, jelo)
);Svaka riječ ovdje je jedno pravilo iz projektovanja, pretočeno u ograničenje koje baza sama čuva:
| Ograničenje | Šta čuva | Odakle dolazi |
|---|---|---|
PRIMARY KEY | jedinstven broj narudžbe | identitet objekta (pogl. 6) |
NOT NULL | obavezna polja | višestrukost 1 (pogl. 6) |
REFERENCES | strani ključ, referencijalni integritet | asocijacija (pogl. 6) |
dostavljac bez NOT NULL | narudžba može biti bez dostavljača | višestrukost 0..1 |
CHECK (udaljenost_km BETWEEN 0 AND 10) | zona dostave | pravilo PP-3 |
CHECK (stanje IN (…)) | samo sedam stanja | dijagram stanja (pogl. 7) |
CHECK (kolicina > 0) | nema stavke s nula komada | zdrav razum i višestrukost |
ON DELETE CASCADE | stavke nestaju s narudžbom | kompozicija (pogl. 6) |
Ovo je odbrana u dubinu: pravilo provjerava i aplikacija i baza. Ako programer napravi grešku u kodu, baza je posljednja straža. Kad se u SQLiteu pokuša upisati stavka s količinom 0, baza odgovori: CHECK constraint failed: kolicina > 0. Napomena za SQLite: provjera stranih ključeva se mora uključiti naredbom PRAGMA foreign_keys = ON;, inače je isključena.
Primijetite i šta baza ne provjerava: da li je prelaz iz NOVA u NA_PUTU dozvoljen. CHECK vidi samo jedan red u jednom trenutku, ne istoriju. Pravilo prelaza čuva operacija promijeni() u kodu (poglavlje 7).
Podaci se upisuju naredbom INSERT:
INSERT INTO stavka VALUES ('N-1001', 1, 2, 950); -- 2 × Pizza Margarita po 9,50 €
INSERT INTO stavka VALUES ('N-1001', 3, 1, 250); -- 1 × Sok po 2,50 €A traže naredbom SELECT, koja ima ustaljen oblik: šta želim (SELECT), odakle (FROM, JOIN), koji redovi (WHERE), kako grupisati (GROUP BY) i kojim redom (ORDER BY).
Upit 1 – stavke narudžbe N-1001, s nazivom jela. Naziv jela je u tabeli jelo, a količina u tabeli stavka; spajamo ih po ključu (JOIN):
SELECT j.naziv, s.kolicina, s.cijena_u_trenutku,
s.kolicina * s.cijena_u_trenutku AS iznos
FROM stavka s
JOIN jelo j ON j.id = s.jelo
WHERE s.narudzba = 'N-1001';| naziv | kolicina | cijena_u_trenutku | iznos |
|---|---|---|---|
| Pizza Margarita | 2 | 950 | 1900 |
| Sok | 1 | 250 | 250 |
Upit 2 – ukupno po narudžbi. Funkcija SUM sabira, a GROUP BY kaže da se sabira posebno za svaku narudžbu:
SELECT n.broj,
SUM(s.kolicina * s.cijena_u_trenutku) AS artikli,
n.popust,
n.dostava,
SUM(s.kolicina * s.cijena_u_trenutku) - n.popust + n.dostava AS ukupno
FROM narudzba n
JOIN stavka s ON s.narudzba = n.broj
GROUP BY n.broj
ORDER BY n.broj;| broj | artikli | popust | dostava | ukupno |
|---|---|---|---|---|
| N-1001 | 2150 | 0 | 300 | 2450 |
| N-1002 | 3200 | 0 | 0 | 3200 |
| N-1005 | 2400 | 240 | 400 | 2560 |
N-1001 daje 2450 centi = 24,50 €, isto kao račun rukom (poglavlje 1), Python klase (poglavlje 6) i kriteriji prihvatanja (poglavlje 3). N-1005 je narudžba iz vježbe 4 poglavlja 6 (25,60 €).
Upit 3 – narudžbe bez dostavljača. Prazna vrijednost u SQL-u je NULL i provjerava se sa IS NULL, a ne sa = NULL:
SELECT broj, stanje FROM narudzba WHERE dostavljac IS NULL;Rezultat: N-1005 | PRIHVAĆENA. To je narudžba kojoj aplikacija treba naći dostavljača.
Upit 4 – promet po restoranu (izvještaj za vlasnike, poslovni cilj iz poglavlja 3):
SELECT r.naziv, COUNT(DISTINCT n.broj) AS narudzbi,
SUM(s.kolicina * s.cijena_u_trenutku) AS promet_artikala
FROM restoran r
LEFT JOIN narudzba n ON n.restoran = r.oznaka
LEFT JOIN stavka s ON s.narudzba = n.broj
GROUP BY r.oznaka;LEFT JOIN znači „uzmi sve restorane, i one bez narudžbi”; obični JOIN bi izostavio restoran bez ijedne narudžbe.
Kad tabela ima milion narudžbi, upit „sve narudžbe restorana R-1” bez pomoći mora pročitati svaki red. Indeks je pomoćna struktura koja to ubrzava, kao kazalo pojmova na kraju knjige: umjesto da listate svih 500 stranica tražeći riječ „normalizacija”, pogledate kazalo i odmah idete na stranicu 214.
CREATE INDEX ix_narudzba_restoran ON narudzba(restoran);SQLite tada pokazuje da upit koristi indeks (SEARCH narudzba USING INDEX ix_narudzba_restoran). Primarni ključ uvijek ima indeks sam po sebi.
Indeks nije besplatan: zauzima prostor i usporava upis, jer se pri svakom novom redu mora dopuniti i kazalo. Zato se indeksi prave za kolone po kojima se često traži (restoran, kupac, stanje, vrijeme), a ne za svaku kolonu.
Kad kupac plati N-1006, baza mora upisati tri stvari: narudžbu, njene stavke i plaćanje. Šta ako server padne poslije drugog upisa? Imali bismo narudžbu sa stavkama, a bez plaćanja, ili plaćanje bez narudžbe: novac uzet, a restoran ne zna ništa.
Transakcija je niz izmjena koje se izvrše sve zajedno ili nijedna. Ako bilo koji korak ne uspije, baza vraća (rollback) sve izmjene te transakcije, kao da se ništa nije desilo. Ako svi uspiju, baza ih potvrđuje (commit) odjednom.
BEGIN;
INSERT INTO narudzba (...) VALUES ('N-1006', ...);
INSERT INTO stavka VALUES ('N-1006', 1, 2, 950);
INSERT INTO placanje (narudzba, iznos, odobreno) VALUES ('N-1006', 2450, 1);
COMMIT; -- ili ROLLBACK; ako nešto nije uspjeloNa praktičnoj nastavi namjerno ćemo pokvariti treći korak (plaćanje upisano za pogrešnu narudžbu) i vidjeti da poslije greške u bazi nema ni narudžbe N-1006: transakcija je vraćena cijela.

Dobra transakcija ima četiri osobine, poznate po skraćenici ACID:
| Slovo | Osobina | Značenje za DeliveryApp |
|---|---|---|
| A | atomarnost (atomicity) | narudžba, stavke i plaćanje upisuju se sve ili ništa |
| C | konzistentnost (consistency) | poslije transakcije sva ograničenja (ključevi, CHECK) i dalje važe |
| I | izolacija (isolation) | dva kupca koja istovremeno naručuju ne vide napola upisane narudžbe jedan drugog |
| D | trajnost (durability) | kad baza kaže „potvrđeno”, narudžba ostaje i ako odmah zatim nestane struje |
Slika iz života: prenos novca između dva računa. Skidanje 100 € s jednog i dodavanje na drugi račun moraju biti jedna transakcija. Ako se desi samo prvo, novac je nestao; ako samo drugo, novac je nastao ni iz čega.
Kraj dvadesetog vijeka obilježio je problem 2000. godine (Y2K). Šezdesetih i sedamdesetih godina memorija je bila skupa, pa su mnogi sistemi godinu čuvali sa dvije cifre: „99” za 1999. Kad bi došla 2000, „00” bi se moglo shvatiti kao 1900, i računi kamata, rokova i starosti bili bi pogrešni. Banke, aerodromi i države potrošile su godine i ogroman novac na pregled i ispravku starih programa i baza prije 1. januara 2000. Pouka za projektanta baze: izbor tipa i veličine kolone je odluka koja živi decenijama. Datum se čuva kao datum, novac kao novac, a ne kao „ono što trenutno stane”.
Odgovori: tipka R ili klik na zamućeno polje.
Cilj je napraviti bazu DeliveryApp iz SQL skripti, postaviti upite i isprobati ograničenja i transakciju. Radi se u folderu kod/ repozitorija deliveryapp-projekat.
sqlite3), pa ništa ne treba instalirati za rad iz koda.kod/ kopirajte schema.sql, podaci.sql i baza.py iz primjeri/deliveryapp/.python baza.pyUkupno po narudžbi (u centima):
('N-1001', 2150, 0, 300, 2450)
('N-1002', 3200, 0, 0, 3200)
('N-1005', 2400, 240, 400, 2560)
Promet po restoranu (artikli, u centima):
('Picerija', 1, 2150)
('Roštilj', 1, 3200)
('Salatarnica', 1, 2400)
Pokušaj nedozvoljenog unosa:
baza je odbila: CHECK constraint failed: kolicina > 0deliveryapp.db u DB Browseru i pogledajte svaku tabelu. Pronađite strane ključeve u kartici Database Structure.U modeli/baza.md nacrtajte ER dijagram iz 8.3 u Mermaidu (erDiagram) i uporedite ga s domenskim modelom iz poglavlja 6: za svaku vezu provjerite da se višestrukost slaže.
U DB Browseru (kartica Execute SQL) ili u Python skripti napišite i pokrenite:
SUM(kolicina), GROUP BY, ORDER BY … DESC, LIMIT 1);/ 100.0);narudzba u tabeli placanje je UNIQUE). U Pythonu:try:
with db: # 'with db' = jedna transakcija
db.execute("INSERT INTO narudzba (broj, kupac, restoran, adresa, udaljenost_km, dostava, vrijeme) "
"VALUES ('N-1006', 'K-01', 'R-1', 1, 4.2, 300, '2026-10-02 13:00')")
db.execute("INSERT INTO stavka VALUES ('N-1006', 1, 2, 950)")
db.execute("INSERT INTO placanje (narudzba, iznos, odobreno) VALUES ('N-1001', 2200, 1)")
except sqlite3.IntegrityError as e:
print("transakcija vraćena:", e)
print(db.execute("SELECT COUNT(*) FROM narudzba WHERE broj = 'N-1006'").fetchone())transakcija vraćena: UNIQUE constraint failed: placanje.narudzba i zatim (0,). Narudžbe N-1006 nema, jer je cijela transakcija vraćena. Ispravite treći korak (plaćanje za N-1006, 2200 centi) i pokrenite ponovo: sada je rezultat (1,).Dajte AI asistentu sadržaj schema.sql (bez podataka o ljudima; ovdje su ionako izmišljeni) i zatražite upit „promet po danu u sedmici za svaki restoran”. Pokrenite predloženi upit i provjerite rezultat ručno na tri narudžbe. Ako upit koristi kolonu koje nema u šemi, to je znak da AI nagađa.
Rezultat koji se predaje: kod/schema.sql, kod/podaci.sql (dopunjeni s R-4), modeli/baza.md s ER dijagramom, fajl kod/upiti.sql s pet upita iz koraka 4 i zapisanim rezultatima, te zapis rezultata koraka 5.
Rješenja su sakrivena – tipka R ili klik na okvir.
Za tabele dostavljac, adresa i placanje iz schema.sql napišite primarni ključ i strane ključeve, i za svaki strani ključ rečenicu veze.
dostavljac: primarni ključ oznaka; nema stranih ključeva. adresa: primarni ključ id; strani ključ kupac → kupac(oznaka): „svaka adresa pripada tačno jednom kupcu, a kupac ima jednu ili više adresa”. placanje: primarni ključ id; strani ključ narudzba → narudzba(broj), i to UNIQUE: „svako plaćanje pripada tačno jednoj narudžbi, a narudžba ima najviše jedno plaćanje”.
U tabeli jelo neko je dodao kolonu naziv_restorana. Koju normalnu formu to krši i koje anomalije donosi?
Krši 3NF: naziv_restorana zavisi od kolone restoran, a ne od ključa id (tranzitivna zavisnost id → restoran → naziv). Anomalija izmjene: ako Picerija promijeni naziv, treba ga promijeniti u svim njenim jelima, a zaboravljeni red daje dva naziva. Uz to se isti naziv bespotrebno ponavlja.
Normalizujte do 3NF tabelu: (broj_narudzbe, datum, kupac, adresa_kupca, dostavljac, vozilo_dostavljaca). Ključ je broj_narudzbe.
Tranzitivne zavisnosti: kupac → adresa_kupca i dostavljac → vozilo_dostavljaca. Rezultat: narudzba(broj, datum, kupac, dostavljac), kupac(oznaka, adresa), dostavljac(oznaka, vozilo). (Ako kupac ima više adresa, adresa ide u posebnu tabelu kao u DeliveryApp.)
Napišite upit koji vraća ukupan iznos koji je kupac K-01 platio, i izračunajte rezultat za podatke iz podaci.sql.
SELECT SUM(p.iznos) FROM placanje p
JOIN narudzba n ON n.broj = p.narudzba
WHERE n.kupac = 'K-01' AND p.odobreno = 1;K-01 ima samo N-1001, pa je rezultat 2450 centi, odnosno 24,50 €.
Za svaku situaciju recite koja osobina ACID je narušena: a) poslije nestanka struje, narudžba koju je baza potvrdila nije u bazi; b) tokom upisa jedan kupac vidi stavke tuđe narudžbe koja još nije završena; c) upisano je plaćanje, ali ne i narudžba; d) poslije transakcije postoji stavka s količinom −1.
a) trajnost (D); b) izolacija (I); c) atomarnost (A); d) konzistentnost (C).