Meno ridondanze grazie alla normalizzazione dei database

Uno dei concetti fondamentali della modellazione dei dati relazionali è la normalizzazione. Nei modelli di database relazionale un buon design di database si contraddistingue per una bassa ridondanza e la ragione è che i dati ridondanti portano ad anomalie semantiche. A propria volta queste anomalie appesantiscono l’elaborazione automatica dei dati e la gestione del database. La normalizzazione è una strategia per eliminare le ridondanze nei database relazionali. In questo articolo vi mostriamo come procedere.

Che cos’è la normalizzazione?

Con normalizzazione si intende un approccio al design di database che serve a evitare le ridondanze nei database relazionali.

Il modello di database relazionale è la tipologia più diffusa per la gestione informatizzata dei dati. Nei database relazionali le informazioni vengono archiviate come record in tabelle, che sono connesse le une alle altre grazie a chiavi. Un record di dati è costituito dai valori che vengono assegnati a determinati attributi tramite le colonne della tabella.

La seguente tabella mostra i dati di fatturazione archiviati di un fornitore fittizio di una fabbrica: Geronimo Scatandro ha ordinato 10 monitor, 12 tappetini per il mouse e 1 sedia da ufficio per la propria attività, mentre l’ordine di Gioia Rinace consiste in 2 computer portatili e 2 cuffie.

Nel database del negozio online vengono assegnati i dati di fatturazione agli attributi numero di fattura (nr. fatt.), data, cliente, numero cliente (nr. cliente), indirizzo, numero posizione fattura (nr. pos.), articolo, numero articolo (nr. art.), quantità e prezzo. Ogni riga della tabella rappresenta un record di dati, che viene indicato anche come tupla.

La sezione di database mostrata sopra è il perfetto esempio di un design di database non riuscito: le ridondanze della tabella si notano già al primo sguardo. Inoltre le colonne cliente e indirizzo contengono dati che hanno più valori. In questi casi si parla di un database non normalizzato.

Il maggiore svantaggio dei database non normalizzati è la necessità di un maggiore spazio di archiviazione, reso appunto necessario dai valori ridondanti. Inoltre gli attributi che hanno dati con più valori sono difficili da leggere e da collegare gli uni agli altri.

Esempio: entrambi i clienti nella sezione di database mostrata hanno sede a Roma. Tuttavia, poiché queste informazioni non vengono considerate separatamente, i database avrebbero una notevole difficoltà nel filtrare ad esempio i clienti provenienti da uno stesso luogo.

Per evitare intervalli di valori doppi o multivalore, nell’ambito dei modelli di database relazionali sono state sviluppate tre forme normali che si susseguono progressivamente aumentando i requisiti.

La forma normale indica lo stato che si vuole raggiungere e ogni forma normale ha dei particolari requisiti che devono essere soddisfatti per raggiungere appunto questo stato. Esistono tre tipi di forme normali (prima, seconda e terza) e per ciascuna sono necessari requisiti diversi.

Fatto

Con normalizzazione si indica la trasposizione di una tabella di database in una forma normale di grado più alto. Se invece si scende di livello a una forma normale di grado più basso, si parla di denormalizzazione.

Normalizzazione: come si normalizza un database

Per illustrare il processo che porta un database relazionale nella prima, seconda e terza forma normale, diamo un’occhiata alle singoli fasi della normalizzazione usando un esempio. Il punto di partenza è la porzione di database presentata nell’esempio sopra.

Prima forma normale (1FN)

Una tabella di un database relazionale rientra nella prima forma normale (1FN) quando sono soddisfatti i seguenti requisiti:

  • tutti i dati hanno valori atomici;
  • tutte le colonne della tabella contengono valori dello stesso tipo.

Un record di dati si definisce atomico quando ogni informazione è attribuita ad un campo di dati separato.

Di seguito trovate la nostra tabella sui dati di fatturazione nella quale abbiamo evidenziato in rosso i campi che o non sono atomici o che non contengono dati equivalenti.

