Esercizi sulla normalizzazione (vedi anche note) 1. Dato lo schema di relazione di una società di investimenti con i seguenti attributi: B = Broker (agente) O = Ufficio del broker (indirizzo) I = Investitore S = Stock (tipo di azione) Q = Quantità di azioni di un certo tipo possedute da un investitore D = Dividendo pagato per unità di un certo tipo di azione abbiamo le seguenti dipendenze funzionali definite su R=(BOSQID): S D per ogni tipo di azione è univocamente identificato il dividendo; I B ogni investitore si serve di un solo agente (ma un agente può servire più investitori); IS Q ogni investitore per un determinato tipo di azioni possiede una quantità definita; B O ogni agente ha un solo ufficio; a) determinare la chiave della relazione; b) se lo schema non è in 3NF, trovarne una decomposizione in schemi 3NF che preservi le dipendenze e dia un join senza perdite. 2. Dato lo schema di relazione di una compagnia di navigazione mercantile con i seguenti attributi: N = Nome della nave T = Tipo di nave V = Identificativo del viaggio (sigla) C = Carico trasportato da una nave in un viaggio P = Porto D = Data abbiamo le seguenti assunzioni: - un viaggio è una sequenza di eventi in cui ogni singola nave ha un unico carico che porta in una sequenza di porti; - una nave visita in un giorno un solo porto che portano ai seguenti vincoli: N T ; V NC ; ND PV per la relazione R=(NTVCPD); a) determinare la chiave della relazione; b) se lo schema non è in 3NF, trovarne una decomposizione in schemi 3NF che preservi le dipendenze e dia un join senza perdite.
3. Dato lo schema di relazione R = (a, b, c, d, e, f, g, h, i) con chiave abcd e le seguenti dipendenze funzionali: a ei bd h c f trovare una decomposizione di R in schemi 3NF che preservi le dipendenze e dia un join senza perdite.
Soluzioni proposte degli esercizi sulla normalizzazione (vedi anche note) Le modifiche sono riportate in grassetto 1. Una chiave della relazione è la coppia di attributi IS. Applicando infatti gli assiomi di Armstrong e le regole derivate abbiamo: IS Q (dato) I B + B O (transitività) I O (aumento) IS OS (decomposizione) IS O; I B (aumento) IS BS (decomposizione) IS B; S D (aumento) IS ID (decomposizione) IS D; IS Q + IS O + IS B + IS D (unione) IS BOQD (I) + F Inoltre I ed S da soli non determinano tutti gli altri attributi. Per verificarlo è sufficiente calcolarne le chiusure transitive rispetto all insieme di dipendenze dato, usando l algoritmo che conosciamo: all inizio Z := I, S := B, grazie alla dipendenza I B che ha I Z; alla prima iterazione del ciclo while Z := IB, S:= BO, grazie alle dipendenze I B che ha I Z e B O che ha B Z ; alla seconda iterazione Z := IBO, S := BO perché l aggiunta di O nel nuovo Z non permette di prendere in considerazione nuove dipendenze; poiché S Z l algoritmo termina, quindi la chiusura cercata è Z = IBO, che non contiene tutti gli attributi dello schema; (S) + F (attenzione anche nel seguito a non confondere S attributo con S variabile dell algoritmo); all inizio Z := S, S := D, grazie alla dipendenza S D che ha S Z; alla prima iterazione del ciclo while Z := SD, S := D, perché l aggiunta di D nel nuovo Z non permette di prendere in considerazione nuove dipendenze; poiché S Z l algoritmo si ferma con Z = SD, che non contiene tutti gli attributi dello schema.
Verifichiamo a scopo didattico, basandoci su questo esempio, che effettivamente, quando la chiave viene individuata basandosi sulle dipendenze date dall esercizio, le dipendenze dalla chiave sono in pratica esplicitamente dedotte da quelle già presenti, e quindi verrebbero comunque eliminate dalla copertura minimale in quanto ridondanti. Di conseguenza è inutile aggiungerle all insieme FO. Se aggiungiamo all insieme originario le dipendenze dovute al vincolo di chiave, si ottiene F = { IS B, IS O, IS Q, IS D, S D, I B, B O }. Notiamo che B O è transitiva (per cui si ha IS B + B O IS O) e che S D e I B sono parziali (dipendenza da una sottochiave). Essendo presenti sia dipendenze parziali che transitive, lo schema non è 3NF. Applichiamo quindi prima di tutto l algoritmo per individuare una copertura minimale di F. Nelle parti destre abbiamo già singleton, quindi passiamo al secondo passo. Cominciamo a verificare se IS B può essere ridotta. Banalmente, eliminando S, riotteniamo una dipendenza già presente in F, quindi possiamo eliminare del tutto IS B, ottenendo il nuovo insieme F = { IS O, IS Q, IS D, S D, I B, B O }. Proviamo ora a sostituire IS O con I O. Dobbiamo verificare a tale scopo se I O F +, cioè se O (I) + F. Possiamo però usare i risulati del calcolo effettuato in precedenza per dimostrare che I da solo non è chiave, perché l insieme di dipendenze rispetto a cui stiamo verificando è equivalente a quello di partenza. La chiusura era risultata essere Z = IBO, che contiene dunque O. Possiamo quindi sostituire IS O con I O, per cui avremo F = { I O, IS Q, IS D, S D, I B, B O }. A questo punto verifichiamo se IS Q può essere ridotta a I Q. Riferendoci ancora al calcolo della chiusura di I già effettuato, vediamo che Q non ne fa parte, quindi la sostituzione non è valida. Passando alla sostituzione alternativa S Q, dobbiamo verificare se Q appartiene a (S) + F. Anche la chiusura di S è già stata calcolata, e non conteneva Q. In definitiva la dipendenza IS Q non può essere ridotta. Banalmente invece IS D può essere eliminata, perché eliminando I otteniamo S D che fa già parte di F. Alla fine di questo passo avremo
F = { I O, IS Q, S D, I B, B O }. Procediamo ora col terzo passo dell algoritmo per la copertura minimale. Proviamo ad eliminare I O, ottenendo il nuovo insieme G = { IS Q, S D, I B, B O } e verificando se ancora O (I) + G (questa volta il calcolo della chiusura va rifatto, perché vogliamo proprio verificare che non cambi). All inizio dell algoritmo per calcolare la chiusura avremo Z := I, S := B, grazie alla dipendenza I B che ha I Z; alla prima iterazione del ciclo while Z := IB, S:= BO, grazie alle dipendenze I B che ha I Z e B O che ha B Z ; alla seconda iterazione Z := IBO, S := BO perché l aggiunta di O nel nuovo Z non permette di prendere in considerazione nuove dipendenze; poiché S Z l algoritmo termina, quindi la chiusura cercata è Z = IBO che contiene O (di fatto abbiamo verificato che eliminando I O la chiusura di I non cambia, e quindi l eliminazione della dipendenza preserva l equivalenza tra gli insiemi ). A questo punto avremo quindi il nuovo insieme F = { IS Q, S D, I B, B O }. Come possiamo osservare, tutte le dipendenze da chiave inserite all inizio in FO sono state a questo punto eliminate, quindi abbiamo verificato che è stato del tutto inutile inserirle. Proviamo ora ad eliminare IS Q. Banalmente Q fa parte della chiusura di IS rispetto all insieme F. Vediamo se Q fa ancora parte della chiusura di IS rispetto all insieme ridotto G ={ S D, I B, B O }. Possiamo già prevedere di no, perché Q non compare a destra di nessun altra dipendenza, ma a scopo didattico dimostriamolo ricalcolando la chiusura di IS. All inizio abbiamo Z := IS, S := DB grazie alle dipendenze I B che ha I Z e S D che ha S Z ; alla prima iterazione del ciclo while abbiamo Z := ISDB, S:= DBO grazie alle dipendenze precedenti con in più la dipendenza B O che ha B Z ; alla seconda iterazione abbiamo Z := ISDBO, S := DBO perché non possiamo prendere in considerazione ulteriori dipendenze; pochè S Z l algoritmo si ferma con Z = ISDBO, che non contiene Q. La dipendenza effettivamente non può essere eliminata. Lo stesso vale per le altre dipendenze
presenti in F, quindi possiamo concludere che la copertura minimale dell insieme di dipendenze originario è F = { IS Q, S D, I B, B O }. Applichiamo infine l algoritmo per trovare la decomposizione di F cercata. Non abbiamo attributi che non compaiono in nessuna dipendenza, non abbiamo dipendenze che coinvolgono tutti gli attributi, quindi procediamo al partizionamento rispetto alle dipendenze presenti in F, ottenendo: ρ ={ ISQ, SD, IB, BO} La chiave originaria IS è gia presente in uno degli elementi della partizione, per cui la decomposizione cercata è R1 = (ISQ), R2 = (SD), R3 = (IB), R4 = (BO). 2. Identifichiamo prima di tutto una chiave per la relazione R = (NTVCPD). Gli attributi T, C e P non si trovano mai a sinistra delle dipendenze funzionali, quindi non sono in grado di determinare altri attributi neppure in modo transitivo. L attributo D non compare mai da solo a sinistra, quindi da solo non può determinare tutti gli altri. Infatti se proviamo a calcolarne la chiusura avremo : (D) + F all inizio Z := D, S := perché non abbiamo alcuna dipendenza la cui parte sinistra sia contenuta in Z; l algoritmo non entra nel ciclo while perché S Z, e quindi termina con Z = D Calcoliamo quindi le chiusure degli attributi N e V. (N) + F all inizio Z := N, S := T grazie alla dipendenza N T con N Z; alla prima iterazione del ciclo while Z := NT, S := T perché l aggiunta di T a Z non ci consente comunque di prendere in considerazione nuove dipendenze; poiché S Z l algoritmo si ferma con Z = NT, che indica che N da solo non può essere chiave. (V) + F all inizio Z := V, S := NC grazie alla dipendenza V NC con V Z; alla prima iterazione del ciclo while Z := VNC, S := NCT grazie alla dipendenza N T con N Z; alla seconda iterazione Z := VNCT, S := NCT perché l aggiunta di T
a Z non ci consente comunque di prendere in considerazione nuove dipendenze; poiché S Z l algoritmo si ferma con Z = VNCT, che indica che V da solo non può essere chiave. Considerando ora le coppi di attributi, di nuovo non prendiamo in considerazione attributi che non compaiono nelle parti sinistre. Inoltre, dalle chiusure calcolate per N e V, la coppia NV non conterrebbe l attributo P nella propria chiusura, quindi prendiamo in considerazione la coppia ND. (ND) + F all inizio Z := ND, S := PVT, grazie alle dipendenze ND PV con ND Z e N T con N Z; alla prima iterazione del ciclo while Z := NDPVT, S := PVTNC, grazie alle dipendenze prese in considerazione prima e in più la dipendenza V NC con V Z; alla seconda iterazione Z := NDPVTC, S := PVTNC Z, per cui l algoritmo termina con Z = NDPVTC, che comprende tutti gli attributi della relazione. Poiché abbiamo già visto che né N né D possono determinare tutti gli altri attributi, ND è una chiave per R. Come verificato nell esercizio precedente, in questo caso è inutile aggiungere ad FO le dipendenze dalla chiave in quanto derivate dalle altre. L insieme di dipendenze FO, applicando la regola di decomposizione alle dipendenze V C e ND PV, è quindi FO = { ND P, ND V, N T, V N, V C }. Possiamo notare la dipendenza parziale N T, e la dipendenza transitiva V C (per cui si ha ND V + V C ND C). Quindi la relazione non è 3NF. Cerchiamo quindi per prima cosa la copertura minimale di FO. Le parti destre sono già singleton, quindi passiamo direttamente al secondo passo dell algoritmo per la copertura minimale. Le dipendenze che possiamo provare a ridurre sono le prime due, in cui possiamo provare ad eliminare N oppure D. riportiamo quindi per comodità le chiusure già calcolate: (N) + F = NT; (D) + F = D Osserviamo che P (N) + F e P (D) + F, e quindi ND P non può essere ridotta. Altrettanto vale per ND V. Possiamo passare quindi al terzo passo dell algoritmo,
provando ad eliminare le dipendenze ridondanti. Cominciamo a verificare se si può eliminare la dipendenza ND P. Possiamo già prevedere di no, perché P non compare a destra di nessun altra dipendenza, ma a scopo didattico dimostriamolo ricalcolando la chiusura di ND rispetto al nuovo insieme G = {ND V, N T, V N, V C }. All inizio dell algoritmo abbiamo Z := ND, S := VT, grazie alle dipendenze ND V con ND Z e N T con N Z; alla prima iterazione del ciclo while abbiamo Z := NDVT, S := VTNC, grazie alle dipendenze viste prima e in più V C con V Z e V N con V Z; alla seconda iterazione abbiamo Z := NDVTC, S := VTNC Z, quindi l algoritmo si ferma con Z = NDVTC, a cui non appartiene P. Di conseguenza la dipendenza ND P effettivamente non può essere eliminata, e rimaniamo con l insieme F ={ ND P, ND V, N T, V N, V C }. Possiamo osservare che anche per le altre dipendenze abbiamo parti destre che compaiono una sola volta, per cui nessuna sarà eliminabile (non riusciamo a raggiungere gli attributi per transitività). La copertura minimale di FO sarà quindi F ={ ND P, ND V, N T, V N, V C }, a cui andiamo ad applicare l algoritmo per la 3NF. Tutti gli attributi compaiono in qualche dipendenza, e non ci sono dipendenze che coinvolgono tutti gli attributi, quindi passiamo all ultima parte ottenendo: ρ = { NDP, NDV, NT, VN, VC} La chiave originaria ND è gia presente in uno, anzi due, degli elementi di ρ, per cui la decomposizione cercata è R1 = (NDP), R2 = (NDV), R3 = (NT), R4 = (VN), R5 = (VC). 3. Poiché la chiave abcd della relazione R = (a, b, c, d, e, f, g, h, i) è data, alle dipendenze date a ei, bd h, c f vanno aggiunte quelle determinate dal vincolo di chiave, cioè abcd e, abcd f, abcd g, abcd h, abcd i perché potrebbero essere non ridondanti (infatti vediamo subito che g non è determinato da
nessun attributo o insieme di attributi nelle dipendenze date, ma ovviamente sarà determinato per definizione dalla chiave). L insieme iniziale di dipendenze è quindi FO = { abcd e, abcd f, abcd g, abcd h, abcd i, a ei, bd h, c f }. Abbiamo quindi le dipendenze parziali a ei (che per decomposizione si spezza in a e e a i), bd h, c f, quindi R non è in 3NF. Passiamo a determinare una copertura minimale per FO, che dopo il primo passo dell algoritmo, che applica la regola di decomposizione per ridurre le parti destre a singleton, è F={ abcd e, abcd f, abcd g, abcd h, abcd i, a e, a i, bd h, c f }. Passando al secondo passo, cominciamo a provare a ridurre abcd e. Possiamo già prevedere di poterla sostituire con a e (e quindi eliminarla in quanto duplicato) perché a abcd, ma a scopo didattico e solo per questa dipendenza seguiamo il procedimento che ci porterebbe alla stessa conclusione. Iniziamo provando a sostituire abcd e con bcd e. Dobbiamo verificare se e (bcd) + F, quindi applichiamo il relativo algoritmo. All inizio Z := bcd, S := hf grazie alle dipendenze bd h con bd Z e c f con c Z; alla prima iterazione del ciclo while abbiamo Z := bcdhf, S := hf, in quanto l aggiunta a Z di h ed f non ci permette di considerare nuove dipendenze; poiché S Z, l algoritmo si ferma con Z = bcdhf. L attributo e non fa parte di questo insieme, quindi abcd e non può essere sostituita con bcd e. In ogni caso il calcolo fatto di (bcd) + F = bcdhf ci servirà di nuovo in seguito. Proviamo allora a sostituire abcd e con acd e. Applicando l algoritmo, all inizio Z := acd, S := eif, grazie alle dipendenze a e, a i, c f; alla prima iterazione del while abbiamo Z := acdeif, S := eif perché non possiamo considerare nuove dipendenze; poiché S Z l algoritmo si ferma con Z = acdeif, che contiene l attributo e, quindi la sostituzione può avvenire. Ricordiamo comunque il risultato (acd) + F = acdeif.
A questo punto F = { acd e, abcd f, abcd g, abcd h, abcd i, a e, a i, bd h, c f }. Proviamo a ridurre ancora acd e. Provare con cd e è inutile, in quanto essendo cd bcd, avremo sicuramente (cd) + F (bcd) + F, e avevamo già verificato che e (bcd) + F. Proviamo allora con ad e, verificando se e (ad) + F. All inizio dell algoritmo Z := ad, S := ei grazie alle dipendenze a e ed a i; alla prima iterazione del while abbiamo Z := adei, S := ei Z quindi l algoritmo termina con Z =(ad) + F = adei, che contiene l attributo e; la sostituzione può essere effettuata, quindi F={ ad e, abcd f, abcd g, abcd h, abcd i, a e, a i, bd h, c f}. Provando ulteriormente a ridurre ad e, vediamo che a e appartiene già ad F, quindi ad e può essere eliminata del tutto. Era questo il risultato che avevamo già previsto. Abbiamo allora F={ abcd f, abcd g, abcd h, abcd i, a e, a i, bd h, c f }. Passiamo alla dipendenza abcd f. La eliminiamo senza ulteriori verifiche in quanto, dopo una sequenza di eliminazioni di attributi a sinistra, potrà essere sicuramente sostituita da c f (essendo c abcd) e quindi eliminata come duplicato. Quindi ora F={ abcd g, abcd h, abcd i, a e, a i, bd h, c f }. Prendiamo in considerazione abcd g, e osserviamo che non esiste in F una dipendenza Y g con Y abcd. Di conseguenza è inutile provare a ridurre questa dipendenza. Esaminiamo allora abcd h. Questa dipendenza può essere eliminata osservando che in F abbiamo bd h (bd abcd ), che sostituirebbe abcd h dopo una sequenza di eliminazioni di attributi a sinistra, portando a un duplicato. Quindi F={abcd g, abcd i, a e, a i, bd h, c f }. Analogo discorso vale per la dipendenza abcd i, grazie alla presenza di a i. Ora F={abcd g, a e, a i, bd h, c f }. Rimane da verificare se si può ulteriormente ridurre bd h. Tuttavia, anche senza effettuare i calcoli, per la forma delle dipendenze di F, è facile
convincersi che (b) + F = b e (d) + F = d (applicando l algoritmo per la chiusura ai due attributi separatamente, abbiamo in entrambi i casi un insieme S vuoto). All insieme F={abcd g, a e, a i, bd h, c f } dovremmo ora applicare il terzo passo per la copertura minimale. Tuttavia vediamo che tutti gli attributi delle parti destre compaiono in esattamente una dipendenza, quindi non troveremmo dipendenze ridondanti. Infine applichiamo l algoritmo per la decomposizione di R in base al nuovo insieme F. Non abbiamo attributi che non partecipano ad alcuna dipendenza, né dipendenze che coinvolgono tutti gli attributi, quindi procediamo secondo le dipendenze presenti in F ottenendo la decomposizione ρ = {abcdg, ae, ai, bdh, cf} La chiave originaria abcd è gia presente in uno degli elementi di ρ, per cui la decomposizione cercata è R1 = (abcdg), R2 = (ae), R3 = (ai), R4 = (bdh), R5 = (cf).