Esporta (0) Stampa
Espandi tutto
1 di 1 hanno valutato il contenuto utile: - Valuta questo argomento

SQL Server 2005 Developer Guidelines - Parte 2

di Davide Mauri - Microsoft MVP

Nell’articolo precedente sono stati definiti alcuni pratici punti chiave per l’approccio corretto allo sviluppo di un database, tenendo conto che in moltissimi casi chi si occupa di sviluppare un’applicazione è incaricato anche dello sviluppo del database a supporto della stessa. In questo articolo continueremo sulla stessa strada, andando però anche a toccare alcune tematiche più concettuali, che ci permetteranno di capire come poter non solo utilizzare al meglio un database, ma anche come disegnarlo e progettarlo bene.

In questa pagina

Cos’è un database? Cos’è un database?
Evitare l’utilizzo dei NULL Evitare l’utilizzo dei NULL
Normalizzazione Normalizzazione
Utilizziamo le Stored Procedure! Utilizziamo le Stored Procedure!
Conclusioni Conclusioni

Cos’è un database?

Per capire perché è necessario seguire certe regole durante la progettazione e lo sviluppo di un database è evidentemente necessario sapere cos’è un database. E’ probabile che intuitivamente tutti credano di sapere cos’è un database; è altrettanto probabile che tale convinzione sia completamente errata.

Tipicamente si è convinti che un database sia un sistema di memorizzazione dei dati, e che la sua funzionalità sia semplicemente quella di preservare i dati che gli affidiamo. Sbagliato. Se così fosse, infatti, un database non sarebbe nulla di più che un file binario ed a questo punto in file binario svolgerebbe in modo molto più performante lo stesso lavoro.

Dagli anni 70, grazie al lavoro di E. F. Codd e compagni (principalmente Darwen, Date e Pascal) i database hanno smesso di essere solamente entità informatiche, e quindi altamente volatili, senza nessuna regola, esistenti e funzionanti solamente nel momento in cui questi fossero programmati da un buon programmatore e fatti funzionare su un computer. In una situazione come questa, infatti, non era possibile progettare un database sulla carta in quanto non esistevano regole che dicessero come farlo, e le possibilità che tale progetto funzionasse erano tutte e totalmente legate alla bravura della persona incaricata del progetto. Per questo motivo E. F. Codd creò un metodo scientifico che definì il concetto di database ed una serie di regole per la sua progettazione. In pratica fece una cosa straordinaria: diede ai database delle solide basi matematiche (logiche in particolare) così che la creazione e la gestione degli stessi non fosse più di qualcosa legato solamente alle capacità personali ed all’esperienza maturata, ma fosse qualcosa di più generale ed universalmente applicabile.

Ecco quindi che, poggiando le proprie fondamenta nella logica matematica, un database è un contenitore di “fatti” reali al quale è possibile fare “domande” ed ottenere risposte inconfutabilmente corrette a patto che siano seguite le regole logico-matematiche che ne definiscono il funzionamento.

In un database devono pertanto essere inseriti solo dati “reali” (e quindi veri) in quanto l’introduzione di un dato con corretto (e quindi falso) impedirebbe al motore del database (che è quindi un motore di inferenza logica) di dare risposte corrette. Appare quindi molto importante come durante la progettazione e la programmazione del database sia necessario definire anche tutta quella serie di meccanismi che impediscano a chiunque, indipendentemente dall’applicazione di accesso ai dati che verrà usata, di poter inserire dati non veri nel database.

Ancora una volta è molto semplice chiarire il perché di questo concetto: se un database contiene dei “fatti” (e tra poco vedremo in che modo), e questi fatti sono i dati di business sui quali andremo a sviluppare le nostre applicazioni, è allora vero che la regola di non inserire “fatti” falsi, e quindi errati, è equivalente all’applicazione di basiche regole di business. Regole di questo tipo sono, ad esempio, il fatto di non poter fare una fattura ad un cliente che non esiste, oppure di non poter ordinare un prodotto non più a catalogo e via dicendo.