Le numerose ridondanze mostrano che la nostra tabella di partenza viola entrambe le condizioni, non rientrando nella prima forma normale.

Per normalizzare il database al livello della prima forma normale, è richiesta la seguente procedura:

  1. separate i dati con più valori in colonne separate;
  2. controllate che i valori di ogni colonna abbiano la stessa unità di misura o che siano dello stesso tipo.

Per portare i record di dati nella forma atomica, gli attributi cliente e indirizzo devono essere separati negli attributi specifici cognome, nome, via, numero civico, codice di avviamento postale (CAP) e città.

N.B.

Per stabilire da quando un valore può essere visto come atomico, occorre tenere conto del contesto di utilizzo. Se ad esempio non è necessaria una separazione tra nome e cognome, anche il nome completo di una persona può contare come valore atomico. Tuttavia nella pratica si consiglia di suddividere i valori composti da più elementi nelle unità più piccole possibili.

Nella colonna “prezzo” si trovano i valori espressi in euro e in centesimi. Occorre perciò decidere a quale tipo di unità di misura attenersi per avere intervalli di valore dello stesso tipo.

Il risultato è una tabella che corrisponde alla prima forma normale, che però a causa di valori doppi non consente ancora un’elaborazione efficiente dei dati. Si consiglia perciò di portare la tabella alla seconda forma normale, per rimuovere le ridondanze.

Consiglio

La prima forma normale prescrive intervalli di valori atomici e consente in questo modo di eseguire query al database. I dati che fanno invece parte di intervalli di valori non atomici non possono essere oggetto di query separate.

Seconda forma normale (2FN)

Una tabella conforme alla seconda forma normale deve soddisfare tutti i requisiti della prima forma normale e in aggiunta il seguente:

  • ogni attributo non chiave deve essere completamente dipendente a livello funzionale dalla chiave primaria.

Nell’introduzione si è definito un database relazionale come un sistema di singole tabelle connesse tra loro grazie a chiavi.

Le chiavi nei database relazionali hanno il ruolo di identificare in modo univoco record di dati (tuple). Una chiave che consiste in un sottoinsieme di attributi che consentono di nominare inequivocabilmente le singole righe di una tabella di database è chiamata superchiave. Questo tipo di chiave può risultare dai valori di una singola colonna o dalla somma dei valori di più colonne.

Nel nostro esempio una possibile superchiave si configura ad esempio negli attributi numero di fattura (nr. fatt.), numero cliente (nr. cliente) e numero posizione (nr. pos.). Nella tabella seguente abbiamo evidenziato la superchiave con un colore diverso.

Una chiave ottenuta da numero di fattura, numero cliente e numero posizione con i valori {124, 12, 1} consente ad esempio di identificare in modo incontrovertibile il record corrispondente all’acquisto del computer portatile da parte di Gioia Rinace:

Tuttavia per una identificazione univoca non sono necessari tutti gli elementi della superchiave selezionata: già una combinazione di numero di fattura e numero posizione, quindi di una parte della superchiave, sarebbe sufficiente a identificare il singolo record di dati. Tali chiavi con un numero minimo di attributi vengono chiamate chiavi candidate o chiavi alternative.

Normalmente viene scelta una chiave candidata per tabella, idealmente con una numerazione progressiva. Una chiave simile viene indicata come chiave primaria e specifica l’ordine dei record.

La chiave primaria, come ogni candidato chiave, può sussistere come pezzo unico o, come nel nostro esempio, come chiave composta. La nostra tabella di esempio utilizza una chiave primaria composta, che consiste nel numero di fattura e nel numero posizione.

