I comandi Linux: MySQL ( 3 ) | OTHER chapters | ||||
Un database relazionale, come MySQL, è composto da più tabelle. Una tabella non è altro che un file di testo, in cui vengono memorizzate una serie di informazioni, organizzate in righe di testo ( row o record ) e in colonne ( field o campi ):
Da questa tabella, per esempio, chiunque possa accedere al database ed alla tabella stessa potrà sapere che Ugo abita a Milano, in via Bai. In questo esempio, potremmo assegnare i seguenti nomi ai campi ( colonne ):
mentre ciascun record ( riga di testo ) sarà assegnato ad un singolo identificativo ( ID ), che, nel nostro esempio, corrisponde ad un nome di persona: ugo, sandro, gianni, marco. Da questo semplicissimo esempio, risultano chiari tre fondamenti dei sistemi di database relazionali:
Aggiungiamo, ora, per ciascun ID cliente, un campo con il codice ( COD ) di ciascun articolo acquistato:
Ugo di Milano, come possiamo constatare, ha acquistato due articoli: l'articolo con codice 123 e l'articolo con codice 774. Non solo: con i suoi due acquisti, Ugo ha rovinato la nostra tabella di database! Perchè? Innanzitutto, grazie a lui ci troviamo con due record che contengono i suoi dati. Se un domani Ugo dovesse arrivare ad acquistare 1000 articoli, noi ci troveremmo con 1000 record a lui dedicati. Il giorno in cui Ugo decidesse di cambiare residenza, quindi, noi ci troveremmo a dover modificare 1000 record, con il rischio di dimenticarne qualcuno. In questo caso, per alcuni record, Ugo continuerebbe ad abitare a Milano, in via bai, mentre per la gran parte dei restanti record abiterebbe nella nuova residenza. Come potremmo, in futuro, decidere quale record detiene il dato corretto? Questa imprecisione ha un nome: inconsistenza dei dati. Dalla inconsistenza dei dati deriva l'inconsistenza e, quindi, l'inaffidabilità del database. L'inconsistenza dei dati e l'inaffidabilità del database derivano, in questo caso, dal peccato originale: la ridondanza dei dati, vale a dire la ripetizione dello stesso dato in più record, oppure, addirittura, in più tabelle. La ridondanza, a sua volta, è stata la necessaria conseguenza di un errore logico, commesso da noi, gli amministratori del database: abbiamo mischiato capra e cavoli, mettendo in una tabella che rappresentava un'entità precisa, la nostra "Clientela", il codice di un articolo prodotto che non è una proprietà del cliente, bensì una proprietà del prodotto stesso! Potreste mai immaginare di presentare Ugo ad un vostro amico, dicendogli: "ti presento Ugo, che abita a Milano, in via Bai, per l'esattezza, e ieri ha acquistato un tosaerbe della ditta Toso Spa, codice prodotto: 123"? Questo potreste dirlo ad un vostro collega di lavoro, che sarebbe in grado di comprendere il contesto in cui il fatto è avvenuto ( sempre che entrambi lavoriate nell'azienda commerciale che ha venduto a Ugo il tosaerbe ), ma non ad un estraneo, che mai si aspetterebbe di conoscere gli ultimi acquisti fatti da tutti coloro che gli vengono presentati. Come correggere l'errore che abbiamo appena commesso? SQL ha poche regole, ma precise. E queste regole mirano al mantenimento della consistenza e affidabilità di un database, attraverso un processo di riduzione della ridondanza dei dati. Il processo viene chiamato normalizzazione. Voglio sottolineare che il processo di normalizzazione non è un puro esercizio accademico, ma un processo di ottimizzazione nell'organizzazione del database, che porterà a grandi benefici, in termini di prestazioni ed efficienza. Il processo di normalizzazione prevede l'adesione a 5 regole auree, chiamate Normal Form, perfezionate dalla comunità MySQL, nel corso degli anni. Noi le riprenderemo dalla descrizione di William Kent, nel suo A Simple Guide to Five Normal Forms in Relational Database Theory, anno 1982. La dipendenza funzionale: definizione di chiave primaria ( Primary Key ) In termini di teoria degli insiemi, una tabella è una "relazione", vale a dire, un insieme di oggetti unici e non duplicabili, chiamati "tuple". Se una relazione corrisponde, in linguaggio SQL, ad una tabella, un tuple corrisponde ad un record della tabella. Ciascun record della tabella è composto da una serie di campi, ciascuno dei quali contiene una proprietà dell'oggetto rappresentato dal record. Affinchè sia mantenuta l'unicità e la non duplicabilità dei tuple ( record ), la relazione che deve instaurarsi tra un campo e l'altro, in un record, deve essere di dipendenza funzionale. Un campo Y è funzionalmente dipendente da un campo X solo quando non sia possibile reperire, all'interno della relazione, un altro tuple, in cui sia presente lo stesso campo X, seguito da un campo Y con diverso valore. All'interno del nostro database di esempio, sappiamo che è presente un articolo, con codice 123, corrispondente ad un tosaerbe della ditta Toso Spa. Questo articolo sarà, molto probabilmente, memorizzato in una tabella, chiamata, presumibilmente, "Articoli", dove troveremo, quasi certamente, i seguenti campi:
E' possibile che l'articolo 123 possa trovarsi in un record successivo della stessa tabella, seguito da un campo DESCRIZIONE diverso da "tosaerbe"? Teoricamente, sì. Ma sarebbe un disastro, per l'azienda venditrice, proprietaria del database, visto che, ogni volta in cui dovesse ricevere un ordine per l'articolo 123, non saprebbe cosa inviare al cliente. E' possibile che l'articolo 123 possa trovarsi in un record successivo della stessa tabella, seguito da un campo FORNITORE diverso da "Toso SPA"? Ancora una volta, teoricamente, sì. Ma sarebbe un disastro, per l'azienda fornitrice, visto che, ogni volta in cui dovesse spedire l'articolo 123 all'azienda cliente, rischierebbe di non vedersela pagare. In questo caso, possiamo affermare che, nella tabella "Articoli", i campi DESCRIZIONE e FORNITORE sono funzionalmente dipendenti dal campo COD. Il campo COD è il campo determinante, poichè determina il valore che troviamo nei campi DESCRIZIONE e FORNITORE. La seconda e la terza Normal Form sono definite in termini di dipendenze funzionali, come sarà chiaro tra poco, poichè richiedono che ciascun campo di un record sia funzionalmente dipendente dal campo definito come chiave primaria del record. Una chiave primaria è un dato, in grado di identificare, in modo assolutamente univoco, un oggetto. Nel nostro esempio, il campo COD esprime il codice prodotto del tosaerbe dell'azienda Toso Spa. Difficilmente, un altro prodotto potrà essere rappresentato dallo stesso codice. Il camp COD, quindi, ha tutte le caratteritiche per assumere il ruolo di chiave primaria, in particolare, l'unicità: una chiave primaria deve identificare un solo record, all'interno di una tabella. Una chiave primaria può essere composta da più di un campo. Se, per esempio, avessimo una tabella in cui venissero memorizzate le giacenze di tutti gli articoli, nei vari magazzini, avremmo una tabella con almeno tre campi:
in cui la sola colonna COD non potrebbe essere identificata come chiave primaria, poichè lo stesso articolo potrebbe trovarsi, come giacenza, in più di un magazzino, come è evidente nel nostro esempio. In questo caso, quindi, è possibile definire una chiave primaria, come composta dai due campi COD e MAGAZZINO, che, nel loro insieme, identificano un oggetto assolutamente unico. Il campo QUANTITA', inoltre, sarebbe in rapporto di dipendenza funzionale con l'intera chiave primaria, vale a dire con l'insieme dei campi che la compongono. Affinchè una chiave primaria mantenga la sua caratteristica unicità, all'interno della tabella, è necessario che l'oggetto che rappresenta venga rappresentato da un identificativo difficilmente duplicabile. Nei nostri esempi precedenti, abbiamo, per ragioni di semplificazione, rappresentato ciascun oggetto della tabella "Clientela" con il solo nome proprio del cliente:
In questo caso, tuttavia, come potremmo garantire l'unicità del record denominato "ugo", all'interno dell'intera tabella? Avremo pure un altro cliente di nome Ugo, che non abiti in via Bai, a Milano! Oppure, addirittura, potremmo benissimo avere un altro cliente, di nome Ugo, che abiti, anche lui, a Milano, in via Bai! Questo pone il problema di trovare un nome identificatore, da assegnare al campo chiave primaria, che sia univoco e non duplicabile. Nel caso delle persone, una soluzione potrebbe essere, in Italia, il codice fiscale, un dato che chiunque possiede e che, per sua propria natura, riveste un carattere di assoluta unicità. La nostra tabella "Clientela", quindi, dovrebbe essere leggermente modificata:
In questo caso, il ruolo di chiave primaria verrebbe svolto dal campo CODICE FISCALE, mentre i restanti tre campi resterebbero in rapporto di dipendenza funzionale con il campo chiave primaria, soddisfacendo, così, le prime tre Normal Form. La scelta di una chiave primaria, come abbiamo visto, potrebbe nascondere qualche insidia, poichè la sua unicità deve essere garantita per tutta la vita della tabella o del database. Esiste un tipo particolare di chiave primaria, chiamata Identity Key, che ci permette di aggirare il gravoso compito di identificare, per ciascuna tabella di un database, una colonna, oppure un gruppo di colonne, che svolga il ruolo di chiave primaria. Questo tipo particolare di chiave primaria è un tipo di dato INTEGER, il cui valore viene incrementato di 1, automaticamente, ad ogni inserimento di un nuovo record, e viene dichiarato al momento di creazione della tabella:
Per ciascuna tabella, è possibile dichiarare una sola colonna come AUTO_INCREMENT. Una colonna AUTO_INCREMENT non può avere un valore di DEFAULT. Nel nostro esempio, avremo una tabella, chiamata
Il mondo SQL si divide tra coloro che considerano questo tipo di chiave numerica assolutamente non conforme agli standard e, quindi, portatrice di insanabili squilibri, per la tabella in cui viene dichiarata e per l'intero database, e coloro che ritengono questo tipo di chiave assolutamente necessaria, per la stabilità di qualsiasi database. Gli argomenti a sostegno sono, spesso, molteplici, ma credo che i due argomenti forti, per un campo e per l'altro, siano:
Se si decide di utilizzare una chiave primaria numerica, è importante ricordare che il tipo di dato assegnato dovrà servire un numero sufficiente di record, per il futuro. Nel nostro esempio, abbiamo usato il tipo di dato INTEGER, che utilizza 4 byte, potendo, quindi, rappresentare più di 4 miliardi di record. Per la mia piccola azienda, è molto probabile che questo numero sia più che sufficiente, per rappresentare la clientela presente e futura. First normal form e definizione di chiave esterna ( Foreign Key ) La prima delle cinque Normal Form ha a che fare con la " forma " di un record. Secondo questa regola, tutte le occorrenze di un record type ( tipo di record ) devono contenere lo stesso numero di campi. In termini SQL, il record type del nostro esempio è la sequenza di campi:
mentre i record:
rappresentano, ciascuno, una occorrenza del tipo di record. La prima Normal Form tende ad escludere la ripetizione di campi in un record, oppure la ripetizione di gruppi di record. Proviamo ad aggiungere, alla nostra tabella "Clientela", il campo "Cellulare":
e proviamo a pensare: che fare, nel caso Ugo avesse due numeri di cellulare? Esistono due diverse soluzioni che, sfortunatamente, contrastano con la prima Normal Form e che, quindi, non sono praticabili. La prima soluzione errata è la duplicazione di un campo:
I database relazionali non ammettono record con un numero variabile di campi. La seconda soluzione impraticabile è la duplicazione di un record:
che, come abbiamo già visto, a lungo andare comporta l'inconsistenza e l'inaffidabilità del database. Che fare, quindi, per rispettare la prima delle Normal Form, se Ugo continuasse a mantenere due numeri di cellulare differenti? Semplice: basterebbe creare una nuova entità, cioè una nuova tabella, chiamata "Rubrica_Telefonica", interamente dedicata ai numeri telefonici dei clienti ed eventualmente arricchita con nuovi campi, vale a dire con nuove proprietà, relative ai singoli numeri telefonici. Per esempio:
La chiave primaria della tabella "Rubrica_Telefonica" potrebbe essere la colonna CELLULARE, visto che ad ogni numero di cellulare corrisponde, solitamente, un solo utente o cliente. Tra le due tabelle, "Clientela" e "Rubrica_Telefonica", viene instaurata una relazione, attraverso il campo ID_Cliente, della tabella "Rubrica_Telefonica", che, per ciascun record, conterrà lo stesso valore contenuto nel campo ID della tabella "Clientela", relativo al cliente possessore del numero di cellulare corrispondente. Se il campo:
è la chiave primaria della tabella "Clientela", il campo:
rappresenta una chiave esterna ( Foreign Key ), poichè rimanda ad un record, e solo ad uno, di un'altra tabella. Secondo la teoria dei database relazionali, una chiave esterna dovrebbe fare riferimento direttamente alla chiave primaria dell'altra tabella ( come nel nostro caso ), anche se SQL permette di richiamare una colonna qualsiasi della tabella esterna, a patto che sia stata definita almeno UNIQUE. Grazie a queste due tabelle, legate l'una all'altra attraverso i campi:
saremo sempre in grado di ricostruire una tabella virtuale ( VIEW ), che contenga, anche solo in parte, le informazioni conservate nelle due tabelle:
Il compito di una chiave esterna ( Foreign Key ) non si esaurisce qui. La chiave esterna, infatti, garantisce la corretta gestione, da parte del RDBMS, della cosiddetta "integrità referenziale", un meccanismo attraverso il quale qualsiasi cambiamento avvenga nella tabella referenziata ( "Clientela", nel nostro caso ), si ripercuote, automaticamente, nella tabella referente ( "Rubrica_Telefonica", nel nostro caso ). Lo ID di un cliente, nella tabella "Clientela", è stato modificato? La modifica si propagherà a tutti i record, assegnati a quel cliente, contenuti nella tabella "Rubrica_Telefonica". Un cliente è stato cancellato, dalla tabella "Clientela"? Tutti i record referenti nella tabella "Rubrica_Telefonica" verranno automaticamente eliminati. Una chiave primaria può essere composta da più campi. In una tabella inventariale, per esempio, un prodotto può essere assegnato ad un magazzino, piuttosto che ad un altro:
PART è il codice articolo di un pezzo di ricambio e WAREHOUSE è il magazzino in cui si conservano i pezzi di ricambio. Questi due campi, insieme, vengono usati come chiave primaria, poichè identificano un oggetto preciso ( l'articolo PART, nel magazzino WAREHOUSE ), di cui il record rappresenta l'insieme delle proprietà. Nei casi di chiavi primarie composte, come questo, la seconda delle normal form viene violata quando un campo di un record, che non sia parte di una chiave primaria, contiene un dato relativo ad un solo campo della chiave primaria. In questo caso, ci troveremmo di fronte ad una dipendenza funzionale, dalla chiave primaria, solo parziale. Vediamo un esempio:
In questo caso, il campo WAREHOUSE-ADDRESS, che dovrebbe contenere l'indirizzo del magazzino, è una proprietà dell'oggetto WAREHOUSE e non dell'oggetto PART, mentre, secondo questa seconda Normal Form, ogni dato, o campo, contenuto in ciascuno dei record che compongono la tabella, deve rappresentare una proprietà della chiave primaria, cioè dell'insieme composto dal campo PART e dal campo WAREHOUSE. Se lasciassimo il campo WAREHOUSE-ADDRESS nella tabella "Inventory", cosa accadrebbe?
Anche in questo caso, il processo di normalizzazione chiede di scomporre la tabella "Inventory" in due tabelle distinte, ciascuna delle quali dedicata ad un'entità distinta:
La relazione tra le due tabelle verrebbe mantenuta dalla chiave esterna:
che referenzierebbe la chiave primaria della tabella "Warehouse":
Anche in questo caso, grazie a queste due tabelle, saremo sempre in grado di ricostruire una tabella virtuale ( VIEW ), che contenga, anche solo in parte, le informazioni conservate nelle due tabelle:
La terza Normal Form è abbastanza simile alla seconda Normal Form: la sola differenza è che la terza Normal Form fa riferimento a campi di un record che non siano chiavi primarie o parti di chiavi primarie. La terza Normal Form viene violata quando un campo, che non sia una chiave primaria, contiene un dato relativo ad un altro campo del record, che non sia la chiave primaria. Per esempio, in una tabella di impiegati, "Employees":
il campo EMPLOYEE potrebbe essere la chiave primaria della tabella, il campo DEPARTMENT qualificherebbe maggiormente il lavoratore EMPLOYEE, esprimendo la direzione di appartenenza, ma il campo LOCATION dovrebbe qualificare l'indirizzo della direzione aziendale, DEPARTMENT, e non del dipendente, EMPLOYEE, generando, in questo modo, le stesse anomalie, o inconsistenze, segnalate per la seconda Normal Form. In termini di dipendenza funzionale, siamo di fronte ad una dipendenza transitiva, in cui una chiave primaria ( EMPLOYEE ) determina il valore di un campo ( DEPARTMENT ), ma non il valore del campo successivo ( LOCATION ), che, invece, viene determinato dal campo precedente ( DEPARTMENT ). Anche in questo caso, la soluzione risiede nella creazione di due tabelle distinte, ciascuna riservata ad una sola entità:
La quarta Normal Form ha a che vedere con le proprietà dette multi-valued, che si incontrano quando è possibile che un record di una tabella richieda la creazione di un altro record, nella stessa tabella, per poter specificare una lista di proprietà dello stesso tipo. Un esempio potrebbe essere una tabella in cui vengono memorizzate le capacità di ciascun impiegato, all'interno di un'azienda:
oppure le lingue parlate:
La quarta Normal Form chiede che un record non contenga più di un campo multi-valued, sempre che le proprietà multi-valued siano indipendenti l'una dall'altra. Quindi, la seguente tabella non è conforme alla quarta Normal Form:
ne, tantomeno, la seguente:
mentre le due tabelle separate precedenti, lo sono. E se le proprietà multi-valued non fossero indipendenti l'una dall'altra? Se, per esempio, il sig. Smith fosse un grande esperto di finanza, ma fosse in grado di misurarsi con i mercati solo in lingua finlandese, la presenza di due proprietà multi-valued, all'interno della stessa tabella, diverrebbe lecita, perchè avrebbe un senso compiuto:
In questo caso, e solo in questo caso, il record non violerebbe la quarta Normal Form.
|
|||||
I comandi Linux: MySQL ( 3 ) | The .bit guides: original contents |