Teoria e pratica per la normalizzazione delle basi di dati (Parte prima)
a cura di Marco Tonti (requisiti: conoscenza dei DB relazionali)

Premessa dell'autore
Mi propongo di parlare della normalizzazione delle basi di dati non solo da un punto di vista formale, né solo da un punto di vista pratico.
La mia intenzione è illustrare come teoria e pratica (leggi buon senso) riescano a convergere in un punto che non appartiene strettamente né all'una né all'altra visione delle cose.
La concezione di questi articoli è quella del percorso, cioè del costante tentativo di capire perché una visione ha dei problemi e cosa provoca tali problemi, e solo in seguito darne un'illustrazione e una spiegazione formale.

Introduzione
(Tratta dal libro di Paolo Ciaccia e Dario Maio "Lezioni di basi di dati" - Progetto Leonardo - Bologna, al quale mi sono ispirato anche per alcuni esempi e definizioni).

"Lo studio delle proprietà delle dipendenze è importante in quanto permette di dotarsi di adeguati strumenti in fase di analisi dei dati e di progettazione degli schemi.

"La teoria della normalizzazione ha come scopo principale quello di fornire gli strumenti teorici e pratici per progettare 'buoni' schemi relazionali, cioè schemi che

"I moderni metodi di progettazione, basati su linguaggi concettuali, se applicati con cura permettono spesso di pervenire a schemi relazionali che presentano già il livello di normalizzazione richiesto. Da questo punto di vista, gli strumenti propri della teoria della normalizzazione non vanno interpretati come finalizzati a produrre 'automaticamente' uno schema a partire dalla specifica dei vincoli, bensí come un ausilio formale alla verifica e al raffinamento."

Come bene espresso da queste parole la definizione delle forme normali per l'organizzazione dei dati ha uno scopo normativo. Servono per avere la certezza di aver ben fatto un lavoro, piú che per lasciar fare a una macchina o a una teoria un lavoro che sono i progettisti a dover fare.

Le forme normali che andrò a trattare sono: 1NF, 2NF, 3NF, BCNF. Ne esistono altre via via piú stringenti che però sono di norma tralasciate. Le prime due verranno trattate in questo articolo, le rimanenti nel prossimo.

Terminologia e nozioni preliminari
La terminologia usata nello studio teorico delle basi di dati è leggermente diversa da quella che si usa solitamente. I record per esempio vengono chiamati tuple, che sospetto sia la forma contratta di t-uple, cioè un insieme ordinato di t elementi. I campi vengono chiamati attributi. Nel corpo dell'articolo userò indistintamente le due forme.

Di particolare importanza è il concetto di chiave. Una chiave è un insieme di attributi il cui valore identifica una e una sola tupla. Oppure, se preferite, due record che hanno lo stesso valore di chiave sono lo stesso record, tralasciando il fatto che tutti i motori DB impediscono l'inserimento di nuovi record che abbiano il medesimo valore di chiave.

Questa definizione vale unicamente se nella tabella vengono inserite informazioni legali, cioè reali. Per esempio, nell'elenco degli abbonati al telefono la combinazione di due attributi che formano la chiave (Prefisso, Numero) deve per forza essere unica. Non possono esistere due diversi abbonati che abbiano lo stesso numero e prefisso, perché in tal caso avremmo un'istanza illegale dello schema, che corrisponde a una situazione impossibile per la natura stessa dei dati della chiave.

È importante far notare che il concetto di chiave è semantico, cioè non è ricavabile dai dati. Dev'essere il progettista a stabilire quali attributi devono andare a far parte della chiave di una tabella. Se per due distinte istanze di una tabella la chiave può avere lo stesso valore, c'è qualcosa che non va o nella struttura o nell'inserimento dei dati.

(Nelle tabelle i nomi degli attributi in corsivo rappresentano gli attributi partecipanti alla chiave).