Per portare una tabella di database nella seconda forma normale non è tuttavia sufficiente determinare la chiave primaria e tutti gli attributi non chiave, ma anche la loro relazione reciproca. Per questo bisogna attenersi alla seguente procedura:

  1. Accertatevi che tutti gli attributi non chiave siano pienamente dipendenti dalla chiave primaria. Questo tipo di dipendenza avviene soltanto quando tutti gli attributi della chiave primaria sono necessari per identificare in modo univoco gli attributi non chiave. Ciò significa anche che le tabelle con chiavi primarie di un solo pezzo corrispondono automaticamente alla seconda forma normale, se tutti i requisiti della prima forma normale sono soddisfatti.
  2. Archiviate tutti gli attributi non chiave che non sono completamente dipendenti dalla chiave primaria intera in tabelle separate.

Se osservate con attenzione la tabella di esempio, vi renderete conto che i requisiti per la seconda forma normale non sono raggiunti, per i seguenti motivi: la colonna data è dipendente soltanto da quella numero di fattura (nr. fatt.), ma non da quella numero posizione (nr. pos.). Lo stesso vale per i dati cliente nome, cognome, via, numero civico, CAP e città.

Per portare una tabella alla seconda forma normale archiviamo perciò in una tabella separata, che chiameremo “fattura”, tutti gli attributi che sono dipendenti soltanto dal numero di fattura.

Chiamiamo poi la tabella con i dati rimanenti “posizione fattura”.

Dopo la normalizzazione il numero di fattura si trova in entrambe le tabelle e le collega l’una all’altra. Mentre l’attributo nella tabella “fattura” funge da chiave primaria, nella tabella “posizione fattura” svolge la funzione di chiave esterna e allo stesso tempo è parte della chiave primaria composta della tabella.

Fatto

Il collegamento delle chiavi esterne consente una query comprensiva di entrambe le tabelle. In questi casi si parla di un join.

I dati di esempio soddisfano ora la seconda forma normale, ma non si può dire che tutte le ridondanze siano state eliminate: questo è proprio il fine della terza forma normale.

Terza forma normale (3FN)

Per portare una tabella alla terza forma normale occorre naturalmente che soddisfi innanzitutto i prerequisiti della prima e della seconda forma normale e in aggiunta il seguente:

  • gli attributi non chiave non possono essere dipendenti in modo transitivo da una chiave candidata.

Una dipendenza transitiva si ha quando un attributo non chiave è dipendente da un altro attributo non chiave e perciò indirettamente (appunto per la proprietà transitiva) dalle sue chiavi candidate.

Il nostro schema di database trasgredisce i requisiti della terza forma normale in più di un punto:

Nella tabella “fattura” il nome e il cognome, come gli attributi strada, numero civico, CAP e città, sono dipendenti non soltanto dalla chiave primaria (il numero fattura), ma anche dal numero cliente.

Nella tabella “posizione fattura” gli attributi “articolo” e “prezzo” non dipendono soltanto dalla chiave primaria, data da numero di fattura e numero posizione, ma anche dal numero dell’articolo. Anche qui viene intaccato il requisito specifico della terza forma normale.

Per eliminare tutte le dipendenze tra attributi non chiave, collochiamo i rispettivi attributi in tabelle separate, che si possono collegare l’una all’altra attraverso le chiavi esterne. Ci sono così le quattro tabelle normalizzate “fattura”, “cliente”, “posizione fattura” e “articolo”.

La chiave primaria della tabella “fattura” è un numero di fattura progressivo. A ogni numero di fattura viene assegnata la data della fattura e un numero cliente.

Maggiori informazioni sui singoli clienti sono salvati nella tabella “cliente”. Le tabelle “fattura” e “cliente” sono collegate tra loro attraverso il numero cliente, che funge nella tabella “cliente” da chiave primaria e nella tabella “fattura” come chiave esterna.

Una tabella centrale nel nostro database di esempio è quella “posizione fattura”, che contiene l’informazione su quali articoli debbano essere assegnati alla rispettiva fattura e su quanti articoli sono stati ordinati. La chiave primaria progressiva della tabella “posizione fattura” si ottiene anche dal numero di fattura e dal numero posizione. Gli articoli in questione sono elencati solamente come numeri di articoli che fungono da chiavi esterne e collegano la tabella “posizione fattura” alla tabella “articoli”.

