Foglio stile dei testi in formato 19 e 5 x 26

M. Barbaro, G. Rorato
Client/Server
4
SQL (STRUCTURE
QUERY LANGUAGE)
Concetti fondamentali sulle
basi di dati
Base di dati
Raccolta di dati permanenti, gestiti da un elaboratore elettronico, suddivisi in Dati e Metadati le cui
caratteristiche sono garantite dal DBMS.
53
M. Barbaro, G. Rorato
Client/Server
Metadati
I metadati comprendono:
-
lo schema della base di dati (Database-Schema).
la definizione della struttura dei dati.
i vincoli d’integrità.
le relazioni tra gli insiemi di dati e le operazioni eseguibili sui dati.
Dati
I dati sono:
-
organizzati in insiemi omogenei.
permanenti.
protetti dagli accessi di utenti non autorizzati e dal mal funzionamento hardware e
softvare.
utilizzabili contemporaneamente da più utenti e per usi diversi.
accessibili solo mediante transazioni.
DBMS: DataBase Management System
È un sistema centralizzato o distribuito che consente di definire gli schemi della Base di Dati e di
scegliere le strutture dati per manipolare i dati stessi iterativamente o da programma, rispettando i
vincoli definiti dallo schema.
Il controllo della Base di Dati è gestito garantendo:
-
-
Integrità, cioè garanzia del rispetto dei vincoli d’integrità della Base di Dati.
Affidabilità, ovvero garanzia che la Base di Dati non contenga dati inconsistenti; è
ottenuta attraverso la gestione dei mal funzionamenti hardware e softvare e delle
esecuzioni concorrenti.
Sicurezza, cioè garantire che i dati siano accessibili solo da utenti autorizzati e nella
qualità/quantità prevista.
54
M. Barbaro, G. Rorato
Client/Server
Modello dei dati
È un insieme dei meccanismi d’astrazione associato ad un insieme predefinito di operatori e vincoli
d’integrità.
Tipi:
-
ad oggetti.
relazionale.
semantico dei dati.
entità relazionale.
reticolare.
gerarchico.
relazionale ad oggetti.
Tipi di linguaggio per le basi di dati
-
DDL (Data-Definition-Language) per la definizione dello schema.
DML (Data-Manipulation-Language) per accedere/manipolare i dati.
Query Language per recuperare/manipolare i dati iterativamente (ma non per HostLanguage).
Tupla
Ennupla di coppie (nome, valore).
Esempio: ((nome, “Mario”), (cognome, “Rossi”), …).
Schema
Ennupla di nomi (nome1, nome2, , nomeN).
Esempio: (nome, cognome, …).
Relazione
55
M. Barbaro, G. Rorato
Client/Server
Tabella con tante colonne (Record) quanti sono gli attributi delle ennuple e con tante righe (Campi)
quante sono le ennuple della relazione.
Tabella
Raggruppa ennuple con lo stesso schema.
Ci sono più tipi di tabelle:
-
Tabella Base o Tabella con 0 o più righe oltre allo schema.
Tabella Virtuale o View con 0 righe oltre allo schema (hanno solo lo schema!); sono entità
logiche (non esistono!) che sono ottenute attraverso altre tabelle.
Chiave
È un attributo o insieme di attributi (Primary-Key) che individuano in modo unico, all’interno di
una tabella, una tupla.
Indice
È una struttura dati che permette di ritrovare rapidamente, all’interno della tabella, una tupla a
partire dal suo valore in un particolare attributo (possiamo dire che è il modo con cui il
programmatore “comunica” alla macchina che un determinato attributo sarà spesso utilizzato per
risalire ad una tupla).
Lo standard ci dice come un indice deve essere dichiarato ma non come deve essere implementato.
Query
E' una forma in cui si fanno le interrogazioni al database; la risposta è sempre una tabella
(eventualmente vuota).
Transazione dal punto di vista del programmatore
È una sequenza di operazioni di lettura e scrittura dei dati eseguite “in gruppo” (come se fossero
un’unica operazione) che devono soddisfare le seguenti 4 regole, chiamate ACID da Gray e Routes
nel 1993:
56
M. Barbaro, G. Rorato
1.
2.
3.
4.
Client/Server
Atomicità: garantisce che solo le transazioni terminate correttamente (CommitedTransaction) modifichino la Base di Dati annullando (Rolled-Back-Transaction) gli
eventuali effetti delle altre (Aborted-Transaction); Riassumendo o vengono portate a
termine tutte le operazioni del gruppo della transazione o nessuna di esse.
Consistenza: l’esecuzione di una transazione non deve essere in contrasto con i vincoli
d’integrità della Base di Dati: se durante la transazione questi vengono violati la
transazione non deve essere conclusa.
Isolamento: l’esecuzione di più transazioni concorrenti apporta alla Base di Dati le stesse
modifiche di un’esecuzione seriale (in un qualche ordine) delle stesse: le transazioni
concorrenti non si devono (reciprocamente o meno) influenzare.
Durabilità o Persistenza: le modifiche apportate da una transazione conclusa con successo
devono essere permanenti ossia non alterabili da eventuali mal funzionamenti successivi.
Transazione dal punto di vista della macchina
È una sequenza di operazioni di lettura e scrittura che inizia e termina con le seguenti operazioni di
transazione (Transaction-Operation):
1.
2.
3.
Begin-Transaction: segnala l’inizio della transazione;
Commit-Transaction: segnala la normale terminazione della transazione richiedendo di
rendere permanenti i suoi effetti sulla Base di Dati;
Abort-Transaction: segnala la prematura/anomala terminazione della transazione
richiedendo di annullare le sue modifiche alla Base di Dati.
La situazione precedente si ottiene attraverso i Sync-Point che sono una sorta di puntatori alla
situazione antecedente l’inizio della transazione corrente; il comando Roll-Back invece ripristina la
situazione fino all’ultimo commit.
SQL
L’SQL (Structure Query Language) è un linguaggio dichiarativo per la dichiarazione e
l’interrogazione di una base di dati. È inteso come linguaggio di programmazione per scambio di
informazioni tra client e server. Di per se però l’SQL non ha nulla a che vedere con l’architettura
client/server. È un linguaggio dichiarativo e maschera completamente l’implementazione della base
di dati. Esso fa si che l’applicazione che vuole accedere alla base di dati utilizzi solo questo
linguaggio per interrogarla. Questo linguaggio è una buona interfaccia di comunicazione perché
prescinde dall’organizzazione effettiva dei dati. Si discuterà di SQL e di SQL nella rete anche se poi
sono la stessa cosa, solo che il secondo da per scontato che tra l’applicazione e la base di dati ci sia
di mezzo la rete. Questo linguaggio quindi può essere visto come linguaggio di comunicazione tra
client e server, quando il server è un DB-server.
57
M. Barbaro, G. Rorato
Client/Server
Inizialmente si voleva consentire a tutti l'accesso alle basi di dati e inoltre si voleva fornire una
versione grafica dell’SQL.. Alla fine l’SQL non ha raggiunto il primo di questi obiettivi perché per
utilizzarlo bene occorre una certa esperienza.
SQL è quindi un linguaggio dichiarativo basato sul Modello Relazionale (modello dei dati che si
basa sul concetto di relazione ennaria).
Il Modello Relazionale ha dato luogo a:
1.
2.
Modelli informativi: E-R model e Semantic Model;
Modello di dati: Database Relazionali (tuple,relazioni).
I comandi principali
Tipi
Non esiste uno standard sui tipi: ogni venditore di SQL può definirli a suo modo anche se,
fortunatamente, alcuni di questi venditori si sono uniti per formare degli pseudo-standard (esempio:
l’SAG (SQL-Access-Group) che definisce un insieme di tipi ma non ne definisce i range; SAA
(System Application Architecture) dell’IBM, non ci sono molte differenze tra i due standard, ma
comunque sono diversi per i domini dei tipi) che garantiscono al programmatore una certa
compatibilità tra le varie versioni.
Tabelle
Ricapitolando, una tupla è una ennupla di coppie le quali coppie sono attributi:
((nome, valore), …) = tupla
Il valore appartiene al dominio dell’attributo, cioè al tipo dell’attributo.
Uno schema è un’ennupla di nomi:
(nome1, …, nomen)
Esempio di tupla:
((nome, “Giovanni”), (cognome, “Rossi”), …)
Esempio di schema:
(nome, cognome, …)
Una relazione algebricamente è vista come un sottoinsieme del prodotto cartesiano del primo
insieme di tuple con il secondo insieme di tuple, si forma un nuovo insieme di tuple con lo stesso
schema. In definitiva, quindi, una relazione è un insieme di tuple con lo stesso schema.
In un database relazionale si rappresenta tutto con tabelle.
58
M. Barbaro, G. Rorato
Client/Server
Una tabella è costituita da un’intestazione dove ci va scritto lo schema (elenco dei nomi) e da un
insieme di tuple.
Una tabella rappresenta sia le tuple che le relazioni.
Ci sono tabelle base o tabelle, con 0 o più righe oltre allo schema, e ci sono anche le tabelle virtuali o view
che hanno 0 righe oltre allo schema, hanno solo lo schema. Le tabelle view servono per avere una
visione virtuale di una certà entità, non contengono dati.