Prima forma normale (1NF)
Io solitamente distinguo a questo punto tra dati attivi e dati passivi.
I dati passivi vengono conservati solo come "appendice" delle informazioni, e non hanno un significato operativo. Al contrario i dati attivi sono quelli sui quali ci si può basare per ottenere informazioni ulteriormente strutturate. Come sarà ovvio tra un momento, questa distinzione può variare a seconda delle necessità che il sistema dovrà soddisfare. Mettiamo di avere una tabella che conservi alcune informazioni su dei magazzini, e precisamente il codice del magazzino, il prodotto contenuto e la sua quantità.

 
cod_mag prodotto quantità
BO1 Scarpe 500
BO1 Calze 1300
MI1 Elefanti 8
MI2 Cravatte 800000

Se il nostro sistema dovesse solamente elencare queste informazioni, e fossero i ragionieri ad aggiornarle e ad elaborarle, tutti questi dati sarebbero passivi.
Qualora volessimo conoscere le merci contenute in un particolare magazzino, l'attributo cod_mag diventerebbe attivo, perché dovrei filtrare la tabella in base al suo valore.
Se poi avessi la necessità di sapere in quali magazzini sono contenuti certi prodotti, anche l'attributo prodotto diventerebbe attivo, per lo stesso motivo di prima.
Potrei inoltre voler sapere anche complessivamente quante unità di un certo prodotto sono sparse per i miei vari magazzini. In questo caso anche il terzo attributo, quantità, diventerebbe attivo.
Se però a me non importasse ottenere alcuna di queste informazioni, bastandomi la possibilità di fare ricerche per magazzino, potrei anche definire i miei dati cosí:

 
cod_mag info
BO1 Le scarpe sono 500 e le calze sono 1300
MI1 Gli elefanti sono 8
MI2 Le cravatte sono 800000

Questa forma sarebbe in un certo senso validissima, se fossero i ragionieri a fare tutto. Ma provate a pensare cosa significa andare a costruire anche le piú semplici interrogazioni su una struttura di questo tipo: improponibile!

Ecco che la distinzione fra dati attivi e passivi diventa importante: i dati attivi devono essere completamente disaggregati. Cioè bisogna poter accedere in modo standardizzato a ogni singolo elemento dell'informazione, se quest'elemento può per noi essere base per un'elaborazione.

Molti fanno l'errore (e io pure l'ho fatto tante volte) di sottovalutare la possibile evoluzione di un sistema. Se domani il committente mi dicesse che vuole automatizzare tutta la gestione, una struttura come l'ultima tabella porterebbe al delirio: sono tutte informazioni che andrebbero buttate o ri-trascritte manualmente. Quindi non fate mai campi NomeCompleto, ma sempre Nome e Cognome.

Proprio per eliminare questo problema è stata definita la Prima forma Normale: Una relazione è in 1NF se e solo se ogni componente di ogni tupla ha un valore atomico, non ulteriormente scomponibile.

La 1NF è di norma implicita: ogni informazione deve essere "atomica", cioè un campo deve contenere una e una sola informazione. Dico che è implicita perché i campi contengono sempre un tipo di dato "semplice" (Stringa, numero, binario...) e mai aggregazioni (insieme, vettore). Le date potrebbero contravvenire a questo principio, perché in una data c'è il Giorno, il Mese e l'Anno che potrebbero essere viste come entità separate e separabili... ma di solito si lascia perdere considerando una "data" come un tutt'uno (d'altra parte è noto che una data è immagazzinata come numero).

Questo va fatto nei limiti del buon senso. Per esempio un indirizzo "Via Santa Caterina 49 Bologna", dovrebbe essere scomposto in parti come Topotassia, Toponimia, Civico, Città vale a dire ("Via", "Santa Caterina", 49, "Bologna"), ma di solito, per fortuna, non si fa, a meno di non averne un'esigenza particolare. Un esempio potrebbe essere che si vuole fare delle spedizioni postali divise per città, e in questo caso anche l'indirizzo andrebbe in una certa misura disaggregato, magari anche solo per Indirizzo e Città. In altre parole, Città diventa un dato attivo nell'economia del database.