Infine la tabella “articoli” contiene informazioni dettagliate sui rispettivi articoli, come il numero dell’articolo e il prezzo. La chiave primaria è il numero dell’articolo progressivo.

Nel nostro esempio può sembrare poco efficiente il fatto di dividere in quattro tabelle quello che stava in due, senza contare che in realtà le ridondanze relative ai dati di soltanto due clienti pesano davvero poco. Ma provate ad immaginare di avere a che fare con diverse centinaia di migliaia di record riguardanti i clienti o il vostro assortimento e di volerli elaborare in un database relazionale in modo coerente e senza contraddizioni! Ciò normalmente può accadere soltanto con uno schema di database conforme alla terza forma normale

Consiglio

Occorre prestare attenzione al fatto che non sempre in un database relazionale si riescono ad evitare i valori doppi. Se si guarda l’esempio del database, si vedrà che collegare le tabelle del database a chiavi esterne può portare a ridondanze.

Anche quando la normalizzazione dei database comporta un grande sforzo di programmazione, la terza forma normale è ritenuta generalmente lo standard per i database relazionali, da cui ci si discosta solo in casi eccezionali. Per esempio può capitare che i database che si trovano nella terza forma normale vengano denormalizzati nella seconda forma normale. Questo accade perché utilizzare join su più tabelle può comportare un grande dispendio di tempo, mentre attraverso la denormalizzazione si diminuisce il numero di tabelle e perciò si abbrevia il tempo della query.

Altre forme normali

Di solito la normalizzazione termina con la terza forma normale. Le seguenti forme normali si riferiscono a database con condizioni speciali e sono pertanto utilizzate solo in casi eccezionali.

Boyce-Codd normal form (3.5NF)

La Boyce-Codd normal form è un affinamento della terza forma normale, il cui requisito aggiuntivo rispetto alle prime due, come già menzionato, è:

  • gli attributi non chiave non possono essere dipendenti transitivamente da una chiave candidata.

Nella Boyce-Codd normal form, invece, vale la regola che:

  • nessun attributo può dipendere in modo transitivo da una chiave candidata, a meno che non si tratti di una dipendenza banale.

Quindi sono rimosse tutte le ridondanze basate sulla dipendenza funzionale, anche se possono permanere ridondanze di altro tipo.

La forma normale Boyce-Codd è rilevante esclusivamente per le tabelle di database che hanno più chiave candidate composte in cui le chiavi si sovrappongono, quindi nel caso in cui lo stesso attributo sia un sottoinsieme di due chiavi candidate.

Le tabelle di database che corrispondono alla terza forma normale e non hanno chiavi candidate composte soddisfano così i requisiti della Boyce-Codd normal form.

La tabella che segue mostra due chiavi candidate, composte ciascuna da due attributi:

  • numero del fornitore e numero articolo
  • fornitore e numero articolo

Entrambe le chiavi consentono l’identificazione di ogni singolo record di dati. L’unico attributo non chiave è il numero. Poiché l’attributo “numero” non ha dipendenze transitive da alcuna chiave candidata, la tabella segue la terza forma normale.

La forma normale Boyce-Codd, invece, non è raggiunta perché esiste una dipendenza tra gli attributi “numero fornitore” e “fornitore”. L’attributo “numero fornitore” è perciò dipendente in modo transitivo dalla chiave candidata che risulta dal fornitore e dal numero articolo; mentre l’attributo “fornitore”, al contrario della chiave candidata, risulta dal numero fornitore e dal numero articolo.

Si possono evitare le dipendenze transitive se dividiamo la tabella di origine nelle tabelle “numero” e “fornitore”, in modo che non ci siano chiavi candidate che si sovrappongano.