Un database è quindi un motore di inferenza logica che ha il compito di preservare i dati legati ai “fatti” che ci interessa memorizzare, e che deve pertanto preservare l’integrità logica degli stessi (sulla base delle regole di business che agiscono sui dati o dipendono da essi), cosi da poter fornire, su richiesta, risposte corrette a domande poste dall’utente sui “fatti” così memorizzati.

Ma come si può memorizzare un “fatto” all’interno di un database, se questo memorizza i dati in tabelle? Per rispondere è sufficiente fare un piccolo esempio. Diciamo di dover implementare un database per una piccola libreria comunale. In pratica dobbiamo quindi memorizzare un fatto come il seguente:

Davide Mauri in data 13 ottobre 2006 ha preso in prestito il libro “Fanteria dello spazio” di Robert Heinlein.

Volendo generalizzare la frase per ogni utente, libro, data ed autore potremmo scrivere:

L’<Utente> in data <Data prestito> ha preso in prestito il libro <Titolo libro> di <Autore Libro>.

Se a questo punto togliamo la parte invariante della frase otteniamo:

<Utente>, <Data prestito>, <Titolo libro>, <Autore libro>

che possono essere le colonne di una nostra primitiva tabella, ed i valori che identificano un determinato fatto le righe della stessa:

Utente

Data prestito

Titolo libro

Autore libro

Davide Mauri

13 Ottobre 2006

Fanteria dello spazio

Robert Heinlein

Ossia una tabella una è rappresentazione della realtà che la nostra applicazione deve gestire. E’ quindi evidente il perché della necessità di assicurarsi che nella tabella ci siano sono fatti veri. Se cosi non fosse la rappresentazione della realtà sarebbe errata e cosi tutte le elaborazioni e le applicazioni fatte su di essa.

Per quanto riguarda la tabella appena definita, credo che intuitivamente via siate già resi conto che non è del tutto corretta in quanto sarà sicuramente problematico aggiornarla, manipolarla e gestirla. Bene, siete sulla strada giusta! Un po’ di pazienza ancora e nella sezione sulla normalizzazione correggeremo, in modo regolamentato – quindi non basato sulle sensazioni, che sono soggettive – la situazione.

Evitare l’utilizzo dei NULL

Quanto detto in precedenza ha subito un riscontro molto pratico nella progettazione delle tabelle dei nostri database. L’utilizzo di NULL deve essere evitato (se possibile) a tutti i costi.

I NULL rendono molto più complessa la scrittura delle query, in quanto introducono il concetto della three-value logic che – basandosi su tre valori – è più complessa della logica binaria (ossia: vero/falso) e mal interpretata dal nostro cervello.

Analizziamo il primo punto: i NULL rendono più difficile la scrittura di query. Supponiamo di avere una tabella contenente i dati (nome, cognome, dipartimento ed interno) del personale di una società.

Per semplicità ipotizziamo di avere un impiegato con questi dati:

insert into dbo.Impiegati values ('Mario', 'Rossi', NULL, '567')

Il nostro scopo è quello di avere una lista di tutti i dipendenti che di cognome fanno “Rossi” e che NON sono nel dipartimento “Sistemi Informativi”. Se la colonna “dipartimento” può contenere i valori NULL, la query seguente NON è corretta:

select
*
from
dbo.Impiegati 
where
cognome = 'Rossi' and dipartimento <> 'Sistemi Informativi'

Il risultato, infatti, non contempla tutti i dipendenti per cui il dipartimento è impostato a NULL. Ogni confronto ed operazione fatta tra un dato conosciuto ed un valore NULL produce il risultato UNKNOWN che non è né falso né vero ma una terza condizione. Il problema è che la clausola where restringe il risultato della query a tutte le righe che sono vere rispetto alle condizioni di ricerca, escludendo i valori unknown e false. La query deve essere riscritta in questo modo:

select
*
from
dbo.Impiegati 
where
cognome = 'Rossi' 
and 
(dipartimento <> 'Sistemi Informativi' or dipartimento is null)

