Perché normalizzare
Un database mal progettato produce ridondanza — lo stesso dato memorizzato in più posti — e anomalie nelle operazioni di modifica. La normalizzazione è il processo che porta uno schema relazionale a una forma che elimina queste anomalie, decomponendo le tabelle in strutture più pulite e coerenti.
Dipendenza funzionale
Esempi di dipendenze funzionali:
codice_fiscale → nome, cognome, data_nascita, comune_nascita
id_ordine → data_ordine, totale, id_cliente
(id_ordine, id_prodotto) → quantità, prezzo_unitario ← dipendenza da PK composta
Prima Forma Normale (1NF)
Esempio di violazione 1NF
Una tabella ORDINE in cui il campo prodotti contiene una lista separata da virgole:
| id_ordine | cliente | prodotti | quantità |
|---|---|---|---|
| 1001 | Mario Rossi | Penna, Quaderno, Matita | 2, 1, 3 |
| 1002 | Anna Bianchi | Monitor | 1 |
Il campo prodotti non è atomico: contiene più valori. Non posso fare una query per “tutti gli ordini che contengono Quaderno” senza analizzare la stringa. Ogni cella deve contenere un solo valore.
Correzione → 1NF
Si spacca ogni prodotto in una riga separata. La PK diventa composta: (id_ordine, id_prodotto).
| id_ordine | cliente | id_prodotto | nome_prodotto | quantità |
|---|---|---|---|---|
| 1001 | Mario Rossi | P01 | Penna | 2 |
| 1001 | Mario Rossi | P02 | Quaderno | 1 |
| 1001 | Mario Rossi | P03 | Matita | 3 |
| 1002 | Anna Bianchi | P04 | Monitor | 1 |
La tabella è ora in 1NF, ma introduce una nuova anomalia: il nome del cliente è ripetuto per ogni prodotto dello stesso ordine. Se Mario Rossi cambia nome (o è registrato con un errore), devo aggiornare più righe. Serve la 2NF.
Seconda Forma Normale (2NF)
Violazione 2NF: dipendenza parziale
Nella tabella precedente con PK composta (id_ordine, id_prodotto):
id_ordine → cliente ✗ dipendenza PARZIALE (solo da metà PK)
id_prodotto → nome_prodotto ✗ dipendenza PARZIALE (solo da metà PK)
Correzione → 2NF
Si separano gli attributi con dipendenza parziale in tabelle proprie:
CLIENTE(id_cliente, nome, cognome)
PRODOTTO(id_prodotto, nome_prodotto, prezzo)
DETTAGLIO_ORDINE(FK:id_ordine, FK:id_prodotto, quantità)
Ora ogni tabella contiene solo attributi che dipendono interamente dalla propria PK. Nessuna ridondanza del nome cliente per ogni prodotto.
Terza Forma Normale (3NF)
Violazione 3NF: dipendenza transitiva
Considera la tabella CLIENTE con il campo città e il relativo codice postale:
| id_cliente | nome | città | provincia | cap |
|---|---|---|---|---|
| 1 | Mario | Milano | MI | 20121 |
| 2 | Anna | Roma | RM | 00100 |
| 3 | Luca | Milano | MI | 20121 |
città → provincia, cap ✗ TRANSITIVA: non-chiave dipende da non-chiave
id_cliente → città → provincia, cap ✗ dipendenza TRANSITIVA
Se il CAP di Milano cambia, devo aggiornarlo in ogni riga di ogni cliente milanese. Se ne dimentico uno, il database è inconsistente: due clienti milanesi con CAP diversi.
Correzione → 3NF
Si estrae la dipendenza transitiva in una tabella separata:
CITTÀ(id_città, nome, provincia, cap)
Ora provincia e CAP esistono una sola volta per ogni città. Aggiornare il CAP di Milano richiede una sola modifica in CITTÀ.
Il percorso di normalizzazione — visione d’insieme
Per le applicazioni scolastiche e la maggior parte dei sistemi aziendali, la 3NF è sufficiente. Le forme normali superiori (BCNF, 4NF, 5NF) affrontano casi più sottili e vengono studiate nei corsi universitari di basi di dati avanzati. Talvolta si sceglie deliberatamente di de-normalizzare per ragioni di performance (es. data warehouse): l’importante è farlo consapevolmente.
Esempio completo: gestione ordini dal caos alla 3NF
Partiamo da questa tabella piatta — come potrebbe essere un foglio Excel — e arriviamo a uno schema normalizzato in 3NF.
Tabella di partenza (non normalizzata)
| id_ord | data | nome_cli | città_cli | prov | prodotti | qtà | prezzi | nome_venditore | zona_venditore |
|---|---|---|---|---|---|---|---|---|---|
| 1001 | 2024-01-10 | Mario Rossi | Milano | MI | Penna, Quaderno | 2, 1 | 0.50, 2.00 | Giulia Verdi | Nord |
| 1002 | 2024-01-11 | Anna Bianchi | Roma | RM | Monitor | 1 | 250.00 | Marco Neri | Centro |
Schema finale normalizzato in 3NF
// 2NF: eliminate dipendenze parziali da PK composta
// 3NF: eliminate dipendenze transitive (prov da città, zona da venditore)
CITTÀ(id_città, nome, provincia)
VENDITORE(id_vend, nome, FK:id_zona)
ZONA_VENDITA(id_zona, nome) ← 3NF: zona non dipende da venditore, è un’entità
CLIENTE(id_cli, nome, cognome, FK:id_città)
PRODOTTO(id_prod, nome, prezzo_listino)
ORDINE(id_ord, data, FK:id_cli, FK:id_vend)
DETTAGLIO_ORDINE(FK:id_ord, FK:id_prod, quantità, prezzo_unitario)
Da 1 tabella con 10 colonne e anomalie evidenti siamo arrivati a 6 tabelle pulite. Ogni dato esiste in un solo posto. Il prezzo di un prodotto si aggiorna una sola volta. La provincia di una città è memorizzata una sola volta. Nessuna anomalia di inserimento, modifica o cancellazione.
- La normalizzazione elimina ridondanze e le tre anomalie: di inserimento, di modifica e di cancellazione.
- 1NF: attributi atomici, nessun gruppo ripetuto, PK definita.
- 2NF: in 1NF + no dipendenze parziali (tutti i non-chiave dipendono dall’intera PK). Si applica solo se la PK è composta.
- 3NF: in 2NF + no dipendenze transitive (nessun non-chiave dipende da un altro non-chiave).
- La dipendenza funzionale (A → B) è la base teorica: B è univocamente determinato da A.
- Per la maggior parte dei sistemi reali, la 3NF è il target. De-normalizzare consapevolmente per performance è accettabile, farlo per pigrizia no.