La strutturazione dei database: la normalizzazione
Nell’articolo precedente abbiamo trattato l’importante concetto della chiave primaria. Oggi vedremo quali sono nella teoria i principali accorgimenti (che prendono il nome di forme normali) da attuare nei database per renderli più efficienti e mantenibili e soprattutto per garantire la consistenza dei dati. Nell’ultimo articolo di questa serie vedremo invece come applicare queste norme nella pratica.
Che cos’è la normalizzazione?
Prima di iniziare vediamo una definizione di normalizzazione:
La normalizzazione è un procedimento volto all’eliminazione della ridondanza e del rischio di incoerenza dal database. Esistono vari livelli di normalizzazione (forme normali) che certificano la qualità dello schema del database.
Questo processo si fonda su un semplice criterio: se una relazione presenta più concetti tra loro indipendenti, la si decompone in relazioni più piccole, una per ogni concetto. Questo tipo di processo non è sempre applicabile in tutte le tabelle, dato che in taluni casi potrebbe comportare una perdita d’informazioni. (fonte: Wikipedia)
Che cosa s’intende con ridondanza?
La ridondanza è una nemica giurata dei database e dobbiamo evitarla come la peste.
Immagina se la tabella che contiene gli articoli di questo blog fosse così:
Ora, quando Maurizio avrà scritto 1000 articoli (manca poco) ci saranno 2000 dati inutili in questo database.
E’ chiaro che i dati dell’autore (il nome, l’email, ecc…) figureranno nella tabella autori. Per mettere in relazione un articolo al suo autore, sarà sufficiente riportare nella tabella articoli la chiave primaria che fa capo all’autore.
La prima forma normale (1NF)
Un database si dice in prima forma normale quando:
- Ogni colonna fa riferimento a dei dati atomici
Questo significa che ogni colonna deve contenere dei valori non divisibili.
Dunque in pratica una colonna nome_e_cognome non permetterebbe alla tabella di essere in prima forma normale. Le conseguenze di questo errore sono evidenti. Non potrei fare ad esempio una ricerca mirata per cognome o per nome. Oppure non potrei ordinare i dati per nome o per cognome.
La non atomicità dei dati può prendere forme anche più complesse: considera ad esempio un database contenente l’elenco degli studenti e dei corsi ai quali sono iscritti.
Ho utilizzato solo il nome dello studente, ma considera che la tabella conterrebbe tutti i dati anagrafici.
Ora, questa tabella non è in prima forma normale. I corsi ai quali lo studente è iscritto non sono espressi in forma atomica.
Per ovviare al problema, la tentazione che potrebbe venire sarebbe quella mostrata nell’immagine sotto:
Ma anche questa non sarebbe una buona soluzione. Ogni nuovo corso introdotto ci obbligherebbe ad aggiungere una colonna alla tabella con effetti disastrosi sulla mantenibilità. Inoltre questo è un esempio di tabella grassa (fat table), questo termine descrive le tendenza (sbagliata) a voler creare un’unica grande tabella che contenga tutto e mescolando dati che non hanno un legame logico.
La logica invece ci dice che i dati anagrafici degli studenti andranno in una tabella, mentre l’elenco dei corsi in un’altra tabella.
Per descrivere a quali corsi è iscritto uno studente andremo a creare un’altra tabella che metterà in relazione studenti e corsi. Vedremo nel prossimo articolo come.
La seconda forma normale (2NF)
Un database si dice in seconda forma normale quando:
- è in prima forma normale;
- i campi non chiave dipendono dall’intera chiave primaria e non da una parte di essa.
Da una parte di essa? Sì, la seconda forma normale va applicata a tabelle con chiavi composte, quindi potremmo dire che i campi non chiave non possono dipendere da solo una parte dei campi che costituiscono la chiave primaria (una tabella con chiave primaria singola, la possiamo considerare implicitamente in seconda forma normale, in quanto è impossibile che possa violarne i principi).
Facciamo subito un esempio. Considera un evento sportivo dove gli atleti svolgono tre corse. Nel nostro database potremmo dare la seguente rappresentazione:
In questa tabella possiamo utilizzare la chiave primaria composta codice_atleta/numero_gara.
Ma non è in seconda forma normale.
Infatti il campo non chiave nome_atleta dipende da una sola parte della chiave primaria (codice_atleta) e non dall’intera chiave.
Per portare questa tabella in seconda forma normale dovremo scomporla in due tabelle
La terza forma normale (3NF)
Un database si dice in terza forma normale quando:
- è in seconda forma normale (quindi implicitamente anche in prima);
- i campi non chiave non dipendono da altri campi non chiave.
E’ molto più semplice di quello che può sembrare. In pratica significa che se un dato può essere calcolato/dedotto/ricostruito a partire da un’altro dato diventa superfluo inserirlo nella tabella.
Facciamo un esempio di un database di ordinazioni di prodotti:
Il campo prezzo (non chiave) dipende dai campi costo_unitario e quantità (non chiave) ed è calcolabile moltiplicando questi ultimi due campi. Dunque il campo prezzo è inutile e deve essere eliminato.
Un altro semplice esempio potrebbe essere il database di un e-commerce. Nel nostro store vogliamo indicare i prezzi in euro e in dollari per ogni nostro prodotto. Ora, sarebbe un errore una tabella del genere:
Il prezzo in dollari dipende infatti dal prezzo in euro (o viceversa). Dunque una colonna va eliminata. Questo renderà il database più efficiente e razionale. Diversamente dovremmo modificare periodicamente tutti i valori per aggiornarli al cambio.
Il prezzo in dollari lo potremo calcolare al momento dell’estrazione dei dati, magari utilizzando un API di qualche sito di quotazioni on-line; in questo modo il cambio sarà sempre aggiornato in tempo reale.
Conclusione
A questo punto disponi delle basi teoriche per realizzare un database correttamente impostato. Nel prossimo articolo vedremo nella pratica come stabilire delle relazioni logiche tra le tabelle allo scopo di ottenere la migliore efficienza dal database in termini di affidabilità, mantenibilità e razionalità.
43 commenti
Trackback e pingback
-
Tweets that mention La strutturazione dei database: la normalizzazione | Your Inspiration Web -- Topsy.com
[...] This post was mentioned on Twitter by Your Inspiration Web, mtx_maurizio. mtx_maurizio said: RT @YIW La strutturazione dei database:… -
I migliori post della settimana #88 | Web Developer / Web Designer / SEO Specialist / Napoli :: EmaWebDesign
[...] 03) La strutturazione dei database: la normalizzazione [...]
Complimenti, avevo studiato queste cose a scuola, più o meno 5 anni fa, ho realizzato vari database ma poi ho smesso. Devo confessarti che dopo questi due articoli si è riaccesa la voglia di metterci mano. Aspetto con ansia il terzo capitolo delle relazioni tra tabelle, che sono a mio avviso la cosa più importante nei database.
E’ la prima volta che scrivo su questo blog e volevo farvi i miei complimenti, siete veramente fantastici!!!
Continuate così!
Grazie Gaetano.
Che genere di studi hai fatto?
I.t.i.s A.Volta a Napoli ad indirizzo informatico programmatore
Anche se come al solito i compagni di classe non hanno voglia di studiare, ero uno che stava abbastanza attento nelle lezioni di informatica. :D
Beh, allora ti occupi ancora di programmazione?
In realtà programmazione vera e propria non l’ho mai fatta.
Ho fatto vari programmini in Access, poi mi sono dedicato al Web Design ed all’assistenza informatica per quanto riguarda reti tra elaboratori e sistemi con Windows Server.
complimenti per l’articolo, molto chiaro :-)
ma tu, dato il concetto di terza forma normale, in una ipotetica tabella “fatture” il totale della fattura lo scriveresti in un campo? :-)
grazie
Rob
Ciao Roberto e grazie.
No, assolutamente. Il totale è un dato ricavabile in modo semplicissimo (è una somma). Puoi ricavarlo a livello di applicazione o ancora più semplicemente a livello di query (SUM).
Anche in casi molto più complessi, se un dato è ricavabile, non va inserito nel database.
grazie maurizio :-)
Bravo bravo, io solitamente quando creo una tabella lo faccio in virtù dello script , quindi se per esempio non dovessi memorizzare il totale di una fattura , e perché lo calcolerà dopo il mio script :) , così il database mi rimane pulito e solo con i dati fondamentali :D
Giusto. Il database deve essere più leggero e razionale possibile. Non sappiamo, al momento che lo progettiamo, quanti dati saranno presenti tra qualche anno.
Ma si può essere più chiari di così???bravo…..bravo….bravo…..:)
Grazie Silvia! :-)
Veramente bravo non avevo mai studiato l anormalizzazioen ma “empiricamente” spesso tendevo ad arrivare alla seconda forma normale.
L’articolo è molto chiaro e semplice da capire.
Ciao Luca e grazie.
In effetti, con un po’ di esperienza (ed avendo versato lacrime amare per aver mal progettato un database) ed un po’ di errori commessi, se si ha una testa un po’ logica ci si arriva anche empiricamente.
E’ chiaro che formalizzando il tutto in regole precise, è molto più semplice.
Bisogna dire che sto semplificando molto il discorso. Ad esempio in realtà esistono anche altre forme normali (anche se sono meno importanti). Oppure possiamo dire che nella progettazione di database esistono anche altre regole; ad esempio i “vincoli di integrità”, ai quali accennerò nel prossimo articolo.
Magari qualche articolo più approfondito sul tuo sito non sarebbe male :)
Anche sui vincoli di integrità ho qualche lacuna oppure, come in questo caso, li applico ma non so che sono vincoli di integrità.
E’ probabile, mi dirai poi.
Bell’articolo.. molto chiaro e preciso, complimenti!
Grazie Giacomo, spero che ti sarà utile.
Sei stato decisamente un ottimo “acquisto” per YIW :)
E pensa che non hanno dovuto nemmeno spendere tanto :-)
…alla fiera dell’est
per due soldi
un “maurizio” nando comprò…
Fossero stati due
Ottimo articolo, chiaro e preciso. :)
Si è rivelata un’ottima lettura anche per me che queste cose sinceramente le conoscevo già, perché spesso tendo a dimenticarmi di determinate mentre vado a creare le tabelle per un qualsiasi script.
Una cosa che mi permetto di dire però è che secondo me non sempre queste cose sono utili da applicare. Prendiamo l’esempio dei commenti… io non sono registrato e quindi inserisco nome, email e sito web ogni volta che commento. In una situazione come questa, la divisione fra i miei dati (che probabilmente saranno ripetuti comunque) ed il commento non avrebbe senso, perché comunque ci sarebbe una “riga” nel db per ogni mio inserimento, quindi questo non risolverebbe minimamente il problema ridondanza. Che senso avrebbe in una situazione del genere applicare questo principio? :)
Quello che hai indicato è un caso molto particolare.
Ovviamente, se i commenti sono aperti (e quindi non è necessario registrarsi), salveremo ogni volta i dati del commento.
Di conseguenza se scrivi più commenti vi sarà una ridondanza.
Ma in un caso del genere non si può fare diversamente.
Anche WordPress (che non è scritto da pivellini), per i commenti registra ogni volta il nome, il sito web e l’email.
Infatti, non c’è altro modo.
Il mio era solamente uno spunto di discussione, perché è vero che ci sono delle regole che vanno rispettate se si vuol fare un lavoro di qualità e che spesso ci semplificano la vita (un caso come quello da te descritto all’inizio dell’articolo, per un sito di grossissime dimensioni con tantissimi utenti che scrivono può generare un volume enorme di “spazzatura” nel db, con tutte le conseguenze del caso), ma c’è sempre il caso particolare in agguato, quindi è sempre bene mantenere un minimo di elasticità mentale e ragionare bene sulle cose in fase di pianificazione :)
Complimenti Maurì.. che bell’articolo !!!
Oh nicolas! Grazie!
Da fare leggere ai miei capi! Che quando sollevo una opinione sulla struttura del db mi danno sempre contro! non normalizzano le tabelle per “risparmiare spazio”. ma quale spazio che abbiamo i server con tera di memoria.. (semplicemente non sanno cosa sia la normalizzazione) e io mi rodo il fegato perche’ poi tocca a me programmare sopra le loro strutture mal fatte, ingestibili e piene di errori!!! Uff.. e’ un mondo difficile!
ps. scusa lo sfogo…
Beh, forse dovresti iniziare a dirgli che uno degli effetti della normalizzazione è proprio il minor utilizzo di spazio.
Guarda il primo esempio (quello sulla ridondanza). Non bisogna farsi ingannare dal fatto che prima avevamo una tabella ed ora ne abbiamo due.
Non sono certamente le strutture delle tabelle a consumare spazio su disco.
Nella fattispecie il fatto di lavorare su due tabelle fa risparmiare diversi byte per ogni record; e nota che non è nemmeno il vantaggio principale!
Ciao Maurizio, innanzitutto complimenti per l’articolo ( e la rinfrescata).
Le regole riportate nell’articolo vanno sempre applicate nel limite del possibile ma in alcuni casi avere una ridondanza “controllata” può agevolare molto le cose e anche le prestazioni…
Prendendo il caso delle fatture ad esempio, se avessi bisogno di fare un riepilogo delle spese in un mese, avendo una tabella con già memorizzati i totali e le date di emissione, impiegherò molto meno tempo a calcolare il totale…
Oppure se un cliente cambia denominazione ( e non p.iva ) e voglio mantenere un archivio delle fatture vecchie, avrò la necessità di mantenere entrambe le anagrafiche….
Wordpress come sicuramente saprai utilizza una cella nella tabella wp_posts per mostrare il numero dei commenti. E ad ogni nuovo commento viene aggiornato questo valore…
Sarebbe interessante vedere una serie di casi in cui la normalizzazione non è sempre possibile o almeno consigliabili.
A mio parere è inutile avere un DB leggero ma lento a causa delle operazioni di estrazione dei dati.
Le JOIN, soprattutto nei CMS, vengono evitate come la peste, a costo di una maggior complessità nella programmazione…e in linea di massima a più tabelle corrispondono più join… (salvo che poi si preveda tutta la gestione in fase di coding)
Il motore MyIsam di Mysql ad esempio non è un db transazionale x cui non supporta l’integrità referenziale etc però va veramente forte…. Non si può minimamente paragonare con Access o Sql Server (fino a certi livelli almeno e/o con hosting condivisi) che sono molto più “JOIN-FRIENDLY” (passami il termine). Palese è l’esempio di php nuke e dotnetnuke…
Cosa ne pensi a riguardo? E soprattutto, nella pratica che sappiamo tutti essere diversa dalla teoria, :-) come ti comporti?
P.S. Ancora complimenti per gli ottimi articolo presenti su YIW
Ciao Cardy e grazie per il tuo intervento.
Le questioni da te sollevate meriterebbero l’approfondimento di un articolo intero; verdò di sintetizzare.
In generale le regole vanno rispettate. Ma poi dobbiamo considerare le circostanze e soprattutto le circostanze molto paricolari dell’utilizzo di database nell’ambito di applicazioni web. Ad esempio nel prossimo articolo di questa serie propongo, per una tabella utenti, di utilizzare il campo email come chiave primaria. Può funzionare, è un dato univoco.
Ma sappiamo benissimo che spesso facciamo passare la chiave primaria nella querystring dell’url. Te lo immagini fa passare un email? Dovremmo codificarlo e decodificarlo.
Sul caso del cliente che cambia denominazione, dovrei pensarci ma credo che ci siano delle soluzioni più eleganti che non scrivere i dati del cliente ad ogni riga.
Su caso di wordpress il ragionamento che è stato fatto in fase di sviluppo è presto spiegato. Questo articolo ha circa 30 commenti. Questo vuol dire che il campo “numero commenti” è stato aggiornato 30 volte (30 query).
Questo articolo è stato letto (diciamo aperto :-) ) 4000 volte. Se ogni volta, oltre a leggere i dati dei commenti (1 query x 4000) avesse anche dovuto calcolare il numero dei commenti contandoli (1 altra query x 4000) ecco che il vantaggio è evidente. 4030 query contro 8000.
Si potrebbe continuare all’infinito ma la conclusione è: esistino e ci sono possibili eccezzioni. Al momento della progettazione dobbiamo fare delle scelte ed esserne consapevoli (e portarne anche le responsbilità)
Io, personalmente, cerco sempre di normalizzare il più possibile.
Non sono per nulla spaventato dalla complessità e dalla maggiore richiesta di risorse di una join. Anche perchè, quando è il caso, penso a rendere il tutto più efficente ad esempio con delle stored procedure. Forse dovrei scriverci un articolo in futuro a beneficio dei prigri programmatori web che praticemente non le utilizzano :-)
Aggiungo che è un argomento di dibattito interessantissimo e sarebbe bello confrontarsi su casi concreti
Ciao Maurizio, hai pensato alla soluzione dei clienti (commento #30) ? :-)
Ti pongo adesso un’altra domanda: ho 3 tipi di oggetti (e quindi 3 tabelle) e ognuno di essi ha 1 pagina unica ( Le pagine sono memorizzate nella tabella pagine). Ogni pagina ha un id (PK) e un url (UNIQUE).
Ora quando digito un url voglio recuperare un oggetto specifico. Come risolveresti la relazione a livello di tabelle tra pagina e oggetto?
1)Aggiungendo 2 campi alla tabella pagine (un per l’id_oggetto e l’altro per il tipo [ipotizzando che il tipo possa essere un valore A,B,C])
2)Aggiungendo l’id_pagina nelle 3 tabelle degli oggetti ( e quando devo recuperare i dati di un oggetto nel caso peggiore dovrò eseguire 3 select)
3)Creando una tabella oggetti_pagine_xref composta di 3 campi (id_oggetto,tipo_oggetto,id_pagina)
Io opterei più per la seconda, e tu?
Ciao Cary.
Premetto che per quanto mi sono sofrzato non sono sicuro di aver capito il problema. Tuttavia, istintivamente, ho l’impressione che sia la terza la migliore soluzione. Quel dover eseguire al massimo 3 select mi disturba. Se il database dovesse essere modificato? Se si espandesse? Se quelle 3 select diventassero 10, 100?
Di principio preferisco le tabelle di relazioni, ovvero quelle tabelle che non contengorno dati ma solo relazioni (come mi sembra essere la terza soluzione).
Questo tipo di tabelle garantiscono una migliore mantenibilità e sono più razionali.
Ciao Maurizio, immagina di avere 3oggetti diversi tra loro (auto, frutti, animali) e ognuno di questi oggetti ha una propria pagina dedicata.
La relazione è 1 a 1 poichè un oggetto può avere una sola pagina e una pagina un solo oggetto. Il problema è che gli oggetti sono eterogenei (e quindi vengono rappresentati da 3 tabelle diverse). non so se ho chiarito un po’ le tue perplessità.
Ero orientato anche io sull’ultima soluzione, anche se per le operazioni CRUD è richiesto un maggiore “sforzo” di programmazione.
Mi sembra inoltre che la 3° soluzione sia l’unica che permetta di raggiungere la 3° forma normale.
Ciao cardy, scusa il ritardo.
A livello teorico (poi come abbiamo già visto, la pratica può essere differente) non importa quanto sforzo bisognerà fare per ricavare un dato, l’importante é che la rappresentazione dei dati nel database sia corretta.
Ottimo articolo, lavoro continuamente con i database, e a causa degli automatismi che si sono innescati non ho più calcato le vie della teoria sulla normalizzazione. Hai esposto tutto con estrema chiarezza, complimenti!
Grazie mille Alex
Ciao Maurizio,
ottimo articolo ma fondamentalmete sono d’accordo con Cardy,
a volte la normalizzazione spinta può rallentare le performance
dell’applicazione.
Meglio tardi che mai. Volevo porre um quesito, ma se nella tabella prodotto il costo del prodotto dovesse cambiare nel tempo, come gestisco le relazioni delle vecchie fatture ?
Di sicuro l’unico articolo con cui sono riuscito a capire! Complimenti!