In alcuni casi può essere necessario dividere una tabella in due o piú tabelle. In particolare sono i casi di quelle relazioni del tipo 1-n (uno a molti) dove a un oggetto possono essere associati vari altri oggetti. Non potendoli incorporare in un attributo (perché dovrebbe contenere un insieme), è necessario disaggregarli ponendoli in un'altra tabella, mantenendo la relazione tra le due grazie ai campi chiave.

Seconda forma normale (2NF)
Una volta stabilito che i dati (o per lo meno quelli attivi) devono essere completamente disaggregati, in questo secondo livello normativo si va ad affrontare il vero e proprio strato logico della struttura.

Ricordiamo cos'è una chiave: è un insieme di attributi i cui valori identificano una e una sola tupla. Il dettaglio sul quale voglio porre l'attenzione è il fatto che una chiave può essere definita contemporaneamente su piú di un campo. Nell'esempio che abbiamo già visto degli abbonati al telefono, la chiave (o meglio, una delle possibili chiavi) è la combinazione di campi (Prefisso, Numero). Due valori di chiave sono identici se sono identici i valori di ognuno dei suoi campi. Possono infatti esistere piú abbonati il cui numero sia 123456, ma necessariamente i prefissi devono essere diversi. Come pure esistono migliaia di persone che hanno lo stesso prefisso ma numeri diversi.

Cominciamo con un esempio

 
cod_articolo magazzino quantità indirizzo
1 BO 1500 Via Vieni 15
2 BO 2300 Via Vieni 15
1 MI 850 Via Vai 47
2 MI 1700 Via Vai 47

È subito evidente che in questa struttura c'è qualcosa che non va. Cerchiamo di capire bene cosa.

La prima cosa che salta agli occhi è la ripetizione dell'informazione legata all'indirizzo del magazzino. Se guardiamo le righe della tabella una per volta questa disposizione è coerente e razionale. Guardando la prima tupla veniamo a scoprire che dell'articolo che ha codice 1 ce ne sono 1500 pezzi nel magazzino di Bologna che sta in via Vieni 15. Una cosa simile la vediamo con la seconda riga, e cosí via. La magagna diventa visibile solo quando osserviamo nel suo intero la tabella.

Ma perché l'indirizzo viene ripetuto per ogni riga? Il motivo è che ci serve una riga per ogni articolo di ogni magazzino, e non una riga per ogni magazzino. Possiamo dire, con buona pace di chi ha definito lo standard relazionale, che questa disposizione è piuttosto innaturale... ma dobbiamo farci i conti.

Cerchiamo di capire meglio: mentre la quantità di un articolo dipende sia dall'articolo in questione che dal magazzino dove si trova (e ci serve una riga per ognuna di queste combinazioni), l'indirizzo del magazzino dipende solo dal magazzino. Questo costringe a moltiplicare l'informazione dell'indirizzo per ogni articolo di quel magazzino.

Infatti mentre la chiave per ogni tupla può assumere solo una combinazione di valori (che poi si usa per scoprire la quantità), la parte di chiave che stabilisce il magazzino può essere ripetuta molte volte.

Bene, credo che abbiamo capito dov'è l'errore. Proviamo a riassumere e astrarre il ragionamento: i problemi nascono quando ci sono dei campi che non cambiano quando varia il valore della chiave (anche solo di una parte della chiave). Nel nostro caso, l'indirizzo rimane sempre lo stesso a meno che non cambiamo magazzino. E questo è logico! Non deve cambiare!

Bisogna fare una distinzione: ho usato il termine "cambiare" in modo improprio, intendendo dire "può cambiare". La distinzione è importante, perché il legame che c'è fra l'indirizzo di un magazzino e il magazzino stesso è esclusivamente semantico. Cioè noi sappiamo che è cosí, ma la matematica non può saperlo. Non si può stabilire una relazione logica di questo tipo solo osservando che certi campi cambiano e altri no al variare in un certo modo della chiave.

 
se l'Articolo e se il Magazzino allora la CHIAVE allora la Quantità allora l'Indirizzo
Cambia Cambia Cambia Può cambiare Può cambiare
Cambia Non cambia Cambia Può cambiare NON può cambiare
Non cambia Cambia Cambia Può cambiare Può cambiare
Non cambia Non cambia Non cambia Non può cambiare NON può cambiare