Query più complessa e che, tra l’altro, rende particolarmente difficile (se non impossibile) l’utilizzo di indici da parte di SQL Server.

Ma non è tutto. Il problema con i NULL è che possono dare un risultato corretto da un punto di vista logico ma sbagliato da un punto di vista reale. Estendendo l’esempio fatto poco fa, supponiamo di avere anche una tabella dei responsabili le cui colonne sono nome, cognome e dipartimento. La richiesta che questa volta dobbiamo soddisfare è la seguente: elencare per tutti i dipendenti che sono imparentati tra loro coloro che lavorano nel dipartimento “Risorse Umane” oppure che NON hanno come responsabile un parente. Per identificare la relazione di parentela ci baseremo solo sul cognome.

Per rendere l’esempio semplice, supponiamo anche che la tabella dei responsabili abbia una sola riga:

insert into dbo.Responsabili values ('Giuseppe', 'Rossi', 'Risorse Umane')

Le tabelle in gioco, pertanto, si presentano in questo modo:

 

La query necessaria per rispondere alla richiesta definita in precedenza è questa:

select
i.*
from
dbo.Impiegati i
inner join
dbo.Responsabili r on i.cognome = r.cognome 
where
i.dipartimento <> r.dipartimento
or
i.dipartimento = 'Risorse Umane'

Se proviamo ad eseguire questa query sui dati di esempio, ci renderemo conto di come l’utilizzo di un NULL fornisce risultati sbagliati. Per dimostrarlo definiamo un concetto semplice ma importante. Attualmente non sappiamo in che dipartimento lavora il sig. Mario Rossi (il valore della colonna dipartimento è NULL) ma siamo sicuri di due cose: o lavora al dipartimento “Risorse Umane” o non ci lavora. Ovvio no?

Analizziamo le due possibilità. Se il sig. Mario Rossi lavora nel dipartimento “Risorse Umane” la seconda parte della clausola where è vera è quindi la select deve restituirci la riga relativa all’impiegato Mario Rossi. Se quest’ultimo non lavora per le risorse umane allora è vera la prima parte della where in quanto il responsabile Giuseppe Rossi è un suo parente ma non lavora nello stesso dipartimento. Anche in questo caso, quindi, la select deve ridarci la riga relativa al sig. Rossi.

Riassumendo, la riga dell’impiegato Rossi deve venire restituita come risultato della query in ogni caso possibile. Anche non sapendo in che dipartimento esso lavori potremmo essere comunque sicuri del fatto che la riga deve essere restituita e quindi ci aspetteremmo che la presenza del NULL come valore del dipartimento non sfugga a questo inconfutabile concetto. Invece no. Se proviamo ad eseguire la query non avremo nessuna riga come risultato! (Tutto il codice necessario è reperibile nel listato 1)

Tutto questo è legato alla “nostra” interpretazione del NULL. Per noi è un valore sconosciuto ma eventualmente deducibile da altre informazioni. Per una macchina un NULL è un valore sconosciuto punto e basta ed è perfettamente gestibile utilizzando la three-value logic. La query, infatti, nel momento in cui non produce nessuna riga come risultato è “corretta” in quanto, come detto in precedenza, tutte le righe risultanti devono soddisfare in toto la clausola where, ossia essere vere rispetto ad essa. Vere, non false o sconosciute. Purtroppo per noi però non è così facile adattare il nostro modo di pensare ad essa e scrivere quindi delle query corrette. Query che comunque sarebbero più complesse (anche di molto) in quanto dovrebbero per forza di cosa tenere conto del NULL, complicandoci quindi la vita senza alcun beneficio pratico.

Come sicuramente avrete notato la cosa è piuttosto subdola, poichè l’identificazione di possibili situazioni errate non è per nulla semplice e, anzi, richiede un certo sforzo mentale necessario per riconoscere i paradossi che si creano. Questo significa che potremmo produrre dei dati errati senza esserne consapevoli, il che è sicuramente un grossissimo problema.