La forma normale Boyce-Codd evita le ridondanze che derivano dal fatto che l’identificazione degli attributi deve essere eseguita più volte attraverso chiavi candidate che si sovrappongono. Nell’esempio sopra la trasposizione alla forma normale 3.5NF evita i doppi valori nella colonna “fornitore”.

N.B.

Si parla di dipendenza banale se un attributo è dipendente in modo funzionale da se stesso. Poiché questo è il caso in ogni stato del database per ogni attributo, le dipendenze banali sono in questo contesto una tautologia.

Quarta forma normale

Una tabella di database è conforme alla quarta forma normale se soddisfa le condizioni della forma normale Boyce-Codd e in aggiunta la seguente:

  • non ci sono dipendenze multivalore a meno che non siano banali.

Una dipendenza multivalore (multivalued dependency) si ha quando due attributi che non sono collegati tra loro sono dipendenti da uno stesso attributo.

Esemplifichiamo il concetto con un esempio:

La seguente tabella mostra quali articoli siano stati acquistati da ogni cliente e a quale indirizzo debbano essere consegnati.

Il cliente con il numero 234 per esempio ha ordinato gli articoli 1-0023-D e 2-0023-D, che devono essere consegnati al suo indirizzo con codice di avviamento postale 12345. Per il cliente 567 occorre consegnare gli articoli 1-0023-D, 3-0023-D, 4-0023-D e 5-0023-D al codice di avviamento postale 56789.

I record di dati si possono identificare soltanto con una superchiave data da tutti e tre gli attributi, cioè numero cliente, numero articolo e codice di avviamento postale. Poiché non ci sono attributi non chiave, si tratta di una 3FN. Inoltre non ci sono dipendenze transitive non banali. Per cui anche la 3.5NF è soddisfatta. Tuttavia ci sono dipendenze multivalore: sia l’attributo numero articolo che l’attributo codice di avviamento postale sono dipendenti dall’attributo numero cliente (nr. cliente), non avendo però alcun collegamento tra di loro.

Lo svantaggio di un simile design di database è che ogni volta che viene ordinato un nuovo articolo per un cliente, occorre anche inserire un codice di avviamento postale nel database, per cui ci sono dati ridondanti.

Queste ridondanze si possono ridurre portando la tabella alla quarta forma normale. Per farlo occorre dividere le tabelle in modo che non ci siano dipendenze multivalore banali o rimangano solo quelle. Creiamo perciò due tabelle separate, il che è possibile perché il numero articolo e il codice di avviamento postale non hanno alcuna relazione tra di loro.

Come mostra l’esempio, la quarta forma normale evita la ridondanza derivante dalle dipendenze multivalore, che in questo caso particolare riguarda la colonna codice di avviamento postale (CAP).

N.B.

Con questo esempio (che risente sicuramente del fatto di essere stato costruito ad hoc per i fini specifici di questa spiegazione) assumiamo che sia possibile inserire un solo codice postale per ogni cliente. Se invece i clienti avessero la possibilità di farsi spedire diversi prodotti a diversi indirizzi, ci sarebbe una dipendenza tra il numero dell’articolo e il codice di avviamento postale e la tabella di partenza corrisponderebbe già alla 4FN anche senza normalizzazione.

5FN: quinta forma normale

Una tabella di database corrisponde alla quinta forma normale se soddisfa tutti i requisiti della quarta e in aggiunta:

  • la tabella non si può ulteriormente suddividere senza perdere informazioni.

Con un esempio vi mostriamo ora in quali circostanze si verifica un caso simile. Supponiamo che l’azienda gestisca un sito web sulla base di TYPO3 e un negozio online con Magento. Tre dipendenti hanno l’incarico di gestire i progetti software: Maria Bianchi, Antonio Rossi e Gisella Martucci, ognuno dei quali ha diverse qualifiche.

La tabella di esempio mostra quale qualifica ha quale collaboratore e in quale progetto software, nonché quale qualifica sia necessaria per quale progetto.