PER CREARE UNA TABELLA SI USA IL SEGUENTE COMANDO:
CREATE TABLE < nome_tabella >
(
< nome_attribuito1 > < tipo1 >
…
< nome_attributon > < tipon >
)
Lo schema viene dato attraverso la definizione dei nomi.
In una tabella alcuni degli attributi possono essere considerati chiavi. Le chiavi sono quegli attributi
che permettono di individuare in maniera univoca una certa tupla. Si possono comporre anche
come unione di più attributi. Quindi una chiave primaria (primary key) è un insieme di attributi che
individua in maniera univoca una tupla.
Con il concetto di indice si va ad individuare in un database quali siano gli attributi che verranno più
richiesti in interrogazioni future. Per esempio un indice potrebbe essere “l’età” se spesso si
considera quest’ultima, anche se non è una chiave primaria. La semantica che sta dietro agli indici
non è standard.

UNA TABELLA SI PUÒ MODIFICARE NEL SEGUENTE MODO:
ALTER TABLE <nome_tabella>
Seguito da:
-
ADD <nome_attributo> <tipo_attributo>
per aggiungere un nuovo attributo allo schema;
DROP (oppure DELETE) <nome_attributo>
per eliminare un attributo dallo schema, si elimina una colonna;
MODIFY <nome_attributo> <nuovo_tipo>
per modificare il tipo di un attributo, modifico una colonna;
ADD e DROP sono comandi standard, mentre MODIFY è un comando non standard.
Dopo una modifica deve accadere che:
59
M. Barbaro, G. Rorato
-
Client/Server
I dati devono essere “riformattati” (esempio: aggiunto un attributo devo aggiungere quel
dato ad ogni tupla della tabella), bisogna prendere la tabella, esportare i dati, fare le
modifiche alla tabella, e reimportarli con le modifiche. Un’operazione cosi’ costa molto.
Si deve mantenere l’integrità (esempio: se una tabella dipende da un’altra si deve
controllare se la modifica ha alterato tale dipendenza), questa operazione costa di più
della precedente.
verificare se le applicazioni client hanno ancora motivo di esistere, perché le applicazioni
client (che accedono al database) possono non trovare più senso nei nuovi dati,
(esempio: se l’attributo “codice avviamento postale” viene modificato da 30010 ad
Italia30010 il client non saprebbe come interpretarlo; a tale scopo si usano le View che
permettono al client di vedere Italia30010 come 30010).
-
-

UNA TABELLA SI ELIMINA CON IL SEGUENTE COMANDO:
DROP TABLE <nome_tabella>
Operazioni di modifica dei dati sulle tabelle

COMANDO PER MODIFICARE I VALORI DI UNA TUPLA:
UPDATE <nome_tabella>
SET
nome = “Giovannino”
WHERE
AND
nome = “Giovanni”
cognome = “Rossi”;
Questo comando ha a che fare con una tupla, modifica i valori di questa tupla. In questo caso viene
messo al posto del nome “Giovanni” il nome “Giovannino”.

COMANDO PER ELIMINARE UNA TUPLA DA UNA TABELLA:
DELETE FROM <nome_tabella>
WHERE

<condizione>;
COMANDO PER INSERIRE I DATI:
INSERT INTO <nome_tabella> (nome1, nome2, …, nomen)
VALUES (valore1, valore2, …, valoren)
60
M. Barbaro, G. Rorato
Client/Server
Dopo l’istruzione INSERT è sempre meglio specificare il nome dei campi della tupla per
specificarne l’ordine. Dopo il comando VALUES vanno inseriti tutti i valori della tupla che
costituisce l’entità. Se non si specificano tutti i nomi dei campi (per esempio potrei metterne meno,
magari solo nome e cognome) gli altri assumono valore nullo (NULL), cioè assenza di valore.
Operazioni di interrogazioni sulle tabelle
Occorre notare che il risultato di un’interrogazione è sempre una tabella eventualmente vuota.

VIENE ORA ANALIZZATO IL COMANDO PER UN’INTERROGAZIONE:
SELECT <nome_attributo>
FROM <nome_tabella>
WHERE <condizione>;
Segue un esempio:
SELECT nome,età
FROM anagrafe;
Il nome della tabella è anagrafe, gli attributi utilizzati sono nome ed età, in questo modo si avrà la
restituzione di tutti i nomi e di tutte le età.
Si può aggiungere anche una condizione, per esempio:
WHERE cognome = “Rossi”
Cosi’ vengono restituiti tutti i nomi e le età corrispondenti al cognome “Rossi”.
Per avere un ordinamento si può aggiungere anche il comando:
ORDER_BY età
Cosi’ si avrebbero le tuple ordinate per età.
Segue un altro esempio:
SELECT nome,SUM(età)
FROM anagrafe
WHERE cognome = “Rossi”
GROUP BY nome
Dopo il comando WHERE, naturalmente c’è la condizione sulle tuple, e con il comando
successivo, cioè con il comando GROUP BY, ragruppo tutte le tuple per nome, per esempio tutti
quelli che si chiamano “Giovanni” da una parte, e cosi’ via ….
Si può aggiungere anche una condizione sul gruppo:
61
M. Barbaro, G. Rorato
Client/Server
HAVING SUM(età) < 100
In questo modo viene fatta la somma delle età presenti in un gruppo, se è < 100 allora la SELECT
ritorna il gruppo altrimenti no.
Gli standard SQL
Il linguaggio SQL è stato presentato in una versione originaria agli inizi degli anni ’80, dopodiché è
stato sottoposto a varie standardizzazioni da parte dei comitati X/OPEN, ANSI e ISO creando
dapprima uno standard SQL-’84 per poi arrivare, passando tramite un paio di versioni successive,
allo standard SQL3 per trattare basi di dati ad oggetti.
In questo paragrafo verranno dapprima brevemente trattati gli standard principali che si sono
susseguiti nei vari anni e le loro relative caratteristiche, dopodiché verrà discusso come è possibile
inserire l’uso di SQL all’interno dei linguaggi di programmazione, con le cosiddette API SQL, e
infine verranno discussi alcuni aspetti dell’utilizzo di SQL in rete.
Si sono susseguite nel corso degli anni dopo la prima versione varie versioni di SQL che si
differenziano principalmente a seconda dei vari livelli o COMPLIANCE level, che rappresentano
vari requisiti a cui ogni venditore può scegliere di aderire:
SQL '89
Rivisto principalmente per introdurre il vincolo d’integrità referenziale, è uno standard a cui ormai
aderiscono tutti i venditori. Ecco i 2 livelli che offre:
1° livello