Come fare, quindi, per e risolvere questo e quello dimostrato all’inizio del paragrafo? C’è un semplice modo: evitiamo l’utilizzo dei NULL. Come vedremo nel seguente capitoletto, non servono.

Normalizzazione

Vedendo la tabella definita nel paragrafo iniziale vi sarete sicuramente resi conto che, così com’è fatta, è sbagliata. Sbagliata non nel senso che non può contenere i valori che vogliamo memorizzare (cosa per se impossibile in quanto siamo partiti proprio da quei valori per creare la tabella), ma nel senso che, se utilizzata in un’applicazione reale, nel breve periodo mostrerà sicuramente problemi che renderanno molto difficile la gestione e la creazione di un’applicazione basata su di essa. Problemi come:

  • Righe duplicate

  • Impossibilità di memorizzare libri che sono stati scritti da più autori

  • Impossibilità di memorizzare utenti che non hanno ancora preso in prestito un libro

  • Difficoltà nell’aggiornamento del nome di un autore (Non è possibile modificare il cognome senza dover toccare anche il nome)

  • Impossibilità di memorizzare libri che non sono ancora stati presi in prestito

  • Non ha senso mettere in un’unica tabella informazioni sui libri, sugli autori e sugli utenti

e fermiamoci qui perché la lista sarebbe troppo lunga. Il fatto che tutti questi problemi siano conosciuti e siano lampanti è dovuto a due fattori:

  • la nostra esperienza passata

  • la conoscenza abbastanza approfondita che abbiamo (o che crediamo di avere) sul funzionamento di una biblioteca (questo punto, volendo, è possibile considerarlo come implicito nel punto precedente)

Da questo si può trarre una considerazione importante: la quantità dei problemi identificabili, e la nostra capacità di disegnare un database che li prevenga, è totalmente dipendente dai due suddetti punti. Il che non è particolarmente bello in quanto, se lo stesso principio si applicasse all’edilizia in quanto la solidità e la stabilità di una costruzione sarebbe totalmente dipendente dall’esperienza maturata dal costruttore.

Evidentemente le cose non possono funzionare in questo modo. Ci deve essere anche nel mondo dei database, che ora hanno l’aiuto di strumenti di logica matematica, un insieme minimo di regole che permetta a chiunque, indipendentemente dalle sue esperienze passate, di disegnare un database corretto. Come succede in tutte le altre discipline scientifiche ed ingegneristiche questo non significa che tutti i database saranno uguali, ma significa che per ogni database potremo far affidamento su delle regole che ci permetteranno di capire se abbiamo fatto una modellazione corretta oppure ci siamo discostati da essa.

Queste regole prendono il nome di “Regole di Normalizzazione”.

Esse ci permetteranno di dimostrare come, in effetti, la tabella in questione sia errata, ma senza per questo dover basare questa affermazione solo sul nostro buon senso; oltre a questo riusciremo anche a trovare il modo corretto per disegnare il database a supporto dell’applicazione di gestione dei prestiti dei libri.

Come iniziare ad affrontare il tema delle regole di Normalizzazione? Lasciamo le spiegazioni formali ad altri testi: per iniziare a capirle ed apprezzarle il modo migliore è quello di metterle in pratica. Partiamo quindi dall’esempio della libreria, modificandolo leggermente in modo da poter dimostrare come applicare le prime tre regole di normalizzazione, che prendono – più correttamente – il nome di “Forme Normali”. In questo articolo vedremo le prime tre, ma in totale ce ne sono ben sei.

Iniziamo da questa tabella (dove è visibile il nome nel box in alto, seguito dai nomi delle colonne):

sql2

Dove, com’è possibile notare, è stata aggiunta la colonna “ISBNLibro”.

La Prima Forma Normale dice che:

  • Le colonne devono contenere valori atomici;

  • Tutte le righe devono avere lo stesso numero di valori;

  • Non devono poter esistere righe duplicate;

