Concetti Base
L’idea è che in molti casi le nostre applicazioni e i nostri
sistemi software hanno esigenze di persistenza: salvare dati
che verranno poi utilizzati in esecuzioni successive di quella
applicazione.
Tipicamente questo non viene fatto da zero; non andiamo a
ricostruire il database ogni volta ma ci appoggiamo a sistemi
che ci offrono questa funzionalità: essi vengono chiamati Sistemi di Gestione Dati (DBMS).
Attraverso il DBMS archivieremo i dati su disco in un database (base dati); il DBMS gestisce per
noi i dati archiviati su disco.
Gli utenti possono parlare direttamente con il DBMS ma solitamente utilizzano applicazioni per
farlo.
Capiremo quindi come immagazzinare dati, parlare con un DBMS e come esso interagisce con
le applicazioni.
DBMS Relazionali
I relational-DBMS sono sistemi per memorizzare e gestire dati.
I più famosi sono Oracle, SQL Server e MySQL; questi sono DBMS relazionali.
Il modello relazionale è quello più diffuso ed utilizzato per quanto riguarda i DBMS.
Linguaggi
Una volta definita la base di dati dobbiamo manipolarla, e lo facciamo attraverso dei linguaggi;
lo standard tra di essi è SQL, un linguaggio per manipolare dati.
Vedremo anche il linguaggio che sta dietro ad esso, ed anche come interagire con SQL
attraverso delle applicazioni.
Amministrazione di Dati
Impareremo anche ad amministrare i dati.
Nel mondo del lavoro esiste una figura apposita chiamata DBA (Database Administrator).
Informazioni
Al giorno d’oggi le informazioni sono per molte azienda la risorsa principale.
Colossi come Uber, Alibaba, Airbnb hanno come risorsa principale le informazioni e si occupano
principalmente di esse.
esempio: Alibaba non ha neanche un magazzino, fa solo da intermediario di informazioni da
cliente a fornitore.
Sistema informativo
Un S.I. è un’entità che ha:
● Compiti:
○ Raccogliere dati
○ Conservare i dati raccolti, archiviandoli
○ Elaborare i dati, trasformandoli in informazioni
○ Distribuire l'informazione agli utilizzatori
● Componenti:
○ Strumenti
○ Procedure
○ Strutture
Un S.I. può anche non essere necessariamente un calcolatore, la sua definizione si rispecchia
anche ad esempio in:
- registri di informazioni (come quelli dell’anagrafe),
- biblioteche.
Sono tutti modi per archiviare informazioni in modo più o meno efficiente.
Nei sistemi informatici, le informazioni si rappresentano come dati, registrati su un supporto che
ne garantisce conservazione e reperibilità.
Base di Dati
Una b.d. è una collezione di dati tra loro correlati che rappresentano le informazioni di interesse
di un S.I.
Anche questa definizione è indipendente dall’automazione: una b.d. può non essere un
calcolatore ma ad esempio un archivio cartaceo.
DBMS - DataBase Management System
In generale un sistema di gestione di basi di dati è un sistema software che fornisce gli strumenti
necessari a gestire basi di dati.
Se ci focalizziamo sui sistemi informativi supportati da un calcolatore, una base di dati è una
collezione di dati gestita da un DBMS
Schema
Lo schema (anche detto schema logico) di una base di dati è un’idea fondamentale che serve
per risolvere il problema delle ridondanze e inconsistenze e permettere un controllo
centralizzato.
Esso:
● descrive la struttura della base di dati.
● astrae dai dettagli dell’implementazione fisica.
● usa un formalismo ad alto livello detto modello dei dati (nel nostro caso parleremo
quasi solo di modello relazionale).
In maniera astratta, utilizzando un formalismo, lo schema logico descrive quindi la struttura dei
dati.
Al contrario, lo schema fisico sarà più concreto e si occuperà degli aspetti di più basso livello.
In questo corso impareremo come definire ed usare gli schemi.
Un’istanza invece è l’effettivo insieme dei dati presenti in un determinato momento in una base di
dati e di conseguenza:
● cambia molto spesso nel tempo
● ha descrizione estensionale.
Linguaggi
Il DBMS mette a disposizione linguaggi dichiarativi per agire sui dati rappresentati secondo il
modello.
In questo modo si può interagire con i dati secondo modalità non anticipate al momento della
definizione e realizzazione della base di dati.
- Interrogazione Procedurale.
che invece consiste nell’effettuare una serie di controlli procedurali sulla dichiarazione
fino ad arrivare al risultato.
Indipendenza Logica
L'Indipendenza logica dei dati consiste nella possibilità di cambiare lo schema logico
senza modificare le viste degli utenti.
Indipendenza Fisica
Indipendenza fisica dei dati significa che l'amministratore del database può
modificare lo schema fisico del database senza alterare lo schema logico o le viste.
2. Schema logico della base di dati indipendente dal workload, ovvero non dipende da
come intendo utilizzare i dati.
Questi punti fermi hanno garantito il successo del modello relazionale negli ultimi decenni.
Tutt’oggi il 90% dei DBMS sono relazionali e utilizzano questi 2 punti fermi; altri DBMS utilizzano
altri modelli e quindi altre regole fondamentali.
Servizi di un DBMS
Il DBMS risponde alle domande precedenti mediante servizi.
● Servizi Esterni
Gli utenti comunicano con il DBMS, tramite comandi in linguaggi dichiarativi,
attraverso servizi esterni:
● Servizi Interni
Il DBMS implementa i servizi esterni mediante molteplici servizi interni, che l’utente non
invoca esplicitamente ma che sono necessari al corretto funzionamento del software:
Esso offre molteplici servizi ed ognuno di essi corrisponde ad uno o più componenti software.
Componenti di un DBMS
Composto da un query manager, un transaction manager e un gestore delle strutture di
memorizzazione.
Tutte le diverse componenti, per il corretto funzionamento, hanno bisogno di accedere a dati
utente e/o dati di sistema.
Il DBMS memorizza al suo interno anche dati che servono per il suo stesso funzionamento;
Ad esempio: sapere quali utenti possono accedere ad una determinata risorsa.
Utenti di un DBMS
Ci sono varie possibili figure che agiscono su un DBMS:
Architettura di un DBMS
1. Centralizzata
2. Client-server
3. Distribuita
4. Parallela
Architettura centralizzata
● I servizi interni ed esterni vengono forniti da una singola macchina
● Le applicazioni risiedono e vengono eseguite sulla stessa macchina che offre i servizi
● Le richieste di servizi esterni al DBMS vengono effettuate dalla stessa macchina che
offre i servizi
Architettura client-server
● I servizi interni ed esterni vengono forniti da una singola macchina, chiamata server
● Le applicazioni risiedono e vengono eseguite su macchine client, dalle quali vengono
richiesti i servizi esterni
Architettura distribuita
● Un’architettura è distribuita quando si verifica almeno una delle due condizioni
○ Le applicazioni, tra loro cooperanti, risiedono su più macchine (nodi):
elaborazione distribuita
○ Il patrimonio informativo (dati), unitario, è ospitato su più macchine (nodi): base di
dati distribuita
● I principi e gli algoritmi su cui si basano i servizi interni ed esterni sono diversi rispetto a
sistemi centralizzati o client-server
● L’architettura client-server è un esempio semplice ma molto diffuso di architettura che
distribuisce l’elaborazione
Il Modello Relazionale
Proposto da E. F. Codd nel 1970 per favorire l’indipendenza dei dati e reso disponibile come
modello logico in DBMS reali nel 1981.
Oggi è il modello logico più diffuso ed è adottato dalla larga maggioranza dei DBMS disponibili a
livello commerciale.
Caratteristiche:
La sua caratteristica principale è la sua semplicità.
Infatti è basato su una semplice struttura dati: la relazione.
Vantaggi:
- semplice rappresentazione dei dati,
- uso di linguaggi dichiarativi e quindi facilità con cui possono essere espresse
interrogazioni anche complesse.
Dominio:
Un dominio è un insieme (anche infinito) di valori.
Per esempio l'insieme {0,1} o quello dei numeri interi o quello delle string (stringhe).
Prodotto Cartesiano:
Il prodotto cartesiano è indicato con il simbolo ×.
Relazione
Siano D 1 , D 2 , … , D n ∈ D domini.
La cardinalità di una relazione è il numero degli elementi dell’insieme ovvero il numero di tuple
appartenenti alla relazione.
una relazione è un sottoinsieme finito del prodotto cartesiano tra i due ovvero r ⊆ D1 × D 2.
Ad esempio:
Quali delle seguenti relazioni coincidono con (sono la stessa relazione di) {(2,2,'b'), (2,2,'c')}?
{(2,2,'c'), (2,2,'b')} sì
{(2,2,'c'),(2,2,'b'), (2,2,'c')} no
{(2,'c',2),(2,'b',2)} no
Notazioni di Relazioni
Notazione Testuale:
Per semplicità e maggiore leggibilità le relazioni e i prodotti cartesiani si possono rappresentare
come tabelle.
E’ importante mantenere l’ordine delle colonne mentre posso invertire senza problemi le righe:
è equivalente.
Notazione Posizionale:
In generale l’unico modo formalmente corretto di definire il prodotto cartesiano
D1 × ....× Dn
è come insieme delle funzioni (totali) che associano ad ogni indice un valore nell‘insieme che ha
quella posizione: {t :[1 ,n ]→ D1 ∪ …∪ Dn ∨t(i)∈ Di ∀ i∈[ 1 , n]}
Le funzioni vanno dall’insieme delle posizioni all’insieme dei possibili valori che possono essere
assunti, in modo tale che la componente i-esima appartenga a D i
Siano:
- R una relazione di grado n
- t=(d 1 , d 2, … , dn)una tupla di R
- i ∈ {1 , ..., n }
Allora t . i oppuret [i] oppuret (i) denota la i−esima componente di t cioè t [i]=D i
Schema:
Se R nome di relazione e{ A 1 , A2 , .. . , A n } un insieme di nomi di attributi.
A questo punto le nostre tuple saranno funzioni applicate ai nomi degli attributi; andremo ad
associare i valori a nomi di attributi (invece che le posizioni).
esempio:
La tuple della tabella seguente sono funzioni: {A1, A2, A3} -> char U integer
Tupla e Relazione:
Sia S=( R( A 1 , A2 , .. . , A n) , dom) uno schema di relazione.
Sottotupla:
se A={ Ai1 , … , A ik }∈ { A1 , … , A n } sottoinsieme dei nomi di attributi.
t [ A ]=[ A i : v i , … , A i : v i ] ovvero t [ A ]( Aij )=v ij
1 1 k k
Una relazione definita su uno schema di relazione è un insieme finito di tuple definite su tale
schema; tale relazione è anche detta istanza dello schema.
Schema:
Film(titolo,regista,anno,genere,valutaz)
dom(titolo) = dom(regista) = dom(genere) = string
dom(anno) = int
dom(valutaz) = real
Valori Nulli
Non sempre sono disponibili informazioni complete su una qualche istanza di entità.
Se per la tupla corrispondente non si sa quale valore assegnare per un qualche attributo, si
introduce un valore speciale: il valore nullo; esso denota la mancanza di valore dovuta alla
nostra mancanza di informazione o all’inutilità di essa.
ad esempio:
se nella sezione “voti” abbiamo un campo “lode” e il voto non è 30 non ha senso inserirlo
Usare un valore v legale per almeno un dominio non è una soluzione, il suo uso non
permetterebbe di distinguere il caso in cui v sia effettivamente il valore dell’attributo dal caso in
cui v indichi il valore nullo.
Per questo assumiamo di denotare il valore nullo con il simbolo NULL oppure ?
- Il valore nullo è un valore ammissibile per ogni dominio,
- I linguaggi come SQL permettono di specificare nella definizione di una relazione quali
attributi non possono mai assumere valore NULL,
- Notazione negli schemi: evidenziamo con un circoletto gli attributi che possono
assumere valori nulli.
In SQL succede l’esatto contrario: tutti i valori ammettono di default il valore NULL e bisogna
specificare quando esso non può essere ammesso.
Caratterizzazione Delle Tuple:
Per ogni entità di un dominio applicativo vi sono uno o più sottoinsiemi dei suoi attributi che
caratterizzano le sue istanze.
Non possono quindi esistere due istanze diverse con gli stessi valori su tutti quegli
attributi.
Chiavi
Una chiave è un sottoinsieme degli attributi della relazione; più in particolare, è un insieme
minimale di nomi di attributi che caratterizzano le istanze di un’entità.
1. Univocità: qualsiasi sia l’istanza di R , non esistono due tuple distinte di R che
abbiano lo stesso valore per tutti gli attributi in X .
se t 1 [ X ]=t 2 [ X ]allora t 1 =t 2
Due tuple non possono avere gli stessi valori per tutti gli attributi chiave.
Un insieme di nomi di attributi che verifica la proprietà (1) ma non la proprietà (2), è detto
superchiave di R .
Quindi una superchiave è un insieme di attributi univoco; una chiave è una superchiave
minimale.
esempio:
1. Film(titolo,regista,anno,genere,valutaz).
Chiavi Candidate:
Una relazione può avere più di un insieme S di (nomi di)
attributi che verificano le proprietà (1) e (2): esse vengono
dette chiavi candidate.
Notazione Testuale:
Le chiavi primarie si rappresentano in sottolineato (underlined) quelle secondarie sottolineando
con righe ondulate oppure in italico (italic).
A1 chiave primaria;
Siccome la chiave primaria è univoca, se sottolineo più chiavi allora vuol dire che mi sto riferendo
ad una tupla di attributi.
Siccome le chiavi alternative non sono univoche: se sottolineo più chiavi allora devo specificare
se mi sto riferendo ad una tupla o a chiavi separate, per evitare ambiguità.
Chiavi Esterne
La chiave esterna non è una chiave, è un nuovo concetto.
Esse indicano che stiamo importando componenti di un’altra relazione e permettono di
modellare le associazioni.
Siano:
- (R( A 1 , . . ., An ), dom) ed ( R ’ (B 1 , . .. , Bm) , dom’) due schemi di relazione.
- Y ={Y 1 ,… , Y k }⊆ U R ’una chiave per R '
- f : Y → U R una funzione iniettiva
tali che dom ’(Y i )⊇ dom(f (Y i )) per ogni i∈[1 , k ] (domini compatibili)
R viene detta relazione referente mentre R ' viene detta relazione riferita.
Abbiamo fra gli attributi di R un insieme di attributi che corrisponde all’insieme di attributi di R ’ e
che ci permetterà di accedere alle tuple di R ’.
Le chiavi esterne permettono quindi di collegare tra loro tuple di relazioni diverse e sono un
meccanismo per realizzare le associazioni per valore.
Il vincolo di integrità referenziale ci assicura che quello che colleghiamo (importiamo) sia
effettivamente presente.
esempio:
R(A,B,C,D,E) è una relazione; poniamo D,E come chiave esterna
Nelle caselle D ed E di R non potremmo avere ad esempio 1 b, poiché non sono valore presenti
nelle tuple di R’.
Per dire che D ed E sono una chiave esterna su R’ andiamo ad usare la notazione di apice:
Le chiavi esterne possono essere chiavi, possono essere sottoinsiemi propri di chiavi ma
possono anche essere disgiunte da tutte le chiavi della relazione referente.
In questo esempio D ed E possono essere una chiave di R come no.
A volte è necessario che una chiave esterna abbia un nuovo nome, per evitare conflitti; ad
esempio, ho due relazioni:
Studenti ¿Matr, … . , Mat r prof ¿ Prof ¿Matr, … . ¿
Non possiamo usare Matr per entrambe perché vediamo che in studenti nasce un conflitto;
dobbiamo chiamarle in modo diverso:
Osservazioni:
1. I nomi degli attributi nella chiave e nella chiave esterna non devono necessariamente
essere gli stessi; se lo sono, semplificano alcune operazioni (join naturale).
2. Per indicare le chiavi esterne si usa il nome della relazione riferita come apice dei nomi
degli attributi usati nella tabella referente, quando non ci siano ambiguità.
4. Una relazione può contenere più chiavi esterne, anche sulla stessa relazione.
5. Sia le chiavi che le chiavi esterne devono essere esplicitamente specificate nello schema
di relazione; la presenza di attributi con lo stesso nome e domini compatibili in relazioni
diverse non implica siano chiavi esterne.
Modello Entità-Associazione
Inventato da Peter Chen nel 1976, il modello ER (entity relationship) è uno dei modelli più
utilizzati nell'ambito della progettazione concettuale;
Inoltre nel modello relazionale non abbiamo modo per distinguere oggetti e legami tra di essi;
sono presenti solo le chiavi esterne ma non ci danno proprio la forte idea di associazione.
E’ costituito da vari costrutti atti a rappresentare concetti di base di un modello dei dati:
● Entità
● Associazioni
● Attributi
● Vincoli
● Domini (composti) di attributi
Entità
Insieme di elementi del mondo reale caratterizzati da caratteristiche comuni (attributi).
Sono rappresentate graficamente da rettangoli contenenti il nome dell’entità:
Associazione
Corrispondenza (legame) tra entità non necessariamente distinte.
Le linee devono essere almeno due e devono avere agli estremi delle entità.
Se due o più linee vanno nella stessa entità bisogna necessariamente
specificare il ruolo.
Ruolo: funzione che un'istanza di entità esercita nell'ambito di un'associazione.
Se la stessa entità compare più volte nell’associazione il ruolo è indispensabile.
L’associazione può essere quindi vista come un insieme di tuple di istanze
dell’entità:
esempio: (f 1 , v 1) (f 2 , v 2) ( p 1 , s 1) ( p 2 , s 2)
E’ importante capire che il legame tra due entità, se è presente, c’è solo una volta, e non può
esserci più volte:
Uno studente non può sostenere più esami per lo stesso corso.
Un cliente non può noleggiare più volte lo stesso video.
Grado di un’associazione
Numero di entità (non necessariamente distinte) che partecipano ad un'associazione.
esempi:
Associazione unaria: grado 1
Attributi
Sono anche chiamati proprietà.
Dal punto di vista semantico è una funzione che associa valori a istanze dell’entità o
dell’associazione a cui si riferisce.
Un attributo è quindi una caratteristica significativa di un’entità ai fini della descrizione della realtà
applicativa di interesse, rappresentata graficamente da un lollipop (lecca-lecca).
Per ciascuna istanza, un attributo associa un insieme di valori, anche se nella maggior parte
dei casi si parla di un solo valore.
Se i vincoli di cardinalità vengono omessi, il default è (1,1), cioè si tratta di attributi obbligatori e
monovalore.
Attributi di Associazioni
Anche le associazioni possono avere attributi.
Il giudizio non è specifico né di un cliente né di un film, ma del legame Cliente-Film che si crea
quando un cliente consiglia un certo film.
Per ciascun legame Cliente-Film il giudizio deve essere unico.
Attributo Composto
Un attributo che possiede dei sotto-attributi.
Si indica con un rettangolo con i bordi arrotondati o un ovale.
Il dominio di un attributo definisce l’insieme dei valori che un attributo può assumere.
Domini semplici o standard: interi, reali, booleani, date, caratteri,stringhe di caratteri, ecc.
Vincoli di Integrità
Il modello ER fornisce costrutti per definire:
1. Vincoli di cardinalità:
a. per associazioni,
b. per attributi.
2. Vincoli di identificazione:
a. per entità.
Essi sono vincoli di cardinalità sulla partecipazione delle entità alle associazioni.
Specificano quindi minimo e massimo numero di istanze di un’associazione a cui ogni istanza di
quella entità deve partecipare.
Graficamente si indica tramite la coppia di cardinalità minima e massima (c_min, c_max) sulla
linea che congiunge entità e associazione.
Di default, se omesso, è (0,n).
esempio:
Un cliente può:
- Non avere in noleggio video (c_min = 0)
- Averne contemporaneamente in noleggio non più di 3 (c_max = 3)
Un video può:
- Non essere correntemente in noleggio (c_min = 0)
- Essere noleggiato da non più di un cliente contemporaneamente (c_max = 1)
- c_max = n ⇒ non esiste limite al numero massimo di istanze di A a cui ogni istanza di
E può partecipare
esempio:
Video:
- c_min = 1 ⇒ ogni video contiene almeno un film,
- c_max = 1 ⇒ ogni video contiene al più un film.
Film:
- c_min = 0 ⇒ possono esistere film in catalogo per cui al momento non sono presenti video,
- c_max = n ⇒ ogni film può essere contenuto in un numero arbitrario di video.
esempio:
I vincoli di cardinalità stabiliscono a quante tuple può partecipare una determinata istanza di una
relazione.
“Una polizza è stipulata da uno e un solo impiegato e copre l'impiegato stesso e un numero
arbitrario di suoi familiari.
Ogni impiegato ha una e una sola polizza attiva e un familiare può essere inserito in una e una
sola polizza attiva.”
La partecipazione di IMPIEGATO a copertura ha vincolo di cardinalità: (1, n); bisogna infatti fare
in modo che sia collegabile a più tuple, poiché deve essere collegato con i vari familiari.
Dobbiamo inoltre scrivere a parole: “tutte le istanze di copertura relative ad un dato impiegato
sono relative alla stessa polizza.”
E inoltre: “tutte le istanze di copertura relative ad una data polizza sono relative allo stesso
impiegato.”
oppure
Vincoli di Identificazione di Entità
Dobbiamo identificare le entità per distinguerle.
Le associazioni invece non hanno bisogno di un identificatore perché sono identificate dalle
entità che collegano.
Identificatori per un’entità: insieme di attributi e/o entità che identificano le istanze dell'entità
(analogo a superchiave).
Durante la progettazione concettuale per ogni entità si devono identificare tutti gli identificatori
minimali.
Gli identificatori hanno senso solamente per tutte le entità e non per le associazioni;
- le istanze di un'associazione sono sempre identificate dalle istanze di entità che mettono
in collegamento.
A volte non è possibile identificare un'istanza di entità solo sulla base dei suoi attributi, cioè due
istanze diverse possono coincidere su tutti gli attributi.
Si utilizza allora il fatto che tale istanza partecipi ad una particolare istanza di associazione con
una data istanza di un'altra entità:
- analogo a usare una chiave esterna come parte della chiave nel modello relazionale.
Tipologie di Identificatori
Gli identificatori vengono divisi in:
● Interni: uno o più attributi dell'entità, quindi attraverso i valori di proprietà.
● Esterni: uno o più entità collegate da una associazione all'entità a cui si riferiscono
(identificazione esterna da tale entità attraverso tale associazione)
Ed in:
● Semplici: costituiti da un solo elemento
● Composti: costituiti da più di un elemento.
Delle 6 combinazioni solo 5 sono possibili, poiché i misti sono per forza composti.
esempi:
Identificatore interno semplice: lo identifichiamo annerendo il pallino;
non possono quindi esistere due clienti con lo stesso valore di codCli; funziona come vincolo di
integrità (univocità) e possiamo avere due o più identificatori interni semplici.
Identificatore interno composto: lo identifichiamo con una barra che unisce le stanghette degli
attributi e annerendo il pallino.
Identifica che la coppia (titolo, regista) è un identificatore.
Ci dice che in una data videoteca c’è un solo video con quella determinata collocazione.
(altro esempio: abbiamo una matricola in UniGE ma non c’è una sola matricola in tutta la
nazione, quando ci identifichiamo a livello nazionale dobbiamo specificare matricola+uni di
provenienza).
Le entità deboli devono avere cardinalità (1,1) rispetto all'associazione attraverso cui avviene
l'identificazione:
- Nel caso di identificazione esterna l'associazione sarà uno a uno,
- Nel caso di identificazione mista l'associazione sarà uno a molti.
Gerarchie di Generalizzazione
Ulteriore costrutto del modello ER, non presente nella formulazione originaria.
Una entità E è una generalizzazione delle entità E1 , … , E n se le sue istanze includono quelle di
E1 , … , E n con:
- E entità padre
- E1 , … , E n entità figlie
Tutte le proprietà dell’entità padre (attributi, identificatori ed associazioni) vengono ereditate dalle
entità figlie.
graficamente:
Vincoli Impliciti
Se E1 è una generalizzazione di E2:
Esempio 2:
E’ parziale perché esistono laureati che non siano né ingegneri o medici, ad esempio i
laureati in informatica.
Associazione di Sottoinsieme
Caso particolare di generalizzazione (parziale ed esclusiva).
Definire una relazione di sottoinsieme tra una entità E1 ed una entità E2 significa specificare che
ogni istanza di E❑ anche istanza di E2 .
Componente Simbolo
Entità
Relazione
Attributo
Attributo Composito
Gerarchia di Generalizzazione
Relazione di Sottoinsieme
Identificatore
Progettazione BD
Nel progettare una base di dati bisogna tenere in considerazione:
- quali entità e associazioni rappresentare e
- come rappresentarle.
Per basi di dati sempre più complesse e di dimensioni sempre più elevate è
indispensabile un approccio sistematico alla progettazione.
Tipologie di Requisiti
Nella specifica dei requisiti risiedono varie tipologie di prerogative:
● Requisiti informativi:
○ caratteristiche e tipologie dei dati.
● Requisiti sulle operazioni:
○ esplicitati nel carico di lavoro (workload).
● Requisiti sui vincoli di integrità ed autorizzazione:
○ proprietà da assicurare ai dati, in termini di correttezza e protezione.
● Requisiti sulla popolosità della base di dati:
○ volume dei dati.
Progettazione Concettuale
Dal documento di specifica si crea uno schema concettuale:
● descrizione formale ed ad alto livello,
● del tutto indipendente dall'implementazione della base di dati:
○ indipendente anche dal tipo di DBMS che sarà utilizzato (ad esempio relazionale
o object-relational),
● definito tramite un opportuno modello concettuale (ad esempio il modello ER).
Progettazione Logica
Traduzione dello schema concettuale nel modello dei dati del DBMS target.
L’output è lo schema logico nel DDL del DBMS target.
Normalizzazione
Verifica di qualità dello schema logico prodotto, effettuata tramite opportuni strumenti formali.
Prende il nome di normalizzazione nel caso di basi di dati relazionali.
Progettazione Fisica
In questa fase vengono effettuate alcune scelte circa la memorizzazione fisica dei dati (ad
esempio, indici).
L’output è lo schema fisico che descrive le strutture di memorizzazione ed accesso ai dati.
Metodologie di Progettazione
Il modello ER è lo strumento tramite cui è possibile definire lo schema concettuale di una base di
dati.
Esistono alcune linee guida per condurre le varie fasi, al fine di:
- ottenere un “buon” schema concettuale partendo dal documento di specifica dei requisiti,
- valutare la “bontà” dello schema concettuale ottenuto.
3. Scegliere per ogni concetto il termine che meglio lo modella e usare esclusivamente
quello,
Progettazione Concettuale
Ristrutturato il documento di specifica, è possibile ottenere da questo un buon schema ER
attraverso:
● Scelta dei costrutti,
● Metodologie per la generazione di diagrammi ER,
● Documentazione dei diagrammi,
● Verifiche di qualità.
Entità
Per rappresentare concetti che descrivono oggetti:
- omogenei, cioè caratterizzati da un insieme di proprietà comuni,
- rilevanti per il dominio considerato, come ad esempio clienti o film della videoteca.
Sono candidati tipici i nomi che compaiono frequentemente nel documento di specifica.
Dal documento di specifica si deducono anche i vincoli di identificazione delle entità.
Gerarchia di Generalizzazione
Per rappresentare concetti che sono un caso particolare di un altro:
es. clienti, clienti standard e clienti VIP.
Si usa quando le entità figlie hanno proprietà in più rispetto all’entità padre:
es. Clienti standard e VIP hanno ciascuno un attributo (diverso) per la gestione dei punti.
Altrimenti si inserisce nell’entità che modella il concetto più generale un attributo che identifica le
varie tipologie di istanze:
es. video con attributo tipo per indicare se DVD o VHS.
Attributo
Si usa per rappresentare una proprietà di un concetto modellato come entità
Es. nome di un cliente, titolo di un film, codice di un video
Associazione
Si usa per rappresentare un legame logico tra concetti modellati come entità.
Es. video consigliato da un cliente.
Una associazione può anche avere delle proprietà, modellate come attributi.
Sono candidati tipici i verbi che mettono in relazione concetti modellati come entità.
Dal documento di specifica si deducono anche i vincoli di cardinalità.
Non sempre è facile decidere se modellare un concetto tramite un’entità o una associazione.
Generazione Diagrammi ER
Tecniche base per generare un diagramma ER finale:
1. Raffinamento, si parte da un’idea generale per poi raffinare nello specifico.
2. Costruzione e integrazione di sottoschemi.
Strategie:
● Top-down (prevale raffinamento),
● Bottom-up (prevale suddivisione in sottoschemi).
● Mista.
Strategia Mista
Si individuano i concetti principali e si realizza uno schema scheletro.
Le soluzioni dei sotto-problemi vengono integrate per arrivare allo schema finale.
Documentazione di Supporto
La documentazione di supporto deve contenere:
5. Informazioni sulle principali scelte progettuali, soprattutto ove siano possibili più
alternative.
6. Dizionario delle entità e delle associazioni, per basi di dati di dimensioni elevate.
Verifiche di Qualità
Vengono fatte frequenti verifiche di completezza e correttezza sugli schemi intermedi.
Vi sono inoltre verifiche sullo schema ER finale riguardo a:
● Correttezza sintattica e semantica.
● Completezza.
● Ridondanza.
- output: schema concettuale, ovvero un diagramma ER con dei brevi testi che
esprimono i vincoli di dominio che in esso non possono essere rappresentati;
indipendente da ogni considerazione implementativa.
- input:
- schema concettuale dei dati
- informazioni sul carico atteso (workload): la caratterizzazione delle operazioni
più frequenti sulla nostra base dati.
- scelta del DBMS
- output:
- schema logico per il DBMS prescelto, contenente a parte i vincoli scritti a mano
a parte perché non rappresentabili,
- equivalente graficamente allo schema concettuale,
- ottimizzato per (lo specifico DBMS) e l’uso atteso.
- obiettivo primario: rappresentazione dei dati focalizzata alla realizzazione della base di
dati e delle relative applicazioni
1. Ristrutturazione:
Schema Concettuale -> Schema Concettuale Ristrutturato
2. Traduzione:
Schema Concettuale Ristrutturato -> Schema Logico (relazionale)
3. Verifica di Qualità.
Output:
Fase di Ristrutturazione
Eliminazione dallo schema ER dei costrutti non direttamente rappresentabili nel modello
relazionale:
● attributi composti,
● attributi con molteplicità > 1 (multivalore),
● generalizzazioni (gerarchie di ereditarietà).
Esempi:
- presenza di cicli tra le associazioni;
- presenza di attributi il cui valore può essere derivato da altri attributi ed/od associazioni
(esempio fascia di età e data di nascita).
Bisogna stimare:
- spazio: occupazione di memoria del dato ridondante,
- costo per mantenere sincronizzati i dati ridondanti,
- stima della frequenza di aggiornamento,
- costo delle operazioni in presenza/assenza di ridondanza, e stima della frequenza di
tali operazioni.
Le stime sono basate su informazioni sul volume dei dati indicate nel carico di lavoro.
Di solito le ridondanze:
- avvantaggiano le query,
- rendono più complesso l’aggiornamento.
esempio: fascia di età insieme a data di nascita, occupa spazio ma permette di guadagnare
tempo perché non devo calcolare nulla. Se si fanno tante interrogazioni sulla fascia di età ha
senso, se no lo spazio occupato non vale il tempo risparmiato.
Partizionamento/Accorpamento di Entità
Sono una l’inverso dell’altra:
Ha senso accorpare due entità quando sono in associazione uno a uno le stesse operazioni
agiscono sugli attributi di entrambe.
Il partizionamento orizzontale divide un'entità in due (o più) entità separandone le istanze in
base del valore di qualche proprietà, sulla base del fatto che le operazioni le usano
separatamente.
Il partizionamento verticale divide un'entità in due (o più) entità separandone le proprietà, sulla
base del fatto che le operazioni le usano separatamente.
Partizionamento:
- conveniente se ci sono operazioni frequenti che coinvolgono solo un sottoinsieme degli
attributi di un’entità E .
esempio:
situazione di partenza: tutto insieme: anagrafica, carriera e situazione economica.
siccome la maggior parte delle volte si usa solo l’anagrafica, conviene partizionare questa entità
in 3 diverse.
Accorpamento:
Considerate due entità E 1ed E 2, accorparle:
- Può generare attributi opzionali in caso di partecipazione opzionale all'associazione di
E 1.
- conveniente se ci sono operazioni frequenti che coinvolgono gli attributi sia di E 1che di
E 2perché evita la navigazione dell'associazione.
esempio: badge e studente in due entità diverse; se il badge non fornisce informazioni particolari
non ha senso porlo come entità separata: lo possiamo dunque accorpare.
Eventuali vincoli di cardinalità esistenti per l'attributo composto vengono associati a ciascuno dei
nuovi attributi/all’associazione generati tramite la ristrutturazione.
Se le componenti dell'attributo composto sono a loro volta attributi composti, si riapplica la
procedura.
Approccio:
- si estraggono dalla documentazione informazioni sul tipo di gerarchia (totale o parziale,
esclusiva o condivisa),
- si sceglie una soluzione di ristrutturazione, sulla base di:
- tipo di gerarchia,
- carico di lavoro.
Possibili soluzioni:
1. Eliminazione entità figlie,
2. Eliminazione entità padre,
3. Sostituzione della generalizzazione con associazioni.
Attributi:
- gli attributi di E 1 , … , En vengono inseriti in E come opzionali,
- bisogna aggiungere un vincolo di integrità per garantire che se tipo = Ei , gli attributi
obbligatori di Ei siano non nulli,
Associazioni:
La partecipazione (obbligatoria od opzionale) di un'entità figlia ad un'associazione diventa la
partecipazione opzionale dell'entità padre alla stessa associazione.
Per ogni associazione, bisogna aggiungere un vincolo di integrità che indichi quali tipi di
istanze dell'entità padre possono essere coinvolti nell'associazione.
Vincoli di integrità:
- applicabile solo se la generalizzazione esclusiva: vincolo per indicare che, nello
schema ristrutturato, non possono esistere istanze di due entità figlie distinte aventi lo
stesso valore per gli identificatori (ereditati dall’entità padre),
- il vincolo di cardinalità di ciascuna entità figlia rispetto alla nuova associazione coinciderà
con il vincolo di cardinalità dell'entità padre rispetto all'associazione eliminata,
- i vincoli di cardinalità delle altre entità diventeranno invece opzionali.
Associazioni:
- la gerarchia viene sostituita da n associazioni Ri uno a uno, che collegano E con Ei
- ciascun Ei è identificata esternamente da E e partecipa obbligatoriamente a Ri
- la partecipazione di E a ciascun Ri è opzionale.
Vincoli di integrità:
- se la generalizzazione è esclusiva, un'istanza di E non può partecipare
contemporaneamente a due o più associazioni Ri .
- se la generalizzazione è totale, ogni istanza di E deve partecipare obbligatoriamente ad
almeno un’associazione Ri .
Osservazioni
Eliminazione entità figlie:
- spreco di memoria per la presenza dei valori nulli
- conveniente solo nel caso in cui le operazioni non fanno distinzione tra le varie sotto-
entità.
Entità → Relazione
Attributi dell’Entità → Attributi della Relazione
Identificatore dell’Entità ≈→ Chiave della Relazione
Associazione → Relazione o Chiave Esterna, a
seconda della molteplicità e del
grado della nostra associazione
Traduzione Entità
Entità Forti (con identificatore interno):
Diventa: E( A 1 , A 2o , A 3 , A 4)
Con domini: E( A 1/ D 1 , A 2o /D 2 , A 3 /D 3 , A 4 /D 4)
estratti dalla documentazione generata dalla progettazione concettuale .
Chiavi candidate: A 1 ,(A 3 , A 4); sceglieremo la chiave con meno attributi e con dominio più
semplice possibile.
altro esempio:
E 2(B 1)
altro esempio:
A 1 , A 3 , A 4 , B 1 ,C 1
Traduzione Associazioni
Un’associazione può essere tradotta come relazione o chiave esterna, a seconda della
molteplicità e del grado della nostra associazione.
Diventa: E 1( A 1 , A 2 , A 3 , A 4)
E 2(B 1, B2 , B3)
A( A 1 E 1 , B 1E 2 , C 1)
Attributi:
- chiavi delle entità coinvolte, come chiavi esterne verso le relazioni che rappresentano le
entità.
- quelli propri dell’associazione.
Un altro esempio:
Diventa:
E 2(B 1, B2 , B3)
Diventa:
E 1( A 1 , A 2 , A 3 , A 4 , B 1 E 2 , C 1)
E 2(B 1, B2 , B3)
Oppure
analogamente:
E 1( A 1 , A 2 , A 3 , A 4)
E 2(B 1, B2 , B3 , A 1E 1 , C 1)
esempio:
Quando è possibile tradurre un’associazione sia come relazione sia come chiave esterna scelgo
di usare la relazione solo quando è molto difficile (è un evento raro) che le due entità
comunichino tra loro attraverso l’associazione; in quel caso le due entità hanno tante istanze
non collegate all’associazione allora conviene tradurre come nuova relazione.
esempio:
Relazione o chiave esterna sono ambi possibili, e nel primo caso la chiave esterna può andare
da ambo le parti.
L’uso della relazione lo limitiamo solo se l’associazione è un caso particolare (un evento raro),
ovvero quando è molto difficile che un armadietto venga assegnato ad uno studente.
L’attributo dell’associazione lo portiamo dietro nella relazione che usiamo per tradurre
l’associazione:
- se usiamo la chiave esterna, lo mettiamo dove mettiamo la chiave esterna,
- se usiamo una nuova relazione lo mettiamo in essa.
1) Algebra relazionale:
Query = espressioni basate su relazioni base + operatori fra relazioni.
2) Calcolo relazionale:
Query = insieme di elementi che soddisfano una formula logica.
L’algebra relazionale è caratterizzata da cinque operazioni di base, che usano solo relazioni
come argomento e producono relazioni:
● Proiezione
● Selezione
● Prodotto cartesiano
● Unione
● Differenza
Arricchita da operazioni derivate, ad esempio il join, che non aumentano il potere espressivo
dell’algebra ma sono comode in pratica.
Per formulare query, spesso ci aiuta saperle formulare in algebra piuttosto che in SQL.
Per avere chiara la corrispondenza tra algebra e SQL, introduciamo alcune operazione
mettendole in parallelo:
Ridenominazione
(estrai le colonne
In algebra è utile per
rinominandole)
garantire l’applicabilità di
alcune operazioni.
esempio:
Per usare la notazione per nome serve un’operazione addizionale che permetta di modificare il
nome degli attributi per evitare clash quando si mettono assieme relazioni con gli stessi nomi di
attributi.
Se usassimo la notazione per posizione non servirebbe: mettendo assieme le relazioni le
posizioni scalerebbero automaticamente.
Formalizzazione
Dati:
- una relazione R con nomi di attributi C 1 , C 2 ,. . . ,Ck e dominio dom ,
Proiezione
SELECT DISTINCT
A1, … , Ak
FROM R
Con eliminazione di
duplicati:
da usare quando ci
potrebbero essere duplicati,
per evitarli.
Esempio:
estraggo la colonna A di R
La cardinalità (il numero di tuple del risultato), non è sempre quella di partenza:
se andassimo a proiettare B su R non otterremo ripetizioni, poiché si estrae un insieme, ed
esso non può avere duplicati, quindi si perderebbe la seconda b.
Formalizzazione
Dati:
- una relazione R con nomi di attributi C 1 , C 2 ,. . . ,Ck e dominio dom ,
⇒ Rango di R ’=n
Esempio:
prendo le tuple con attributo A uguale ad ‘a’.
Formalizzazione
Dati:
- una relazione R
- una formula F
Formule
Una formula (predicato) F su una relazione R ha una delle seguenti forme:
- ha la forma e op e’ dove:
Validità Di Formule
Valutare una formula F su una tupla t di una relazione R può produrre true, false o ?
(unknown):
● il risultato si indica t[F],
● la valutazione è definita induttivamente
- t [ e op e ’]=? se t [e] oppure t [ e ’ ] sono il valore nullo.
Prodotto Cartesiano
esempio:
Vincoli di Applicabilità di R X S :
Devo prendere due relazioni disgiunte; se hanno degli attributi in comune li devo prima
rinominare.
Formalizzazione
Date:
- una relazione R 1con nomi di attributi A 1 , A 2 , . .. , An e dominio dom 1
- una relazione R 2con nomi di attributi B1 , B2 , . . ., Bk e dominio dom 2tali che
{ A 1 , A 2, . . . , An}∩{B 1, B 2, . . ., Bk }=∅
- ha nomi di attributi A 1 , A 2 , . .. , An , B 1 , B 2 , . .. , Bk
Rango di R = Rango di R1 + Rango di R2
- Il dominio dom coincide con dom 1sugli attributi Aie con dom 2sugli attributi Bi
Una tupla t appartiene ad R se e solo se essa è la concatenazione di una tupla t 1di R 1e
una t 2 di R 2.
Unione
Unione R ∪S SELECT *
FROM R
Le due relazioni devono UNION
avere lo stesso schema. SELECT *
FROM S
L’unione, essendo
insiemistico, rimuove a
prescindere duplicati, a meno
che non usiamo:
...
UNION ALL
...
esempio:
unione di R ed S.
Vincoli di Applicabilità di R ∪ S :
Le due relazioni devono avere lo stesso schema.
Formalizzazione
Date:
- due relazioni R 1e R 2con nomi di attributi A 1 , A 2 , . .. , An e dominio dom
L’unione R=R 1 ∪ R 2:
- ha nomi di attributi A 1 , A 2 , . .. , An
Rango di R=Rango di R 1=Rango di R 2
- Dominio dom
Differenza
oppure
SELECT *
FROM R
MINUS
SELECT *
FROM S
esempio:
differenza tra R ed S.
Vincoli di Applicabilità di R ¿:
Le due relazioni devono avere lo stesso schema.
Formalizzazione
Date:
- due relazioni R1 e R2 con nomi di attributi A1,A2, . . . ,An e dominio dom
La differenza R = R1 - R2 :
- Dominio dom
Intersezione
Intersezione R ∩S SELECT *
FROM R
Le due relazioni devono INTERSECT
avere lo stesso schema. SELECT *
FROM S
E’ un operatore derivato, lo
possiamo scrivere utilizzando
la differenza.
Vincoli di Applicabilità di R ∩S :
Le due relazioni devono avere lo stesso schema.
Semantica statica: R e S devono avere lo stesso schema (per definizione di differenza) con
stessi domini.
La relazione risultato ha lo stesso schema e lo stesso dominio delle relazioni argomento.
Grado di R∩S = Grado di R = Grado di S.
La semantica è l’insieme delle tuple contenute in R e in S.
Join
Esempio:
prodotto cartesiano:
applico la condizione C=E.
Join Naturale
Non c’è una condizione; la condizione predefinita sarà: “gli attributi con lo stesso nome che
contengono lo stesso valore”.
Per fare R ⋈ S:
1. in S si ridenominano le colonne con lo stesso nome di colonne di R, usando nomi nuovi e
temporanei (quindi irrilevanti),
2. si fa il prodotto cartesiano,
3. si selezionano solo le tuple in cui le colonne di R comuni a S coincidono con le rispettive
colonne di S ridenominate,
4. si proietta eliminando le colonne ridenominate.
Formalizzazione
Date due relazioni:
- R 1con attributi C 11 , … ,C 1 n e dominio dom1
- R 2con attributi C 21 , … ,C 2z e dominio dom2
allora:
R 1 ⋈ R 2=Π schema σ F (R 1 × ρ A 1, … , Ak ←B 1 , …, Bk( R 2) )
- lo schema è quello di R 1seguito da quello di R 2in cui sono stati cancellati gli attributi
che già compaiono in R 1.
- le tuple sono la concatenazione di una tupla di R 1con una di R 2che coincide con la
prima sugli attributi in comune, da cui sono stati cancellati i valori di tali attributi.
Casi particolari di R ⋈ S:
- se U R=U S allora join naturale = intersezione
- se U R ∩ U S =∅ allora join naturale = prodotto cartesiano
Se si usano gli stessi nomi per gli attributi in chiave e chiave esterna il join naturale permette di
navigare in modo naturale le associazioni.
Possiamo inoltre specificare su che colonna fare il join tramite la clausola USING:
SELECT * FROM R
JOIN T USING C
ex. CLIENTE ha CodCli da condividere ma ha anche DataN che vuol dire Data di Nascita.
VIDEO ha CodCli da condividere ma ha anche DataN che vuol dire Data di Noleggio.
Outer Join
Ci permette di mantenere nel risultato le tuple che non hanno alcun match.
Consideriamo:
● R, S Relazioni,
● F Formula (Predicato).
SELECT * FROM R
[LEFT, RIGHT, FULL] OUTER JOIN S ON F
Bisogna specificare:
● LEFT il completamento avviene solo per R;
● RIGHT il completamento avviene solo per S;
● FULL il completamento avviene per entrambe le tuple;
Esempio:
Viene inclusa anche l’ultima tupla, completandola con NULL poiché non vi è stato alcun match.
SELECT DISTINCT
FROM
WHERE
Le query SPJ ci permettono di capire se una tupla appartiene o meno al risultato guardando una
per volta le tuple della relazione.
Se so per certo che per risolvere un'interrogazione devo guardare in un istante un insieme di
tuple in contemporanea so per certo che non posso usare questo frammento.
Il frammento SPJ ha solo Query Monotone.
La query è una funzione che trasforma un insieme di relazioni in una relazione:
Q(DB) = R
esempio:
Ordinamento
Il linguaggio SQL prevede una clausola per ordinamento, per facilità di comprensione del
risultato:
ORDER BY A1 [ASC, DESC]
Con:
- ASC ascendente,
- DESC discendente.
Esempio:
SELECT *
FROM R
WHERE F
ORDER BY A1 ASC, A2 DESC
Esempio: gli identificativi dei professori il cui nome contenga la stringa ‘te’ che abbiano uno
stipendio compreso tra i 12500 e i 16000 euro l’anno.
Recap
Operatori Unari
(estrai le colonne
rinominandole)
Proiezione π A 1 ,... , Ak SELECT
(estrarre una fetta verticale A1, … , Ak
della relazione) Proietto A1, …, Ak. FROM R
Senza eliminazione di
duplicati:
Efficiente ma se ci sono
duplicati non ottengo una
relazione.
Utile quando so che non ci
sono duplicati (estrazione di
una chiave).
SELECT DISTINCT
A1, … , Ak
FROM R
Operatori Binari
Unione R ∪S SELECT *
FROM R
Le due relazioni devono UNION
avere lo stesso schema. SELECT *
FROM S
L’unione, essendo
insiemistico, rimuove a
prescindere duplicati, a meno
che non usiamo:
...
UNION ALL
...
oppure
SELECT *
FROM R
MINUS
SELECT *
FROM S
Operatori Derivati
Intersezione R ∩S SELECT *
FROM R
Le due relazioni devono INTERSECT
avere lo stesso schema. SELECT *
FROM S
E’ un operatore derivato, lo
possiamo scrivere utilizzando
la differenza.
Comandi principali:
Tipi di Dato
I tipi di dato in SQL:2003 si suddividono in:
- tipi predefiniti, che a loro volta si dividono in:
- tipi numerici,
- tipi carattere,
- tipi temporali;
- tipi user-defined.
SMALLINT 16 bit; per risparmiare spazio quando si sa che i valori sono limitati.
INTEGER 32 bit
BIGINT64 bit; per memorizzare valori più grandi.
- NUMERIC [(precisione[,scala])]
La precisione indica il numero complessivo di cifre;
La scala indica il numero di cifre dopo la virgola.
- DECIMAL [(precisione[,scala])]
Può essere implementato con precisione maggiore di quella richiesta
ex. DECIMAL(4,1) ⊇ [-999.9,999.9]
Stringhe di lunghezza inferiore ad n vengono completate con spazi fino a raggiungere lunghezza
n.
Default CHAR = CHAR(1).
CHARACTER VARYING/VARCHAR:
VARCHAR(n) stringhe di caratteri con lunghezza massima n
Per ogni valore di CHAR(n) si alloca la lunghezza predefinita, mentre per valori di VARCHAR ci
si adatta alla lunghezza effettiva.
Creazione di Relazioni
Sintassi base: CREATE TABLE <nome relazione>
(<specifica colonna> [, specifica colonna]*)
Vincoli
In SQL, al contrario dell’algebra relazionale, è permesso avere duplicati, per efficienza, ma è da
evitare.
Obbligatorietà di Colonne
Rispetto al modello relazionale il default è a rovescio: se non si specifica esplicitamente la
colonna può assumere valori nulli.
Per specificare che una colonna non può assumere valori nulli si aggiunge il vincolo NOT NULL
nella specifica della colonna.
colonna ::= <nome colonna> <dominio> [DEFAULT <valore default>] [NOT NULL]
Esempio:
CREATE TABLE Video
(colloc DECIMAL(4) NOT NULL,
titolo VARCHAR(30) NOT NULL,
regista VARCHAR(20) NOT NULL,
tipo CHAR DEFAULT 'd');
Chiavi
Chiavi primarie indicate dalla parola chiave PRIMARY KEY:
- per ogni tupla i valori degli attributi specificati sono non nulli e diversi da quelli di ogni
altra tupla.
In una tabella è possibile specificare più chiavi UNIQUE ma una sola PRIMARY KEY.
Siccome una relazione SQL può contenere tuple duplicate può avere senso definire chiavi
costituite da tutte le colonne di una relazione.
Sia UNIQUE che PRIMARY KEY possono essere specificate in due modi:
1) Mettere le specifiche subito alla destra dell’attributo; funziona solo se la chiave è
costituita da un solo attributo:
(<nome colonna> <dominio> PRIMARY KEY, ...)
un altro esempio:
CREATE TABLE Noleggio
(colloc DECIMAL(4),
dataNol DATE DEFAULT CURRENT_DATE,
codCli DECIMAL(4) NOT NULL,
dataRest DATE,
PRIMARY KEY (colloc,dataNol), UNIQUE (colloc,dataRest));
Come abbiamo già detto, se la chiave è formata da una sola colonna si può far seguire la
specifica della colonna da UNIQUE o PRIMARY KEY.
esempio:
CREATE TABLE Video
(colloc DECIMAL(4) PRIMARY KEY,
titolo VARCHAR(30) NOT NULL,
regista VARCHAR(20) NOT NULL,
tipo CHAR DEFAULT 'd');
equivalente a:
CREATE TABLE Video
(colloc DECIMAL(4),
titolo VARCHAR(30) NOT NULL,
regista VARCHAR(20) NOT NULL,
tipo CHAR DEFAULT 'd‘,
PRIMARY KEY (colloc));
Chiavi Esterne
Poniamo il caso di avere una tabella noleggio che fa riferimento a video:
In questo modo stiamo dicendo che colloc di NOLEGGIO è una chiave esterna verso la tabella
VIDEO; colloc potrà quindi assumere solamente valori dell’attributo colloc della tabella VIDEO.
Posso anche specificare esplicitamente il nome dell’attributo a cui faccio riferimento con la
chiave esterna:
Se volessimo usare una chiave esterna di piu’ attributi dovremmo usare la clausola opzionale
FOREIGN KEY del comando CREATE TABLE con sintassi:
Clausola ON DELETE
Per specificare le azioni da eseguire nel caso di cancellazione di una tupla t della tabella R_TA
(riferita) tramite chiave esterna dalla tabella R_NTE (riferente).
Clausola ON UPDATE
Per specificare le azioni da eseguire nel caso di modifica di una tupla t della tabella R_TA
(riferita) tramite chiave esterna dalla tabella R_NTE (referente).
Le opzioni sono le stesse ed hanno lo stesso significato delle opzioni viste per la cancellazione.
Differenza: l’opzione CASCADE assegna alla chiave esterna il nuovo valore di chiave di t.
La relazione viene cancellata solo se non è riferita da altri elementi dello schema della base di
dati.
La relazione e tutti gli elementi dello schema della base di dati che la riferiscono vengono
cancellati.
Modifica di Relazioni
Come vedremo meglio in seguito, tra le modifiche possibili vi sono anche l’aggiunta e la modifica
di vincoli.
ADD CONSTRAINT <specifica vincolo>
DROP CONSTRAINT <nome vincolo>
Esempio:
ALTER TABLE Film
ADD CONSTRAINT UNIQUE(studio,titolo,anno)
aggiunge il vincolo che uno stesso studio non produca due film con lo stesso titolo nello stesso
anno
Funzioni
In SQL troviamo svariate funzioni, tra cui quelle:
- scalari;
- di gruppo.
Funzioni Scalari
Le funzioni scalari usano operatori aritmetici o algebrici come:
A+5 A+B A||B (concatena le stringhe)
Esse le possiamo utilizzare nel SELECT (per farci restituire qualcosa attraverso la combinazione
di due caselle diverse) oppure nel WHERE (per specificarle come condizioni).
Esempio: nella nostra videoteca una funzione utile che possiamo avere è quella che fa la
differenza tra DataNol e DataRes per trovare ad esempio i video che sono in noleggio da più di 2
giorni:
SELECT * FROM NOLEGGIO WHERE (dataRest - DataNol) day > INTERVAL ‘2’ days
Possiamo inserire funzioni scalari nel SELECT, WHERE, GROUP BY, HAVING, anche se in
quest’ultima non ha molto senso, dato che è più ragionevole porre le condizione nel WHERE.
Funzioni di Gruppo
Le funzioni di gruppo o aggregate estraggono informazioni aggregate da insiemi di dati.
Si usano solo per costruire le espressioni nella clausola di proiezione di un’interrogazione,
ovvero solo nel SELECT, e in caso nel HAVING.
La forma generale di applicazione è <nome-fun>([DISTINCT] <espressione>):
● in <espressione> possono comparire nomi di attributi, costanti, funzioni, ma non
funzioni aggregate,
● l’applicazione può essere usata in espressioni più complesse.
L’insieme da usare come argomento si calcola valutando l’espressione su tutte le tuple che
soddisfano la clausola di qualificazione dell’interrogazione:
● eliminando i duplicati se compare DISTINCT,
● escludendo i valori NULL.
Quelle Numeriche sono:
1. SUM,
2. AVERAGE,
3. MIN/MAX.
Esse si applicano ad un nome di attributo e si applicano a tutti i valori che quell’attributo ha
nell’insieme.
Esempio:
Esempio:
Determinare il numero di film presenti in catalogo, il numero dei loro registi e la loro valutazione
minima, media e massima.
Soluzione:
Ci sono altre funzioni che non presentiamo, fra cui ad esempio STDEV (deviazione standard) e
VAR (varianza).
Esempio:
Count(*) Restituisce il numero di tuple
Operatore di Raggruppamento
Dopo il FROM ed il WHERE possiamo specificare la clausola GROUP BY che ci permette di
partizionare una relazione in base al valore di una o più colonne, facendo quindi degli
aggregati parziali o partizioni.
Esempio:
R viene divisa in due gruppi, quello verde e quello blu; di essi vengono contate le tuple e fatta la
media dei valori della colonna C.
Un altro esempio: determinare per ogni regista quanti suoi film con valutazione superiore a 3
sono presenti in catalogo:
SELECT regista,
COUNT(*) AS numeroFilmBuoni
FROM Film
WHERE valutaz >= 3
GROUP BY regista;
Il risultato di un’interrogazione che contiene una clausola GROUP BY contiene tante tuple:
- quanti sono i gruppi di tuple risultanti dal partizionamento,
- quanti sono i valori distinti delle proiezioni sulle colonne usate per raggruppare.
Il risultato contiene una sola tupla per ogni gruppo le colonne su cui non si raggruppa delle tuple
in uno stesso gruppo possono assumere diversi valori..quale scegliere?
Esempio:
Partizionare i noleggi in base al cliente che li ha effettuati ed al regista del film noleggiato.
Per ogni gruppo, determinare il numero di noleggi e la durata massima (in giorni) di tali noleggi:
L’interrogazione:
Non ha molto senso raggruppare su una chiave poiché non da risultati molto significativi
(saranno tanti gruppi costituiti da una sola tupla ciascuno).
Nell’esempio, raggruppare per colloc e DataNol non avrebbe avuto senso.
Per ogni cliente determinare nome, cognome e quanti noleggi ha effettuato nell’ultimo anno:
Non posso mettere nel SELECT, senza applicare funzioni di gruppo, attributi su cui non ho
raggruppato.
Clausola Having
Specifica un filtro sui gruppi ottenuti dal partizionamento.
Viene inserita dopo il GROUP BY ed è una combinazione Booleana di predicati (o un atomo);
ciascun atomo deve coinvolgere funzioni di gruppo e non può contenere condizioni sui
valori delle singole colonne.
Non vi è alcuna relazione tra le funzioni di gruppo eventualmente usate nella clausola SELECT e
quelle usate nella clausola HAVING.
Esempio:
Per ogni regista che ha girato almeno due film prima del 2000, determinare quanti sono tali film,
di quanti generi diversi e la valutazione minima, media e massima di tali film
Altro esempio: Selezionare i generi di film di Tim Burton con più di 2 film al loro interno.
Valori Nulli
SQL usa una logica a tre valori TRUE (T), FALSE (F), UNKNOWN (?).
UNKNOWN (?) = valore di verità di una condizione di ricerca applicata ad una data tupla non è
determinabile;
● un atomo valutato su un attributo a valore nullo restituisce ?
● il valore di verità di una formula dipende dalla propagazione delle funzioni booleane.
Esempio:
SELECT colloc FROM Noleggio
WHERE codCli = 6635 AND dataRest > DATE '15-Mar-2012';
non restituisce i noleggi in corso
Ribadiamo:
- nelle espressioni (ad es. aritmetiche) se un argomento è NULL allora il valore
dell’intera espressione è NULL.
- esempio: le tuple relative a noleggi correnti hanno durata:
(dataRest – dataNol) DAY indeterminata.
- nel calcolo delle funzioni di gruppo vengono escluse le tuple che hanno valore
nullo per la colonna su cui la funzione è calcolata.
- questo ha come conseguenza che, ad esempio:
SUM(e1 + e2) può dare risultato diverso da SUM(e1) + SUM(e2).
Esempio:
La presenza di due tuple distinte con valori nulli non viene impedita dalla specifica di vincolo
UNIQUE (definito basandosi su uguaglianza):
- due espressioni con valore nullo non sono uguali ma non sono distinte, cioè
vengono considerate duplicati:
- SELECT DISTINCT: si ha al più un NULL nel risultato,
- GROUP BY: si ha al più un gruppo per il valore NULL.
Esiste un predicato IS DISTINCT FROM che coincide con <> tranne che per il valore nullo, cioè
se e1 ed e2 sono NULL:
- e1 <> e2 restituisce UNKNOWN,
- e1 IS DISTINCT FROM e2 restituisce FALSE
Sotto-Interrogazioni
Interrogazioni che nella clausola WHERE (o HAVING) contengono altre interrogazioni.
Si usa una query per rappresentare un valore in un atomo:
- clausola di selezione di SELECT ma anche di INSERT, DELETE, UPDATE, come
vedremo.
Esempio: determinare il titolo di tutti i film che hanno la stessa valutazione di ‘le iene’ di ‘quentin
tarantino’:
Esempio più complesso: determinare i film la cui valutazione è superiore alla media
SELECT *
FROM Film
WHERE valutaz > ( SELECT AVG(valutaz)
FROM Film);
La sotto-interrogazione restituisce il valore 3.55, quindi solo i film con valutazione superiore a
tale valore vengono selezionati.
Inoltre ogni volta che aggiungo un film viene ricalcolata la media e quindi anche i film che
soddisfano l’interrogazione.
Sotto-Interrogazioni Semplici
Si dividono in:
- scalari,
- table subquery.
Sotto-Interrogazioni Scalari
Sono sotto-interrogazioni che restituiscono un solo valore:
- sintatticamente si usano come un valore di quel tipo.
Tutti gli esempi visti sono sotto-interrogazioni scalari: erano tutti numeri e si usavano come tali.
Se una sotto-interrogazione scalare restituisce più di una tupla si genera un errore a run-
time; se ad esempio nel primo esempio avessimo cercato solo film con il titolo ‘Le Iene’ senza
cercare anche il regista ‘Quentin Tarantino’, nel momento in cui venisse aggiunto un altro film
con lo stesso titolo, otterremo un errore in fase di esecuzione.
Se nessuna tupla verifica la sotto-interrogazione, viene restituito il valore NULL.Sotto-
Interrogazioni: Table Subquery
Sotto-interrogazioni che restituiscono più tuple, ovvero una tabella (non un singolo valore).
ANY:
Con A→ v
sq→ v1 , ... , v n
ALL:
Con A→ v
sq→ v1 , ... , v n
La condizione è vera se l’operatore di confronto theta fra il valore dell’espressione e ogni valore
dell’espressione restituisce vero.
Esempio:
Determinare titolo, regista ed anno dei film più vecchi di almeno un film di Quentin Tarantino (si
può fare anche con MAX).
Le interrogazioni
SELECT ... FROM ... WHERE .... exp >= ALL SELECT exp’ ….
e
SELECT ... FROM ... WHERE .... exp >= SELECT MAX(exp’) ….
Sono equivalenti?
Esempio: per determinare il titolo ed il regista del film drammatico di valutazione minima:
L’interrogazione:
equivale a:
Esempio:
Max restituisce 3, quando andrò a fare t3[B] = 3 restituirà true.
Il risultato sarà quindi a3.
Quando andrò a fare t3[B] = 3 farà tutti i confronti e quello con NULL risulterà UNKNOWN.
equivale a:
Esempio: elencare il titolo dei film di Quentin Tarantino usciti nello stesso anno di un film di Tim
Burton
SELECT titolo
FROM Film
WHERE regista = 'quentin tarantino'
AND anno IN (SELECT anno FROM Film WHERE regista = 'tim burton');
esempio:
Restituire il titolo dei film di Quentin Tarantino usciti in un anno in cui non sono usciti film di Tim
Burton.
Esempio: elencare il titolo dei film presenti nella videoteca con lo stesso anno di uscita e genere
di un film di Tim Burton.
SELECT titolo FROM Film WHERE regista <> 'tim burton' AND
(anno,genere) IN ( SELECT anno, genere FROM Film
WHERE regista = 'tim burton');
Modello di Esecuzione
Costruire in ordine:
Sotto-Interrogazioni Correlate
Negli esempi visti, ovvero nelle sotto-interrogazioni semplici:
● ogni sotto-interrogazione viene eseguita una volta
● il risultato è usato per selezionare tutte le tuple
Vogliamo un meccanismo per definire sottointerrogazioni che dipendono dalla specifica tupla
candidata.
Esempio: si vogliono determinare titolo, regista ed anno dei film la cui valutazione è superiore
alla media delle valutazioni dei film del loro regista.
- vogliamo confrontare la valutazione di ciascun film con la media delle valutazioni dei soli
film dello stesso regista.
ex. prendo ‘tim burton’ e prendo in considerazione la media dei suoi film; prendo
‘tarantino’ e prendo in considerazione la media dei suoi film.
Serve un'interrogazione esterna che selezioni i film in base ad un predicato sulla valutazione.
La sotto-interrogazione deve calcolare la media delle valutazioni dei film del regista di ogni tupla
candidata:
SELECT titolo, regista, anno FROM Film
WHERE valutaz > ( SELECT AVG(valutaz) FROM Film
WHERE regista = (valore di regista nella tupla candidata));
In molti casi le relazioni nella subquery coincidono con un sottoinsieme di quelle nella query
esterna:
- la relazione nella subquery copre quella della query esterna,
- per poter fare riferimento alle colonne delle tuple candidate nella subquery si fa uso degli
alias di relazione che sono un meccanismo di renaming.
Alias di Relazione
Analogo agli alias di colonna:
Si definisce nella clausola FROM facendo seguire il nome della relazione da un identificatore,
oppure AS seguito da un identificatore.
Esempio:
SELECT X.titolo, X.regista, X.anno FROM Film X
WHERE X.valutaz > ( SELECT AVG(valutaz) FROM Film
WHERE regista = X.regista);
oppure:
Altri Utilizzi
Gli alias di relazione sono utili quindi anche per abbreviare la scrittura di query:
SELECT X.a FROM LaMiaTabellaConNomeSignificativoQuindiLungo X
Essi possono essere usati anche non nelle sotto-interrogazioni correlate per fare riferimento a
due diverse tuple della stessa relazione.
Esempio: Trovare registi che hanno girato almeno 2 film dello stesso genere:
E’ indifferente ciò che poniamo nella clausola di selezione della subquery (la cosa più
semplice è selezionare tutto).
Esempio: i registi di cui sono usciti (almeno) due film diversi lo stesso anno:
A livello sintattico è indifferente, mentre a livello di efficienza sono più convenienti, in ordine:
1. IN o NOT IN
2. INTERCEPT o EXCEPT/MINUS
3. EXISTS o NOT EXISTS
Divisione
Divisione in Algebra
Ha senso scrivere R ÷ S solo se lo schema di S è strettamente incluso con quello di R.
Ci devono essere in S degli attributi di R ma non tutti.
- Serve a selezionare le tuple che su un sottoinsieme degli attributi (il divisore) compaiono
con tutte le possibili combinazioni di valori, come ad esempio:
- i film che sono disponibili in ogni formato video,
- i clienti che hanno affittato tutti i film di un certo regista.
Esempio 2: determinare i codici dei clienti che hanno noleggiato tutti i film di Tim Burton.
Viene “riformulata” come:
Determinare i codici dei clienti per cui non è possibile determinare un film di Tim Burton che il
cliente non ha mai noleggiato.
In algebra:
Divisione come Operatore Derivato
La divisione può essere riscritta attraverso la differenza; a livello informale possiamo infatti
pensare la divisione come: ciò che non è un pezzo di tupla a cui manca un abbinamento
con una tupla di S.
b) A questo punto andiamo a prendere “ciò che non è un pezzo di tupla a cui manca un
abbinamento con una tupla di S” facendo una semplice differenza.
π ¿ ¿(R){π ¿¿ ¿ ¿
Divisione in SQL
In SQL possiamo esprimere la divisione:
● attraverso il NOT EXISTS;
● mediante l’uso di funzioni di gruppo.
Esempio 1 in SQL:
Il secondo metodo non funziona perché non siamo sicuri che T non possa essere abbinata ad
altro che a tuple di S; per farlo funzionare possiamo aggiungere una condizione nel WHERE,
dove restringiamo le tuple abbinabili.
Attraverso il WHERE andiamo quindi ad eliminare le tuple che non c’entrano nulla con S:
SELECT A1 FROM R
WHERE (A2, A3) IN (SELECT * FROM S) -- eliminiamo le tuple inutili
GROUP BY A1 HAVING COUNT(*) = (SELECT COUNT(*) FROM S)
Esempio 2 in SQL:
Per determinare i clienti che hanno noleggiato tutti i film di Tim Burton confrontiamo il numero di
film di Tim Burton con il numero dei film di Tim Burton noleggiati dal cliente:
Modifica Di Tuple
INSERT inserimento
UPDATE modifica
DELETE cancellazione
Si applicano a una o più tuple selezionate con clausole analoghe a quelle usate per la ricerca.
Esempio:
Inserire un nuovo film, con:
● titolo → ``La tigre e la neve'‘
● regista → Roberto Benigni
● anno di produzione → 2005
● genere → commedia
● valutaz → 3
oppure
Eccezione 1
Se la lista delle colonne manca, equivale alla lista di tutte le colonne di R nell’ordine dato dal
comando CREATE TABLE.
Eccezione 2
Se una colonna di R non compare nella lista, l’attributo corrispondente viene inizializzato con il
valore di default se specificato nel comando di creazione di R, con NULL altrimenti.
Se non c’è default e una colonna ha vincolo di obbligatorietà ma non abbiamo messo la colonna
si genera errore.
Esempio:
Inserire un nuovo film,
● titolo → ``La tigre e la neve'‘
● regista → Roberto Benigni
● anno di produzione → 2005
● genere → commedia
Manca valutaz: valutazione non è specificata quindi assume il valore di default, se presente,
oppure il valore nullo.
Equivalentemente:
Esempio:
Inserire in data odierna noleggi relativi al cliente 6635 e a tutti i video contenenti film di Gabriele
Salvatores che non aveva ancora noleggiato.
Se ci sono più video per uno stesso film? Visto che andiamo a recuperare i codici dei video e li
associamo a 6635, allora questo cliente li noleggia tutti!
Se alcuni video sono già noleggiati? Vengono comunque associati a 6635, quindi li inserisce lo
stesso, senza tener conto della situazione.
Eccezioni
Valgono le eccezioni già discusse per l’inserimento con valori espliciti:
1. se la lista delle colonne manca, equivale alla lista di tutte le colonne di R nell’ordine
dato dal comando CREATE TABLE.
2. se una colonna di R non compare nella lista, l’attributo corrispondente viene
inizializzato con il valore di default se specificato nel comando di creazione di R, con
null altrimenti.
3. se non c’è default e una colonna ha vincolo di obbligatorietà ma non abbiamo messo la
colonna si genera errore.
Cancellazione
Esempio:
Cancellare il film “La tigre e la neve”:
Modifica
Esempi:
1) Raddoppiare le valutazioni dei film (ad esempio in corrispondenza ad un cambio di scala
da 0-5 a 0-10):
2) Il cliente 6635 restituisce tutti i video che ha attualmente in noleggio e segnala che il
noleggio è iniziato ieri (e non come erroneamente diversamente registrato):
UPDATE Noleggio
SET dataRest = CURRENT_DATE, dataNol = (CURRENT_DATE – 1 DAY)
WHERE codCli = 6635 AND dataRest IS NULL;
3) Aumentare del 10% la valutazione dei film horror usciti dopo il 1999 che sono stati
noleggiati da almeno due clienti
4) Assegnare ad ogni film che è stato noleggiato almeno una volta nell'ultimo mese, una
valutazione pari al 110% della valutazione media dei film dello stesso anno e genere:
In questo caso quello che potrebbe succedere è che l’aggiornamento di un film particolare di X
possa modificare l’interrogazione successiva, il tutto nello stesso momento.
Film dello stesso anno e dello stesso genere potrebbero quindi avere valori diversi.
Esempio:
Assegnare ad ogni film che è stato noleggiato almeno una volta nell'ultimo mese, una
valutazione pari al 110% della valutazione media dei film dello stesso anno e genere:
La parte blu e quella rossa vengono valutate in base all’istanza della BD prima dell’esecuzione
dell’UPDATE; la valutazione media viene quindi calcolata sulle tuple inizialmente contenute in
Film.
La stessa valutazione viene assegnata a tutti i film dello stesso anno e genere, noleggiati almeno
una volta nell’ultimo mese.
Si evitano feedback e diventa deterministico il risultato.
Vincoli di Integrità
SQL permette di specificare vincoli di integrità semantica arbitrari, ovvero proprietà che i dati
devono verificare.
Ci sono vincoli più generici che non rientrano in queste categorie; ad esempio:
● Come possiamo ad esempio specificare che la valutazione di un film deve essere un
numero compreso tra 0 e 5?
● Come possiamo specificare che non è possibile modificare la data di restituzione di un
video assegnandogli una data precedente a quella memorizzata?
Vincoli Statici
Per rappresentare vincoli su una singola tupla è possibile usare CHECK; essi sono presenti in
ogni DBMS relazionale.
Per rappresentare vincoli su tuple o relazioni multiple si devono utilizzare le asserzioni; esse
non sono implementate in ogni DBMS (ad esempio non sono implementati in PostgreSQL).
La condizione dopo CHECK è una formula, della stessa forma di quelle che potrebbero essere
inserite in una clausola WHERE.
Essa può anche includere sottointerrogazioni.
E’ un approccio conveniente?
Per capire se l’uso di altre interrogazioni nei vincoli CHECK dobbiamo discutere più in dettaglio
su come avviene la verifica dei vincoli di integrità e quando essi vengono violati.
Esempio:
CREATE TABLE Film ( ... valutaz DECIMAL(3,2)
CHECK (valutaz BETWEEN 0.00 AND 5.00), ...);
Esempio:
CREATE TABLE Noleggio ( ...
CHECK (dataRest >= dataNol) );
Esempio:
CREATE TABLE Suggerimento(user...,voto....,titolo...,regista...);
CREATE TABLE Film ( ...
valutaz DECIMAL(3,2)
CHECK (valutaz = ( SELECT AVG(voto) FROM Suggerimento S
WHERE titolo = S.titolo
AND regista = S.regista),
...);
Quando si inserisce un suggerimento (senza aggiornare valutaz del film corrispondente) non
viene verificata la violazione del vincolo; in un certo momento quindi il vincolo sulla
valutazione non vale più.
Questo accade per un aspetto prestazionale; SQL si cura di mantenere solo i vincoli della
relazione principale e non tutti i vincoli delle relazioni correlate a quella che si sta modificando,
Casi Particolari
Un vincolo CHECK richiede che ogni tupla nella relazione soddisfi la condizione, quindi:
- la relazione vuota soddisfa sempre tutti i vincoli CHECK,
- un vincolo CHECK non può imporre ad una relazione di contenere almeno)un certo
numero di tuple che soddisfano una condizione.
Suggerimenti
Usando sotto-interrogazioni è possibile ma assolutamente da evitare esprimere vincoli
CHECK che verificano condizioni arbitrarie anche su altre tabelle.
I vincoli CHECK devono usare solo condizioni che si riescono a verificare esaminando la
singola tupla cui associamo il vincolo; questo per:
- migliore comprensibilità dello schema
- maggiore efficienza nella verifica dei vincoli
Condizioni che richiedano di esaminare più tuple (della stessa o di altre tabelle) vanno espresse
con altre modalità:
- asserzioni (si veda in seguito), che lo standard prevede ma i DBMS non supportano,
- trigger.
Assegnare un Nome ai Vincoli
Come fare per aggiungere un vincolo a una tabella esistente?
Come fare per eliminare un vincolo da una tabella esistente?
Operazioni possibili attribuendo un nome ai vincoli al momento della loro definizione (possibile
anche per i vincoli predefiniti).
● eliminarli:
ALTER TABLE DROP CONSTRAINT <nomeC>
Esempio:
Consideriamo la tabella video e supponiamo di attribuire un nome ad ogni vincolo in essa.
Asserzioni
Vincoli su tuple multiple o relazioni multiple possono essere specificati utilizzando le asserzioni.
Elementi dello schema, manipolate da appositi comandi del DDL.
Transazioni
Sommario:
1. Cos’è una transazione
2. Proprietà transazioni
3. Transazioni flat
4. Transazioni e vincoli di integrità
Cos’è una Transazione
Quando si interagisce con una base di dati, spesso è necessario eseguire più comandi SQL
insieme, che corrispondono a una singola logica applicativa, che potrebbe essere anche
molto complesse; questo lo possiamo fare in 2 modi:
Esempio:
Trasferimento di 50 euro da un conto all’altro:
Che cosa succede se si verifica un problema dopo l’esecuzione della prima operazione e il
sistema non è in grado di eseguire la seconda?
Vi possono essere situazioni di inconsistenza.
Esempio:
Supponiamo di voler aumentare del 10% gli stipendi degli impiegati di una certa sede.
Più tuple di impiegato dovranno essere aggiornate attraverso operazioni elementari; che cosa
succede se per qualche problema (per esempio la caduta di sistema) viene aggiornato lo
stipendio solo di alcuni impiegati della sede considerata?
Concetti di Base
Come avere garanzie su come l’applicazione viene eseguita e come i dati vengono acceduti e
modificati?
La transazione può quindi essere vista come un grafo che ha come etichette:
- dei nodi: i vari stati della BD,
- degli archi: le varie operazioni elementari.
Vengono eseguite n*2 operazioni elementari con n=numero di tuple (cardinalità) della relazione
Film; questo poiché vengono effettuate n operazioni di lettura e scrittura.
Proprietà di Transazioni
Il DBMS garantisce che le transazioni vengano eseguite nel rispetto delle proprietà ACID:
● Atomicità
● Consistenza
● Isolamento
● Durabilità (persistenza)
Le proprietà ACID sono garantite da specifiche sotto-componenti presenti del DBMS, ovvero:
- il gestore della concorrenza
- e il gestore del ripristino
Esempio:
E’ atomica se o effettua entrambe le operazioni (prelievo e versamento) o non ne effettua alcuna:
Supponete di effettuare un prelevamento bancomat dal vostro conto corrente. Quale proprietà
ACID garantisce che il saldo non venga modificato nel caso in cui, per un guasto, non vi venga
erogato l'importo richiesto? Atomicità.
Consistenza
Una transazione deve agire sulla base di dati in modo corretto:
● la transazione trasforma la base di dati da uno stato consistente ad un altro stato
ancora consistente; questo per lo stato iniziale e finale gli stati intermedi sono un
discorso a parse.
● il gestore della concorrenza (concurrency control) garantisce la consistenza
(coadiuvato dal gestore dell’integrità).
Esempio:
E’ consistente se l'importo prelevato è lo stesso versato sul conto (quindi la somma dei saldi è
sempre costante).
Isolamento
L’esito (lo stato generato) di ogni transazione non deve essere influenzato da esecuzioni
concorrenti di altre transazioni:
- una transazione non può, quindi, leggere risultati intermedi di altre transazioni,
deve quindi essere isolata;
- la proprietà di isolamento è assicurata dal gestore della concorrenza.
Esempio:
E’ isolata se il programma non vede gli effetti di altre transazioni che leggono e scrivono sul
conto 123 e sul conto 235, se non sono ancora state concluse.
Supponete di effettuare un prelevamento bancomat dal vostro conto corrente. Nello stesso
momento, supponete che la banca stia addebitando su tutti i conti correnti i costi del trimestre.
Quale proprietà garantisce che alla fine di entrambe le operazioni al vostro saldo siano state
addebitate entrambe le operazioni? Isolamento.
Durabilità o Persistenza
I risultati di una transazione terminata con successo devono essere resi permanenti nella
base di dati nonostante possibili malfunzionamenti del sistema:
- la persistenza è assicurata dal gestore del ripristino.
Esempio:
E’ duratura (persistente) se, terminata la transazione, i conti correnti 123 e 235 riflettono il
trasferimento effettuato.
Supponete di effettuare un prelevamento bancomat dal vostro conto corrente. E' possibile che,
dopo l'erogazione dell'importo richiesto, il saldo non venga immediatamente aggiornato ma che
l'aggiornamento viene effettuato a fine giornata?
No, per la proprietà di persistenza.
Non si tratta di consistenza perché non stiamo parlando di vincoli di integrità.
Transazioni Flat
Esistono diversi tipi di transazioni; il tipo più semplice corrisponde alle transazioni flat.
Esse sono usate in tutti DBMS disponibili in commercio (pur con qualche estensione).
Le loro tecniche di implementazione e limitazioni sono ben note; esse sono transazioni semplici,
di breve durata.
Sono infatti convenienti quando dobbiamo organizzare la nostra logica applicativa con codice
semplice e di breve durata.
1) Terminazione Corretta:
Una transazione termina correttamente quando, dopo aver eseguito tutte le proprie
operazioni, esegue una particolare istruzione SQL, detta COMMIT [WORK]
Vi possono essere inoltre terminazioni non corretta implicite, quando il sistema non è in grado
(ad es. per un guasto o per la violazione di un vincolo) di garantire la corretta prosecuzione della
transazione, che viene quindi abortita.
Anche in questo caso il DBMS deve “disfare”(UNDO) le eventuali modifiche apportate al DB dalla
transazione.
A. Guasto:
Si verifica un guasto e la transazione non può essere portata a termine.
Il guasto avviene ad esempio in seguito a cadute di sistema,
mancanza di corrente o rottura del disco.
B. Violazione di un Vincolo:
Un secondo caso di terminazione non corretta implicita si verifica nel caso di violazione
di vincoli di integrità da parte della transazione, ovvero nel caso in cui uno stato di essa
provochi la violazione dell’integrità della BD.
In presenza di transazioni, la verifica dei vincoli di integrità (CHECK e asserzioni) può
essere gestita in modo flessibile.
Esistono due diverse modalità di controllo che stabiliscono quando effettuare la valutazione del
vincolo e ripristinare la consistenza:
Si può specificare quale utilizzare tramite specifica di comandi appositi nel momento della
dichiarazione del vincolo.
In entrambi i casi, in caso di violazione, la transazione viene abortita.
Nel caso di valutazione immediata la transazione non avrà successo e terminerà con un
ROLLBACK, poiché a metà tra i due inserimenti saremo in uno stato di inconsistenza;
con una valutazione differita la transazione invece va a buon fine, terminando quindi con
COMMIT.
1) Valutazione immediata:
FOREIGN KEY (titolo, regista) REFERENCES Film NOT DEFERRABLE
BEGIN
COMMIT;
Il vincolo differibile non per forza fa sì che la transazione sia differita; si può anche specificare,
come in questo caso, valutazione immediata per una transazione.
BEGIN
COMMIT;
Nonostante vi sia IMMEDIATE nel vincolo andiamo a rendere la transazione differita inserendo in
essa:
BEGIN
In questo caso poniamo già nella chiave esterna la dichiarazione differita della transazione;
BEGIN
COMMIT;
Anche se il vincolo la dichiara come differita, andiamo a renderla immediata attraverso il codice:
BEGIN
COMMIT;
Ricapitolando
1) Con NOT DEFERRABLE tutte le transazioni potranno essere solo immediate.
2) Con DEFERRABLE può essere sia immediata che differita, a seconda di quello che
dichiariamo dopo.
Concorrenza
Sommario:
● Obiettivi gestione della concorrenza
● Anomalie
● Protocolli di locking per la gestione della concorrenza
● Livelli di isolamento
Architettura di Riferimento
Le transazioni possono essere viste come sequenze di operazioni elementari di input/output
(Read/Write) su oggetti astratti (poniamo X, Y, Z), come ad esempio:
● Valori di attributi,
● Tuple,
● Tabelle.
Su disco i dati possono essere solo rappresentati come file su blocchi; per poterli elaborare,
essi devono essere movimentati in memoria centrale.
La lettura e la scrittura possono avvenire solo in termini di blocchi; la memoria centrale contiene
quindi un buffer dove possiamo memorizzare i vari blocchi e suddividerne le varie pagine.
Anomalie
Il Transaction Manager garantisce che transazioni che eseguono in concorrenza non
interferiscano, ovvero siano isolate tra loro.
Ogni transazione pensa di essere l’unica transazione ad essere eseguita in quel momento.
1) Lost Update
T2 legge il valore di X e lo porta in memoria centrale prima che T1 (che lo ha già letto) lo
modifichi, quindi lo riscriva su disco.
Quanto svolto da T2 si basa su un valore di X “intermedio”, e quindi non stabile, infatti la nuova
data viene rimossa dal calendario come effetto del rollback.
Abbiamo quindi una lettura sporca.
Le conseguenze sono impredicibili, dipendono da cosa fa T2; nel nostro caso la conseguenza
immediata è che T2 potrà vedere ma non comprare il biglietto.
3) Unrepeatable Read
Tra le due letture è intervenuta una scrittura dello stesso dato letto; vi è quindi un’anomalia delle
letture irripetibili.
4) Phantom Row
E’ quindi un caso analogo a unrepeatable read ma su insiemi di tuple; vengono infatti eseguite
letture ripetute per una transazione T1, intervallate da una o più scritture per lo stessa tabella in
un’altra transazione.
Si verifica con inserimento o cancellazioni di righe quindi dette fantasma.
Anomalie e Serializzabilità
Schedule concorrente:
● Anomalie,
● Migliori prestazioni.
Schedule seriale:
● No anomalie, ovvero isolamento totale,
● Peggiori prestazioni.
Uno schedule serializzabile è uno schedule che produce lo stesso risultato, ovvero lo stesso
stato della base di dati, che sarebbe prodotto da uno schedule seriale che esegue le stesse
transazioni.
Vediamo solo il primo tipo, più semplice e più usato nei DBMS commerciali.
Protocolli di Locking
Una comune tecnica usata dai DBMS per evitare i problemi visti consiste nell’uso di lock.
I lock - “blocchi” sono un meccanismo comunemente usato dai sistemi operativi per disciplinare
l’accesso a risorse condivise.
Per eseguire un’operazione su una risorsa condivisa è prima necessario “acquisire” un lock
sulla risorsa interessata, ad esempio su una tupla.
Quando T richiede un lock sul dato esso viene accordato a T in funzione della seguente tabella
di compatibilità:
Se T richiede un lock exclusive, se un qualsiasi lock su quel dato è stato concesso allora la
richiesta di lock non viene accordata poiché potrebbe creare anomalie.
allora durante la sua esecuzione non si possono generare anomalie (isolamento totale
transazione).
Un protocollo che adotta questo comportamento per tutte le transazioni, e quindi garantisce
serializzabilità (quindi isolamento totale) si chiama Strong 2-phase locking.
Dopo un lasso di tempo di stallo il sistema decide quindi di abortire una delle due transazioni; in
questo modo la transazione non abortita può proseguire.
In questo caso l’esecuzione corretta richiede che T2 aspetti la terminazione di T1 prima di poter
leggere il valore di X.
Il protocollo di locking non è sufficiente; ci sono diverse soluzioni adottabili, a diversi gradi di
complessità.
Una tra le più semplici consiste nel si acquisiscono i lock non sulle singole tuple ma sulle
tabelle (si cambia la granularità del lock).
Livelli di Isolamento
Il protocollo Strong 2-Phase Locking garantisce:
- schedule serializzabili e quindi transazioni completamente isolate,
- che le anomalie di lost update, unrepeatable read, dirty read (e phantom row con lock su
tabella) non si possano presentare.
Il prezzo da pagare è:
- una maggiore rigidità del sistema,
- un numero potenzialmente elevato di operazioni in conflitto da bloccare, e quindi
potrebbero esserci delay significativi.
SERIALIZABLE
Protocollo di locking utilizzato:
- Strong 2PL
- con acquisizione lock su tabelle e indici (li vedremo nel seguito) per evitare Phantom
rows.
Le transazioni leggono solo modifiche effettuate da transazioni che hanno effettuato il commit.
Nessun valore letto o scritto da una transazione T viene modificato prima che T abbia terminato
la sua esecuzione.
REPEATABLE READ
Protocollo di locking:
- Strong 2PL
- senza lock su tabelle complete.
READ COMMITTED
E’ un livello più flessibile che non si basa sullo Strong 2PL
Protocollo di locking:
● I lock esclusivi vengono mantenuti fino al termine della transazione,
● I lock condivisi vengono acquisiti e rilasciati appena possibile.
READ UNCOMMITTED
Protocollo di locking:
● I lock esclusivi vengono mantenuti fino al termine della transazione,
● I lock condivisi non vengono richiesti.
Si aumenta ulteriormente l’interleaving di transazione poiché si rilassa di molto la modalità con
cui le operazioni di lettura possono essere eseguite in concorrenza.
T può vedere modifiche effettuate da transazioni concorrenti, anche se non hanno ancora
effettuato il commit.
Nessun valore scritto da T può essere modificato da altre transazioni prima che T abbia
terminato.
Non vengono acquisiti lock condivisi prima di effettuare le letture.
Tutte le anomalie di lettura sono possibili (ma non si perdono aggiornamenti – no anomalia di
lost update).
BEGIN [TRANSACTION]
[ISOLATION LEVEL {SERIALIZABLE | REPEATABLE READ | READ COMMITTED | READ
UNCOMMITTED}]
Può anche essere impostato con il comando SET TRANSACTION, per modificare il livello di
isolamento di tutte le transazioni da quel momento in avanti.
SERIALIZABLE:
● Minore concorrenza (aumenta le situazioni di conflitto),
● Non genera alcuna anomalia.
READ UNCOMMITTED:
● aumenta concorrenza (riduce situazioni di conflitto, solo tra operazioni di scrittura e
conseguenti “blocchi”)
● Può generare un numero elevato di anomalie, tutte quelle possibili in lettura.
Spesso READ COMMITTED è il giusto compromesso.
Le scritture vengono gestite in accordo al 2PL strong; i lock dovranno essere quindi sempre
rilasciati al termine delle transazioni.
E’ quindi un livello di isolamento interessante ma che non garantisce la piena serializzabilità delle
transazione.
Viene adottato da PostgreSQL.
Ripristino
Sommario:
● Obiettivi gestione del ripristino,
● Ripristino basato sui file di log,
● Problemi prestazionali ripristino,
● Ripristino per media failure.
Malfunzionamenti
I possibili malfunzionamenti possono essere classificati in 3 gruppi:
1. Transaction failure: è il caso in cui una transazione abortisce per sua scelta
(ROLLBACK) gli effetti della transazione sul DB devono essere annullati.
2. System failure: il sistema ha un guasto hardware o software che provoca l’interruzione
di tutte le transazioni in esecuzione, senza però danneggiare la memoria permanente
(dischi) spesso dovuto a problemi a memoria volatile (memoria principale e cache).
3. Media failure: il contenuto (persistente) della base di dati viene danneggiato problemi a
memoria non volatile (dischi, nastri).
Se si verifica un malfunzionamento si passa prima allo stato failed e poi allo stato aborted.
I due stati finali possibili sono quindi committed e aborted, a seconda che sia o no andato tutto
bene.
Osservazioni
1) In caso di Abort, eventuali scritture esterne osservabili (cioè scritture che non possono
essere "cancellate", ad es. su terminale o stampante) eseguite dalla transazioni non
possono essere eliminate.
2) Dopo il rollback di una transazione, il sistema ha due possibilità:
● rieseguire la transazione: ha senso solo se la transazione è stata abortita per
system failure o media failure,
● eliminare la transazione: se si verificano transaction failures che possono
essere corretti solo riscrivendo il programma applicativo.
Esempio:
Consideriamo una transazione che agisce sul saldo di due conti correnti A e B; la transazione
sposta 10k da un conto all’altro:
A questo punto:
- Si riesegue T:
- Non si riesegue T:
In entrambi i casi lo stato risultante è inconsistente poiché abbiamo modificato la base di dati
prima di avere la certezza che la transazione avrebbe terminato con successo.
Di fatto la transazione non era atomica, lo stato non è stato riportato a quello prima dell’inizio
della transazione; vediamo come.
File di log:
- E’ un file, anche se nell'implementazione effettiva possono essere usati più file fisici,
- in cui attraverso scritture sequenziali (ovvero un record), si memorizzano informazioni
minimali per ogni modifica di blocco/pagina eseguita dalla transazione;
- e anche le informazioni di inizio (BEGIN) e fine transazioni (COMMIT/ROLLBACK);
- per semplicità nel seguito assumiamo che un blocco corrisponda a un singolo record.
Esempio:
Record diversi hanno colori diversi in base alla transazione a cui si riferiscono.
Prima di scrivere su disco una pagina P modificata, il corrispondente record di Log deve essere
già stato scritto nel file di Log.
In questo caso non ci sarebbe alcun modo di riportare il DB allo stato iniziale e si perdono
quindi atomicità e durabilità.
La persistenza infatti viene garantita solo quando la transazione ha terminato la sua
esecuzione con successo (ha eseguito COMMIT) e tutti i record di log sono stati scritti su
memoria stabile; a quel punto la transazione può passare nello stato committed.
Lo stato corrente della base di dati su disco riflette solo le azioni delle transazioni il cui
effetto è già stato reso persistente;
Se guardiamo quindi solo le informazioni presenti su disco non abbiamo il quadro completo di
cosa sta succedendo; per averlo dobbiamo combinarle con quelle presenti nei log.
La responsabilità di garantire il rispetto del protocollo WAL è del Buffer Manager, che gestisce il
buffer del DB e il buffer del Log (maggiori dettagli in seguito).
Quando una transazione è in partially commit potrebbe essere arrivata a COMMIT ma qualche
operazione potrebbe non aver elaborato fatto il log, siamo in uno stato di failed.
A quel punto si passa allo stato aborted dove viene ripristinato lo stato della BD prima dell’inizio
della transazione, garantendo atomicità.
Quando arriviamo invece allo stato commit vuol dire che tutti le modifiche della BD sono state
registrate nei log.
Implementazione Del Protocollo Wal
Passo 3 viene sempre eseguito prima del Passo 4; vi sono però due domande ancora irrisolte:
1) Quando eseguire Passo 4 rispetto a quando la pagina P viene modificata nel buffer?
Politica No-Steal: Si mantiene la pagina P nel buffer e si attende che T abbia eseguito COMMIT
prima di scriverla su disco (copia su disco dal momento del COMMIT, non prima).
Ritarda l’aggiornamento della base di dati su disco fino a che non siamo certi che la transazione
sia andata a buon fine.
Peggiora gestione del buffer: si rischia di esaurire lo spazio a disposizione in memoria centrale.
Politica Steal: Si scrive P su disco quando più conviene (ovvero quando è necessario per
liberare il buffer o per ottimizzare le prestazioni di I/O), eventualmente anche prima della
terminazione di T.
Politica Force: Prima di scrivere il record di COMMIT sul Log si forza la scrittura su disco di
tutte le pagine modificate da T.
Politica Steal: Alcune pagine modificate da una transazione potrebbero già essere state scritte
su disco
E’ necessario abortire la transazione ed eseguire un’operazione di UNDO di T.
Siamo quindi nel caso di un log a modifiche immediate: si scandisce il Log a ritroso (usando i
prevLSN) e si ripristinano nel DB le before(P) delle pagine P modificate da T.
System Failure
Si verifica un problema alla memoria centrale (ROLLBACK di sistema).
1) La transazione T è nello stato committed (ha già scritto COMMIT nel file di log); tutte le
modifiche sono state rese persistenti su disco:
● Politica No-Force: non è detto che tutte le modifiche operate da T siano state
riportate su disco.
Si esegue quindi un REDO di T; si scandisce quindi il Log a ritroso (usando i
prevLSN) e si ripristinano nel DB le before(P) delle pagine P modificate da T.
● Politica Force: tutte le modifiche operate da T sono già state riportate su disco;
non si fa quindi mai REDO di T.
Checkpoint
Per migliorare le prestazioni, periodicamente si può eseguire un checkpoint, ovvero una
scrittura forzata su disco delle pagine modificate.
In caso di system failure, se T ha eseguito COMMIT prima del checkpoint, si è sicuri che per T
non si dovrà eseguire REDO in caso di guasto.
Dati Derivati
In SQL è possibile:
● Creare una colonna con contenuto derivato
● Creare una relazione con schema e contenuto iniziale derivato
● Creare una relazione derivata (virtuale), ovvero le viste.
Colonne Derivate
Vogliamo modificare la tabella Noleggio, aggiungendo la durata in giorni; La durata può essere
derivata, e quindi utilizziamo l’istruzione GENERATED (e non aggiungiamo a mano una nuova
colonna):
Ogni volta che inseriremo nuove tuple nel noleggio il valore di questa colonna verrà aggiornato
automaticamente.
Relazioni Derivate
Vogliamo creare una nuova relazione ClienteV (vip) con lo stesso schema di Cliente più una
colonna bonus:
Creiamo una nuova tabella il cui schema è analogo a quello di cliente; in questo modo copiamo
la struttura di cliente e aggiungiamo un ulteriore attributo.
I vincoli non vengono copiati.
L’istanza non viene copiata (allo stato iniziale non ci sono tuple).
La nuova tabella non ha alcun collegamento con quella di partenza; le modifiche sono locali alle
due tabelle e non si propagano in nessun modo.
oppure
Esempio:
Viste (View)
In alcune situazioni creare una tabella con schema e contenuto iniziale derivato non basta.
Ad esempio, supponiamo che un nuovo socio della videoteca abbia necessità di gestire (vedere,
manipolare) solo i noleggi dei clienti over 65:
Serve un meccanismo per legare l’istanza di Noleggi Over65 e la query che ne definisce
l’istanza iniziale; questo concetto sono le viste.
Una vista è:
Esempio:
CREATE VIEW Over65 AS
SELECT *
FROM Noleggio NATURAL JOIN Cliente
WHERE (CURRENT_DATE – dataN) YEAR >= 65
La vista può poi essere usata come una normale relazione SELECT * FROM Over65.
Il contenuto della vista Over65 corrisponderà in ogni momento al risultato della
sottointerrogazione.
Comando di Creazione
CREATE VIEW <nomeVista> [(<listaColonne>)]
AS <interrogazione> [WITH [{LOCAL| CASCADED}] CHECK OPTION];
Con:
● listaColonne: lista di nomi da assegnare alle colonne della vista;
Ci permette di ridenominare gli attributi ritornati dall’interrogazione.
Obbligatoria solo se l'interrogazione contiene colonne virtuali senza nome nella
clausola di proiezione;
● interrogazione: interrogazione di definizione, non ci sono restrizioni sulla forma.
Le colonne della vista corrispondono in numero e dominio a quelle nella clausola di proiezione
dell’interrogazione.
Esempio:
Vista che restituisce codice cliente, data di inizio noleggio e collocazione dei video in noleggio da
più di tre giorni:
Un'alternativa è:
Esempio: Vista che restituisce il numero di telefono del cliente, la data di inizio noleggio, il titolo
del film e l'importo dovuto per i video in noleggio da più di tre giorni (supponendo che la tariffa
giornaliera di noleggio sia 5 Euro).
I nomi delle colonne della vista sono telefono, dataNol, titolo e importo.
Esempio:
Vista che per ogni cliente visualizza il nome, la residenza, l'anno di nascita, il numero di noleggi
effettuati e la durata massima di tali noleggi:
I nomi delle colonne della vista sono nome, residenza, annoN, numNol, durataM.
Tutte le operazioni però subiscono forti restrizioni facili da capire se ci si ricorda che una vista
corrisponde a una query.
Interrogazioni su Viste
Una vista può essere utilizzata nelle interrogazioni.
Esempio:
Determinare numero di telefono del cliente che ha il maggior debito con la videoteca,
relativamente ai video in prestito da più di tre giorni.
Utilizziamo la vista InfoNol3gg:
Nota: aggreghiamo i debiti di clienti diversi che hanno dato lo stesso recapito telefonico alla
videoteca (stessa famiglia).
Problemi
Restrizione: non è ammesso l’uso di funzioni di gruppo su colonne di viste che sono a loro
volta definite tramite funzioni di gruppo.
Motivazione:
- la valutazione di un'interrogazione Q su una vista può essere effettuata componendo Q
con l'interrogazione di definizione,
- in SQL non è possibile applicare funzioni di gruppo in cascata.
Aggiornamenti su Viste
SE possibile, l'esecuzione di un'operazione di aggiornamento su una vista viene
propagata alla relazione su cui la vista è definita.
Esempio Modifica
Una modifica a una colonna di una vista viene scaricata sulla modifica alla colonna
corrispondente nella relazione di base.
Se la colonna della vista è virtuale (definita da un’espressione) non è sempre possibile
stabilire su quale colonna della relazione di base agire e/o quale valore assegnarle;
Esempio:
Esempio Inserimento
Un inserimento in una vista viene scaricato su un inserimento nella relazione di base.
Se la vista non contiene una colonna della relazione di base su cui è specificato un
vincolo NOT NULL e non è specificato nello schema un valore di default:
1. il comando di inserimento sulla vista non specifica un valore per la colonna,
2. la colonna è obbligatoria e senza valore di default,
3. non si può effettuare l’inserimento nella relazione di base.
Esempio:
CREATE VIEW ClientiConNoleggiAperti AS
SELECT codCli, dataNol FROM Noleggio WHERE dataRest IS NULL;
Esempio Cancellazione
Una cancellazione da una vista viene scaricata su una cancellazione dalla relazione di base.
Se la vista è definita come join di più relazioni di base si può cancellare da una delle relazioni di
base (quale?); cancellare da tutte le relazioni di base vuol dire porre a NULL il valore
dell'attributo di join in una o più relazioni.
Esempio:
Considero R ed S:
Creo una vista VV su R ed S:
CREATE VIEW V AS SELECT * FROM R NATURAL JOIN S
Aggiornamenti Su Viste
Le restrizioni indicate sono sufficienti affinché le operazioni di aggiornamento della vista
possano essere scaricate in maniera univoca su aggiornamenti della relazione di base; non
sono però necessarie.
Lo standard ammette anche aggiornamenti su viste la cui interrogazione di definizione contiene
più di una relazione purché sia possibile stabilire una corrispondenza uno a uno tra le tuple della
vista e le tuple delle relazioni di base.
I DBMS commerciali ammettono al più aggiornamenti su viste contenenti un'unica
relazione nell'interrogazione di definizione.
Espansione di Viste
Il sistema espande le viste nel modo più semplice possibile, che spesso coincide con
l’ampliamento delle condizioni del WHERE.
Esempio:
Date le relazioni R(A,B) e S(B,C), la vista V1 definita come
V1 = SELECT * FROM V2 WHERE C < 18
e la vista V2 definita come
V2 = SELECT * FROM R NATURAL JOIN S
come viene riscritta la vista V1 dal sistema?
Esempio:
Si consideri la seguente vista definita su due tabelle di base R(A,B,C) e S(C,D,E):
CREATE VIEW V AS
SELECT D, MAX(A) AS M
FROM R NATURAL JOIN S
WHERE E = 6
GROUP BY D;
SELECT *
FROM V
WHERE D > 5
SELECT D, MAX(A)
FROM R NATURAL JOIN S
WHERE D > 5 AND E = 6
GROUP BY D;
Ulteriore problema
Consideriamo:
Quindi:
● la data di noleggio specificata è oggi,
● la condizione nell’interrogazione (noleggio iniziato almeno tre giorni fa) non è
verificata dalla nuova tupla,
● le restrizioni per l’inserimento sono soddisfatte,
● la tupla viene inserita in Noleggio ma non apparterrà alla vista Nol3gg.
Per assicurare che le tuple siano inserite/modificate tramite una vista solo se verificano la
condizione nella sua interrogazione di definizione, si usa la clausola CHECK OPTION del
comando CREATE VIEW.
CHECK OPTION
Con questa clausola specifichiamo che nel momento in cui eseguiremo un inserimento, esso
sarà effettuato solo se la tupla inserita soddisfa la condizione di definizione della vista.
Nell’esempio precedente, se Nol3gg è definita come:
L’inserimento di tuple che non soddisfano l’interrogazione di definizione della vista, come (6635,
CURRENT_DATE, 1128), non è permesso.
La situazione si complica ulteriormente nel caso di viste definite in termini di altre viste ognuna di
tali viste potrebbe o meno essere definita con CHECK OPTION.
La CHECK OPTION può essere specificata con due possibili alternative: LOCAL oppure
CASCADED (default).
La differenza tra LOCAL e CASCADED è rilevante nei casi in cui una vista è definita in
termini di un'altra vista.
Local
Vediamo un esempio:
- V1 è creata a partire da V2,
- V2 è creata da R.
Con local ogni CHECK OPTION vale solo per quella determinata relazione.
Cascaded
Vediamo un esempio:
- V1 è creata a partire da V2,
- V2 è creata da R.
Introduzione
La gestione degli accessi è verificata dal sistema attraverso il Query Manager e la sua
sottocomponente: il gestore degli accessi.
Non tutti gli utenti di un sistema di basi di dati possono eseguire le stesse operazioni:
● Il gestore della videoteca può eseguire tutte le operazioni su tutte le tabelle della base di
dati,
● Il cliente della videoteca può solo leggere le tabelle della base di dati.
Il controllo dell’accesso regola le operazioni che si possono compiere sui dati (e altre risorse) in
una base di dati.
Scopo:
● limitare e controllare le operazioni che gli utenti effettuano,
● prevenire azioni accidentali o deliberate che potrebbero compromettere la correttezza e
la sicurezza dei dati.
La gestione delle autorizzazioni può essere oltremodo complessa per questo il DBMS fornisce
strumenti per realizzare le protezioni, che sono definite dall’amministratore della base dati (DBA).
Questi strumenti vengono utilizzati per specificare la politica del controllo dell’accesso.
Il DBA ha il compito di conferire agli utenti i “giusti” privilegi, utilizzando particolari comandi
SQL, attraverso il:
- Data Control Language: estende lo Storage Definition Language (SDL) con altri
comandi utili all’amministrazione della base di dati, inclusa la gestione dei privilegi.
Concetti Fondamentali
La specifica delle politiche di sicurezza si basa su tre entità fondamentali:
● oggetti: risorse a cui vogliamo garantire protezione,
● soggetti: entità “attive” che richiedono di poter accedere agli oggetti,
● privilegi: determinano le operazioni che i soggetti possono fare sugli oggetti.
Le autorizzazioni sono triple di (oggetti, soggetti, privilegi) e sono mantenute nel database su
disco all’interno di cataloghi.
Il controllo dell’accesso è effettuato mediante il gestore degli accessi, detto anche reference
monitor, che:
- intercetta ogni comando inviato al DBMS,
- stabilisce, tramite l'analisi delle autorizzazioni, se il soggetto richiedente può essere
autorizzato a compiere l'accesso richiesto.
Principi Generali
Descriviamo come gestire le autorizzazioni.
Il modello realizza una politica di tipo discrezionale adottando il paradigma di sistema chiuso:
- un accesso è concesso solo se esiste un'esplicita autorizzazione per esso, ovvero se
esiste una terna che lo specifica.
Nei DBMS relazionali, le autorizzazioni possono essere specificate, quindi concesse, e revocate
tramite comandi SQL:
- comando GRANT: concede privilegi su una risorsa a uno o più utenti
- comando REVOKE: toglie a uno o più utenti i privilegi che erano stati loro concessi.
Ogni richiesta di esecuzione di comando SQL deve poi essere autorizzata, ovvero l’utente che
esegue l’operazione deve avere i privilegi necessari.
Un principio fondamentale è che un utente che ha ricevuto un certo privilegio può a sua volta
accordarlo ad altri utenti solo se è stato esplicitamente autorizzato a farlo.
Vengono utilizzati opportuni cataloghi per memorizzare le autorizzazioni e verificare gli accessi.
Comandi
Comando GRANT
L’inserimento di una nuova autorizzazione nel sistema e quindi la concessione di nuovi privilegi
avviene tramite il comando GRANT.
● <nome oggetto>: indica il nome della risorsa della base di dati su cui sono concessi i
privilegi (ad esempio, una tabella).
● <lista privilegi>: indica l'insieme dei privilegi concessi con il comando GRANT la parola
chiave ALL PRIVILEGES indica tutti i privilegi previsti dal modello.
Esempio:
Supponiamo che tutte le tabelle della base di dati della videoteca siano state create dall’utente
luca:
Comando REVOKE
Un utente può revocare solo i privilegi da lui stesso concessi.
È possibile revocare più privilegi con un unico comando di REVOKE.
Un unico comando di REVOKE può essere utilizzato per revocare gli stessi privilegi sulla stessa
relazione ad utenti diversi.
<nome oggetto>: indica il nome dell'oggetto della base di dati su cui sono revocati i privilegi.
La clausola opzionale GRANT OPTION FOR serve per revocare la sola grant option,
mantenendo il diritto ad esercitare i privilegi oggetto del comando di revoca.
La revoca può essere richiesta con o senza cascata:
● revoca senza cascata (RESTRICT): l'esecuzione del comando non viene concessa se
questo comporta la revoca di altri privilegi, oppure la cancellazione di oggetti dello
schema (ad esempio viste, lo vedremo in seguito): valore di default
● revoca con cascata (CASCADE): revoca anche tutti i privilegi che erano stati propagati,
generando una reazione a catena ed eventuali elementi della base di dati che erano stati
creati sfruttando questi privilegi
Esempio:
Gestione dell’accesso
Le informazioni sull'insieme di autorizzazioni correntemente presenti nel sistema sono
memorizzate in cataloghi di sistema.
L’organizzazione di queste tabelle non segue uno standard, ogni sistema utilizza il proprio
schema.
Il gestore dell’accesso:
- durante l’esecuzione di comandi di GRANT e REVOKE, modifica opportunamente il
contenuto dei cataloghi (inserendo, aggiornando o modificando tuple)
- durante l’esecuzione di altri comandi SQL, legge il contenuto dei cataloghi per stabilire se
il comando può essere eseguito.
Le informazioni contenute nei cataloghi possono essere rappresentate in astratto come terne
oppure come un insieme di grafi, chiamati grafi delle autorizzazioni.
Esiste un grafo per ogni privilegio su una certa tabella.
Esempi:
Esempio:
barbara: GRANT select ON Film TO matteo;
Esempio:
matteo: GRANT select ON Film TO elena;
Matteo non ha acquisito il privilegio con grant option, quindi non lo può delegare.
Il grafo non cambia.
Esempio:
marina: GRANT select, insert ON Film TO Paolo;
Marina ha acquisito il privilegio INSERT con grant option, quindi lo può delegare, ma non ha
acquisito il privilegio di SELECT, quindi non lo può delegare (esecuzione parziale del comando).
Nuovi grafi:
Esecuzione del Comando Revoke
Il gestore dell’accesso, durante l’esecuzione di un comando di REVOKE:
1. Verifica che l’utente u che esegue il comando abbia i diritti per poterlo eseguire,
leggendo i cataloghi (presenza di arco uscente da u nel grafo).
2. Se l’utente può eseguire il comando di REVOKE, le autorizzazioni revocate vengono
cancellate dai cataloghi (cancellazione dell’arco dal grafo).
3. In alcuni casi il comando può essere eseguito solo parzialmente:
● solo alcune autorizzazioni tra quelle richieste vengono modificate o cancellate
● dipende anche anche dalla modalità di REVOKE (RESTRICT/CASCADE).
Poiché il privilegio di select era stato concesso a Paolo senza GRANT OPTION, Paolo non può
averlo delegato ad altri e quindi l’arco tra Barbara e Paolo si può rimuovere:
Il privilegio è stato concesso a Barbara a Marina con GRANT OPTION e Marina lo ha a sua volta
concesso ad Anna. Quindi sarebbe necessario rimuovere due archi dal grafo, ma poiché la
REVOKE è RESTRICT, questo non è possibile e il grafo non cambia.
Il privilegio è stato concesso a Barbara a Marina con GRANT OPTION e Marina lo ha a sua volta
concesso ad Anna. Quindi è necessario rimuovere due archi dal grafo, poiché la REVOKE è
CASCADE, questo è possibile e il grafo diventa:
Fonti Indipendenti
La situazione si può verificare quando un utente riceve lo stesso privilegio da più fonti
indipendenti tra loro.
luca: REVOKE select ON Film FROM barbara;
Dopo la revoca Barbara mantiene ancora tale privilegio, grazie all'autorizzazione concessale da
Elena.
Barbara non potrà però più concedere a terzi il privilegio select su Film (l’autorizzazione acquisita
da Elena non prevede grant option).
Barbara perde grant option e quindi in cascata vengono cancellati tutti gli altri privilegi concessi
da Barbara.
Un ultimo esempio:
Supponiamo che Luca revochi il privilegio SELECT concesso a Giovanna, con modalità
CASCADE.
Questo non comporterebbe la revoca del privilegio concesso da Giovanna a Matteo, in quanto
Giovanna continua ad avere il privilegio SELECT con grant option sulla relazione Film
concessole da Barbara.
Controllo dell’Accesso basato sui Ruoli
Nel modello di controllo dell’accesso di SQL, i soggetti possono rappresentare ruoli.
Le autorizzazioni specificate per un ruolo sono quelle necessarie per esercitare le funzioni
connesse al ruolo stesso.
Ogni utente che ricopre un ruolo acquisisce tutte le autorizzazioni ad esso connesse.
Vantaggi:
● controllo dell’accesso più flessibile,
● possibilità che un utente ricopra ruoli diversi in momenti diversi,
● semplificazione dell’attività di amministrazione.
I comandi GRANT e REVOKE possono essere utilizzato anche per gestire autorizzazioni riferite
ai ruoli.
Esempio:
Concessione privilegi ai ruoli con concessione dei privilegi.
Esempio:
Il ruole direttoreVideoteca è concesso a Luca, che a sua volta può delegarlo (ADMIN OPTION).
Dati due ruoli r1 ed r2, r1≥r2 implica che r1 eredita tutti i privilegi assegnati ad r2, quindi tutti gli utenti
associati ad r1 sono anche utenti associati ad r2.
Il ruolo r1 (più in alto nella gerarchia) può effettuare sugli oggetti del sistema tutte le
operazioni che possono essere effettuate da ruoli corrispondenti a funzioni più in basso (r2)
nella gerarchia.
Se r1≥r2, possiamo anche dire che gli utenti con ruolo r1 sono abilitati a rivestire anche il ruolo r2.
Esempio:
I privilegi attribuiti al commesso vengono ereditati dal direttoreVideoteca (il direttore può svolgere
tutte le attività che svolge un commesso ma il viceversa non è vero).
Agli utenti con ruolo direttoreVideoteca viene quindi implicitamente attribuito anche il ruolo
commesso.
3. Gerarchie tra Ruoli:
Per ogni ruolo ogni ruolo r1 in e per ogni ruolo r2 in , il comando impone r1≥r2.
Esempio:
CREATE ROLE direttoreVideoteca;
CREATE ROLE commesso;
GRANT commesso to direttoreVideoteca;
Esempio:
REVOKE delete ON Clienti FROM direttoreVideoteca;
Elimina il ruolo:
REVOKE direttoreVideoteca FROM amministratoreVideoteca;
È però sufficiente definire una vista che selezioni le tuple di interesse e autorizzare l'accesso alla
vista invece che alla relazione di base.
Esempio:
Barbara esegue il comando (quando lo può fare?)
Barbara concede il privilegio di SELECT sulla vista Commedie a Marina (quando lo può fare?)
Privilegi Statistici
Le viste permettono di concedere anche privilegi statistici: ad esempio, un utente potrebbe non
essere autorizzato a vedere i titoli dei film noleggiati da ciascun cliente, ma solo il numero di
noleggi effettuati;
Basta definire una vista che computa il numero di noleggi effettuati da ogni cliente e concedere
all'utente l'accesso alla vista invece che alle relazioni di base.
Le viste consentono di realizzare il così detto controllo dell'accesso in base al contenuto.
Un utente può creare una vista solo se ha il privilegio SELECT sulle relazioni/viste su cui è
definita.
Esempio:
Barbara ha solo il privilegio SELECT su Film, con GRANT OPTION
P1 INTERSECT P2 = {SELECT}, quindi Barbara può esercitare il privilegio SELECT sulla vista.
Barbara può concedere a terzi tale privilegio, in quanto possiede GRANT OPTION su SELECT.
Esempio:
Elena ha i privilegi SELECT, INSERT e UPDATE sulla relazione Noleggi, con GRANT OPTION.
P1 INTERSECT P2 = {SELECT}, quindi Elena può esercitare solo il privilegio SELECT sulla
vista.
Elena può concedere a terzi tale privilegio, in quanto possiede GRANT OPTION.
Per estendere il potere espressivo di SQL si combina SQL con un linguaggio di programmazione
generico.
● SQL consente accesso ottimizzato ai dati
● il linguaggio di programmazione garantisce completezza computazionale ed
operazionale
Viene richiesto un buon livello di integrazione tra SQL ed il linguaggio general purpose
preposto.
Vi sono due approcci possibili per farlo:
Accoppiamento Interno
Estensioni procedurali di SQL.
Si estende SQL con costrutti standard nei linguaggi di programmazione.
Si usa per definire ed eseguire funzioni e procedure all’interno del DB:
- richiamabili internamente o da qualunque applicazione,
- riuso/centralizzazione di manutenzione e verifica,
- approccio misto all’accoppiamento.
Creazione Routine
CREATE [ OR REPLACE ] FUNCTION <nomeRoutine> ( [<listaParametri>] )
[ RETURNS <tipoRitorno> ]
AS
$$ <corpoRoutine> $$
LANGUAGE plpgsql ;
La lista dei parametri è costituita da vari parametri a loro volta costituiti da:
<parametro> := [<modo>] [<nome>] <tipo>
Quindi:
- RETURNS presente: si parla di funzione (oppure se ci sono parametri OUT o INOUT),
- RETURNS assente: si parla di procedura (se non ci sono parametri OUT o INOUT).
Infine corpoRoutine è il corpo della routine, organizzato in blocchi; tutto il codice deve essere
racchiuso tra $$ e $$.
plpgsql è un linguaggio ed è l’unico che vedremo.
Chiamata di Funzione
Una chiamata di funzione può comparire in ogni posizione in cui può comparire [un insieme
formato da] una singola tupla, ovvero in un comando SQL o in una istruzione PL/pgSQL.
La sintassi è la seguente:
<nomeFunzione> (<listaArgomenti>);
listaArgomenti è una lista di argomenti; vengono solo inseriti i parametri in input e non quelli in
output; ogni parametro deve ovviamente avere tipo conforme con il parametro formale
contenuto nella dichiarazione.
La chiamata di una funzione restituisce un valore conforme al tipo di ritorno.
Se la clausola RETURNS non è presente (o è indicato RETURNS record), viene restituito un
record costituito dai valori assegnati ai parametri OUT e INOUT nell’ordine in cui sono definiti.
Il risultato di una funzione è quindi sempre una tupla formata da:
- un solo elemento, rappresentato dal valore di ritorno, se non ci sono parametri OUT e
INOUT,
- i valori assegnati a parametri OUT e INOUT, nell’ordine con cui compaiono nella
segnatura.
Esempio 1:
SELECT AggiornaVal1(‘drammatico’);
aggiorna la valutazione dei film di genere drammatico e restituisce tabella vuota;
Esempio 2:
CREATE FUNCTION AggiornaVal2 (IN ilGenere CHAR(15) )
RETURNS NUMERIC(3,2) AS
$$ <<aggiorna la valutazione dei film di genere uguale ad ilGenere e restituisce valutazione
media dei film con quel genere> $$
LANGUAGE plpgsql;
Esempio 3:
CREATE FUNCTION AggiornaVal3
(IN ilGenere CHAR(15), OUT val NUMERIC(3,2) ) AS
$$ <aggiorna la valutazione dei film di genere uguale ad ilGenere e restituisce valutazione media
dei film con quel genere nel parametro di output val> $$
LANGUAGE plpgsql;
Esempio 4:
CREATE FUNCTION AggiornaVal4 (IN ilGenere CHAR(15), OUT val NUMERIC(3,2), OUT
minAnno INTEGER )
AS
$$ <aggiorna la valutazione dei film di genere uguale ad ilGenere e restituisce:
(1) valutazione media dei film con quel genere nel parametro di output val
(2) l’anno di produzione del film più vecchio, con genere ilGenere, nel parametro di output
minAnno> $$
LANGUAGE plpgsql;
Sezione di Dichiarazione
Sintassi:
DECLARE <listaDichiarazioni>
Dove:
● CONSTANT: <nome> non può essere riassegnata (costante).
Possibili errori:
● assegnare ‘cinque’ a valutaz
● assegnare qualsiasi cosa a genere
● assegnare NULL a codCli
Sezione di Esecuzione
Contiene costrutti procedurali e statement SQL che comunicano fra loro usando le variabili
precedentemente dichiarate.
In SQL le variabili possono essere usate in:
● clausola WHERE di statement SELECT, DELETE, UPDATE
● clausola SET di uno statement di UPDATE
● clausola VALUES di uno statement di INSERT
● clausola INTO di uno statement SELECT (si veda oltre)
Nella parte procedurale ad una variabile può essere assegnato un valore ottenuto da una query.
Assegnazione
Sintassi:
<nome var> := <espressione>
Esempio:
DECLARE ilGenere CHAR(15);
BEGIN
ilGenere := ’comico’;
…
ilGenere := (SELECT genere FROM Film WHERE titolo = ‘pulp fiction’ AND regista =
‘quentin tarantino’);
…
END;
La query deve essere scalare, ovvero restituire un singolo valore di quel tipo.
Stampe in Output
Sintassi:
RAISE NOTICE <stringa> [, <espressione>]
Dove <stringa> viene visualizzata nella zona ‘Messaggi’ dell’output e può essere:
- normale,
- parametrica: se contiene %, a video % viene sostituito con il valore di <espressione>
Esempio:
DECLARE ilGenere CHAR(15);
BEGIN
ilGenere := ’comico’;
RAISE NOTICE ‘Il genere considerato e`` %’, ilGenere;
…
END;
Esempio:
DECLARE
ilTitolo VARCHAR(30);
ilRegista VARCHAR(20);
BEGIN
ilTitolo := ‘Pulp Fiction’;
ilRegista := ‘quentin tarantino’;
UPDATE Film
SET valutaz = valutaz * 1.1
WHERE titolo = ilTitolo AND regista = ilRegista;
END;
Per comandi SELECT che restituiscono esattamente una tupla, i valori degli attributi della
tupla possono essere inseriti direttamente in variabili utilizzando lo statement:
SELECT … INTO …
Esempio:
DECLARE
laValutaz NUMERIC(3,2);
BEGIN
[...]
SELECT valutaz INTO laValutaz
FROM Film
WHERE titolo = ‘Mediterraneo‘ AND regista =
‘gabriele salvatores’;
END;
Se la query restituisce più di una tupla, nelle variabili vengono inseriti i valori degli
attributi della prima tupla restituita; se la query restituisce un insieme vuoto, viene assegnato
NULL alle variabili.
Se si vuole che in entrambi i casi precedenti venga generato un errore, è necessario utilizzare
la parola chiave STRICT.
oppure:
IF <expr booleana>
THEN <istruzioni>
[ELSEIF <expr booleana> THEN <istruzioni> ]
END IF;
Iterazione certa perché le due espressioni sono valutate una sola volta prima di iniziare
l’esecuzione
var indice = variabile di tipo intero automaticamente dichiarata dal ciclo e inizializzata con la
prima espressione ha il ciclo come scope; è automaticamente incrementata del passo ad ogni
ciclo; se REVERSE è specificata decrementata quando raggiunge il valore della seconda
espressione si esegue il ciclo per l’ultima volta.
Cursori
Se il comando SELECT restituisce un insieme di tuple spesso è necessario iterare sul
risultato; per farlo si usano i cursori.
Un cursore è un puntatore ad una tupla contenuta nel risultato di un’interrogazione SQL;
esso è quindi associato alla valutazione di un’interrogazione.
In ogni momento, la tupla puntata dal cursore, e quindi i valori degli attributi di tale tupla, possono
essere letti.
Apertura:
OPEN <nome cursore>
Esegue l’interrogazione associata al cursore e lo inizializza prima della prima tupla del risultato.
Posizionamento:
Ci sono molte possibili opzioni (vedi manuale), la più comune è l’avanzamento:
FETCH <nome cursore> [INTO <lista variabili>]
Che posiziona il cursore sulla tupla del risultato successiva a quella corrente.
Sposta il cursore avanti o indietro ed opzionalmente legge i valori della tupla di arrivo e li
assegna.
Con l’opzione INTO è possibile inserire in variabili i valori degli attributi della tupla puntata dal
cursore dopo l’avanzamento:
● se tale tupla non esiste vengono assegnati dei null
● il numero di variabili deve corrispondere al numero di colonne della query associata al
cursore
● ad ogni variabile dell'elenco, da sinistra a destra, è assegnato il valore della colonna
corrispondente come posizione
● il tipo di dato di ogni variabile deve essere lo stesso o compatibile con quello della
colonna corrispondente.
Per verificare lo stato di un’operazione, è disponibile la variabile FOUND:
● Inizialmente è posta a FALSE,
● Viene posta a TRUE dalle seguenti operazioni:
○ SELECT INTO, se viene restituita una tupla,
○ UPDATE, INSERT, DELETE, se almeno una tupla viene aggiornata,
○ FETCH, se dopo lo spostamento il cursore punta ad una tupla (non ha quindi
raggiunto la fine del result set),
○ Ciclo FOR, se viene eseguita almeno una iterazione.
Chiusura:
CLOSE <nome cursore>
Disabilita il cursore:
● se si vuole usarlo dopo averlo chiuso è necessario riaprirlo,
● se lo si riapre si riesegue la query.
Esempio d’uso:
DECLARE
ilTitolo VARCHAR(30);
ilRegista VARCHAR(20);
valElevata CURSOR FOR
SELECT titolo, regista FROM Film WHERE valutaz > 3.00;
BEGIN
[...]
OPEN valElevata;
FETCH valElevata INTO ilTitolo, ilRegista;
SQL Dinamico
PL/pgSQL permette di eseguire comandi creati durante l’esecuzione dell’applicazione.
Sintassi:
EXECUTE <espressione di tipo stringa>
[ INTO [STRICT] <lista variabili> ]
<espressione di tipo stringa>: sarà il codice SQL da eseguire (non può contenere SELECT
INTO).
Esempio:
DECLARE
ilTitolo VARCHAR(30);
ilRegista VARCHAR(20);
ilComando VARCHAR(100) := ’SELECT AVG(valutaz) FROM Film’;
BEGIN
[...]
IF (ilRegista IS NOT NULL) THEN
ilComando := ilComando ||’ WHERE regista=ilRegista’;
IF (ilTitolo IS NOT NULL) THEN
ilComando := ilComando ||’ AND titolo=ilTitolo’;
END IF;
ELSEIF (ilTitolo IS NOT NULL) THEN
ilComando := ilComando ||’ WHERE titolo=ilTitolo’;
END IF;
EXECUTE ilComando;
END;
Gestione degli Errori
Gli errori si gestiscono tramite il meccanismo delle eccezioni.
Le eccezioni possono essere catturate all’interno di un qualunque blocco.
BEGIN
<istruzioni>
EXCEPTION
WHEN <condizione> [ OR <condizione> ... ] THEN
<handler_statements>
[ WHEN <condizione> [ OR <condizione> ... ] THEN
<handler_statements> … ]
END;
La gestione degli errori è analoga a quella dei linguaggi general purpose, e simula l’uso di
try/catch.
Il valore 00000 indica che non si è verificato alcun errore; ci sono valori che indicano warning (il
programma non abortisce, ma l’esecuzione ha avuto problemi).
Bisogna quindi conoscere i codici di errore per poter programmare; oltre ad essi però si possono
usare delle costanti standard (molto più gestibili).
Esempio: Codice 2000, costante no_data, vuol dire che la query non restituisce alcun dato.
Accoppiamento Esterno
Necessario all’integrazione di due linguaggi:
- SQL,
- linguaggio di programmazione generico.
DBMS Attivi
I trigger forniscono (in maniera reattiva) funzionalità altrimenti delegate ai programmi applicativi.
Il comportamento reattivo è definito centralmente una sola volta ed è condiviso da tutte le
applicazioni che usano il DB.
Questo porta benefici in termini di:
● efficienza
● costi di manutenzione
● uniformità di gestione dei dati (quindi loro consistenza)
● integrazione con le altre componenti del DBMS
Esempio di Trigger:
Gestione automatizzata di un magazzino in cui se la quantità di un prodotto scende sotto le 4
unità devo ordinare 100 item di tale prodotto:
Problemi
● minore efficienza (controlla anche se non è cambiato nulla)
● determinare la frequenza ottima di polling
Problemi:
● Compromette la modularità e la riusabilità del codice,
● La correttezza di ciascuna applicazione dipende dalla correttezza e integrazione di tutte.
DBMS attivi
Trigger
Alcune operazioni sono automaticamente eseguite quando si verifica una determinata situazione
interna o esterna alla base di dati.
La situazione può corrispondere a eventi specifici (insert, update, ecc.) o particolari condizioni o
particolari stati o transizioni di stato.
Un trigger (o regola attiva) è il costrutto sintattico per definire la reazione del
sistema è specificato nel DDL del DBMS.
Paradigma ECA
Paradigma più noto per la definizione dei trigger è Evento-Condizione-Azione (ECA):
ON evento
IF condizione
THEN azione
Inoltre, si possono specificare azioni diverse per eventi diversi e stessa condizione.
Azioni
Le azioni ammesse in un trigger dipendono in genere dalla modalità BEFORE/AFTER con cui è
stato specificato l’evento.
Le azioni dei trigger BEFORE sono soggette a varie limitazioni, spesso è ammesso solo
ROLLBACK.
Creazione Trigger in SQL:200N
CREATE TRIGGER <nome trigger>
{BEFORE | AFTER} <evento> ON <tabella soggetto>
[WHEN <condizione>]
{<comando SQL> |
BEGIN ATOMIC <sequenza di comandi SQL> END};
SQL:200N - Evento:
Possibili eventi: INSERT, DELETE, UPDATE, UPDATE [OF <lista attributi>] per la tabella
soggetto.
Un solo evento può attivare un trigger, quindi non sono possibili eventi composti.
È possibile specificare che il trigger sia attivato prima (before) o dopo (after) l’esecuzione
dell’operazione associata all’evento.
Tuple/Tabelle Di Transizione
La tupla/l’insieme delle tuple che sono state modificate nell’operazione che ha attivato il trigger
(“versione” prima e dopo la modifica):
● OLD o OLD ROW (equivalenti)
● NEW o NEW ROW (equivalenti)
● OLD TABLE
● NEW TABLE
Cancellazione:
DROP TRIGGER <nome trigger>;
Clausola Referencing
Con la clausola REFERENCING si specificano alias a livello di tabella o tupla di transizione
La parola chiave OLD/NEW specifica alias per la tabella/tupla di transizione prima/dopo
dell’esecuzione dell’evento.
Esempio:
Trigger T
● Evento: inserimento nella relazione Film
● Condizione: valutaz IS NULL
● Azione: calcolo del valor medio di valutaz ed assegnazione di tale valore moltiplicato per
1.1 all’attributo valutaz delle tuple inserite
Esempio:
Esecuzione orientata all’insieme.
La condizione viene valutata e l’azione viene eseguita una sola volta, indipendentemente dal
numero di film inseriti tutti i 5 film inseriti avranno lo stesso valore per l’attributo valutaz.
Esecuzione orientata all’istanza
La condizione viene valutata e l’azione viene eseguita una volta per ogni film inserito (quindi 5
volte) i 5 film inseriti avranno valori dell’attributo valutaz potenzialmente diversi
La media è calcolata su insiemi differenti di valori
Dipende:
● Dal tipo di trigger (before/after)
● Dal tipo di esecuzione (row/statement)
● Dall’evento che ha attivato il trigger
Insert
Non si possono specificare clausole REFERENCING OLD; le tuple inserite dall’evento non
esistevano prima della sua esecuzione.
Delete
Non si possono specificare clausole REFERENCING NEW; le tuple cancellate dall’evento non
esistono più dopo la sua esecuzione
Se il trigger è di tipo BEFORE le tuple cancellate
● sono visibili nella tabella soggetto
● e possono essere accedute usando la tupla di transizione OLD
Se il trigger è di tipo AFTER le tuple cancellate
● non sono visibili nella tabella soggetto
● ma possono essere accedute usando la tupla o la tabella di transizione OLD
Update
I valori precedenti e correnti delle tuple possono essere acceduti usando le clausole
REFERENCING OLD e NEW:
● a livello di tupla nei trigger di tipo BEFORE
● a livello di tupla o di tabella nei trigger di tipo AFTER
Se il trigger è di tipo AFTER l’effetto della modifica è visibile anche nella tabella soggetto.
Recap
Modello di Esecuzione
Attività fondamentali in un ADBMS:
1. Rilevare gli eventi ed attivare i trigger corrispondenti,
2. Processo reattivo: selezionare ed eseguire i trigger.
In base allo standard, viene attivato dopo l’esecuzione di ogni comando SQL.
Possono essere eseguite concorrentemente.
Selezione Trigger
Il trigger da eseguire viene selezionato sulla base del tipo di trigger (before/after) e della modalità
di esecuzione (row/statement).
Priorità:
● lo standard assegna priorità assolute in base al tempo di creazione;
un trigger “vecchio” è eseguito prima di un trigger “giovane”,
● PostgreSQL assegna priorità assolute in base al nome;
i trigger sono selezionati in ordine alfabetico.
Esecuzioni In Cascata
L’esecuzione dell’azione di un trigger può provocare nuovi eventi, i quali possono a loro volta
attivare altri trigger.
Tali trigger possono essere gestiti:
● con la modalità iterativa: essere aggiunti all’insieme di trigger da considerare
● con la modalità ricorsiva: dare origine ad una nuova esecuzione dell’algoritmo durante
l’esecuzione dell’azione del trigger correntemente attivato; quest’ultima è assunta dallo
standard SQL.
Terminazione
Il processo reattivo potrebbe non terminare;
Lo standard non fornisce indicazioni su questo aspetto; non vengono quindi poste restrizioni
sintattiche per evitare la non terminazione.
Di solito però i DBMS hanno un limite superiore al numero di trigger attivabili ricorsivamente.
Con le asserzioni:
CREATE ASSERTION
VerificaNoleggi
CHECK (NOT EXISTS
(SELECT * FROM Noleggio
WHERE dataRest IS NULL
GROUP BY codCli
HAVING COUNT(*) > 3));
Con i Trigger:
CREATE TRIGGER VerificaNoleggi
AFTER INSERT ON Noleggio
REFERENCING NEW ROW AS NR
FOR EACH ROW
WHEN
(SELECT COUNT(*)
FROM Noleggio
WHERE dataRest IS NULL AND
codCli = NR.codCli) > 3
ROLLBACK;
Con BEFORE il funzionamento sarebbe analogo; dal punto di vista dell'efficienza sarebbe anche
migliore, poiché rende il rollback più agevole.
Invece di abortire la transazione se il vincolo è violato si può anche annullare l’inserimento.
Con le asserzioni:
CREATE ASSERTION
VerificaNoleggi
CHECK (NOT EXISTS
(SELECT * FROM Noleggio
WHERE dataRest IS NULL
GROUP BY codCli
HAVING COUNT(*) > 3));
Con i trigger:
CREATE TRIGGER
VerificaNoleggi
AFTER INSERT ON Noleggio
REFERENCING NEW ROW AS NR
FOR EACH ROW
WHEN (SELECT COUNT(*)
FROM Noleggio
WHERE dataRest IS NULL AND
codCli = NR.codCli) > 3
DELETE FROM Noleggio
WHERE colloc = NR.colloc AND
dataNol = NR.dataNol;
Se un noleggio causa la violazione del vincolo deve essere impedito tutto il noleggio, non solo i
video eccedenti.
Trigger in PostGreSQL
Il trigger può essere BEFORE o AFTER per comandi di INSERT, DELETE, UPDATE su
relazione (di base, no viste).
È possibile specificare UPDATE OF su una lista attributi e INSTEAD OF solo per trigger su viste.
Solo per FOR EACH ROW è possibile specificare più di un evento (in OR).
La clausola referencing è invece disponibile solo per transition table
La modalità di attivazione si modifica con SET CONSTRAINTS, come già visto per i vincoli:
● Solo FOR EACH ROW
● No tabelle di transizione
E le clausole:
● DISABLE TRIGGER [NomeTrigger | ALL | USER]
● ENABLE TRIGGER [NomeTrigger | ALL | USER]
del comando di ALTER TABLE
A tale funzione si può passare una lista di argomenti (stringhe, separate da virgola).
La stessa trigger function può essere utilizzata come azione di più trigger, l’uso dei parametri
permette di specializzarne l’utilizzo.
Le funzioni sono specificabili in diversi linguaggi, vedremo solo trigger function specificate in
PL/pgSQL.
Quando una funzione PL/pgSQL è dichiarata come trigger function vengono automaticamente
create diverse variabili speciali nel top-level block.
Queste variabili sono:
● NEW: contiene la nuova versione della tupla per le operazioni
● INSERT/UPDATE nei trigger row-level, NULL nei trigger statement-level
● OLD: contiene la vecchia versione della tupla per le operazioni
● DELETE/UPDATE nei trigger row-level, NULL nei trigger statement-level
Una trigger function deve restituire NULL oppure una tupla con la stessa struttura della tabella su
cui è stato attivato il trigger.
Il valore di ritorno è utilizzato solo dai trigger before row; il valore di ritorno NULL segnala al
trigger manager di non eseguire il resto dell’operazione per tale tupla; l’operazione
corrispondente all’evento non viene eseguita, quindi i trigger successivi non sono eseguiti.
Il valore di ritorno diverso dal valore NEW originale modifica la tupla che verrà inserita o
aggiornata: per modificare tale riga è possibile modificare i campi di NEW e restituire la NEW
modificata o costruire una nuova tupla e restituirla.
Per tutti gli altri tipi di trigger il valore di ritorno è ignorato (e può essere o meno NULL).
PostgreSQL - Modalità di Esecuzione
La scelta della regola dipende dal tipo di trigger come in SQL200N:
Normalizzazione
Introduciamo quindi la normalizzazione, legata fortemente al concetto di qualità:
Forma Normale
Una forma normale è una proprietà di una base di dati relazionale che ne garantisce la
“qualità”, cioè l'assenza di determinati difetti.
Quando una relazione non è normalizzata presenta ridondanze e si presta a comportamenti
poco desiderabili durante gli aggiornamenti, ovvero anomalie.
Quindi: Qualità dei dati = assenza di (particolari tipi di) ridondanza = assenza di anomalie.
Esempio di Relazione con Anomalie:
Al contrario:
● Ogni studente ha un unico anno di corso (anche se sostiene più esami).
● Ogni corso ha un certo numero di crediti.
● Ogni studente nell’esame di ogni corso prende un unico voto (anche se può prendere
voti diversi negli esami di corsi diversi).
Dipendenza Funzionale
Per studiare in maniera sistematica questi aspetti, è necessario introdurre un vincolo di integrità:
la dipendenza funzionale.
Allora esiste in r una dipendenza funzionale (FD) da Y a Z se, per ogni coppia di tuple t1 e t2
in r con gli stessi valori su Y, risulta che t1 e t2 hanno gli stessi valori anche su Z.
Una relazione R è in forma normale di Boyce e Codd se, per ogni dipendenza funzionale (non
banale) X → Y definita su di essa, X contiene una chiave K di R.
La forma normale richiede che i concetti in una relazione siano omogenei (solo proprietà
direttamente associate alla chiave).
Le dipendenze funzionali e le chiavi sono quindi collegate; possiamo quindi determinare le chiavi
dalle dipendenze funzionali, attraverso il concetto di chiusura.
Chiusura
La chiusura di un insieme di attributi X rispetto a un insieme di dipendenze funzionali F, indicato
come X +¿ F ¿, è l’insieme degli attributi che dipendono funzionalmente da X.
Un insieme di attributi K è superchiave (proprietà di univocità) se e solo se K+ F contiene
tutti gli attributi della relazione.
È chiave se è anche minimale.
Posso anche dedurre la chiave a partire dalle FD e dalla chiusura:
1) guardo gli attributi che non sono mai a sx delle dipendenze,
2) considero il loro insieme e ne calcolo la chiusura: se la loro chiusura contiene tutti gli
attributi della relazione sono chiave.
Esempio:
MatricolaStudente → NomeStudente
MatricolaStudente → CognomeStudente
MatricolaStudente → Anno
CodiceCorso → SiglaCorso
CodiceCorso → NomeCorso
CodiceCorso → Crediti
MatricolaStudente CodiceCorso → Voto
MatricolaStudente+
MatricolaStudente(0) = MatricolaStudente
Se una relazione non soddisfa la BCNF la rimpiazziamo con altre relazioni che soddisfano la
BCNF, decomponendo sulla base delle dipendenze funzionali, al fine di separare i concetti.
Decomposizione
Creiamo quindi una relazione per ogni dipendenza funzionale, contenente gli attributi
coinvolti nella dipendenza; dopo aver unificato le dipendenze con la stessa parte sinistra.
Esempio:
Consideriamo nuovamente CSZ con dipendenze CS → Z, Z → C e chiavi CS, ZS
Gli attributi primi sono C, S, Z:
CS → Z soddisfa BCNF
Z → C non soddisfa BCNF ma C è primo.
CSZ con CS → Z, Z → C è in 3NF.
La terza forma normale è quindi meno restrittiva della forma normale di Boyce e Codd (e
ammette relazioni con alcune anomalie).
Essa aumenta quindi i casi di ridondanza; ha il vantaggio però di essere sempre
raggiungibile: è sempre possibile trovare una decomposizione in 3NF che sia lossless join
e preservi le dipendenze.
Funziona sempre a patto che le dipendenze siano in forma minimale, ovvero non ottenibili da
altre dipendenze.
Una possibile strategia che combina BCNF e terza forma normale è la seguente:
1. se la relazione non è normalizzata si decompone in terza forma normale
2. alla fine si verifica se lo schema ottenuto è anche in BCNF
Se una relazione ha una sola chiave allora le due forme normali coincidono.
Se esistono almeno due chiavi (come in CSZ), potrebbe non esistere un decomposizione BCNF
che sia lossless join e preservi le dipendenze.
La teoria della normalizzazione può essere usata nella progettazione logica per verificare lo
schema relazionale finale.
Il fatto di utilizzare la metodologia di progettazione vista aiuta a produrre schemi normalizzati
(spesso in 3NF), ma non lo garantisce: in alcuni casi può essere accettabile utilizzare
schemi non normalizzati, per motivi di efficienza, ma è importante che la scelta sia
consapevole e documentata.
Esempio: consideriamo:
Determinare nome, cognome e numeri di telefono dei clienti (o di un cliente) è costoso: richiede
un join.
Consideriamo quindi una possibile denormalizzazione:
In essa:
● è presente ridondanza: nome, cognome, … sono ripetuti per ogni numero di telefono,
● determinare nome, cognome e numeri di telefono dei clienti (o di un cliente) è più
efficiente,
● Ma, ad esempio, determinare l’età media dei clienti è meno efficiente (date di nascita
più “sparpagliate”)
La scelta dello schema dipende dalle operazioni da eseguire (workload).
Un altro esempio:
Vediamo anche il caso in cui volessi gestire anche più indirizzi; la ristrutturazione standard è la
seguente:
Vi è quindi ridondanza: nome, cognome, dataN sono ripetuti per ogni numero di telefono e ogni
indirizzo; questo accade in generale per tutte le combinazioni.
Ulteriore nesting:
Se la nostra esigenza fosse determinare per ogni film il suo genere, l’anno e i giudizi ricevuti, con
nome e cognome del cliente che li ha formulati, con lo schema:
Sulla base del workload, possiamo inserire anche ulteriore ridondanza; consideriamo ad
esempio un caso in cui le query piuu eseguite siano:
● Q1: Età media dei clienti
● Q2: Nome e cognome dei clienti e relativi film consigliati
● Q3: Genere e anno dei film e relativi giudizi ricevuti, con nome e cognome del cliente che
li ha formulati
Con:
- latenza: tempo necessario per accedere al primo byte;
- tempo di trasferimento: tempo necessario per muovere i dati.
I tempi di accesso alle memorie e la velocità con cui ruotano gli HD non seguono la legge di
Moore (non migliorano esponenzialmente) e per questo diventa sempre più oneroso muovere i
dati lungo i livelli della gerarchia.
Un DB, a causa della sua dimensione, risiede normalmente su dischi.
I dati devono essere trasferiti in memoria centrale per essere elaborati dal DBMS; il
trasferimento non avviene in termini di singole tuple, bensì di blocchi (o pagine, termine
comunemente usato quando i dati sono in memoria).
Poiché spesso le operazioni di I/O costituiscono il collo di bottiglia del sistema, si rende
necessario ottimizzare l’implementazione fisica del DB, attraverso:
- Gestore delle strutture di memorizzazione:
- Organizzazione efficiente delle tuple su disco,
- Gestione efficiente dei buffer in memoria,
- Strutture di accesso efficienti.
- Elaboratore delle interrogazioni:
- Strategie di esecuzione efficienti per le query.
Dischi Magnetici
Un hard disk (HD) è un dispositivo elettromeccanico per la conservazione di informazioni sotto
forma magnetica, su supporto rotante a forma di piatto su cui agiscono delle
testine di lettura/scrittura.
L'informazione è memorizzata su una superficie del disco in cerchi concentrici di piccola
ampiezza, ognuno con un diametro distinto, detti tracce; vi sono circa 16mila tracce per
superficie in un HD.
Per i dischi a più piatti, le tracce con lo stesso diametro sulle varie superfici sono dette cilindro.
Dati memorizzati su uno stesso cilindro possono essere recuperati molto più velocemente che
non dati distribuiti su diversi cilindri.
Ogni traccia è divisa in settori, le più piccole unità di dati che possono essere lette o scritte;
generalmente la loro size è 512 byte e ci sono 100-1000 settori in una traccia.
Per dischi a piatti multipli vi è una testina per superficie, tutte montate sullo stesso braccio;
quindi, un cilindro comprende tutte le testine sopra le rispettive tracce.
Latenza
Le prestazioni interne dipendono principalmente dal tempo di latenza, ovvero il tempo
impiegato per raggiungere le informazioni di interesse.
Esso è composto da:
● Command Overhead Time: tempo necessario a impartire comandi al drive (dell’ordine di
0.5 msec e trascurabile).
● Seek Time(Ts): tempo impiegato dal braccio a posizionarsi sulla traccia desiderata; dai 2
ai 20 msec, e quindi molto determinante.
● Settle Time: tempo richiesto per la stabilizzazione del braccio
● Rotational Latency (Tr): tempo di attesa del primo settore da leggere; da 2 a 11 msec,
anch’esso importante.
Tempo di Trasferimento
È il tempo necessario a trasferire i byte e dipende dalla velocità massima alla quale il drive può
leggere o scrivere dati, che è tipicamente dell’ordine di qualche decina di MB/sec.
Si riferisce quindi alla velocità con cui si trasferiscono bit dai (sui) piatti sulla (dalla) cache del
controller; si può stimare come:
○ Esempio:
● 512 bytes/sector
● 368 sectors/track
● 7200 rpm (rounds/minute)
● 60/7200 secondi per ogni round
● il transfer rate è pari a:
(512 x 368) / ( 60 / 7200) = 21.56 MB/sec
Blocchi
I dati sono trasferiti tra il disco e la memoria principale in unità chiamate blocchi.
Un blocco (o pagina) è una sequenza contigua di settori su una traccia, e costituisce l’unità di
I/O per il trasferimento di dati da/per la memoria principale.
La dimensione del blocco dipende dal sistema operativo; la dimensione tipica di una pagina è di
qualche KB (4 - 64 KB).
Pagine piccole comportano un maggior numero di operazioni di I/O.
Pagine grandi tendono ad aumentare la frammentazione interna (pagine parzialmente riempite)
e richiedono più spazio in memoria per essere caricate.
Il tempo di trasferimento di un blocco (Tt) è il tempo impiegato dalla testina per trasferire un
blocco nel buffer, una volta posizionata all'inizio del blocco; tale tempo è molto più breve del
tempo di seek e dipende dalla dimensione della pagina (P) e dal transfer rate (Tr).
Esempio:
con un transfer rate di 21.56 MB/sec e P = 4 KB si ha Tt= 0.18 msec, con P = 64 KB si ha:
Tt = 2.9 msec
Ricapitolando:
A livello fisico un DB consiste di un insieme di file, ognuno dei quali viene visto come una
collezione di pagine, di dimensione fissa (es: 4 KB).
Ogni pagina/blocco memorizza più record (corrispondenti alle tuple logiche); a sua volta un
record consiste di più campi, di lunghezza fissa e/o variabile, che rappresentano gli attributi.
Abbiamo compreso che le prestazioni di un DBMS dipendono fortemente da come i dati sono
organizzati su disco: l’allocazione dei dati dovrebbe mirare a ridurre i tempi di accesso ai dati, e
per far questo bisogna sapere come (logicamente) i dati dovranno essere elaborati e quali sono
le relazioni (logiche) tra i dati.
Tutte queste informazioni non possono essere note al file system, quindi la gestione
viene lasciata ai sistemi di gestione dati.
Esempio: se due relazioni contengono dati tra loro correlati (mediante join) può essere una
buona idea memorizzarle in cilindri vicini, in modo da ridurre i tempi di seek (certamente nello
stesso tablespace).
Record
I dati sono generalmente memorizzati in forma di record: un insieme di valori collegati.
Ogni valore è formato da uno o più byte e corrisponde ad un campo del record.
Una collezione di nomi di campi a cui sono associati i tipi corrispondenti costituisce un tipo di
record.
Per ogni tipo di record nel DB deve essere definito uno schema (fisico) che permetta di
interpretare correttamente il significato dei byte che costituiscono il record.
La dimensione di un record è solitamente molto minore di quella di una pagina, quindi ci sono
spesso molti record in una singola pagina; tuttavia a volte esistono le cosiddette “long tuples” che
sono record che eccedono la dimensione di blocco/pagina.
In tutti gli altri casi i record devono essere memorizzati su un solo blocco (non metà in uno e
metà in un altro).
File
Un file è una sequenza di record.
Un file è detto file con record a lunghezza fissa se tutti i record memorizzati nel file hanno la
stessa dimensione (in byte); altrimenti, parliamo di file con record a lunghezza variabile.
Allocazione Continua
I blocchi del file sono allocati in blocchi di disco contigui.
● La lettura dell’intero file molto efficiente.
● L’espansione (aggiornamento) del file è invece difficile.
Allocazione Concatenata
Ogni blocco di un file contiene un puntatore al successivo blocco del file.
● L’espansione del file è facile,
● La lettura dell’intero file è lenta.
A seconda dell’organizzazione primaria dei dati scelta, il file che contiene i record dei dati può
essere:
● file heap (o file pila): i record dati vengono memorizzati uno dopo l’altro in ordine di
inserimento,
● file ordinato (o file sequenziale o file clusterizzato): i record dati sono memorizzati
mantenendo l’ordinamento su uno o più campi,
● file hash: i record che condividono lo stesso valore per uno o più campi sono
memorizzati consecutivamente.
Le organizzazioni ordinate e hash si possono ottenere usando opportune strutture ausiliarie di
accesso.
Il buffer manager di un DBMS usa alcune politiche di gestione che sono più sofisticate delle
politiche usate nei SO.
I SO in genere si basano si politiche LRU (Least Recently Used):
Le pagine utilizzate meno, di recente, vengono sovrascritte; le politiche di LRU non sempre sono
le più adatte per i DBMS per motivi legati alla gestione del recovery.
Un DBMS è in grado di predire meglio di un SO il tipo dei futuri riferimenti: Come vedremo,
esistono algoritmi di join che scandiscono N volte le tuple di una relazione.
In questo caso la politica migliore sarebbe la MRU (Most Recently Used):
● Si rimpiazza la pagina usata più di recente,
● Verrà infatti usata solo dopo avere letto tutte quelle successive.
Indici
Le organizzazioni dei file viste (heap, sequenziale, hash) non permettono di ottenere prestazioni
soddisfacenti se si eseguono ricerche con condizioni su attributi che non sono quelli utilizzati per
organizzare i record nel file.
Il problema si pone in generale quando una interrogazione accede solo un piccolo sottoinsieme
dei dati, che devono essere estratti da un file di grandi dimensioni.
Indici
Per ovviare a questi limiti si creano delle strutture ausiliarie di accesso, chiamate anche indici,
che forniscono cammini di accesso alternativi ai dati, per localizzare velocemente i dati di
interesse.
Questi cammini di accesso permettono di determinare direttamente i record che verificano una
data query.
Le strutture ausiliarie di accesso permettono di eseguire in maniera più efficiente operazioni di
ricerca (spesso basate su condizioni di selezione e join), rispetto ad una certa chiave di ricerca.
Si usa il termine chiave di ricerca per indicare un attributo o insieme di attributi usati per la
ricerca (anche diversi dalla chiave primaria).
Il vantaggio di usare un indice nasce dal fatto che la chiave è solo parte dell’informazione
contenuta in un record; pertanto, l’indice occupa uno spazio minore rispetto al file dati.
L’uso di indici rende l’esecuzione delle interrogazioni più efficiente, ma rende più costosi
gli aggiornamenti.
● Indici hash: esiste una funzione hash h per cui, se una coppia (k i , r i ) appartiene
all’indice allora h( k i )=r i .
Le coppie non vengono memorizzate su un file apposito.
Gli indici ordinati sono indici in cui le coppie(k i , r i ) vengono mantenute esplicitamente e
memorizzate in un file su disco, ordinate rispetto ai valori della chiave di ricerca k i.
Se l’indice è piccolo, può essere tenuto in memoria principale.
Molto spesso, però, è necessario tenerlo su disco e la scansione dell’indice può richiedere
parecchi trasferimenti di blocchi.
Viene quindi utilizzata una struttura multilivello che permette di accedere più velocemente alle
coppie dell’indice.
L’indice assume una struttura ad albero in cui ogni nodo dell’albero corrisponde a un blocco
disco.
Gli indici ad albero sono organizzazioni ad albero bilanciato, utilizzate come strutture di
indicizzazione per dati in memoria secondaria.
Requisiti fondamentali per indici per memoria secondaria:
● bilanciamento: l’indice deve essere bilanciato rispetto ai blocchi e non ai singoli nodi (è
il numero di blocchi acceduti a determinare il costo I/O di una ricerca),
● occupazione minima: è importante che si possa stabilire un limite inferiore
all’utilizzazione dei blocchi, per evitare un sotto-utilizzo della memoria,
● efficienza di aggiornamento: le operazioni di aggiornamento devono avere costo
limitato.
B+-Tree
Esistono diverse strutture ad albero, la struttura più utilizzata dai DBMS commerciali è il B+-tree.
In esso ogni nodo corrisponde ad un blocco e le operazioni di ricerca, inserimento e
cancellazione hanno costo, nel caso peggiore lineare nell’altezza dell’albero h, logaritmico
nel numero di valori distinti N della chiave di ricerca, in quanto si può provare che h è in
O(log N).
Il numero massimo m-1 di elementi memorizzabili in un nodo è l’unico parametro dipendente
dalle caratteristiche della memoria, cioè dalla dimensione del blocco.
Il B+-tree garantisce un’occupazione di memoria almeno del 50% (almeno metà di ogni blocco
allocato è effettivamente occupato).
Le coppie(k i , r i )sono tutte contenute in nodi (quindi blocchi) foglia e le foglie sono collegate a
lista mediante puntatori (PID) per favorire ricerche di tipo range.
La ricerca di una chiave deve individuare una foglia.
I nodi interni memorizzano dei separatori (anch’essi valori della chiave di ricerca k i) la cui
funzione è determinare il giusto cammino nella ricerca di una chiave.
Vi è quindi parziale duplicazione delle chiavi (perché alcune sono presenti in nodi foglia e nodi
interni).
Un B+-albero di ordine m è un albero bilanciato che soddisfa le seguenti proprietà:
● ogni nodo contiene al più m - 1 elementi,
● ogni nodo, tranne la radice, contiene almeno (m/2)-1 elementi, la radice può contenere
anche un solo elemento,
● ogni nodo non foglia contenente j elementi ha j +1 figli.
Il sottoalbero sinistro di un separatore contiene valori di chiave minori del separatore, quello
destro valori di chiave maggiori od uguali al separatore.
Nel caso di chiavi alfanumeriche facendo uso di separatori di lunghezza ridotta, tramite tecniche
di compressione, si risparmia spazio.
Si trasferisce la radice in memoria e si esegue la ricerca tra le chiavi contenute per determinare
se scendere nel sottoalbero sinistro o destro.
Una volta raggiunta una foglia, o la chiave cercata è presente in tale foglia o non è presente
nell’albero.
Il costo della ricerca di una chiave nell’indice è il numero di nodi letti, cioè h.
Altezza
E’ il numero di nodi che compaiono in un cammino dalla radice ad un nodo foglia.
I B+-tree permettono di prevedere con sufficiente approssimazione l’altezza media dell’albero in
funzione delle chiavi presenti, quindi si possono stimare i costi della ricerca e delle operazioni di
aggiornamento.
Varianti
Esistono varianti, ad esempio i B-tree (predecessori), una variante in cui le entrate dell’indice
sono associate alle chiavi anche nei nodi interni dell’albero, senza duplicazione delle chiavi.
● Migliora occupazione di memoria,
● La ricerca di una singola chiave è più costosa in media in un B+-tree (si deve
necessariamente raggiungere sempre la foglia per ottenere il puntatore ai dati),
● B+-tree meglio per ricerche di intervalli di valori.
Clusterizzati e Non Clusterizzati
Un indice ad albero è clusterizzato se il file dei dati è ordinato rispetto alla chiave di ricerca,
in caso contrario si definisce non clusterizzato.
Un file dei dati ordinato è sempre associato a un indice ad albero clusterizzato: si sfrutta
ordinamento delle foglie dell’indice per ordinare il file dei dati; le operazioni di inserimento,
cancellazione e aggiornamento nel file ordinato sono facilitate dalla presenza dell’indice.
Esiste al più un indice clusterizzato per tabella (indipendentemente dal tipo).
Se l'indice è primario, la clusterizzazione non aiuta a rendere più efficienti le operazioni.
Se l'indice non è primario, la clusterizzazione invece aiuta a rendere più efficienti le operazioni.
Questo perché se la granularità di acquisizione è ad esempio 2 blocchi, se ho le informazioni da
acquisire già adiacenti tra di loro le posso acquisire insieme.
Indici Hash
L’uso di indici ad albero ha lo svantaggio di richiedere la scansione di una struttura dati,
memorizzata su disco, per localizzare i dati; questo perché le associazioni (k i , r i ) vengono
mantenute in forma esplicita, come record in un file.
L’associazione tra i valori della funzione Hash e gli indirizzi su disco avviene tramite un bucket
index:
L’area primaria viene allocata al momento della creazione dell’indice hash, l’area di overflow
successivamente, quando ce n’è bisogno.
Il sistema ottimizza l’allocazione dell’area primaria: se un bucket contiene più blocchi in area
primaria, questi sono memorizzati in modo possibilmente contiguo su disco (e certamente sullo
stesso cilindro).
Il tempo di latenza è quindi minimizzato per gli accessi all’area primaria.
Il sistema non è in grado di ottimizzare l’allocazione dell’area di overflow: i blocchi dell’area
di overflow vengono memorizzati dove si può; è quindi richiesto maggiore tempo di latenza per
l’accesso all’area di overflow.
Cancellazione
Si cercano i record da cancellare, come descritto per l’operazione di ricerca per uguaglianza.
Si elimina il record dal bucket.
La cancellazione di un record da un’area di overflow può determinare la cancellazione dell’area
di overflow (se è l’ultimo record nell’area).
Il DBA ha al più la possibilità di agire sulla funzione, ma non in tutti i DBMS si può specificare (in
PostgreSQL non si può).
Tali proprietà dipendono dall’insieme delle chiavi su cui si opera e quindi non esiste una funzione
universale ottima.
In genere le funzioni hash operano su insiemi di chiavi intere.
Se i valori delle chiavi sono stringhe alfanumeriche, si può associare in modo univoco
ad ogni chiave un numero intero, prima di applicare la trasformazione.
Costi
Un indice hash supporta in modo efficiente ricerche per uguaglianza, inserimenti e cancellazioni;
non è necessario accedere un file come per gli indici ordinati.
In assenza di overflow, il costo di accesso a indice è costante.
Costo di accesso al bucket index se non è già in memoria (1).
Costo di accesso al bucket (1 se corrisponde a un singolo blocco)
Costo di scrittura, per inserimento e cancellazione (1).
In presenza di overflow, le prestazioni non sono facilmente determinabili.
Un file dei dati di tipo hash è sempre associato a un indice hash clusterizzato:
● le operazioni di inserimento, cancellazione e aggiornamento nel file ordinato sono
facilitate dall’uso dell’organizzazione hash;
Attributo e Multi-Attributo
Un indice hash multiattributo è definito su una lista di attributi:
● diversi ordinamenti degli attributi chiave potrebbero corrispondere a diverse
organizzazioni (dipende dalla funzione hash).
→ indice su (dataNol,colloc) potrebbe essere diverso da indice su (colloc,dataNol).
Un indice multi-attributo su A1, A2, …, An permette di determinare direttamente le tuple che
soddisfano condizioni di uguaglianza su tutti gli attributi A1,…, An o sui primi i<n.
Nozione di Aggregato
Una situazione in cui una entità viene rappresentata come una relazione che ingloba
informazioni relative ad altre entità ed altre associazioni, per permetterne un recupero più
immediato.
Riduce quindi il numero di join limitando la ridondanza.
_ indica il cammino.
! indica il cammino vuoto.
Caso 2:
Caso 3:
Ci servono le informazioni sui film, tutto il resto è riferito ai film.
L’entità aggregante è quindi Film.
La selezione è di nuovo tutti i film (con cammino nullo)
Nella lista di proiezioni metto i film con i loro attributi e i clienti con i loro attributi; siccome ho
bisogno di un attributo di un’associazione lo indico dopo la segnatura del cammino.
Caso 4:
A questo punto troviamo una condizione nella lista di selezione, dove indichiamo gli attributi che
ci interessano.
Caso 5:
C’è scritto dato un film, quindi si riferisce all’indentificatore di film: titolo,regista.
Ci interessano tutti i dati dei video e quindi nella lista di proiezione indichiamo Video.
Caso 6:
Caso 7:
Esempio Q1:
Esempio Q2:
Esempio Q4:
Esempio Q7:
Step 3
1) Creiamo una relazione Nested Relational R_E per ogni entità aggregante.
3) Nello schema risultante aggiungiamo poi vincoli di chiave ed eventuali vincoli di chiave
esterna.
Cliente:
Film:
Video:
Aggiungiamo i vincoli di chiave esterna e otteniamo lo schema finale:
Query Processor
Passi nell’esecuzione di una interrogazione:
3) Ottimizzazione Logica:
● input: espressione algebrica canonica
● output: piano di esecuzione logico ottimizzato (logical query plan – LQP)
4) Ottimizzazione Fisica:
● input: piano di esecuzione logica ottimizzato,
● output: piano di esecuzione fisico ottimale (physical query plan - PQP).
E al corrispondente albero:
Piano II porta ad una esecuzione più efficiente, in quanto evita l’esecuzione del prodotto
cartesiano riducendo la dimensione dei risultati intermedi generati e il numero di operazioni
eseguite.
Il piano fisico può contenere nodi aggiuntivi, corrispondenti a ulteriori operazioni da eseguire per
motivi di efficienza (ad esempio ordinamento).
A livello di piano fisico, si sceglie anche un ordine di esecuzione per le operazioni associative e
commutative (join, unione, intersezione) ed una modalità per passare i risultati intermedi da un
operatore al successivo.
3) Stima dei Costi e Scelta del Piano Migliore:
I costi sono calcolati in base alla stima della dimensione dei risultati; in base ad essi
viene scelto il piano di esecuzione migliore.
Ottimizzazione Logica
Input: espressione algebrica canonica; output: piano di esecuzione logico ottimizzato:
espressione algebrica (in un’algebra estesa) per l’interrogazione, rappresentata come albero.
L’espressione algebrica canonica viene rappresentata come piano di esecuzione logico; il piano
di esecuzione logico iniziale viene poi trasformato in un piano equivalente ma più efficiente da
eseguire, usando le proprietà dell'algebra relazionale.
Gli operatori sono quelli dell'algebra relazionale estesa, che include anche gli operatori di SQL.
L’ottimizzazione logica si basa su equivalenze algebriche; esse vengono utilizzate come regole
di riscrittura, guidati da opportune euristiche per passare da un’espressione algebrica (quindi da
un LQP) ad un’altra, ad essa equivalente ma più efficiente.
Equivalenze Algebriche
Equivalenza Algebrica: due espressioni e1 ed e2 dell’algebra relazionale sono dette equivalenti
se, per ogni possibile base di dati in input D, producono lo stesso risultato in output quando
vengono eseguite su D.
Selezione: permette di gestire cascate di selezioni e stabilisce la commutatività della selezione.
Commutazione di selezione e proiezione: se una selezione con predicato P coinvolge solo gli
attributi A1,......,An, allora:
Altre equivalenze:
- Prodotto Cartesiano e join,
- commutatività e associatività dell’unione,
- commutazione di selezione e unione,
- commutazione di selezione e differenza,
- commutazione di proiezione e unione.
Nessuna equivalenza si riferisce all’ordine con cui eseguire un insieme di join: l’ordine di
esecuzione dei join, infatti, viene deciso nella fase successiva (ottimizzazione fisica), sulla base
di informazioni relative alla dimensione delle relazioni e valutazione del costo dei diversi ordini di
esecuzione.
Euristiche
Le euristiche permettono di trasformare le equivalenze in regole di riscrittura.
Si basano sull’idea di:
- anticipare il più possibile le operazioni che permettono di ridurre la dimensione dei
risultati intermedi, ovvero selezione e proiezione,
- fattorizzare condizioni di selezione complesse e lunghe liste di attributi in proiezioni,
per aumentare la possibilità di applicare regole di riscrittura.
Euristica 3: Introdurre ulteriori proiezioni nell’espressione, gli unici attributi da non eliminare
sono quelli che:
- appaiono nel risultato della query,
- sono necessari in operazioni successive.
L’output della fase di ottimizzazione logica è un singolo LQP ottimizzato; se portassimo avanti
più piani logici il costo sarebbe infatti troppo grande.
Ottimizzazione Fisica
input: piano di esecuzione logica (LQP) ottimizzato; output: piano di esecuzione fisico ottimale
(physical query plan - PQP).
Un piano di esecuzione fisico è un algoritmo di esecuzione dell’interrogazione, rappresentato
come albero, sul livello fisico.
Piani Fisici
Ogni piano di esecuzione fisico si compone di una serie di operatori fisici connessi ad albero.
Le foglie del piano di accesso sono le relazioni di base presenti nel piano logico ottimizzato, gli
altri nodi sono operatori che agiscono su 1 o 2 insiemi di tuple in input e producono 1 insieme di
tuple in output.
Gli operatori fisici sono implementazioni specifiche di operatori logici (ad esempio del join);
vengono tradotti in funzione dei valori delle statistiche, dei parametri di sistema (es. dimensione
del buffer pool) e della logica dell’algoritmo.
Per ogni operatore logico, esistono diversi algoritmi di realizzazione, che possono utilizzare
diverse informazioni contenute a livello fisico.
Gli operatori fisici si basano su tre principali tecniche:
1. iterazione: si esaminano le tuple della relazione di input sequenzialmente (scansione
sequenziale).
2. indici: se è specificata una selezione o una condizione di join su una relazione di base,
si usa un indice per esaminare solo le tuple che soddisfano la condizione.
3. partizionamento: si partizionano le tuple in base ad una chiave (ad esempio usando una
funzione hash) si decompone il problema eseguendo l’operazione prima sulle partizioni
(più piccolo, meno costoso) e poi si integrano i risultati.
Diversi algoritmi possono quindi portare a costi differenti; in particolare il costo è il numero di
operazioni di I/O (non consideriamo il tempo di CPU); nel determinarlo in genere non si tiene in
considerazione il costo di scrittura dell’output finale.
Può anche essere scelto più di un cammino di accesso, combinando poi i risultati con un
ulteriore operatore fisico.
I sistemi privilegiano cammini di accesso su fattori booleani.
Costi dei metodi:
1. Scansione sequenziale:
E’ l’unica opzione in assenza di indici ed ha costo:
NB(R): numero di blocchi del file per R
Selezione
La selezione se viene applicata a un risultato intermedio può essere implementata solo con una
scansione sequenziale della relazione in input: non è mai possibile utilizzare un indice su un
risultato intermedio, poiché l’indice è sempre costruito su relazioni di base.
Proiezione
La proiezione in SQL può prevedere:
Ordinamento:
1. Si accede sequenzialmente a R per ottenere un insieme di tuple che contengono solo gli
attributi desiderati.
2. Si ordina questo insieme di tuple rispetto a tutti gli attributi di proiezione,
3. Si scandisce il risultato ordinato, eliminando le tuple duplicate (che sono adiacenti).
L’ordinamento non è solo importante nel secondo passo di questa operazione, ma in molti casi:
- se l’interrogazione SQL da eseguire contiene una clausola ORDER BY
- per avere i dati ordinati in modo da eseguire altre operazioni in modo più efficiente: come
abbiamo visto , proiezione con eliminazione dei duplicati oppure come vedremo, join,
raggruppamento.
Per implementare l’ordinamento, non si possono usare algoritmi di ordinamento classici perché i
dati da ordinare sono troppi per stare in memoria principale; vi sono due approcci principali:
1. merge sort esterno a due fasi,
2. uso di B+ tree.
Fase 2 (merge): le liste di pagine ordinate generate dalla Fase 1 vengono fuse, fino a
produrre un’unica lista di pagine ordinate.
2) Uso di B+ Tree:
Se la relazione da ordinare ha un indice di tipo B+ tree sugli attributi rispetto ai quali vogliamo
ordinare si può pensare di effettuare l’ordinamento attraversando le pagine foglia dell’indice:
- se l’indice è clusterizzato è una buona idea,
- se l’indice non è clusterizzato può essere una pessima idea.
B+ tree clusterizzato su A: File dei dati ordinato rispetto ad A, basta accederlo sequenzialmente;
costo: B(R) accessi al file dei dati.
B+ tree non clusterizzato su A, costo: nel caso peggiore, effettuo tanti accessi al file dei dati
quanti sono i record.
Approccio Index-Based:
Al posto dell’ordinamento si può usare un approccio Index-Based.
Per proiezioni su relazioni di base, se esiste un indice ordinato con chiave di ricerca uguale alla
lista di proiezione, si può accedere a tutte le foglie dell’indice invece che al file dei dati.
Join
L’operazione di join è una operazione molto costosa:
date due relazioni R e S, contenenti T(R) e T(S) tuple, rispettivamente, richiede T(R) * T(S)
confronti.
Esistono moltissime implementazioni del Join, che mirano a sfruttare al meglio le risorse del
sistema e le (eventuali) proprietà degli insiemi di tuple in ingresso per evitare di eseguire tutti i
possibili T(R) * T(S) confronti:
● Nested Loop Join (semplice e a blocchi),
● Index Nested Join,
● Merge Join,
● Hash Join.
Conveniente se la relazione inner può essere mantenuta in MM, in questo caso il costo è di B(R)
+ B(S)
Merge Join
Il Merge Join è applicabile quando le relazioni in input sono ordinate rispetto all’attributo di
join.
L’algoritmo sfrutta il fatto che entrambi gli input sono ordinati per evitare di fare inutili confronti;
l’idea è simile all’algoritmo di merge sort.
Poiché le tuple sono ordinate in base all'attributo di join ogni tupla (e quindi ogni blocco) viene
letta esattamente una volta.
Questo porta il numero di letture nell’ordine di B(R) + B(S) se si accede sequenzialmente alle
due relazioni.
Il Merge Join è in generale usato solo per predicati di join di uguaglianza (equi-join), perché negli
altri casi i suoi vantaggi si riducono considerevolmente.
Il vantaggio del Merge Join è che viene sostanzialmente ridotto il numero di confronti tra i record
delle due relazioni, grazie all’ordinamento.
Hash Join
Un altro modo per ridurre il numero di confronti è partizionare i record usando una funzione hash.
L’algoritmo di Hash Join, applicabile solo in caso di equi-join, non richiede né la presenza di
indici né input ordinati.
Quindi se due tuple sono associate a due valori diversi della funzione hash (applicata agli
attributi di join) allora sicuramente le tuple non possono essere in join.
Recap Join
Uguaglianza su più di un attributo: si può usare un indice su tutti gli attributi o uno qualsiasi
degli attributi e poi filtrare il risultato rispetto alle condizioni rimanenti (analogo a selezioni
composte); in merge e hash join, si deve ordinare/partizionare su tutti gli attributi di join.
Theta-join con disuguaglianza: si può usare l’index nested loop se l’indice è ordinato; merge e
hash join non sono applicabili.
Elaborazione del Piano Fisico - Materializzazione
Un piano di esecuzione fisico è un albero.
Vediamo ora come si combinano insieme i risultati delle varie operazioni viste finora.
Pipeline
Un modo alternativo di eseguire un piano di accesso è quello di eseguire più operatori in
pipeline, ovvero non aspettare che termini l’esecuzione di un operatore per iniziare l’esecuzione
di un altro.
Quando possibile è più efficiente.
Osserviamo che, nel caso del join, il pipeline si può applicare solo se l’operatore fisico esegue un
ciclo sulla relazione outer:
● Nested loop semplice,
● Nested loop a blocchi,
● Index nested loop: in questo caso, il pipeline viene applicato alla relazione outer mentre
la relazione inner, utilizzata ad ogni ciclo per i confronti, deve essere materializzata.
Il pipelining permette di risparmiare il costo di scrivere il risultato intermedio e di
rileggerlo successivamente.
Poiché tale costo può essere significativo si preferisce il pipelining alla materializzazione
se l’algoritmo per l’operatore lo permette.
Per la determinazione della stima dei costi delle varie operazioni e della dimensione del risultato,
si utilizzano dati contenuti nei cataloghi di sistema.
Tali statistiche sono aggiornate alla creazione di un indice e in seguito solo periodicamente,
poiché aggiornarle dopo ogni modifica ai dati sarebbe troppo costoso; poiché le stime dei costi
sono approssimate comunque si accetta che tali valori non siano completamente accurati.
Molti DBMS prevedono un comando (ANALYZE in PostgreSQL) per richiedere esplicitamente il
loro aggiornamento.
Si stima poi che sP(R) selezioni un numero di tuple pari a T(R) * F(P).
Approccio Generale
In generale:
1. Prima si ragiona su quali indici potrebbero migliorare le prestazioni delle query più
importanti, analizzando una query alla volta,
2. Quindi si considera se l’aggiunta di ulteriori indici può migliorare la soluzione.
1) Primo passo:
Si considerano una alla volta le interrogazioni, partendo da quelle ritenute più importanti (= usate
più di frequente):
Per ogni interrogazione:
a) Gli attributi che appaiono in una clausola WHERE sono candidati come chiavi di
ricerca per un indice;
b) Si prova a ipotizzare un potenziale piano di esecuzione ottimale per l’interrogazione;
c) Si individuano gli indici che permettono al sistema di prendere in considerazione il
piano individuato nello spazio dei piani, tenendo presente gli algoritmi di realizzazione
messi a disposizione dal sistema;
d) Si valuta empiricamente l’efficacia degli indici creati sull’interrogazione (cioè si esegue
l’interrogazione in presenza e in assenza degli indici e si confrontano le prestazioni);
e) Se gli indici non sono efficaci, cercare di comprenderne la motivazione ed eventualmente
rimuoverli dal livello fisico.
Bisogna sempre considerare che solo un indice per relazione può essere clusterizzato.
Tabelle Piccole
Per una tabella memorizzata su pochi (eventualmente 1) blocchi, è opportuno evitare di creare
indici.
Infatti in questo caso la scansione sequenziale è sempre una strategia efficiente; vengono
accedute tutte le pagine (poche) su cui la relazione è memorizzata.
Per query con condizioni di selezione e join di uguaglianza, un indice hash può garantire
migliori prestazioni, ma la presenza di overflow può però limitare il vantaggio.
Nei casi dubbi, meglio indice ad albero; per query con condizioni di selezione e join di tipo
intervallo, solo un indice ordinato permette l’accesso ai dati.
Indici Multiattributo
Gli indici multi-attributo richiedono uno spazio maggiore per la loro memorizzazione e vengono
aggiornati più spesso.
Permettono di utilizzare l’indice in un maggior numero di interrogazioni.
Join
Equijoin: R join S on R.A = S.B, con S relazione inner.
Condizioni su Espressioni
Molti ottimizzatori non usano piani di esecuzione indicizzati in presenza di espressioni.
In questi casi, può convenire cercare di riscrivere l’interrogazione, aggiungendo filtri o
modificando le condizioni ed eliminare le espressioni.
Viste
L’uso delle viste semplifica la specifica delle query: le query su viste però non sono mai più
veloci di quelle senza uso di viste, quindi bisogna usarle con prudenza.
Durante l’esecuzione di query che le coinvolgono infatti il sistema deve espanderle.
Esempio:
CREATE MATERIALIZED VIEW Noleggi_Commedie AS
SELECT dataNol, codCli FROM Noleggi NATURAL JOIN Video
NATURAL JOIN Film WHERE genere = ‘commedia’
Sintassi completa:
BUILD: specifica quando il contenuto della vista deve essere determinato per la prima volta;
- IMMEDIATE: calcola e memorizza i dati al momento della definizione della vista;
- DEFERRED: il calcolo e la memorizzazione avviene al primo utilizzo.
REFRESH:
- COMPLETE: ad ogni aggiornamento, si ricalcola completamente il contenuto della vista;
- FAST: aggiornamento incrementale;
- NEVER: nessun aggiornamento automatico.
Clausola DISTINCT
La presenza della clausole DISTINCT comporta l’eliminazione dei duplicati dal risultato e quindi
richiede una operazione di ordinamento (costosa).
Se non è necessario usare DISTINCT, bisogna quindi evitare di usarlo inutilmente.
Ad esempio DISTINCT non è necessario quando selezioniamo attributi chiave.
Sottointerrogazioni
Durante la fase di ottimizzazione fisica, se possibile le interrogazioni che contengono
sottointerrogazioni vengono trasformate in interrogazioni equivalenti senza
sottointerrogazioni.
Se non è possibile eliminare la sottointerrogazione (ad es. sottointerrogazioni scalari), la
sottointerrogazione viene valutata e il valore ottenuto dalla sua interrogazione sostituito
nell’interrogazione principale
Le sottointerrogazioni correlate devono essere invece valutate in riferimento al valore della tupla
candidata esaminata nell’interrogazione principale, e quindi l’ottimizzatore ha molti meno
margini di ottimizzazione.
In generale, non tutti gli ottimizzatori trattano in maniera efficace le sottointerrogazioni (e sono in
grado di riconoscere quando una interrogazione può essere riscritta senza
utilizzo di sottointerrogazioni).
La presenza di sottointerrogazioni e l’eventuale incapacità del sistema ad eliminarle tramite
riscrittura può portare a non utilizzare indici esistenti nel piano fisico individuato.
Quindi, se è possibile, è preferibile riscrivere una interrogazione con sottointerrogazioni in una
equivalente ma senza sottointerrogazioni.
La strategia di riscrittura dipende dal tipo di sotto-interrogazione:
- correlata/non correlata,
- scalare/non scalare.
Le sotto-interrogazioni non correlate scalari non sono problematiche, la maggior parte dei
sistemi ottimizza indipendentemente query interna (che restituisce un singolo valore) e query
esterna) senza alcun problema di riscrittura.
Le sotto-interrogazioni non correlate non scalari possono invece essere problematiche ed è
conveniente tradurle ad esempio trasformandole attraverso un join; se l’interrogazione riscritta
necessita di una clausola DISTINCT non è detto che risulti più efficiente.
Per le sotto-interrogazioni non correlate non scalari la strategia è quindi la seguente:
- vengono combinati gli argomenti delle due clausole di FROM;
- vengono messe in congiunzione le clausole di WHERE:
- Si rimpiazza la condizione “outer.attr1 IN (SELECT inner.attr2 ...)” con “outer.attr1 =
inner.attr2” nella clausola di WHERE.
- Viene mantenuta la clausola SELECT della query esterna.
La strategia funziona per qualunque livello di annidamento ma potrebbe introdurre duplicati non
restituiti dalla query iniziale.
In questo caso è necessario aggiungere una nuova clausola DISTINCT che potrebbe limitare il
beneficio della riscrittura.