2° livello

in cui vengono definiti:
il DDL (Data-Definition-Language) e degli standard per la definizione di create, alter, drop,
primary-key, foreign-key; in particolare gli ultimi due sono esempi di vincoli d’integrità già visti
nel capitolo introduttivo all’SQL..
il DML (Data-Manipulation-Language) con la definizione di uno standard per select,
update, delete e quindi in generale di comandi per eseguire aggiornamenti di dati.
in cui viene definito, oltre a quanto presente nel livello precedente,
il DCL (Data-Control-Language) con la definizione di commit, grant, revoke ossia in
generale meccanismi di autorizzazione per controllare in modo flessibile le modalità
d’accesso da parte degli utenti alla base di dati.
62
M. Barbaro, G. Rorato
Client/Server
SQL '92
E' uno standard utilizzato dai venditori principalmente al livello DML; esso prevede inoltre tre
diversi livelli di linguaggio e chi aderisce allo standard deve fornire almeno uno dei modi di usare
SQL cioè API, SQL dinamico, ecc., che saranno analizzati nei paragrafi successivi:
1° livello (Entry)
2°livello (Intermediate)


gli operatori di UNION JOIN, INTERSECT, ecc., esiste supporto per l’SQL dinamico,
livelli di isolamento per le transazioni, CAST per datatype, CASCADE DELETE per non
violare i vincoli d’integrità quando si verificano operazioni di cancellazione su di una
tabella.
viene introdotto il Flagger che è un procedimento automatico che consente di evidenziare
in qualsiasi punto, con delle Flag, un comando che si allontana dallo standard previsto
da questo livello o comunque da quello del fornitore del database.
3° livello (Full)





contiene tutte le caratteristiche di SQL’89, interfacce per l'uso di
Embedded SQL (descritto più avanti nel capitolo) all'interno dei
linguaggi Ada e C, la variabile di stato SQLSTATE di cui si accennerà
nel paragrafo relativo all'SQL3.
in cui vengono trattati:
in cui vengono introdotti:
Scroll-Cursor, CASCADE UPDATE per non violare i vincoli d’integrità quando si
verificano operazioni di inserimento in una tabella, ecc.
Da notare c’è il fatto importante che se si lavora a livello di rete occorre utilizzare
SQL’92 dato che questa è la prima versione in cui si introducono i seguenti concetti
chiave:
SQL-Agent, a cui si sottopongono i comandi SQL che poi esso può invocare.
SQL-Connection, che si occupa di tutto ciò che riguarda la nascita della nuova sessione
e di tutto ciò che si verifica tra l’agente e il database.
SQL-Multiple-Connection, che consente a più agenti di accedere contemporaneamente
allo stesso database o ad un database di aprire più connessioni con un altro database.
Esiste inoltre un particolare comando, “SET CONNECTION <nome_Database>”, che
permette di commutare agevolmente tra una connessione e un'altra e di decidere dove
fare una determinata operazione.
Codici di Ritorno o SQLSTATE, che indicano lo stato del comando invocato. Per
esempio 5 caratteri tipo “_ _ _ _ _” e GET DIAGNOSTIC che restituisce una
descrizione particolareggiata della situazione.
63
M. Barbaro, G. Rorato
Client/Server
SQL3
Finora poco utilizzato, racchiude quasi tutto ciò che c’è in commercio ed è stato pensato per
trattare basi di dati ad oggetti. Definisce, tra le altre cose, concetti che verranno spiegati all'interno
del capitolo, cioè come introdurre in modo dinamico un’interfaccia di comandi SQL per accedere
ad un database (es. ODBC), le stored procedure e come un linguaggio di programmazione generico
come C, C++, COBOL, possa collegare le sue variabili alle variabili esterne di un database.
API per SQL
Uno degli obiettivi principali di SQL è sempre stato, fin dall’inizio, che fosse possibile utilizzarlo
direttamente da utenti occasionali senza ricorrere a programmi specifici sviluppati da esperti. Per
questo motivo è necessario poter usufruire di opportuni linguaggi di programmazione che
consentano sia l’accesso ad una base di dati che la realizzazione di interfacce, anche grafiche, per
permettere il dialogo con gli utenti; inoltre è utlie anche poter scrivere applicazioni che raccolgano
risultati di operazioni statistiche, di ottimizzazione, ecc. In questo paragrafo verrà analizzata una
delle soluzioni adottate per risolvere questi problemi, ovvero quella di fornire i linguaggi di
programmazione tradizionale con una libreria opportuna detta anche API (Application-ProgramInterface).
Tutte le modalità di interazione tra SQL e i linguaggi di programmazione, esistono perché SQL non
è un linguaggio procedurale e quindi le API, che possono essere usate sia per accesso a database locali
che a database in rete evitando di usare le chiamate ad RPC dirette, sorgono per permettere di
fornire ai linguaggi di programmazione delle interfacce per invocare direttamente i comandi SQL
necessari. Le maggiori differenze che esistono tra i vari tipi di API SQL dipendono soprattutto dai
differenti livelli di astrazione forniti da questi differenti metodi di invocare l’SQL.
Un altro tipo di problema che insorge con le API SQL è a causa della differenza di sintassi tra i comandi
SQL e gli altri comandi dei linguaggi di programmazione. Per gestire ciò si fa riferimento a vari binding tra
i linguaggi, facendo ovviamente attenzione al fatto che un valore modificato da un linguaggio di
programmazione potrebbe essere troncato in un database SQL e viceversa (SQLSTATE tiene
comunque informati sugli errori).
Ancora, SQL e la maggior parte dei linguaggi di programmazione differiscono nella gestione dei
dati in quanto i database relazionali sono abituati a lavorare su più tabelle simultaneamente a
differenza dei linguaggi di programmazione che trattano singole variabili. Per ovviare a questo
impedence mismatch bisogna fornire le applicazioni di cursori che possano manipolare righe di dati
marcando la posizione corrente del programma nel database ad ogni momento.
Per finire, ogni errore segnalato in un’operazione SQL deve essere trattato a dovere segnalandolo
con un opportuno codice (ad es. numerico) o un’opportuna area apposita predefinita; anche questo
compito di gestione degli errori fa sempre parte dei compiti del programmatore dell’applicazione.
E' possibile, per finire, schematizzare cosa il database tipicamente esegue (tramite static o dinamic
binding, indifferentemente), quando un comando SQL viene invocato:
1.
2.
3.
Lo scompone in nomi, valori e operazioni attraverso un meccanismo chiamato Parsing.
Le entità risultato del Parsing vengono sottoposte ad un controllo di validità.
Vengono verificati i diritti d’esecuzione.
64
M. Barbaro, G. Rorato
4.
5.
Client/Server
Si sequenzializzano le operazioni coinvolte nel comando (definizione del piano
d’accesso).
Si collegano i parametri di input ed output (collegamento I/O).
Embedded SQL
E’ il tipo più comune di API SQL. Il programmatore semplicemente include comandi SQL
all’interno di altri linguaggi di programmazione (es.: COBOL, C, ecc.). Per far ciò viene usato un
preprocessore, fornito dal venditore del database, che è usato per controllare il codice prima della
compilazione e convertire le chiamate dell’embedded SQL in chiamate a librerie di database. Fatto
ciò il programma può essere compilato e sottoposto al linker normalmente.
Verranno analizzate ora alcune questioni che possono sorgere per capire come avviene la gestione
dell'embedded SQL.
In particolare ecco indicati i passi principali da seguire per inglobare i comandi SQL nel linguaggio
ospite:
1.
2.
Si scrive normalmente il codice nel linguaggio ospite marcando opportunamente le linee
dei comandi SQL.
Si precompila il codice in modo da “trasformare” i comandi SQL in chiamate a
procedure contenute nella Libreria SQL.
Segue un esempio (Listato 1) su come marcare i comandi SQL all'interno di un linguaggio.
Listato 1
/*si presuppone che prima di questa riga ci sia del codice scritto in C*/
EXEC SQL <riga_comando_SQL>
/*la riga precedente serve ad indicare il comando SQL che si vuole utilizzare*/
EXEC SQL BEGIN DECLARE SECTION
/*la riga precedente serve ad indicare l'inizio della sezione dedicata all'SQL;
segue ora una definizione di alcune variabili C che serviranno per lo scambio di
variabili tra C ed SQL.*/
int a;
int b;
char nome[20];
EXEC SQL END DECLARE SECTION
/*fine della sezione SQL*/
Da notare che in molte implementazioni SQL, come per esempio ORACLE, è definita un'area, la
SQLCA (SQL Communications Area) in cui vengono gestite informazioni, errori e altro; esistono, ad
esempio, delle variabili e dei comandi con particolari funzioni:
65
M. Barbaro, G. Rorato



