• anomalie-dati-database-relazionali

    Prevenire anomalie nei dati nei database relazionali

    Il secondo appuntamento con i Database Relazionali descrive come prevenire le anomalie dei dati in un database relazionale e segue un precedente articolo in cui abbiamo introdotto il concetto dei database relazionali.

    Progettazione e implementazione di un database relazionale

    Nell’articolo dal titolo I database relazionali abbiamo visto che la progettazione di un database relazionale viene effettuata attraverso otto passi:

    1. pianificazione;
    2. raccolta dei requisiti;
    3. progettazione concettuale;
    4. progettazione logica;
    5. progettazione fisica;
    6. sviluppo/implementazione/collaudo;
    7. rilascio (deployment);
    8. manutenzione/supporto continuo.

    Il ciclo di vita di sviluppo di database consiste di questi passaggi, e hai ragione se pensi che sembra davvero un sacco di lavoro. A volte c’è il desiderio di abbreviare il processo e saltare direttamente al DBMS e iniziare a costruire il prodotto finale. Nella mia esperienza, quando si fa così, finisce quasi sempre in un disastro. Il processo esiste perché c’è una reale necessità di affrontare con attenzione tutte le esigenze del progetto relative ai dati. Per dare più risalto a questa convinzione, possiamo prendere in considerazione alcuni dei problemi che possono sorgere quando non si è curato qualche aspetto dei passi elencati sopra; e vedere come un database ben progettato e strutturato in realtà protegge te e il tuo progetto da futuri mal di testa su come risolvere determinate problematiche.

    database-relazionali

    Eliminazione delle informazioni duplicate

    Il foglio di calcolo tipico ha diverse colonne, che conterranno lo stesso pezzo di informazioni più e più volte. Quando questi tipi di strutture vengono spostati in un DBMS, a volte, viene commesso l’errore di mantenere tutte queste informazioni. Un primo problema per lo sviluppo di un database relazionale è capire se si stanno memorizzando informazioni ridondanti e occorre effettuare subito uno sforzo per eliminare qualsiasi potenziale tendenza alla memorizzazione di dati duplicati.

    I dati duplicati sono un problema per almeno tre motivi:

    • rallentano il sistema (occupando banda di rete in più e spazio su disco sul server);
    • sono più difficili da gestire (richiedendo più accessi in lettura e scrittura per aggiornare i record);
    • si corre il rischio che i dati duplicati diventino incoerenti.

    Vediamo come è facile creare duplicati e come una corretta progettazione del database può evitarci problemi futuri. Supponiamo di avere un e-commerce che vende libri e nella tabella sotto abbiamo una tabella semplificata che tiene traccia degli acquisti dei clienti.

    fatture

    Abbiamo i dati suddivisi per il numero di fattura, e possiamo vedere il nome dei clienti, il loro indirizzo di spedizione, quale libro hanno ordinato, e quale casa editrice ha stampato quel particolare libro. Abbiamo tre fatture per lo stesso cliente, Mario, che ha ordinato tre diversi libri della stessa casa editrice. Questa tabella contiene tutte le informazioni che servono per spedire correttamente i libri al cliente. Ma cosa succede se il cliente chiama e comunica un diverso indirizzo di spedizione (per un errore o per un’esigenza mutata), e quindi ha bisogno di avere spediti i libri ad un altro indirizzo? Per effettuare questa modifica nel database dobbiamo aggiornare l’indirizzo su tutte e tre le fatture e questa operazione può prestarsi a degli errori. Che cosa succede se si aggiornano solo due, e si perde il terzo? O che cosa succede se si correggono due fatture in modo corretto, ma non la terza? Quella struttura che inizialmente sembrava soddisfare le esigenze dell’attività, diventa difficile da gestire nel lungo periodo.

    La soluzione è quella di rimuovere i componenti che si ripetono più volte dalla tabella delle fatture, e metterli in altre tabelle. Possiamo farlo determinando quali campi dipendono da altri campi. Nel nostro caso, l’indirizzo non ci fornisce informazioni sulla fattura, ma sul cliente che ha effettuato l’ordine. In altre parole, l’indirizzo dipende dal cliente. Tutte le volte che viene individuata una casistica del genere, in primo luogo, occorre rimuovere la colonna che non si riferisce all’entità principale della tabella, e la si mette in una nuova tabella. In secondo luogo, occorre fare una copia della colonna da cui dipende il dato e copiarla nella nuova tabella a fianco.

    Se applichiamo questo procedimento alla tabella fatture, verrà rimossa la colonna indirizzo dalla tabella principale, e creata una nuova tabella che memorizza solo di dati dei clienti. Ora abbiamo un modo trovare l’indirizzo corretto. Quando vogliamo sapere dove spedire l’ordine per il cliente Mario Bianchi, basta accedere alla nuova tabella Clienti, e troviamo l’indirizzo del cliente una sola volta. L’aggiornamento del campo indirizzo diventa istantaneo e con molte meno possibilità di commettere errori, perché dobbiamo cambiare una sola informazione in un solo posto, invece di dover scorrere e aggiornare tutte le fatture di quel cliente. La stessa situazione vale con le case editrici dei libri. Il campo Casa Editrice non dipende dalla fattura o dal cliente. Anche in questo caso, la soluzione è quella di scomporre le tabelle in tabelle più piccole in base all’entità a cui si riferiscono. Quindi creo una nuova tabella Prodotti, prendo le colonne “Prodotto” e “Casa Editrice” dalla tabella delle fatture e le copio nella tabella Prodotti. Se le copie di un libro finiscono esaurite e il libro viene stampato da un’altra casa editrice, basta semplicemente aggiornare una singola riga della tabella Prodotti, piuttosto che scorrere ogni fattura e cambiarla da lì.

    fatture-clienti-prodotti

    Eliminare la ridondanza presente nei file fogli elettronici più semplici, come ad esempio abbiamo visto con gli indirizzi dei clienti, e fare uso di più tabelle per dividere i dati in gruppi più piccoli, rende i dati più facili da gestire nel lungo termine.

    Rimozione delle informazioni inconsistenti

    Avere dati duplicati aumenta il rischio di avere informazioni inconsistenti, per esempio nel caso di errori di battitura o di diversi modi di memorizzare la stessa informazione. Nel caso della tabella prodotti abbiamo due colonne: Prodotto e Casa Editrice. La colonna Casa Editrice ripete più volte la stessa informazione (New Skills for IT). Inserire delle informazioni inconsistenti con questa struttura è molto facile. Basta uno spazio in più o una lettera maiuscola al posto di una minuscola e per il database si tratterà di due informazioni diverse.

    prodotti

     

    Abbiamo già visto che uno dei modi in cui possiamo risolvere questo problema è quello di ridurre la quantità di informazioni ridondanti con la creazione di tabelle separate usando componenti più piccoli che sono più facili da mantenere. Per fare questo utilizziamo una serie di identificatori univoci per le diverse entità del database (prodotti, case editrici, fatture, clienti, etc …).

    La procedura da eseguire è la seguente:

    1. si esegue una copia delle informazioni ridondanti (ad esempio la Casa Editrice) e la si inserisce in una nuova tabella.
    2. si da un nome chiaro (ad esempio “Case Editrici”) alla nuova tabella e si rinomina il campo che contiene l’informazione copiata in “Nome Casa Editrice”.
    3. si crea una nuova colonna nella tabella “Case Editrici” che memorizza un numero che identifica l’informazione in modo univoco. Se ne esiste uno che identifica l’informazione in modo univoco, ad esempio un codice fiscale per le persone, si può usare quello. In alternativa, possiamo crearne uno ad uso interno, ad esempio un valore numerico autoincrementante. Nel nostro caso creiamo la colonna “ID Casa Editrice” e assegnamo un identificatore univoco per ciascun dato (identificatore di cui spesso l’utente non è a conoscenza perché viene “nascosto” nell’estrazione dei dati).
    4. si ritorna nella tabella Prodotti e si sostituisce la colonna “Casa Editrice” con la colonna “ID Casa Editrice” della tabella “Case Editrici”, con l’effetto di avere non più il nome della casa editrice ripetuto più volte, ma solo il suo identificatore, che è un riferimento ad una riga di un’altra tabella nel database.
    5. si aggiorna il nome della colonna “Casa Editrice” della tabella Prodotti in “ID Casa Editrice”.

    prodotti-case-editrici

    L’effetto di questa procedura è di avere un unico posto in cui memorizzare le informazioni specifiche della casa editrice, che non appartengono né alle fatture, né ai prodotti. La stessa procedura va applicata anche ai clienti in relazioni con le fatture e ai prodotti in relazione con le fatture.

    Guardando la struttura delle tabelle, si potrebbe pensare che era più intuitiva la struttura iniziale, in cui in un’unica tabella c’erano tutte le informazioni. Da una tabella contenente le fatture, adesso ne abbiamo ben quattro: Fatture, Clienti, Prodotti e Case Editrici. Per ottenere l’informazione completa dobbiamo collegare tutte queste tabelle tra loro. Ma non è un lavoro che deve fare il programmatore. Questo compito spetta al DBMS opportunamente istruito su come recuperare queste informazioni. La struttura del database non è stata progettata con lo scopo principale di essere la più intuitiva possibile per l’utente, ma piuttosto con lo scopo che il sistema possa trovare rapidamente le informazioni e in una modalità che riduca al minimo i duplicati e le anomalie sui dati. Con questi accorgimenti siamo in grado di ridurre al minimo la possibilità di avere errori a causa di informazioni ripetute che possono diventare inconsistenti.

    Spezzare le informazioni in componenti più piccoli

    Uno dei vantaggi nell’avere i dati memorizzati in un database relazionale ben strutturato consiste nel poter effettuare una vasta gamma di query sui dati molto facilmente. Uno dei modi per garantire che il database sia flessibile nei tipi di query che si possono effettuare è di rendere il campo più descrittivo e specifico possibile, con la conseguenza che i campi memorizzano informazioni molto più piccole. Un tipico esempio di come i dati dei clienti verrebbero memorizzati in un foglio di calcolo è il seguente.

    clienti

    Nella tabella clienti, viene memorizzato il nome del cliente e l’indirizzo di fatturazione. Si tratta di una struttura di archiviazione che funziona bene per un’esigenza immediata relativa al business dell’e-commerce di esempio. Ma al di là delle esigenze di fatturazione, se dovesse essere utile compilare un elenco di tutti i clienti che provengono dalla stessa provincia, con la configurazione attuale saremmo costretti ad effettuare un parsing del campo indirizzo per determinare in quale provincia si trovano. Potremmo individuare una regola in base alla quale i due caratteri tra parentesi forniscono la sigla della provincia (o qualche altra regole del genere), ma non è detto che funzioni: dipende da come viene popolato il campo volta per volta e non è garantito che l’utente che compila il campo segua questo formalismo.

    Le colonne come quella contenente l’indirizzo di fatturazione in inglese sono chiamati multi-part fields, perché memorizzano più informazioni in un unico campo, e sono più complessi da gestire il DBMS. In alcuni casi occorre individuare e riconfigurare tutti i campi che contengono pezzi di informazioni che possono essere suddivisi in blocchi più piccoli, se le operazioni che si pensa di effettuare ne possono trarre beneficio. L’idea è di separare i componenti dell’indirizzo in componenti più piccoli. L’indirizzo di fatturazione si separa facilmente in colonne indirizzo, codice postale, città e provincia.

    clienti-colonne singole

    Dopo questa modifica, per la ricerca dei clienti appartenenti alla stessa provincia, il DBMS deve semplicemente cercare la sigla nella colonna Provincia, senza dover utilizzare regole di parsing complicate e non garantite.

    Una considerazione da fare è l’opportunità (o meno) di spezzettare ulteriormente il campo indirizzo. Vale la pena dividere il campo in elementi più piccoli (nome della via, numero civico, etc …)?  La risposta a questa domanda dipende dalle esigenze del progetto.Può essere utile effettuare delle ricerche dei clienti in base alla via? O che abitano nello stesso numero civico? Nel caso di un e-commerce che vende libri, probabilmente no. Pertanto va bene accorpare in un unico campo Indirizzo il nome della via e il numero civico. La struttura del database è più semplice e non creano problemi di nessun tipo. Se l’applicazione avesse avuto bisogno di un’informazione specifica su vie e numeri civici (ad esempio nel caso di una società di servizi della rete idrica che ha necessità di raggiungere i clienti a livello di strada e di numeri civici) avrebbe avuto senso dividere ulteriormente il campo Indirizzo.

    Un altro esempio che si verifica quasi sempre riguarda la memorizzazione dei nominativi. L’approccio iniziale e più intuitivo è quello di avere un singolo campo contenente nome e cognome del cliente, che risolve il bisogno immediato di sapere a quale cliente spedire i prodotti acquistati. Ma ci sono altre modalità di utilizzo dei nominativi da parte dell’applicativo. Ad esempio può servire realizzare un elenco di clienti ordinati alfabeticamente per cognome e poi per nome. Oppure può servire avere il nome per inviare al cliente una lettera commerciale. Oppure può servire una combinazione di caratteri dal nome e dal cognome (ad esempio iniziale del nome, seguita da un punto, seguita dal cognome) e per generare automaticamente un account all’e-commerce in sede di registrazione. Se il campo con il nominativo del cliente viene memorizzato come un’unica stringa di caratteri, tutti questi scenari si complicano. La soluzione più semplice è quello di suddividere l’informazione su più colonne.

    clienti-nome-cognome

    Memorizzando nome e cognome come campi separati, siamo in grado di gestire più comodamente gli esempi appena citati. In questo modo non abbiamo perso nulla, ma solo guadagnato in termini di flessibilità nel modo di presentare le informazioni per gestire una più grande varietà di casi d’uso attuali e futuri.

    Prevenire conflitti di dati

    Un’altra fonte di conflitti di dati frequente si verifica quando i valori memorizzati sono calcolati da altre informazioni di cui si sta già tenendo traccia. Se cambia un valore, allora deve essere aggiornato anche il valore derivato dal calcolo. Ad esempio rivediamo le fatture per l’e-commerce. Il cliente Mario Bianchi ha acquistato due copie del libro “Controllo di versione con Git”. Il prezzo del libro (memorizzato nella tabella prodotti) è di 15,00 €. La fattura mostra correttamente il prezzo totale di 30,00 €.

    fatture-prodotti

    Cosa succede viene modificata la quantità? Il campo contenente il prezzo totale della tabella fattura deve essere aggiornato.  Se dopo la modifica della quantità, per qualche motivo il prezzo totale non viene aggiornato, abbiamo un conflitto di dati: il prezzo totale non equivale alla quantità ordinata moltiplicata per il prezzo per prodotto. La soluzione a questo problema può sembrare sorprendente: semplicemente non memorizzare questo tipo di informazioni derivate. Sarà il DBMS a fare il lavoro per noi e a calcolare questi valori al volo, al bisogno, piuttosto che memorizzarli in modo permanente nelle tabelle, dove possono diventare obsoleti se non vengono prese le debite precauzioni. Ogni volta che si vogliono memorizzare dati in un campo che sia un totale, una media, o un minimo, o un massimo, può essere molto meglio calcolare questi valori al volo. Questo vale per diversi tipi di operazioni matematiche, come trovare il prezzo totale quando si conosce la quantità del prodotto ordinato ed il relativo prezzo per unità, o determinare l’importo delle tasse dovute dato un imponibile ed un tax rate. Tutti i DBMS forniscono una serie di funzioni matematiche che è possibile utilizzare per calcolare i dati a partire dai parametri di input.

    Il problema con la memorizzazione delle informazioni calcolato non si limita ai dati di tipo numerico. Possiamo applicare lo stesso concetto ai dati testuali con le funzioni di manipolazione di stringhe, come l’estrazione di sottocaratteri (ad esempio la prima lettera del nome), o la concatenazione (ad esempio per combinare il campo Nome e il campo Cognome per ottenere il nome completo).

    Tutte le volte in cui il DBMS può calcolare un valore a partire da altri, conviene non memorizzare un’informazione che può generare facilmente conflitti e lasciare al DBMS il compito di calcolarli, semplificando il lavoro del programmatore e riducendo la possibilità di conflitti sui dati.

    Recuperare l’informazione completa

    Una delle anomalie sui dati riguarda il caso di dati fondamentali completamente mancanti o non correttamente inseriti. Nel caso dell’e-commerce, per completare un acquisto, alcune informazioni non possono assolutamente mancare. Riprendiamo la tabella delle fatture e passiamo in rassegna ciascuna colonna per individuare quelle che sono effettivamente indispensabili. Non è infatti ragionevole avere un modulo (ad esempio di registrazione) in cui tutti i campi devono essere valorizzati (secondo nome, numero di fax, etc …). Nei database relazionali, per indicare che il valore di un campo è indispensabile, si specifica il campo come NOT NULL: il DBMS non consente la memorizzazione del record se quel campo viene lasciato vuoto.

    Vediamo quindi alcuni esempi di colonne per la tabella Fatture:

    • il numero identificativo della fattura viene impostato automaticamente e non deve inserirlo l’utente;
    • il nome del cliente è un dato necessario;
    • il prodotto ordinato è un dato necessario;
    • la quantità di prodotti ordinati è un dato necessario;
    • l’indirizzo di fatturazione è un dato necessario;
    • l’appartamento (nell’indirizzo di fatturazione) non è indispensabile;
    • la città di fatturazione è un dato necessario;
    • la provincia di fatturazione è un dato necessario;
    • il CAP di fatturazione è un dato necessario;
    • l’indirizzo di spedizione dovrebbe essere un dato necessario, ma se coincide con l’indirizzo di fatturazione è possibile evitare di renderlo necessario e farlo inserire di nuovo all’utente;
    • l’appartamento (nell’indirizzo di spedizione) non è indispensabile;
    • la città di spedizione dovrebbe essere un dato necessario, ma se coincide con la città di fatturazione è possibile evitare di renderlo necessario e farlo inserire di nuovo all’utente;
    • per la provincia di spedizione vale il ragionamento esposto sopra;
    • per CAP di spedizione vale il ragionamento esposto sopra;
    • il codice coupon non è indispensabile (il cliente può averlo o non averlo);
    • il numero di carta di credito è un dato necessario (se è l’unico metodo di pagamento accettato);

    La distinzione sull’obbligatorietà di ogni singola colonna viene affrontata nella fase della raccolta dei requisiti, generando regole di business particolari che dovrebbero essere applicate alla struttura dei dati.

    Un’ultima considerazione sulla tabella Fatture. Il campo quantità, oltre ad essere reso obbligatorio, può anche essere costretto da una regola del DBMS ad assumere un valore numerico maggiore di zero, in modo che non può assumere accidentalmente non appropriato.

    Mantenere una struttura consistente

    Un limite introdotto nella tabella Fatture consiste nel fatto che ciascuna fattura ha un solo campo per l’inserimento di prodotti, anche se ovviamente i clienti possono acquistare più prodotti in solo ordine. Inserire più valori nello stesso campo non è assolutamente una buona soluzione. Ancora una volta, occorre usare un’altra tabella (che descrive ciascun prodotto ordinato nella stessa fattura) in cui collocare le colonne Prodotto e Quantità e un riferimento alla fattura.

    righe-fattura

    Con questa modifica, la tabella Fatture memorizza solo le informazioni sulla transazione complessiva (il numero della fattura, il cliente, la data, e così via). Per trovare quello che effettivamente è stato ordinato, si prende il numero di fattura e si cercano le  singole voci nella nuova tabella “Righe Fattura”. Ad esempio, il cliente Mario Bianchi nella fattura 276 ha ordinato 3 prodotti. Il dettaglio con i prodotti (e le relative quantità) è nella tabella “Righe Fattura”.

    Una delle abitudini che viene a volte adottata con i fogli di calcolo, è la tendenza a memorizzare più pezzi di informazioni dello stesso tipo in un unico campo. Ad esempio, se l’utente ha più conti correnti o più carte di credito, c’è la tentazione di inserire tutti questi valori (magari separati da una virgola) all’interno di un campo. Non è una buona idea. Ma non è una buona idea nemmeno quella di aggiungere alla tabella un campo “Carta di credito 1” e “Carta di credito 2” e così via (non puoi sapere quante carte di credito ha l’utente e su quale colonna cercare un determinato valore). La soluzione è sempre la stessa: creare una nuova tabella in cui memorizzare queste informazioni ed evitare i campi multivalore.

     

    Giulio Cantali – IT Consultant

    Creatore di Database Master, il primo percorso per diventare esperti di database

     

     

Lascia un commento

Se vuoi condividere la tua opinione, lascia un commento

Puoi usare questi tag e attributi: HTML:

<a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code class="" title="" data-url=""> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong> <pre class="" title="" data-url=""> <span class="" title="" data-url="">