Nella gestione delle basi di dati, con il termine normalizzazione si indica una procedura utilizzata per l’eliminazione o per la massima limitazione dei dati duplicati (il noto problema della “ridondanza"), inoltre, si tratta di un processo grazie al quale è possibile risolvere totalmente o in parte le problematiche relative all’incoerenza dei dati, un evento che si verifica quando il medesimo campo presenta dei valori diversi in tabelle differenti.
In questa breve trattazione verrà approfondito il discorso relativo alla normalizzazione dei database, introducendo alcune regole utili per ottenere migliori risultati in termini di prestazioni tramite questa procedura.
Un primo esempio di normalizzazione
La normalizzazione di una base di dati non presenta un’unica modalità d’esecuzione, sono infatti disponibili diversi livelli di normalizzazione denominati “forme normali" attraverso i quali è possibile stabilire la qualità di uno schema associato ad un database.
In linea generale si possono isolare tre tipologie di problematiche risolvibili o arginabili grazie alla normalizzazione:
- duplicazione (ridondanza): si manifesta quando un database presenta inutilmente e più volte uno stesso dato, il fenomeno della ridondanza in informatica non ha necessariamente un’accezione negativa, parlando di basi di dati però la presenza di duplicazioni genera una problematica dovuta a “dati non normalizzati"; ad esempio, in un database che memorizza dei recapiti, è necessario avere un campo che registra i nomi delle province per esteso quando se ne ha a disposizione un altro in cui inserire le relative sigle?
- incoerenza: si tratta di una problematica correlata in genere alla generazione di errori dopo i quali un Database manager notifica che è stata eseguita un’operazione ma questa non riuscita; l’incoerenza si presenta in particolare nel caso in cui uno stesso campo contenga valori diversi (quindi incoerenti) in diverse tabelle, in linea generale è possibile affermare che essa si manifesta quando le tabelle non vengono aggiornate o quando un aggiornamento è stato effettuato in modo scorretto.
- irregolarità dei dati: rientrano in questa categoria tutte le problematiche che, pur non essendo correlate a ridondanza e incoerenza, sono in grado di generare eccezioni, decrementi nelle prestazioni e altri risultati inattesi.
La normalizzazione non è in genere una procedura immediatamente realizzabile, più spesso per arrivare ad essa si procede per gradi fino al massimo livello possibile di ottimizzazione; si analizzi per esempio la seguente istruzione per la creazione di una tabella:
CREATE TABLE `vendite`.`ordini` (
`CodiceCliente` VARCHAR( 9 ) NOT NULL ,
`Nome` VARCHAR( 10 ) NOT NULL ,
`Cognome` VARCHAR( 15 ) NOT NULL ,
`CodiceFiscale` VARCHAR( 16 ) NOT NULL ,
`CodiceOrdine` INT( 5 ) NOT NULL
) ENGINE = InnoDB
Nell’istruzione mostrata saltano immediatamente agli occhi alcuni problemi legati alla definizione dei campi da inserire all’interno della tabella: i dati relativi a “Nome", “Cognome" e “CodiceFiscale" verranno inutilmente ripetuti ad ogni occorrenza per la memorizzazione di un determinato ordine; una semplice soluzione a questa anomalia potrebbe essere quella di evitare la ridondanza separando i dati relativi agli ordini dalle informazioni relative ai clienti, per cui, una procedura di normalizzazione richiederà le seguenti istruzioni:
CREATE TABLE `vendite`.`clienti` (
`CodiceCliente` VARCHAR( 9 ) NOT NULL ,
`Nome` VARCHAR( 10 ) NOT NULL ,
`Cognome` VARCHAR( 15 ) NOT NULL ,
`CodiceFiscale` VARCHAR( 16 ) NOT NULL,
PRIMARY KEY ( `CodiceCliente` )
) ENGINE = InnoDB
CREATE TABLE `vendite`.`ordini` (
`CodiceOrdine` INT( 5 ) NOT NULL,
`CodiceCliente` VARCHAR( 9 ) NOT NULL,
PRIMARY KEY ( `CodiceOrdine` )
) ENGINE = InnoDB
Accadrà così che gli ordini effettuati da uno specifico cliente, dovessero anche ripetersi numerose volte, non determineranno la ripetizione di informazioni inutili.
Normalizzazione e dipendenza funzionale
Le problematiche elencate in precedenza e l’esempio mostrato si ricollegano ad un concetto fondamentale per la normalizzazione, quello di dipendenza funzionale, essa è chiamata in questo modo perché rappresenta un vincolo d’integrità che descrive legami funzionali fra attributi in relazione tra loro; un vincolo d'integrità è costituito da procedure il cui compito è in pratica quello di garantire la corretta gestione delle relazioni e delle regole che da esse derivano.
La dipendenza funzionale si verifica per esempio fra un certo attributo (“a") e una chiave nel caso in cui i valori associati ad “a" dipendono dai valori via via associati alla chiave, vi è quindi una relazione biunivoca tra i valori dell’attributo e quelli della chiave.
Un semplice esempio di dipendenza funzionale è descritto dalla seguente istruzione:
CREATE TABLE `agenda`.`contatti` (
`Nome` VARCHAR( 10 ) NOT NULL ,
`Cognome` VARCHAR( 15 ) NOT NULL ,
`Telefono` INT( 10 ) NOT NULL ,
PRIMARY KEY ( `Nome` , `Cognome` )
) ENGINE = InnoDB
Se infatti è possibile che ad un nome e ad un cognome non corrisponda un numero di telefono, è invece necessario che ad un recapito telefonico corrisponda un’utenza, per cui è chiaro che “Telefono" dipende in modo funzionale da una chiave, quindi, ad ogni persona corrisponderà un numero di telefono.
Oltre alle comuni tipologie di dipendenza funzionale ne esiste anche una particolare detta transitiva, a questo proposito si analizzi il seguente esempio:
CREATE TABLE `wiki`.`animali` (
`Id` INT( 11 ) NOT NULL AUTO_INCREMENT PRIMARY KEY ,
`Specie` VARCHAR( 20 ) NOT NULL ,
`Razza` VARCHAR( 20 ) NOT NULL
) ENGINE = InnoDB
Nell’istruzione proposta è visibile una relazione in cui il valore relativo a “Razza" dipende direttamente da “Specie", mentre “Specie" dipende da “Id", quest’ultimo è infatti la chiave primaria che identificando univocamente la specie concorre in modo sostanziale a determinare la razza.
La prima forma normale
La prima forma normale prevede di poter essere utilizzata soltanto nel caso in cui tutti gli attributi (e i relativi valori) di una relazioni siano dei valori atomici, quindi non ulteriormente scomponibili; si analizzi il seguente esempio di istruzione:
CREATE TABLE `clienti`.`aziende` (
`PartitaIva` VARCHAR( 10 ) NOT NULL ,
`RagioneSociale` VARCHAR( 30 ) NOT NULL ,
`SedeLegale` VARCHAR( 50 ) NOT NULL ,
PRIMARY KEY ( `PartitaIva` )
) ENGINE = InnoDB
Nel campo “SedeLegale" possono essere inseriti dei valori non atomici, per esempio delle stringhe composte da via e località, la prima forma normale verrebbe quindi rispettata creando una tabella con queste caratteristiche:
CREATE TABLE `clienti`.`aziende` (
`PartitaIva` VARCHAR( 10 ) NOT NULL ,
`RagioneSociale` VARCHAR( 30 ) NOT NULL ,
`Via` VARCHAR( 30 ) NOT NULL ,
`Localita` VARCHAR( 30 ) NOT NULL ,
PRIMARY KEY ( `PartitaIva` )
) ENGINE = InnoDB
Nella prima forma normale esiste necessariamente una chiave primaria che identifica univocamente ogni componente della relazione, la sua assenza è quindi una violazione di questa forma; si analizzi l’esempio seguente:
CREATE TABLE `clienti`.`aziende` (
`RagioneSociale` VARCHAR( 30 ) NOT NULL ,
`Via` VARCHAR( 30 ) NOT NULL ,
`Localita` VARCHAR( 30 ) NOT NULL ,
) ENGINE = InnoDB
In esso la violazione della prima forma normale e evidente dato che gli attributi utilizzati sono atomici ma non è stata definita alcuna chiave primaria.
La seconda forma normale
Una specifica relazione si dice in seconda forma normale quando soddisfa la prima forma normale e, inoltre, in essa ogni attributo che appartiene alla chiave è funzionalmente indipendente da una qualsiasi “chiave candidata”, cioè il risultato dell'insieme minimo di chiavi per identificare univocamente dei record; si analizzi il seguente esempio:
CREATE TABLE `sport`.`calcio` (
`Id_campionato` INT( 3 ) NOT NULL ,
`Id_divisione` INT( 3 ) NOT NULL ,
`Id_calciatore` INT( 2 ) NOT NULL ,
`Marcature` INT( 2 ) NOT NULL
PRIMARY KEY ( `Id_campionato` , `Id_divisione` , `Id_calciatore` )
) ENGINE = InnoDB
Nel caso esposto la chiave candidata è rappresentata dalle tre chiavi primarie, esse infatti devono essere utilizzate tutte per ottenere un’informazione completa o, più precisamente uno specifico record e tutti i valori in esso contenuti (“un determinato giocatore ha segnato un certo numero di goal mentre militava in una specifica divisione di un campionato di Calcio”); “Marcature” è invece l’unico campo a non rappresentare una chiave, non permette quindi di reperire con certezza le informazioni contenute negli altri campi.
La terza forma normale
Si dice che una relazione sia in terza forma normale nel caso in cui, oltre ad essere in seconda forma normale, è strutturata in modo che ciascun attributo che non partecipa alla chiave non è nello stesso tempo dipendente in via transitiva da una qualsiasi chiave candidata; la terza forma normale esclude quindi di per sé la possibilità che si verifichi una dipendenza funzionale transitiva, in essa gli attributi che non sono delle chiavi dipendono dalla sola chiave, per cui non possono essere presenti attributi dipendenti da attributi diversi che non siano anche delle chiavi.
Il terzo livello di normalizzazione può essere rappresentato come un’estensione della prima forma, in essa però non devono essere presenti dati duplicati all’interno di una stessa tabella, nella terza forma normale non vi deve essere invece ridondanza nei campi delle tabelle di un database, è sufficiente un campo con specifici valori a cui relazionarsi.
Conclusioni
In questa breve trattazione è stato affrontato il discorso relativo alla normalizzazione, un’operazione grazie alla quale è possibile rimuovere dati duplicati, incoerenze e anomalie da una base di dati; per far questo sono stati proposti degli esempi pratici di ottimizzazione della struttura delle tabelle e presentate le diverse forme di normalizzazione utilizzabili durante le progettazione di un database.