Il primo punto implica il fatto che memorizzare nome e cognome di un utente (o di un autore) in una sola colonna non sia una brillantissima idea: il nome dovrà essere memorizzato in una colonna separata dal cognome. Questo fa si che i due valori così ottenuti siano “atomici”, ossia non ulteriormente scomponibili senza aumentare la complessità del sistema, ma non avendo però nessun beneficio in cambio. (Ad es., sarebbe possibile scomporre i nomi nelle lettere che li formano, ma questo non comporterebbe nessun beneficio; si commetterebbe pertanto l’errore di una normalizzazione eccessiva.)

Il fatto che ogni riga debba avere lo stesso numero di valori significa che nessuna riga deve poter memorizzare valori NULL, per i motivi definiti nel capitoletto precedente.

La necessità di impedire l’esistenza di righe duplicate si traduce nella necessità di identificare quell’insieme di valori (e quindi di colonne) che sono o devono essere univoci all’interno della tabella, pena la violazione delle regole di business. Nell’esempio preso in considerazione fino ad ora, nessuna regola è stata definita in merito a questo argomento. Dato che l’identificazione di questo insieme di colonne dipende da esse, è bene definirne almeno una:

Un libro (identificato dal proprio codice ISBN) non può essere prestato più di una volta al giorno.

(NB: non ci stiamo preoccupando di come sia possibile restituire il libro, ma questo è un esempio, possiamo semplificare anche di molto la realtà, per poterci concentrare sui concetti che ci interessano. )

Applicando le regole della prima forma normale la tabella diventa:

Le colonne segnate in rosso sono la Primary Key della tabella, ossia quell’insieme di colonne i cui valori sono univoci e, di riflesso, permettono di identificare univocamente una riga tre tutte le altre, cosa altrimenti impossibile se non specificando tutti i valori delle colonne.

Ora che la tabella è nella prima forma normale i problemi relativi alle righe duplicate ed alla difficoltà di manipolazione di valori non atomici sono risolti. E’ possibile quindi passare ad applicare la Seconda Forma Normale, e per farlo le regole sono:

  • La tabella è in Prima Forma Normale;

  • Le colonne che non compongono la chiave dipendono funzionalmente da essa;

Il concetto di dipendenza funzionale è davvero molto semplice: se tra due colonne c’è una relazione per cui per ogni valore di una colonna “A” esiste uno ed un solo valore per la colonna “B” allora “B” dipende funzionalmente da “A”; non è ovviamente detto che per ogni valore di “B” ci sia uno ed un solo valore di “A”. Un esempio pratico rende la comprensione immediata. Supponiamo di avere una tabella in cui ci siano le colonne: “codice fiscale”, “nome” e “cognome”. E’ evidente come per ogni valore di “codice fiscale” esista uno ed un solo valore di “nome” e “cognome”. E’ altrettanto evidente come non sia vero il contrario. In questo caso, quindi, “nome” e “cognome” dipendono funzionalmente dal “codice fiscale”.

Tornando ad analizzare la seconda forma normale, notiamo come indichi che in una tabella, oltre le colonne che compongono la Primary Key, debbano esserci solo colonne funzionalmente dipendenti dalla chiave stessa. Tutte le colonne che non lo sono saranno messe in altre tabelle, create ad-hoc per l’occasione.

Nell’esempio della libreria, quindi, è necessario compiere le seguenti modifiche:

  1. ISBNLibro, TitoloLibro, NomeAutore, CognomeAutore verranno spostate nella tabella “Libri”, la cui Primary Key sarà “ISBNLibro”.

  2. NomeUtente e CognomeUtente verranno spostate nella tabella “Utenti”, dove – data l’impossibilità, con le colonne disponibili, di definire una Primary Key corretta, verrà aggiunta una colonna fittizia “Id” che fungerà da Primary Key.

