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 nome20; 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