Poročilo knjižnice se je skrčilo po brisanju članov
Šolska knjižnica uporablja relacijsko bazo s tremi tabelami: Knjiga(id_knjige, naslov, avtor, leto), Clan(id_clana, ime, priimek, email) in Izposoja(id_izposoje, id_knjige, id_clana, datum_izposoje, datum_vrnitve). Rok izposoje je 30 dni; obravnavani datum je 15. maj 2024. V tabeli Izposoja je tuji ključ id_clana nastavljen z ON DELETE CASCADE. Knjižničarka je marca iz baze izbrisala dva člana, ki se nista več vpisala. Ob zaključnem poročilu se je izkazalo, da se je število izposoj v letu 2023 zmanjšalo s 1.240 na 1.198, čeprav se nobena izposoja ni zgodila v letu 2024. Poročilo o članih z izposojenimi knjigami ne vsebuje članov, ki trenutno nimajo izposojene nobene knjige. Poizvedba SELECT * FROM Izposoja WHERE datum_vrnitve = NULL vrne prazno množico, čeprav je v tabeli 37 vrstic z neizpolnjenim datumom vrnitve. Vodja knjižnice želi, da baza omogoča statistiko po letih in hkrati spoštuje pravilo, da osebnih podatkov nekdanjih članov ne hranimo dlje, kot je potrebno.
1. Napiši poizvedbo SQL, ki vrne vse člane in število njihovih trenutno izposojenih knjig (datum_vrnitve IS NULL), vključno s člani brez izposoj, urejeno padajoče po številu. Pojasni, zakaj mora biti pogoj v stiku ON in ne v WHERE.
Read the worked answer
SELECT c.id_clana, c.ime, c.priimek,
COUNT(i.id_izposoje) AS st_izposoj
FROM Clan AS c
LEFT JOIN Izposoja AS i
ON i.id_clana = c.id_clana
AND i.datum_vrnitve IS NULL
GROUP BY c.id_clana, c.ime, c.priimek
ORDER BY st_izposoj DESC;
Zakaj ON in ne WHERE
LEFT JOIN ohrani vse vrstice leve tabele (Clan) tudi takrat, ko na desni strani ni ujemanja; manjkajoče stolpce zapolni z NULL. Pogoj v ON se uporabi med združevanjem, zato član brez aktivne izposoje ostane v rezultatu, njegov i.id_izposoje pa je NULL.
Če bi pogoj zapisali v WHERE, bi se uporabil po združevanju in bi vrstice z NULL vrednostmi izločil. Član, ki ima vse knjige že vrnjene, bi iz rezultata izginil, namesto da bi imel vrednost 0 – poročilo bi bilo napačno. LEFT JOIN se s takim pogojem v WHERE dejansko obnaša kot INNER JOIN.
Zakaj COUNT(i.id_izposoje) in ne COUNT(*)
COUNT(*) prešteje vrstice, torej bi pri članu brez izposoj vrnil 1 (vrstica z NULL). COUNT(stolpec) ne šteje NULL vrednosti, zato vrne pravilno 0.
2. Poizvedba z datum_vrnitve = NULL vrne prazno množico. Pojasni vzrok in napiši pravilno poizvedbo, ki vrne člane z zamudo pri vračilu (rok 30 dni, danes 15. 5. 2024).
Read the worked answer
Vzrok
V SQL je NULL oznaka za »neznano«, ne za vrednost. Primerjava z NULL vrne tretjo logično vrednost UNKNOWN, vrstica pa se ne izbere, ker WHERE izbere le vrstice, kjer je pogoj TRUE. Zato = NULL nikoli ne vrne ničesar; pravilno je IS NULL (oziroma IS NOT NULL).
Pravilna poizvedba
SELECT c.ime, c.priimek, k.naslov, i.datum_izposoje
FROM Izposoja AS i
JOIN Clan AS c ON c.id_clana = i.id_clana
JOIN Knjiga AS k ON k.id_knjige = i.id_knjige
WHERE i.datum_vrnitve IS NULL
AND i.datum_izposoje < DATE '2024-05-15' - INTERVAL '30' DAY;
Razlaga
datum_izposoje < 15. 4. 2024pomeni, da je od izposoje minilo več kot 30 dni; knjiga še ni vrnjena (IS NULL), torej je v zamudi. Enakovredno bi lahko zapisalidatum_izposoje + INTERVAL '30' DAY < DATE '2024-05-15'.- Opozorilo: zapis datuma se med sistemi razlikuje (
DATE_SUB,date('now','-30 days')), zato je treba preveriti dialekt. Če se primerja datum z nizom, mora biti niz v obliki, ki jo sistem pravilno pretvori. - Sorodna past:
NOT IN (SELECT id_clana FROM ...), kjer podpoizvedba vrača NULL, vrne prazno množico iz istega razloga – tudi tam je treba NULL obravnavati izrecno.
3. Pojasni, kaj se je zgodilo ob brisanju dveh članov, in predlagaj spremembo modela ter politike brisanja, ki ohrani statistiko in hkrati spoštuje varstvo osebnih podatkov.
Read the worked answer
Kaj se je zgodilo
ON DELETE CASCADE pomeni, da baza ob brisanju vrstice v tabeli Clan samodejno izbriše vse pripadajoče vrstice v Izposoja. Referenčna celovitost je ostala nespremenjena, a zgodovina je izgubljena: 42 izposoj iz leta 2023 je izginilo skupaj s članoma, zato se je poročilo skrčilo s 1.240 na 1.198. Statistika je postala neresnična za nazaj, kar je resen problem, ker se poročila pogosto pripravljajo retrogradno.
Predlog modela
- Mehko brisanje: v tabelo
Clandodamo stolpcaaktiven(privzeto 1) indatum_izbrisa. Član ob »brisanju« ostane v bazi, le označen je; poizvedbe za tekoče poslovanje filtrirajoWHERE aktiven = 1. - Tuji ključ
ON DELETE RESTRICTnamesto CASCADE – baza prepreči brisanje člana, ki ima izposoje, in s tem prepreči tiho izgubo podatkov. - Anonimizacija po preteku roka: ko osebni podatki niso več potrebni, se
ime,priimekinemailnadomestijo z oznako (na primer »Nekdanji član 4711«),id_clanapa ostane, da tuji ključi in agregati še naprej delujejo. Anonimizirani zapis ni več osebni podatek, zato ga lahko hranimo za statistiko.
Utemeljitev: načelo omejitve hrambe zahteva, da osebnih podatkov ne hranimo dlje, kot je potrebno, ne pa, da uničimo celotno poslovno evidenco. Ločitev identitete od dogodka je standardna rešitev. Brisanje naj bo dokumentirano (kdo, kdaj, zakaj), obnovitev pa mogoča iz varnostne kopije.