E’ bene notare come per arrivare a questo passaggio abbiamo, in modo “automatico” applicato per due volte la regola della seconda forma normale. Per accorgersi della cosa è sufficiente immaginare il processo che abbiamo seguito intuitivamente: dalla tabella originale abbiamo estratto tutte le colonne non funzionalmente dipendenti dalla Primary Key e le abbiamo messe in un’altra tabella. Tale tabella intermedia è costituita dalle colonne “ISBNLibro”, “TitoloLibro”, “NomeAutore”, “CognomeAutore”, “NomeUtente”, “CognomeUtente”. Dato che non ci possono essere due libri con lo stesso ISBN - questa regola di business non è mai stata definita e la identifichiamo ora grazie al processo di normalizzazione – la Primary Key di questa nuova tabella è proprio “ISBNLibro”. A questo riapplichiamo le regole per la seconda forma normale a suddetta tabella, ed identifichiamo come “NomeUtente” e “CognomeUtente” non dipendano funzionalmente da “ISBNLibro”. Questa osservazione ci porta a creare una nuova tabella, contenente solo “NomeUtente” e “CognomeUtente”, che vengono quindi rimosse dalla posizione precedente. Il risultato sono i punti elencati poc’anzi e il diagramma del database diventa il seguente:

In pratica, tramite l’applicazione della seconda forma normale, abbiamo seguito un procedimento scientifico che, indipendentemente dall’esperienza personale e dal buon senso, ci ha permesso di arrivare ad ottenere le tabelle che dal primo momento avremmo voluto creare dando libero sfogo al nostro intuito. Il che non è poco perché significa che potremo usare questo metodo anche quando l’intuito non ci verrà in aiuto. Non da ultimo, potremo insegnarlo.

Il modello che ora è in seconda forma normale, però, è ancora migliorabile. In particole il nostro istinto ci dice che una tabella dedicata agli autori sarebbe auspicabile, così da non aver ripetizioni all’interno della tabella “Libri” nel caso in cui un autore abbia scritto più libri.

La Terza Forma Normale si preoccupa proprio di risolvere questo tipo di problema. Per essere in terza forma normale è necessario che una tabella abbia queste caratteristiche:

  1. Sia in seconda forma normale;

  2. Le colonne che non compongono la chiave devo dipendere funzionalmente da essa in modo diretto

“Dipendere in modo diretto” significa quello che intuitivamente già stiamo pensando, ossia che una colonna deve essere legata alla Primary Key senza l’ausilio di colonne “intermediarie”. Nella tabella “Libri”, invece, possiamo verificare che “NomeAutore” e “CognomeAutore” dipendono funzionalmente dal titolo del libro che a sua volta dipende funzionalmente dal codice ISBN. Detto in altri termini “NomeAutore” e “CognomeAutore” dipendono dalla chiave in modo indiretto anziché in modo diretto.

Per rendere la tabella aderente alla terza forma normale, quindi, è sufficiente prendere le due colonne in questione e metterle in una nuova tabella tutta loro. Il risultato è questo:

Che è molto vicino al disegno corretto del database, e che ha risolto praticamente tutti i problemi descritti nella lista iniziale…tranne uno. Non è ancora possibile memorizzare un libro che è stato scritto da più autori. La soluzione al problema è ancora una volta ovvia e molto semplice. Chi ha un minimo di esperienza nel disegno di database, avrà già trovato la soluzione, ma ancora una volta cerchiamo di non lasciarci guidare dall’esperienza e dal buon senso; procediamo invece facendo un’ulteriore verifica dell’estrema potenza ed utilità del processo di normalizzazione.

Se un libro può essere scritto da più autori potremmo immaginare di mettere più di una colonna IdAutore nella tabella libri (IdAutore1, IdAutore2, ecc.). Questo però ci porta a violare la prima forma normale, in quanto non tutte le colonne sono sempre valorizzate (ad esempio se un libro ha solo un autore il valore della colonna IdAutore2 sarà impostato a NULL). Per risolvere questo problema dobbiamo rimuovere tutte le colonne “IdAutore” e creare una nuova tabella che chiameremo “AutoriLibri” le cui colonne saranno “ISBN” e “IdAutore”. A questo punto di disegno è completo:

Prima di concludere vi invito a riflettere su un punto molto importante. La normalizzazione è un processo formale che non solo ci ha aiutato a disegnare correttamente il modello del database cosi che sia esente da errori ben noti, ma ci ha anche permesso di partire da un’unica tabella e da pochissime regole di business e di identificare tutte quelle erano invece sottointese e quindi non facilmente identificabili a priori. Ogni nuova tabella creata rappresenta un’entità (fisica o meno) che è in qualche modo legata alle regole di business che ci hanno portato a “scoprirla”, e questo significa che attraverso il processo di normalizzazione possiamo anche migliorare la conoscenza di ciò che dobbiamo modellare. Il tutto è applicabile a qualsiasi realtà, indipendentemente dalla conoscenza che abbiamo della stessa: abbiamo cioè tra le mani un insieme di regole che ci permettono di avere un approccio scientifico alla modellazione dei nostri dati. Non è poco: attualmente è l’unica metodologia ad avere questa importantissima caratteristica.

Utilizziamo le Stored Procedure!

Dopo la parte più teorica dedicata alla normalizzazione è bene tornare a parlare degli strumenti che SQL Server ci mette a disposizione. Questo anche per rispondere ad un dubbio che, credo, vi sarà già venuto in mente. La normalizzazione porta alla creazione di “n” tabelle che rendono quindi più difficile all’utente finale l’accesso ai dati stessi, in quanto sono sparsi in diverse entità che devono essere poi messe in join tra loro per ottenere i dati cercati. La risposta a questo dubbio sta nella possibilità e necessità di NON lasciare all’utente finale l’accesso diretto alle tabelle. Già le viste permettono di soddisfare questa necessità, ma le stored procedure hanno una marcia in più ed inoltre sono strumenti formidabili per poter ottimizzare e gestire l’accesso ai dati da parte di utenti ed applicazioni.

I motivi per cui ogni accesso al database dovrebbe essere sempre fatto tramite stored procedure sono (in ordine di importanza) i seguenti:

  1. Creano un livello di astrazione dall’implementazione fisica dei dati.

  2. Incapsulano tutta la logica necessaria per operare sui dati in modo consistente in un solo punto.

  3. Facilitano la gestione della sicurezza.

  4. Permettono un ottimale riutilizzo dei piani di esecuzione.

  5. Diminuiscono l’utilizzo delle risorse di rete.

Il primo punto è piuttosto importante soprattutto in considerazione del fatto che un database tipicamente ha una vita media tre volte più lunga di quella di un’applicazione e, soprattutto, ha una vita “propria” indipendente da quella delle applicazioni che lo utilizzano. Questo significa che durante la vita del database le applicazioni cambieranno e, molto probabilmente, cambierà il database stesso, così da soddisfare le esigenze di nuove applicazioni o di rinnovate regole di business. In entrambi i casi utilizzando le stored procedure ci si garantisce il disaccoppiamento del database dalle applicazioni in modo che ogni modifica a queste ultime non impatti necessariamente sul database né che modifiche strutturali, anche complesse, al database stesso richiedano una necessaria modifica delle applicazioni che lo usano. In pratica le stored procedure fanno da “contratto” tra applicazioni (o utenti) e database: fintanto che il contratto è rispettato gli sviluppatori dell’applicazione e gli sviluppatori del database (ed i relativi dba) sono indipendenti gli uni degli altri e possono pertanto agire in modo autonomo per migliorare le performance e le stabilità dell’intero sistema applicazione (ossia applicazione + database) senza intralciarsi a vicenda.

Nel caso di database complessi l’utilizzo di stored procedure permette inoltre agli sviluppatori di non preoccuparsi del database, della sua complessità e di tutti i problemi ad essa connessi. Le stored procedure saranno preparate dal team dedicato allo sviluppo del database (ovviamente in accordo con il team di sviluppo dell’applicativo) e quindi dovranno solo essere invocate ed utilizzate senza troppi problemi.