Maria Bianchi usa le proprie conoscenze di PHP e SQL nel progetto Magento e ricorre a SQL e a JavaScript per il sito web TYPO3. Anche Antonio Rossi si occupa della programmazione PHP su Magento e lavora con JavaScript su TYPO3. Infine Gisella Martucci si occupa soltanto del progetto TYPO3 e si fa carico da sola della programmazione PHP. Così dalla tabella si deduce che per Magento sono necessarie conoscenze di PHP e SQL, mentre per il progetto con TYPO3 sono richieste competenze di PHP, SQL e JavaScript.

La tabella possiede soltanto una chiave composta da tutti e tre gli attributi e che quindi corrisponde almeno alla terza forma normale e alla Boyce-Codd normal form. Inoltre, poiché non ci sono dipendenze tra tutti e tre gli attributi, la tabella è a pieno diritto normalizzata alla quarta forma normale.

Ora bisogna verificare se anche la 5FN è soddisfatta. Cominciamo a dividere la tabella di partenza “Qualifiche dei dipendenti per i progetti assegnati” in tre tabelle: “progetto”, “qualifica collaboratore” e “requisiti del progetto”.

La tabella “progetto” mostra quale collaboratore è impiegato in quale progetto software.

La tabella “qualifica collaboratore” permette di vedere quale collaboratore padroneggia quale linguaggio di programmazione o di database.

Quale qualifica sia necessaria per quale progetto risulta invece chiaro nella tabella “requisiti del progetto”.

A un primo sguardo la parte di database presa in considerazione dopo la divisione sembra molto più chiara. Ma nell’ambito della normalizzazione queste tabelle forniscono anche lo stesso della tabella iniziale?

Per accertarcene dobbiamo utilizzare un join, cioè eseguire una query del database su tutte e tre le tabelle. Il risultato vi sorprenderà!

Nella ricostruzione della tabella iniziale dobbiamo per forza assumere che ogni dipendente che prende parte al progetto contribuisca con la propria qualifica al progetto, se il progetto lo richiede. L’informazione che Martucci si fa in carico da sola della programmazione PHP per il progetto TYPO3 è andata persa. La tabella iniziale quindi non si può ulteriormente suddividere senza perdite, perciò rispecchia già la quinta forma normale.

In pratica è molto raro incappare in schemi di database che soddisfino i requisiti della 4FN ma non quelli della 5. La 5NF è tuttavia importante per quei casi di applicazione in cui bisogna ottenere nuove informazioni dai dati disponibili.

L’esempio mostra che sia Bianchi che Rossi possiedono conoscenze PHP che, siccome sono legate al progetto TYPO3, possono essere utilizzate in futuro anche in quell’ambito. Questa informazione può essere utile alle aziende per organizzare in modo più efficiente lo sviluppo software in questo progetto.

Vantaggi e svantaggi della normalizzazione

Il fine della normalizzazione è la riduzione dei valori doppi. Se portate il vostro database a una delle forme normali, avrete il vantaggio che lo schema raggiunto presenterà meno ridondanze rispetto a quello di partenza. La normalizzazione in questo modo alleggerisce il carico del vostro database.

D’altra parte la normalizzazione comporta la collocazione degli attributi in tabelle separate e ciò potrebbe richiedere l’integrazione di chiavi esterne e portare perciò a ridondanze nelle chiavi. Tuttavia lo svantaggio principale è il fatto che i dati logicamente associati non sono più salvati insieme nei database normalizzati. Se si desidera riunire i dati che sono stati suddivisi in diverse tabelle, è necessario un join.

Grazie ai join si possono filtrare informazioni complesse nelle query del database, ma bisogna tener conto che l’implementazione dei join è più complicata rispetto alle semplici interrogazioni. In ogni caso serve molto più tempo quando vengono eseguiti dei join tramite molteplici tabelle del database.