Vediamo allora di stabilire qualche concetto. Se il valore di un campo può variare a qualsivoglia variazione della chiave, possiamo dire che quel campo dipende dall'intera chiave, mentre se il valore di un altro campo può variare solo alla variazione di una parte della chiave, allora quel campo non dipende dalla chiave, ma solo da una parte di essa.

Questo che abbiamo appena definito in parole povere è il concetto di dipendenza funzionale (FD), che ci sarà necessario in tutto il seguito. Vediamo di esprimerlo meglio. Un attributo dipende funzionalmente da un altro se quando varia il secondo varia anche il primo.

Formalmente:
Siano R1 e R2 due record (istanze legali) di una tabella. Diciamo che l'attributo Y dipende funzionalmente dall'attributo X, se per identici valori di X abbiamo identici valori di Y. Cioè se R1.X = R2.X allora R1.Y = R2.Y
Scriveremo allora che X -> Y (X determina funzionalmente Y)

Riportiamolo al nostro esempio dei magazzini. Possiamo dire che la quantità di un certo prodotto in un certo magazzino è definita funzionalmente dal prodotto e dal magazzino (vale a dire Prodotto, Magazzino -> Quantità), mentre l'indirizzo di un magazzino dipende funzionalmente solo dal magazzino (Magazzino -> Indirizzo). In pratica, presi due qualsiasi record R1 e R2, avremo sempre che se R1.Magazzino = R2.Magazzino allora R1.Indirizzo = R2.Indirizzo, il che è ovvio oltre che corretto.

Attenzione: la relazione non è reversibile! Il fatto che due magazzini abbiano lo stesso indirizzo, non implica che siano lo stesso magazzino! (Magari abbiamo nella stessa costruzione al primo piano un magazzino alimentare fresco e al secondo uno di scatolame che potremmo volere tener distinti)

In realtà la dipendenza funzionale tra due attributi è tra due qualsiasi attributi, in generale non è necessario che quello che determina l'altro appartenga alla chiave. Le dipendenze funzionali vanno inoltre sotto al nome piú generico di vincoli.

Giusto per snellire la lettura, diamo le definizioni di Attributo primo e di Attributo non primo. Un attributo primo è un attributo che appartiene alla chiave. Un attributo non primo è un attributo che non appartiene alla chiave.

Abbiamo cosí raggiunto la definizione della Seconda forma normale: Uno schema è in 2NF se e solo se ogni attributo non primo dipende completamente dalla chiave.

In altre parole: se abbiamo una chiave K formata, diciamo, dagli attributi X Y e Z, non deve esistere nessun attributo C non primo (quindi diverso da X Y e Z) che abbia un vincolo di dipendenza funzionale con nessuno dei componenti della chiave, cioè non dev'essere X -> C, Y -> C, Z -> C, né con raggruppamenti di questi, come XY -> C, XZ -> C, YZ -> C.