Client/Server
SQLSTATE è la variabile che gestisce la situazione sugli errori.
SQLCODE è la variabile che contiene il codice dell’ultimo errore.
EXEC SQL WHENEVER SQLERROR GOTO <numero_linea>; Whenever introdotto da
SQL-92 ha il compito di gestire SQLSTATE e SQLCODE per le condizioni di
eccezione.
Con quest’ultimo comando viene imposta l’esecuzione di un determinato gestore in caso di errore;
il gestore indicato tratterà tutti gli errori generati dalle righe di codice che vanno dalla prima riga
dopo il suddetto comando sino all’ultima riga prima di un altro comando del genere.
Per poter legare l’applicazione al database occorre prima effettuare su di questo un’operazione di log
utilizzando il comando CONNECT che a sua volta esteso con il comando SET CONNECTION,
permette di avere più sessioni contemporaneamente e di commutare da una all’altra (Listato 2)
Listato 2
EXEC SQL CONNECT TO <nome_Database> AS <nome_Database>
/*la riga precedente indica il comando tramite cui connettersi a runtime con un
database*/
USER <user>
WITH <PASSWORD.password>
/*le due righe precedenti indicano come fare un'operazione di login sul database
appena aperto*/
//la riga seguente indica invece come spostarsi su di un’altra connessione:
EXEC SQL SET CONNECTION <nome_Database>
//per disconnettersi al termine si usa il comando DISCONNECT
Se si vuole far gestire al linguaggio ospite delle tabelle lo si può fare attraverso un riferimento ad
una struttura dati usata per gestire le copie delle ennuple del risultato; questo riferimento viene
chiamato cursore (Listato 3). All'interno del medesimo listato seguono i comandi per aprire (OPEN),
usare (FETCH) e chiudere (CLOSE) il cursore all'interno di un ipotetico ciclo di esecuzione, per
memorizzare i risultati di estrazione del comando SQL all'interno delle variabili di interscambio
Cognome e Nome. In particolare, la sintassi da adottare per utilizzare queste ultime variabili è
quella di precedere il nome della variabile con il carattere ":" come si può vedere nell'esempio
relativo.
Listato 3
/* segue una prima parte con la dichiarazione delle variabili di “interscambio”
dati delimitata dai comandi di BEGIN e END */
EXEC SQL DECLARE SECTION BEGIN
int eta;
char nome20;
char cognome 20;
EXEC SQL DECLARE SECTION END;
66
M. Barbaro, G. Rorato
Client/Server
/*segue ora la dichiarazione del cursore "CursorePersona" per fare un'operazione
di SELECT da un'anagrafe, tramite cui ricavare nome e cognome di tutte le
persone con cognome Rossi*/
EXEC SQL DECLARE CursorePersona CURSORE FOR
SELECT COGNOME, NOME FROM ANAGRAFE
WHERE COGNOME = “Rossi”;
/*Comandi sui cursori*/
EXEC SQL OPEN CursorePersona
WHILE (…) {
EXEC SQL FETCH CursorePersona INTO :Cognome :Nome
…
}
EXEC SQL CLOSE Cursore Persona;
/*il comando FETCH permette di accedere ripetutamente ad un cursore*/
EXEC SQL FETCH <Nome_Cursore> :INTO <variabile>;
Esistono inoltre dei comandi che permettono al cursore di spostarsi in modo diverso sulle righe di
una tabella, o più semplicemente in un modo non-sequenziale come spesso potrebbe capitare durante
un’applicazione:
EXEC SQL DECLARE <Nome_Cursore> SCROLL CURSOR FOR <opzione>
In particolare il campo opzione può assumere uno tra i valori NEXT, PRIOR, END, BEGIN.
Tra i vantaggi riscontrabili con questo metodo si può notare che si utilizza pura sintassi SQL;
d’altro canto, come svantaggi si può dire che il precompilatore SQL può entrare in conflitto con gli
eventuali altri precompilatori del linguaggio ospitante e, in particolare, se poi si accede a più
database si può avere a che fare con più API e quindi più precompilatori che a loro volta
potrebbero avere una sintassi molto simile; inoltre il passaggio di dati tra SQL e il linguaggio
ospitante potrebbe troncare dei valori.
Module Language
Alcuni database (come PL/SQL di Oracle) forniscono quest’altro tipo di API che permettono a
moduli SQL scritti da un utente con la sintassi usata dal venditore del database e memorizzati di
solito in file appositi e separati, di essere invocati da un’applicazione, come fossero semplici
procedure. Inoltre è compito del programmatore tenere aggiornati e consistenti questi moduli con il
resto del programma se appunto l’applicazione che li invoca cambia.
Nei moduli sono presenti dichiarazioni di procedure (C-like) in cui sono specificati:





Nome del modulo.
Dichiarazioni.
Il linguaggio che richiederà il modulo.
Schema che verrà utilizzato per il DateBase.
Procedure e relativi parametri (tra cui c’è sempre presente l’SQLSTATE).
67
M. Barbaro, G. Rorato