Il punto numero due è anch’esso particolarmente importante. La possibilità di raccogliere tutta la logica necessaria per portare a compimento una transazione (ad esempio l’inserimento di un ordine in un database complesso può essere un’operazione essa stessa complessa, andando a coinvolgere decine di tabelle) permette di centralizzare la manutenzione (e quindi anche l’ottimizzazione) del codice stesso. Va da se che ciò comporta importanti benefici: migliorando le prestazioni di una stored procedure si miglioreranno le prestazioni di TUTTI le applicazioni che la usano!

Per quanto riguarda la gestione della sicurezza, le stored procedure permettono di ottenere risultati altrimenti impossibili. SQL Server permette, infatti, di negare ad un utente l’accesso diretto ad un oggetto (tabella o vista, ad esempio), lasciando però la possibilità di accedervi tramite una stored procedure.

Un esempio è fornito nel listato 2. In questo listato si mostra come è possibile rendere visibili delle righe di una tabella ad un utente che ne fa richiesta in base all’appartenenza dell’utente ad un determinato gruppo di utenti.

Oltre a rendere più efficace ed efficiente la gestione della sicurezza intesa in senso comune, questo approccio permette di regolamentare l’accesso ai dati in binari ben precisi e sicuri. Si pensi ad esempio ad un database dove per inserire un nuovo cliente sia necessario inserire i dati in più di una tabella. Se fosse possibile fare un’INSERT diretta sulla tabella dei clienti potremmo dimenticarci di inserire le righe nelle altre tabelle (ad esempio in quella che categorizza il cliente appena inserito); negando l’accesso diretto alla tabelle e veicolando l’operazione attraverso una stored procedure questo problema viene risolto alla radice, indipendentemente dall’applicazione che ora o in futuro verrà utilizza per la gestione della clientela.

Gli ultimi due punti della lista sono importanti a pari merito. Le stored procedure permettono un riutilizzo del piano di esecuzione, e quindi ad ogni esecuzione SQL Server può evitare di doverlo ricalcolare tutte le volte. La gestione di questa cache e la sua invalidazione è completamente gestita da SQL Server e pertanto non dobbiamo preoccuparci di nulla. Dato che il piano di esecuzione sta ad una query come le indicazioni di rotta fornite da un navigatore satellitare stanno ad un percorso, è facilmente intuibile come più una query è complessa più il riutilizzo del suo piano di esecuzione è auspicabile. Se poi la query è anche molto utilizzata ovviamente questo è ancor più vero, in quanto il tempo e le risorse di sistema globalmente risparmiate saranno proporzionali al numero di esecuzioni della stessa.

Il fatto, inoltre, che una stored procedure possa contenere anche numerose righe di codice, ma che per esser richiamata necessiti solo di una manciata di byte (ossia il nome della procedura più i valori degli eventuali parametri) comporta un notevole risparmio delle risorse di rete, ancora una volta risparmio direttamente proporzionale alla quantità di codice contenuto nelle stored procedure ed alla frequenza di invocazione dello stesso.

Come dire che più il vostro database sarà utilizzato più risorse verranno risparmiate e, di riflesso, utilizzate meglio. In pratica nella stessa unità di tempo SQL Server sarà in grado di soddisfare più richieste. Il che si traduce in un’unica parola: efficienza.

Conclusioni

Se sentite la voglia di approfondire il discorso sui database in modo più preciso e rigoroso (in questo articolo non ho di proposito utilizzato nessun termine formale, in modo da rendere la lettura più agevole ed intuitiva possibile) i libri riportati di seguito potranno essere un ottimo inizio:

Database in Depth, C.J.Date, ISBN 0-596-10012-4

Practical Issues in Database Management, Fabian Pascal, ISBN 0-201-48555-9

Pro SQL Server 2005 Database Design & Optimization, Louis Davidson, ISBN 1-59059-529-7

Inside SQL Server 2005: T-SQL Programming, Itzik Ben-Gan, ISBN 0-7356-2197-7


Mostra:
© 2014 Microsoft. Tutti i diritti riservati.