Formalmente:
sia la chiave K un sottoinsieme degli attributi di una tabella T, e A un attributo non primo (cioè che appartiene a T ma non a K).
Perché T sia in 2NF non deve esistere un K' sottoinsieme proprio di K tale che K' -> A (Non dev'esserci una dipendenza funzionale tra un sottoinsieme proprio della chiave e un attributo non primo)

Come corollario sappiate che uno schema 1NF che abbia solo chavi "semplici", cioè composte da un solo attributo, è automaticamente in 2NF. Se avete capito bene la spiegazione, dovreste intuirne subito il motivo.

(Per chi è curioso ecco la dimostrazione. Sia K la chiave di una tabella, con K composta da un singolo attributo. Ipotizziamo, per assurdo, che esista K' sottoinsieme proprio di K che determini funzionalmente un attributo non primo A. Questo K' sarebbe composto di zero attributi essendo un sottoinsieme proprio, il che è assurdo. Questo contraddice l'ipotesi e dimostra il teorema. Spiegandomi meglio: non può esserci una dipendenza funzionale tra una parte della chiave e un attributo non primo perché la chiave ha già la minima dimensione possibile)

Per eliminare l'incongruenza, è sufficiente suddividere la tabella in due, isolando le relazioni funzionali che comportano dei conflitti. In questo caso dovremmo crearci una tabella che esprima Magazzino -> Indirizzo e un'altra che esprima Prodotto, Magazzino -> Quantità.

Uso e consumo della 2NF
Ma tutto questo parlare di formule e concetti a cosa ci porta? Ci porta a capire come, fin da subito, progettare bene una base di dati. Sarà risultato lampante a tutti che la tabella dell'esempio precedente era proprio "pensata male". E per chi è abituato a pensare a oggetti sarà sembrato persino un abominio.

Infatti, se avessi dovuto pensare a oggetti a una struttura del genere avrei creato un oggetto magazzino che avesse come proprietà il suo indirizzo e un insieme di oggetti articolo. Un oggetto articolo avrebbe avuto la quantità contenuta. La conseguenza nella strutturazione della base dei dati sarebbe stata immediata: una tabella MAGAZZINI e una tabella ARTICOLI. Nel caso dei DB, gli oggetti vengono raccolti tutti nella stessa tabella (infatti potremmo pensare alla struttura di una tabella come alla classe che definisce le istanze dell'oggetto)

Le cose importanti da capire sono le seguenti:

I vincoli sono lo strumento che noi usiamo per definire la parte essenziale della nostra conoscenza del mondo, almeno per il progetto di una base di dati. Sarebbe stupido non usarli, visto che sono un formalismo snello ed efficiente.

Infatti visti i vincoli (che non sono altro che espressioni formali di concetti che noi abbiamo) del caso precedente, ci sarebbe dovuto venire spontaneo progettare nel modo giusto la struttura dei dati.

L'ottica giusta di vedere queste conquiste teoriche non è quella del "Accidenti mi tocca anche fare l'analisi", ma "Devo fare il progetto in un certo modo, altrimenti non passa l'analisi". E questo modo è quello giusto. Perché l'analisi è impietosa, è formale, è fatta in base a regole che non si fanno abbindolare dalle nostre sensazioni, dalle nostre convinzioni.

Questi concetti ci aiutano a capire non "come si fanno" le cose, ma come "non si devono fare", perché dimostrano che determinati approcci sono intrinsecamente incoerenti con la concezione relazionale, e quindi sbagliati.

Inoltre, una dipendenza funzionale astrae e generalizza il concetto di chiave. Infatti, se diciamo che un attributo A è funzionalmente determinato da un attibuto B (che scriviamo B -> A) possiamo in un certo senso dire che B è il campo chiave di A, perché univocamente lo determina. Allora l'obiettivo della normalizzazione non è altro che quello di avere una medesima e singola chiave (anche se magari composta da piú attributi) per ogni attributo della tabella. Tale considerazione guiderà anche i livelli di normalizzazione che illustrerò in seguito.

Bisogna poi stare attenti a non confondersi, a non farsi fuorviare da un'apparente ripetizione: potremmo avere il caso in cui ogni articolo contenuto in ogni magazzino abbia lo stesso numero di unità. Ma questo è un tipo di ripetizione che non ci disturba, perché non è "strutturale", non è implicita. Il fatto che in piú tuple un attributo abbia il medesimo valore è un caso, non una necessità matematica. Il motivo è che ognuna di quelle informazioni ha un significato (ma non necessariamente un valore) diverso da un record all'altro. Invece certi campi hanno un valore che viene compulsivamente ripetuto per via della dipendenza funzionale con una parte ripetitiva della chiave.

(segue)