Client/Server
Cursori (con le relative procedure per aprirli, usarli e chiuderli).
Tra i vantaggi di questo metodo va considerato il fatto che si tratta di programmazione in SQL
puro mentre, d’altro canto, alcune difficoltà possono capitare perché occorre sincronizzare nel
codice le invocazioni con i moduli.
Esempio di definizione di un modulo da utilizzare in un programma C:
MODULE Inventory_access
NAMES ARE "Latin-1"
LANGUAGE C
SCHEMA Inventory
/* segue la definizione di una procedura da utilizzare per aggiornare i campi
(nome, numero, ecc.) di un ipotetico inventario. Da notare la consueta notazione
per indicare le variabili cioè ":"nome_variabile */
PROCEDURE add_part
(SQLSTATE,
:number
INTEGER,
:name
VARCHAR(10),
:count
INTEGER,
:restocked
DATE);
INSERT INTO Inventory
VALUES (:number, :name, :count, :restocked)
/* segue la definizione di un cursore per fare un operazione di SELECT
all'interno dell'inventario e estrarre dei campi in base al valore assunto da
part-number*/
DECLARE CURSOR found_part FOR
SELECT *
FROM Inventory
WHERE part-number = :number;
...
Ecco come fare per accedere a questo modulo da un programma scritto in C:
/* Seguono alcune dichiarazioni per introdurre le variabili corrispondenti a
quelle del modulo */
char
char
long
char
long
DATE
SQLSTATE[6];
OK_STATE[6] = "00000";
part_number;
part_name[10];
inventory_count;
date_restocked;
/* Segue del codice per utilizzare la procedura add_part */
...Codice C per inizializzare le variabili introdotte poc'anzi...
ADD_PART (SQLSTATE,
part_number,
part_name,
inventory_count,
date_estocked);
if(SQLSTATE != OK_STATE)
/* la riga predecedente si riferisce a del codice per la gestione di eventuali
errori, ricordando la funzione della variabile SQLSTATE */
68
M. Barbaro, G. Rorato
Client/Server
SQL dinamico
Il modo di invocare i comandi SQL tramite i vari tipi di API può essere sia statico che dinamico. I
comandi SQL statici sono descritti pienamente quando viene definito il programma e non possono
cambiare a run-time. Il vantaggio dell’SQL statico è che il database può potenzialmente determinare
una strategia di ricerca ottimale per le query (cioè un piano d’accesso) al momento della
compilazione.
D’altro canto i comandi SQL dinamici sono costruiti dal programma mentre esso gira utilizzando
speciali comandi SQL. Se si vogliono ottimizzare le query dinamiche SQL lo si può fare a run-time
tramite il motore del database. Tutto ciò introduce un overhead aggiuntivo, dovuto per esempio al
parsing della linea di comando, ma fornisce una grande flessibilità nel tipo di programmi che
possono essere scritti con le API.
L’SQL dinamico può essere usato sia con le API di tipo embedded che con quelle tipo di Module
Language.
L'inserimento dei comandi
L'SQL dinamico può essere sia preparato e eseguito in un solo passo, tramite EXECUTE
IMMEDIATE, che preparato per l'esecuzione, con PREPARE, e poi invocato tramite INVOKED
tante volte a seconda di quante volte è richiesto. L'unica differenza di base tra i due metodi è che
l'ultimo preserva i risultati della preparazione allocando delle risorse di sistema per un eventuale uso
futuro. Queste risorse sono allocate all'interno di una SQL descriptor Area (SQLDA) che può essere
allocata esplicitamente tramite il comando ALLOCATE, utilizzata per gli interscambi tra SQL e il
linguaggio ospitante con DESCRIBE e deallocata tramite DEALLOCATE; la SQLDA contiene
una grande varietà di informazioni sui descrittori, tra cui:





Type.
Length (nel caso in cui il tipo in questione sia “composto” come ad esempio, nel caso di
un array).
Name (nome della colonna).
Data.
SCALE (Numero di scala in una notazione numerica o decimale).
Segue ora un esempio (Listato 4) di preparazione di un comando e di un descrittore.
Listato 4
Si vuole inserire una query di questo tipo:
SELECT * FROM ANAGRAFE
WHERE COGNOME = ?
In particolare il carattere “?” sta ad indicare che la variabile cognome verrà
definita a run-time.
69
M. Barbaro, G. Rorato
Client/Server
Per inserire tale query verranno utilizzati i comandi ALLOCATE, DESCRIBE e
DISALLOCATE tramite i quali è possibile prelevare il tipo del descrittore a runtime, che verrà poi usato per completare la query.
/* Segue ora del codice scritto in C.
Nelle prime righe viene copiata la query nella stringa dyn_sql*/
char dyn_sql 50;
short dyn-type;
strcpy (dyn-sql, “select nome from anagrafe where cognome = ?”);
/* Nelle prossime due righe viene definito un descrittore per lo scambio di 5
parametri:*/
EXEC SQL ALLOCATE DESCRIPTOR
‘q_desc’ WITH MAX 5;
/* Nelle righe successive si provvede a preparare il piano d'interrogazione
prelevando la query dalla variabile dyn_sql */
EXEC SQL PREPARE interrogazione
FROM : dyn_sql;
/* Viene ora indicato che si vuole utilizzare il descrittore di interscambio
definito poche righe fa, cioè q_desc */
EXEC SQL DESCRIBE INPUT
USING SQL DESCRIPTOR ’q_desc’;
/* Nelle prossime righe viene indicato al programma di indicizzare il primo
campo del descrittore di lunghezza 5 q_desc, per ottenere il tipo del marker e
memorizzarlo nella variabile di interscambio dyn_type. Il programma può quindi
successivamente usare il codice numerico contenuto in questa variabile per
sapere il tipo della variabile dinamica */
EXEC SQL GET
DESCRIPTOR ‘q_desc’ VALUE 1 :dyn-type = TYPE;
L'SQL dinamico permette anche di cambiare gli attributi di un parametro dinamico e questo tipo di
operazione va sotto il nome di cast. Un programma C può così usare il comando SET
DESCRIPTOR per forzare il database a cambiare un valore decimale in un valore intero come
nell'esempio che segue:
EXEC SQL SET DESCRIPTOR 'desc' TYPE = 4
In modo analogo, se si vuole inserire una data del tipo data = "gg/mm/aaaa" , sarà possibile
convertirla automaticamente nel formato che il database prevede per le date.
Una volta che un comando SQL è stato preparato e i parametri dinamici sono stati assegnati
correttamente, viene fatto partire un comando EXECUTE che inizia a processare il database. Nel
caso il comando non abbia parametri dinamici è possibile eseguire il comando SQL
immediatamente, una sola volta (con tale comando non c’è la memorizzazione del piano d’accesso):
EXEC SQL EXECUTE IMMEDIATE: cmd
char cmd =”select * from...”
70
M. Barbaro, G. Rorato
Client/Server
Quando si utilizzano invece i parametri dinamici il comando EXECUTE deve poter indicare al
database dove essi sono; questo può essere fatto indicando dei descrittori che puntano a locazioni
all'interno della SQLDA o usando delle variabili del linguaggio. Lo stesso deve essere fatto se il
comando SQL dinamico restituisce dei risultati, ad esempio dopo un comando di FETCH. Il
vantaggio dell'utilizzo dei descrittori consiste nel fatto che il programma può così controllare i tipi
dei dati e scegliere se effettuare delle operazioni di cast o sollevare degli errori nel caso che i risultati
non siano consistenti con quanto ci si aspettava. Segue un esempio di utilizzo di descrittori in un
comando EXECUTE:
EXEC SQL EXECUTE :dyn_sql
USING SQL DESCRIPTOR 'parameter1', 'parameter2'
Into SQL Descriptor 'result1', 'result2', 'result3'
Nel caso il risultato della query sia una tabella l'SQL dinamico permette anche di introdurre i
cursori la cui specifica può essere ritardata fino ad un comando di tipo PREPARE
Esempio:
/* Segue la preparazione del cursore "cursore" */
EXEC SQL DECLARE cursore CURSOR FOR query;
... codice C ...
strcpy(dyn_sql, "SELECT * FROM Inventario WHERE articolo = ?");
EXEC SQL PREPARE query FROM dyn_sql;
... codice C ...
/* la riga successiva indica l'apertura del cursore */
EXEC SQL OPEN cursore USING SQL DESCRIPTOR ‘descrittore0’;
/* l'operazione di fetch che segue permette di prelevare i dati risultanti dalla
query riga per riga */
EXEC SQL FETCH cursore INTO SQL DESCRIPTOR ‘descrittore1’;
E’ infine possibile notare che in quest’ultimo esempio, rispetto a prima, è possibile ottenere più
righe di una tabella iterando più volte l’ultimo comando di fetch; nei casi precedenti infatti, se si
ripeteva più volte lo stesso comando non si faceva altro che ottenere nel descrittore di output
sempre la prima riga risultante dal comando. In questo modo è possibile abbinare i due metodi,
basta infatti osservare l’utilità del primo metodo in casi in cui, per esempio, si voglia ottenere il count
delle righe di una tabella; così facendo, una volta ottenuto questo risultato, sarà possibile invece
usare il secondo metodo per iterare su tutte le righe della tabella.
CLI
E’ un altro tipo di libreria API che può essere invocata direttamente dalle applicazioni con lo stesso
tipo di chiamate che farebbe un precompilatore con l’embedded SQL.
Viene utilizzata perché ci possono essere dei casi in cui un precompilatore non può proprio essere
usato; infatti può accadere, ad esempio come si diceva in precedenza, che esso vada in conflitto con
altri tool di sviluppo che richiedono il loro proprio precompilatore oppure che, ancora, chiamando
71
M. Barbaro, G. Rorato
Client/Server
differenti database server dallo stesso client il precompilatore per un database vada in collisione con
uno di quelli usati per un altro database.
CLI è comunque un’interfaccia che risiede ad un livello più basso delle API viste finora e quindi
certi aspetti tipici dei linguaggi ad alto livello sono assenti e vanno gestiti esplicitamente dal
programmatore (per esempio fare un’operazione di fetch di ogni riga di dati in un’area di memoria
dopo una query).
Tutto sommato, anche se ogni venditore di database potrebbe avere una sua differente CLI, questo
tipo di API sta lentamente raggiungendo la standardizzazione: un esempio di standard CLI si trova
nel SAG (SQL-Access-Group) X/Open il quale prevede 23 funzioni tra le quali quelle che si
occupano delle connessioni (apertura e chiusura), dell’esecuzione dei comandi, del commit e del
rollback, comunque nulla riguardo ai cursori.
Ecco un Esempio di CLI:
db_p = dbopen(login_info, “banking_data”);
/* la riga precedente permette l'accesso ad un ipotetico server di una banca.
Questo database viene aperto con la chiamata alla procedura dbopen passandogli
delle informazioni come la login e il tipo di accesso*/
/* segue un utilizzo del database tramite la definizione di un'operazione di
SELECT per estrapolare il nome di una persona il cui conto sia in rosso. */
dbuse(dp_p, “customer_accounts”);
dbcmd(db_p, “SELECT name FROM Account”);
dbcmd(dp_p, “WHERE Balance <0”);
/* definita l'operazione di estrazione è finalmente possibile mandare in
esecuzione il comando SQL tramite la chiamata alla procedura dbsqlexec() e
passandogli come parametro la variabile in cui è stato definito il comando cioè
dp_p */
dbsqlexec(dp_p);,
Graficamente nella Figura 31
Figura 31
Schema su come avviene
l'utilizzo delle CLI all'interno
di un linguaggio di
programmazione. Sono
rappresentati I vari passi fino
alla creazione di una libreria
SQL.
72
M. Barbaro, G. Rorato
Client/Server
CLI permette quindi un binding diretto con le librerie SQL mentre risente ancora della mancanza
di uno standard più maturo e completo, dei pochi tentativi da parte dei venditori di implementare
uno standard, dell’interfaccia a basso livello verso il sistema SQL.
ODBC
Microsoft ha esteso lo standard CLI X/OPEN creando le API ODBC (Open Database
Connectivity).
Più in generale, l’API ODBC è attualmente il più diffuso standard per l’utilizzo di DBMS
relazionali. ODBC specifica un DDL ed un DML relazionali basati sullo standard CLI SQL
proposto dal comitato SAG.
Uno strumento che implementa l’API ODBC è composto principalmente da un insieme di driver
ODBC chiamati anche gateway; un gateway traduce le chiamate ODBC in chiamate ad uno specifico
sistema e gliele invia, appoggiandosi ad un sistema di comunicazione (Figura 32).
Figura 32
ODBC e il suo utilizzo come
standard CLI per interagire con
I DBMS relazionali.
Per quanto riguarda ODBC sono state definite finora oltre 81 funzioni all’interno dei 3 seguenti
livelli di conformità:
•
•
•
Core API, equivalente allo standard X/OPEN.
1°livello API che include il Core e aggiunge supporto per impostare e leggere comandi e
opzioni di connessione, spedire o ricevere parte di un valore di un parametro,
informazioni statistiche, di catalogo (informazioni su tabelle, chiavi private, indici,...), su
colonne speciali.
2°livello API che include il primo livello e aggiunge supporto per gestire informazioni
sulle connessioni, elencare le risorse disponibili, spedire e ricevere vettori di valori di
73
M. Barbaro, G. Rorato
Client/Server
parametri, descrivere cursori di tipo scrollable, eseguire il “count” delle righe di una
query SQL e invocare DLL (dynamically linked library) per eseguire conversioni di dati.
La Microsoft fornisce anche un Window-Based SDK for ODBC, ovvero uno strumento che permette
di creare facilmente driver ODBC per nuovi database; esso è basato sulla cosiddetta SPI (ServiceProvider-Interface) che è un'interfaccia che può essere usata per la traduzione da ODBC a CLI
standard.
Nella Figura 33 è rappresentato un esempio.
Figura 33
Schema di un applicazione che
utilizza ODBC. Da notare
l'utilizzo del livello SPI che
funziona da filtro sulle
chiamate.
Ecco ora i passi principali da seguire nello scrivere un’applicazione ODBC :
1.
Connessione alla sorgente dati.
2.
Preparazione ed esecuzione di uno o più comandi SQL:





3.
4.
5.
impostazione della stringa di comando e dei parametri.
impostazione dell’area per i risultati e dei cursori.
invio del comando per la sua esecuzione.
controllo ed eventuale gestione degli errori.
descrizione dei risultati e fetch.
Commit o Rollback.
se ci sono altre transazioni ritornare al punto 2;
Disconnessione dalla sorgente dati.
Graficamente nella Figura 34.
74
M. Barbaro, G. Rorato
Client/Server
Figura 34
I passi da seguire per scrivere
un'applicazione che utilizzi
ODBC. I vari comandi sono
spiegati successivamente.
Ecco un'analisi dei comandi relativi alla Figura 34:
CONNECT
•
“SQLAllocHandle” inizializza l’interfaccia ODBC.
•
“SQLAllocHandle(SQL_Handle_ENV, SQL_NULL_Handle, &henv)” associa un
Environment-Handle all’area di memoria dell’ambiente ODBC.
•
“SQLAllocHandle(SQL_Handle_DBC, henv, &hconn)” associa un Connection-Handle appartenente
all’ambiente ODBC all’area addetta alla gestione di una connessione.
•
“SQLGetInfo” recupera gli attributi di un driver e di una sorgente dati come filename, versione, livello di
conformità, ecc.
•
“SQLConnect(hconn, source, uid, pwd)” attiva una connessione.
•
“SQLSetStmtAttr” abilita l’esecuzione asincrona di un comando; può essere usato per ottenere più righe
in una volta, locking su ResultSet, per fissare LibroMark in Result-Set;
EXECUTE
•
“Repeatable” / “Nonrepeatable” come nell’Embedded SQL Dinamico.
•
“SQLPrepare” / “SQLExecDirect” possono essere eseguite in modo asincrono: durante l’esecuzione di
un comando l’applicazione può usare un’altra connessione o preparare ed eseguire altri comandi.
•
“SQLBindParameter” specifica per i marker (?) alcuni particolari come, ad esempio, dove memorizzarli,
di che tipo sono, che precisione hanno,... Da notare altri supporti alla gestione dei parametri che sono
“SQLParamData” / “SQLPutData” che concatenano una lista di parametri, “SQLParamOptions”
per i comandi con più parametri opzionali.
•
“SQLExecute” attraverso un puntatore all’Access-Plan (da notare che la sua memorizzazione può essere
pesante).
PROCESS
•
“SQLNumResultCols” / “SQLColAttribute” / “SQLDescribeCol” per conoscere la struttura del
Result-Set.
•
“SQLBindCol” collega una locazione con un risultato del Result-Set: Row-Wise / Column-Wise.
•
“SQLFetch” muove il cursore alla prossima riga del Result-Set; si può usare la SQLFetchScroll con i
comandi next, prior, first, last per spostarsi tra le righe.
•
“SQLRowCount” se non è presente una select restituisce il numero di righe influenzate dal comando.
75
M. Barbaro, G. Rorato
Client/Server
COMMIT
•
Esiste anche la modalità a COMMIT automatico (SQLSetConnectAttr) e in questo caso per ogni
comando, “SQLEndTran(connessione, commit/rollback)” che gestisce la semantica della transazione, è
implicita..
DISCONNECT
•
“SQLFreeHandle” rilascia la connessione.
•
"SQLDisconnect(hconn)".
JDBC
Introdotto in aggiunta a RMI assieme a Java V1.1, JDBC può essere definito come la versione
JAVA di ODBC; esso permette, grazie alla portabilità dei programmi Java, di poter sviluppare
applicazioni che sono indipendenti sia dal tipo di sistema che gestisce la base di dati, che dal tipo di
piattaforma dove viene eseguita l’applicazione stessa.
Ecco qui i 7 passi di base da compiere per interrogare un database:
1.
Caricare il driver JDBC; un driver JDBC è quella parte di codice in grado di "dialogare"
con il server del database. Ecco ora un esempio di comando tramite cui eseguire il
caricamento del driver; da notare che il comando può generare un'eccezione, da gestire
opportunamente, nel caso la classe indicata non esista:
2.
Definire il tipo di URL della connessione, l’host, il numero di porta ed il database. Nel
caso si usi JDBC da un'applet bisogna considerare alcune restrizioni di sicurezza; infatti
le applet possono fare connessioni di rete solo con il server da cui sono state caricate.
Le connessioni al database sono del tipo:
3.
Stabilire la connessione. Per far ciò si usa una combinazione di dati come URL, nome
dell'utente e password utilizzando la classe DriverManager nel seguente modo:
Class.forName("nome di una classe")
"jdbc:connectionType://host:port/database"
String user = "nomeutente", password = "password";
Connection c = DriverManager.getConnection(url, user, password);
4.
Creare un oggetto relativo al comando. L'oggetto può essere creato a partire dalla classe
Connection nel seguente modo:
Statement statement = connection.createStatement();
5.
Eseguire un comando di tipo interrogazione (SELECT) o di aggiornamento (INSERT).
Una volta che si possiede l'oggetto creato al punto precedente, lo si può utilizzare per
fare delle query SQL; in particolare, tramite alcuni metodi (come execute.Update) è
anche possibile fare delle modifiche al database aggiungendo delle stringhe a seconda dei
casi (UPDATE, INSERT, DELETE). Segue ora un esempio:
76
M. Barbaro, G. Rorato
Client/Server
String query = "SELECT col1, col2, col3 FROM table";
ResultSet results = statement.executeQuery(query);
6.
7.
Calcolare i risultati. Il modo più semplice per calcolarli è farlo una riga alla volta,
utilizzando il metodo next della classe ResultSet per spostarsi alla riga successiva. La
classe stessa fornisce poi vari metodi, per esempio getInt, una volta all'interno della
riga, per restituire dei risultati differenti a seconda dei tipi Java.
Chiudere la connessione. Questo è un passo che si può saltare se si continua a
processare o se si esce dall'applicazione; comunque ecco il comando per chiudere la
connessione esplicitamente:
connection.close();
SQL in rete
In una interazione client/server all’interno di un database ogni codifica di un comando SQL, il tipo
di RPC e di protocolli di rete utilizzati, il modo di formattare record multipli in pacchetti di
risposta, le informazioni diagnostiche, ecc., rientrano a far parte del cosiddetto format and protocol
(FAP) del particolare database nella rete. A dispetto dei vari standard per le API SQL, far
combaciare le FAP specifiche di un dato venditore è ancora necessario per far comunicare un client
con un server. Infatti, poiché FAP non è standardizzato, l’unico modo per far comunicare il
software è di utilizzare un singolo FAP che sia uguale sia nel client che nel server. Inoltre, se si
vuole far sì che un client possa comunicare con server di più venditori, bisogna dotarlo di tanti
driver quanti sono ciascuno di questi; nella Figura 35 è rappresentata un'esemplificazione di quanto
detto finora.
Figura 35
L'utlizzo di alcuni FAP in un
esempio di applicazione di
SQL in rete.
Comunque, al fine di poter far comunicare tra loro i prodotti di case produttrici diverse, ciascuna
di esse si è più o meno adeguata ad uno degli standard FAP presenti sul mercato:
77
M. Barbaro, G. Rorato
•
•
•
Client/Server
RDA (Remote-Database-Access) della ISO
DRDA (Distributed-Relational-Database-Architetture) della IBM
DAL (Data-Access-Language) della Apple
Un approccio alternativo per permettere la cosiddetta interoperabilità tra i vari FAP è l'utilizzo di
uno specifico FAP per ogni singolo venditore e permettere l'interazione con esso da parte dei
prodotti degli altri database tramite un cosiddetto gateway per ogni principale concorrente. La
maggior parte dei venditori di database forniscono gateway verso altri prodotti, ad esempio Sybase
ne possiede uno per accedere ai database della Oracle utilizzando le API di Sybase.
Purtroppo, poiché questi gateway sono spesso aggiornati, molto spesso il software non riesce a
stare al passo con le nuove versioni; occorre quindi stare attenti a controllare le versioni con cui il
prodotto è compatibile in quanto molto spesso passare ad un sistema operativo successivo o
cambiare protocollo di rete può voler significare perdere l’interoperabilità introdotta dal Gateway.
Usare l’SQL nella rete non è sempre efficiente, se infatti un’operazione di un client prevede di
manipolare una grossa quantità di dati essa può causare una situazione, nei casi più estremi, di
congestione della rete.
Stored procedure
Per impedire che si verifichino casi di congestionamento della rete a cui si accennava nel paragrafo
precedente, alcuni database come Sybase e Oracle, hanno dato la possibilità ai client di invocare
delle cosiddette remote stored procedure, che sotto parecchi aspetti sono simili alle API del Module
Language. Queste subroutine girano sul database server e invece di spedire diversi e separati
comandi SQL (e ricevere i rispettivi risultati) il client può spedire un singolo messaggio per invocare
una stored procedure contenente questi comandi. Le stored procedure sono quindi simili alle RPC
eccetto per il fatto che sono memorizzate nel database assieme ai dati e sono accessibili solo
all’interno del sistema del database. In aggiunta a ciò le stored procedure sono utili anche perché
consentono di condividere fra le applicazioni del codice di interesse comune, semplificando la
gestione delle applicazioni e la loro manutenzione; in particolare, essendo gestite in modo
centralizzato, una eventuale modifica non richiede di essere apportata a tutte le applicazioni che ne
fanno uso, risparmiando quindi del traffico di rete.
Le stored procedure forniscono inoltre un’eccellente performance nelle query perché contengono
un piano d’accesso alle query stesse compilato staticamente e inoltre sono tipicamente usate per
rafforzare l’integrità dei dati o le cosiddette business rules; d’altro canto esse, poiché caricano il server,
potrebbero diminuirne le prestazioni.
Esempio di stored procedure:
/* La procedura ritorna il
variabili nome e cognome */
risultato
di
una
SELECT
all'interno
delle
due
/* Nelle righe seguenti avviene la definizione della procedura, in particolare
vengono indicati i parametri che vengono passati, come anni che è un campo
intero, e i parametri che vengono restituiti, come nome e cognome che sono due
stringhe di caratteri */
CREATE PROCEDURE SelezionaPersone(anni integer)
78
M. Barbaro, G. Rorato
Client/Server
RETURNS (
nome VARCHAR(29),
cognome VARCHAR(20)
)
AS
BEGIN
/* Ora inizia la definizione del comando memorizzato all'interno della stored
procuedure */
/* La Select restituisce il nome e il cognome delle persone di un Anagrafe che
abbiano un'età pari a quella indicata dal parametro anni. Questi valori vengono
memorizzati, come si diceva, nelle variabili nome e cognome tramite la consueta
notazione INTO :variabile */
FOR
SELECT nome, cognome FROM Anagrafe
WHERE eta:=anni
INTO
:nome,
:cognome
DO
SUSPEND;
END
A questo punto è possibile utilizzare la procedure in un'applicazione scrivendo:
SELECT * FROM SelezionaPersone(50)
I trigger
Normalmente le stored procedure sono esplicitamente invocate da comandi SQL speciali;
comunque, alcuni database aggiungono un particolare tipo di stored procedure, i trigger, che sono
invocati automaticamente dal motore del database nel caso un particolare evento accada nel
database.
L’utilizzo principale di un trigger è per rafforzare l’integrità dei dati o in genere è una politica
amministrativa all’interno di un database.
Un trigger può così assicurare che, per esempio, dopo un update di un campo, gli altri campi
rimangano consistenti oppure che venga eseguita di conseguenza una transazione.
Alcuni database, come per esempio Ingres, permettono ai trigger di innescare altri trigger una volta
eseguiti (fenomeno dei trigger in cascata), mentre altri, come Sybase, non permettono questo
fenomeno per garantire una certa predicibilità dopo l’esecuzione di un trigger. In quest’ultimo caso
le stored procedure tendono ad avere un codice più “consistente” poiché devono contenere al loro
interno gli algoritmi per gestire le politiche che non possono più essere invocate automaticamente
dall’interno della stored procedure.
Segue un esempio di Trigger; in questo caso l’evento che genera il trigger è
UPDATE, che modifica il record old.DataNascita, altri eventi possono essere del
tipo AFTER INSERT, AFTER DELETE, BEFORE UPDATE, BEFORE INSERT, BEFORE DELETE.
CREATE TRIGGER etaPersona FOR ANAGRAFE
AFTER UPDATE AS
BEGIN
IF (old.DataNascita <> new.DataNascita) THEN
INSERT INTO...
79
M. Barbaro, G. Rorato
Client/Server
END
L’uso di trigger e delle stored procedure deve essere bilanciato con l’extra overhead che
introducono all’interno del server, infatti bisogna ricordare che tutti i processi di un server vanno
condivisi tra i vari client. Così, se un’operazione è computazionalmente costosa sarebbe meglio
dedicarle delle risorse nel desktop, se invece è ricca di operazioni di I/O è probabilmente meglio
implementarla come una stored procedure per evitare di generare un eccessivo traffico di rete.
OLAP: Online Analytical Processing
Aspects
Finora si è discusso degli aspetti OLTP (online transaction processing) della computazione
client/server nei database; comunque la computazione client/server è usata anche per supportare
online analytical processing (OLAP) e relativi tool, tramite i quali gli utenti possono analizzare e
navigare attraverso i dati per scoprire aspetti statistici come “mode”, eccezioni e capire i dettagli che
guidano la loro attività lavorativa.
Le applicazioni OLAP sono caratterizzate da differenti tipi di query e da differenti tempi di risposta
rispetto ai sistemi OLTP. Infatti mentre i sistemi OLTP di solito trattano piccole quantità di dati
perlopiù appartenenti a record molto vicini o correlati, le applicazioni OLAP di solito leggono,
integrano e consolidano un grande numero di dati. Per far ciò efficientemente l’informazione in
un’applicazione OLAP è ottimizzata in un ipercubo multidimensionale (Figura 36) che è possibile
guidare opportunamente per supportare le analisi dell’utente.
Per esempio, una tipica applicazione OLAP può essere l’analisi dei dati nelle vendite di un’azienda
negli ultimi mesi per capirne le performance (Tabella 1 Database delle vendite di un'azienda).
Tabella 1 Database delle vendite di un'azienda
Data
Mercato
Regione
Unità
Ricavato
Gen 1995
Gen 1995
Feb 1995
Feb 1995
5000
20000
6000
50000
10000$
20000$
30000$
15000$
Fermagli
Spaziatori
Fermagli
Fermagli
Toscana
Lazio
Friuli
Liguria
Le colonne di dati di questo database di vendite possono essere considerate come una dimensione
che può essere riassunta, resa più dettagliata, ecc. a seconda di come richiesto durante l’analisi. In
questo modo si può creare e ingrandire un ipercubo OLAP aggiungendogli volta per volta delle
nuove dimensioni.
Le dimensioni qualitative contengono categorie che rappresentano differenti valori che possono
assumere i dati. Per esempio, la dimensione Regione contiene la categoria delle regioni tipo la
Toscana, il Lazio, ecc.; in dati più complessi le categorie possono essere organizzate in livelli
gerarchici con operazioni di tipo drill-down che forniscono utili raggruppamenti di informazione.
Drilling down si riferisce al movimento dal dettaglio più generale a quello più particolare, è così una
sorta di “zoom”. D’altro canto il termine rolling up invece si riferisce al movimento contrario.
80
M. Barbaro, G. Rorato
Client/Server
Gli indicatori quantitativi delle varie performance si chiamano misure e sono combinati per categoria
come richiesto dall’utente.
I database OLAP molto spesso hanno una dimensione del tempo durante il quale sono state prese le
misure; a seconda dell’analisi, questa dimensione può essere ingrandita in altre unità di misura come
mesi, semestri, anni dall’applicazione OLAP.
In aggiunta a quanto detto finora ecco una lista delle caratteristiche principali di un’applicazione
OLAP:







Accesso dinamico a grandi quantità i dati.
Veloce aggregazione dei dati grazie al drill down e al rolling up.
Sovrapposizione di diversi dati provenienti da dettagli diversi della stessa dimensione.
Rapido cambio di prospettiva sui dati stessi.
Calcoli sui dati (medie, percentuali, massimo/minimo, ecc.).
Calcoli di dati derivati.
Varie visualizzazioni della stessa informazione (tabelle, grafici, mappe, ecc.).
E’ ovviamente sempre più costoso, con l’aumentare delle dimensioni di un database, rappresentare
con i database relezionali bidimensionali un dato multidimensionale utilizzato da un’applicazione
OLAP e così, per motivi di efficienza, molti sistemi di sviluppo OLAP usano o un vero MDD
(Multidimensional Database) o un database relazionale con alcune capacità extra OLAP. Ecco
comunque più in dettaglio i due tipi principali di database OLAP:


Sistemi RDBMS modificati (come Oracle Express, DSS/Server, RedBrick Warehouse):
questi database relazionali (R/OLAP) sono estesi creando un ipercubo tramite varie
operazioni tipo JOIN, esplosioni, ecc. Questi sistemi tendono a perdere performance
quando un’applicazione interagisce interattivamente aggiornando il database, in quanto
gli indici vanno ricalcolati ogni volta; d’altro canto vanno invece bene in caso di richieste
di dati OLAP su larga scala in quanto accettano cambiamenti alle dimensioni delle
tabelle senza perdere troppa performance.
Database Multidimensionali Puri (come Essbase, TM/1, LightShip Server): questi
sistemi sono dei puri database multidimensionali che sono ottimizzati per l’uso delle
applicazioni OLAP. Questi database non fanno uso di chiavi in quanto tipicamente
memorizzano i dati in array cosicchè non è necessario aggiornare gli indici ai dati quando
cambiano i valori in un array. Questi sistemi tendono a non avere una buona
performance nel caso il numero delle dimensioni cambi regolarmente e inoltre tendono
a non “scalare” altrettanto bene come i database di tipo R/OLAP.
81
M. Barbaro, G. Rorato
Client/Server
Figura 36
Come appare un ipercubo
multidimensionale OLAP in
un esempio.
82
M. Barbaro, G. Rorato
Client/Server
83