Sei sulla pagina 1di 222

Basi di Dati

Lezione 22 Febbraio - Presentazione

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).

Lezione 23 Febbraio - Introduzione

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.

Le informazioni devono avere 3 caratteristiche principali:


● Disponibilità controllata e selettiva (condivisione controllata),
● Permanenza sicura,
● Flessibilità e facilità di manipolazione.

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.

Dati & Informazioni


Un’informazione è tutto ciò che produce variazioni nel patrimonio cognitivo di un soggetto, il
precettore dell'informazione.

Nei sistemi informatici, le informazioni si rappresentano come dati, registrati su un supporto che
ne garantisce conservazione e reperibilità.

Il sistema informativo fornisce un contesto interpretativo per trasformare dati in


informazioni.

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

Un DBMS collabora con il S.O. per garantire 5 punti fondamentali:

1. Evitare ridondanza e inconsistenza;


Se abbiamo varie applicazioni che utilizzano gli stessi dati è importante che essi restino
consistenti tra loro, ovvero che non siano discordanti.

2. Facilità di accesso ai dati;


Se costruisco varie applicazioni che condividono parti di codice devo evitare che ogni
accesso ai dati mi richieda la riscrittura e la duplicazione dello stesso codice.

3. Integrità dei dati;


Bisogna stabilire delle regole che vincolino i dati all’insieme dei possibili valori ammessi
in un determinato contesto.
Ad esempio: il campo “semestre” avrà come valori ammissibili 1 (primo) e 2 (secondo).

4. Accesso concorrente ai dati;


Se su un file accedono molte persone/applicazioni bisogna gestire gli accessi concorrenti
sincronizzandoli tra di loro.
Questo va fatto con granularità molto fine, ovvero agendo solo ed esclusivamente su una
determinata entità, per evitare di andare a bloccare altre parti del sistema.

5. Protezione dei dati;


Controllo dell’accesso a granularità molto fine e anche basata sul contenuto.
Anche in questo caso bisogna decidere con granularità molto fine a quali risorse
un’entità può accedere.

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).

Garantendo un’unica rappresentazione centralizzata e condivisa.

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.

Differenza tra Istanza e Schema


Uno schema descrive i dati ed inoltre:
● Cambia raramente
● Ha descrizione intensionale (descrive le caratteristiche che devono avere i dati).

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.

Un esempio è una tabella:


Lo schema di essa è la sua intestazione, che definisce cosa c’è in una determinata cella.
L’istanza è l’insieme dei dati effettivamente presenti all’interno delle celle in un determinato
momento.

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.

Inoltre ottimizza le operazioni e quindi garantisce efficienza.


Vi è una sostanziale differenza tra una:
- Interrogazione Dichiarativa;
ad esempio: “voglio trovare il cliente che il 01/01/20 ha affittato il video “I found my
stepsister alone in my bedroom”.

- Interrogazione Procedurale.
che invece consiste nell’effettuare una serie di controlli procedurali sulla dichiarazione
fino ad arrivare al risultato.

La prima in questo caso risulta molto più efficiente.

Livelli nella Rappresentazione dei Dati - astrazione di un DBMS


1) Livello Esterno (o vista),
○ descrive una porzione dell’intero schema della base di dati,
○ possono essere definite più viste di una stessa base di dati,
○ è un livello presente solo in DB con schemi medio-grandi.

2) Livello Logico, descrive lo schema logico:


○ quali sono i dati memorizzati nella base di dati,
○ eventuali associazioni tra di essi,
○ vincoli di integrità semantica e di autorizzazione,
○ viene espresso tramite le strutture del modello dei dati,
○ è il livello principale di comunicazione con il DBMS.

3) Livello Fisico, definisce lo schema fisico:


○ strutture di memorizzazione dei dati (file, record, ecc.),
○ solo il DBA, se necessario, scende a questo livello.

Indipendenza dei dati


Con l’utilizzo dei 3 livelli si facilitano l’accesso ai dati, lo sviluppo di applicazioni e la
manutenibilità; questi 3 aspetti vengono garantiti perché i 3 livelli assicurano indipendenza
logica e fisica;

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.

Punti Fermi per i DBMS Relazionali


1. Viene preferito un Integration DB piuttosto che un Application DB.
Infatti un Integration DB integra i dati in un unico schema logico condiviso, mentre in un
Application DB ogni applicazione ha e gestisce i propri file e dati di interesse.

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.

Modello Dati - Linguaggi


Gli utenti che usano un sistema relazionale, interagiscono con esso attraverso 3 linguaggi:

● Data Definition Language (DDL) ,


○ Permette di creare, modificare e interrogare lo schema della base di dati e quello
delle viste.
○ Livello logico ed esterno.

● Data Manipulation Language (DML)


○ Permette di creare, modificare e interrogare le istanze della base di dati.
○ Livello logico (ed esterno)

● Storage Definition Language (SDL)


○ Definisce lo schema fisico del DB.
○ Livello fisico.

Controllo dell’Accesso ai Dati


Bisogna stabilire vari punti per definire come il DBMS possa soddisfare i 5 punti fondamentali
che deve garantire; ad esempio:
- chi può accedere alla b.d.,
- a quali risorse si può accedere,
- chi può condividere privilegi e su cosa,
- chi può revocare i privilegi e su cosa.

Ci sono poi molte questioni riguardo al come accedere e manipolare le risorse.

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:

I servizi esterni possono essere invocati:


● direttamente, tramite API per l’esecuzione dei comandi dichiarativi,
● indirettamente, tramite applicazioni ed interfacce (testuali o grafiche).

● 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:

● Controllo di integrità, mantenere consistenza e correttezza dei dati,


● Strutture di memorizzazione, rappresentare in memoria secondaria i costrutti del
modello dei dati,
● Ottimizzazione di interrogazioni, determinare strategie efficienti di accesso ai dati,
● Protezione dei dati, evitare accessi non autorizzati,
● Ripristino della base di dati, evitare che errori e malfunzionamenti ,
○ Rendano inconsistente la base di dati,
○ Provochino perdite di dati.
● Controllo della concorrenza, evitare inconsistenze dovute ad accessi concorrenti.

In pratica questi punti si occupano di garantire i 5 punti fondamentali ma anche


l’efficienza del DBMS.

Definizione Ampliata del DBMS


Il DBMS è un sistema software che permette di gestire:
- grandi quantità di dati persistenti e condivisi,
- in modo efficiente,
- garantendone l’integrità,
- seguendo una determinata politica degli accessi,
- con esecuzioni coordinate e protette da malfunzionamenti.

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.

Il query manager è composto da:


● Gestore degli accessi,
● Gestore dell’integrità,
● Elaboratore delle interrogazioni.

Anche se nell’architettura si parla di interrogazioni (query), il query manager si occupa di


analizzare, autorizzare, ottimizzare ed eseguire tutti i comandi DML.

Il transaction manager è composto da:


● Gestore della concorrenza,
● Gestore del ripristino.

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:

● Progettista di basi di dati,


● Programmatore applicativo,
● Amministratore della base di dati (DBA),
● Progettista, sviluppatore di 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

Lezione 25 Febbraio - Modello Relazionale

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.

Questo modello è caratterizzato da precise basi matematiche:


- logica dei predicati del primo ordine,
- teoria degli insiemi.

Teoria degli Insiemi


Basata sui concetti di:
- Dominio,
- Prodotto Cartesiano.

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).

I domini che useremo più spesso sono:


int per indicare gli interi
real per indicare i reali
string per indicare le stringhe
date per indicare le date

Indicheremo con D l’insieme di tutti i domini.

Prodotto Cartesiano:
Il prodotto cartesiano è indicato con il simbolo ×.

Consideriamo D 1 , D 2 , … , D n ∈ D domini (n insiemi anche non distinti).

Il prodotto Cartesiano D 1 × D 2 × … × D n è definito come:


{(v 1 , v 2 ,. . . , v n )∨v 1 ∈ D1 ,. . . , v n ∈ Dn }
è l’insieme di tuple di n componenti in cui v1 appartiene a D 1 , v 2 appartiene a D 2 e così via

Ogni elemento del prodotto cartesiano è detto tupla.


Il prodotto Cartesiano D 1 × D 2 × … × D n ha grado n .

Relazione
Siano D 1 , D 2 , … , D n ∈ D domini.

Una relazione su D 1 , D2 , … , D n è un sottoinsieme finito del prodotto cartesiano


D 1 × D 2 × … × Dn .

Una relazione su D 1 , D 2 , … , D n ha grado n ; esso risulta quindi equivalente al numero di domini


coinvolti nella relazione; inoltre, ogni tupla di una relazione di grado n ha n componenti, uno per
ogni dominio su cui è definita la relazione cui la tupla appartiene.

La cardinalità di una relazione è il numero degli elementi dell’insieme ovvero il numero di tuple
appartenenti alla relazione.

ex. D1 = {0,1,2}, D2 = {a,b}

una relazione è un sottoinsieme finito del prodotto cartesiano tra i due ovvero r ⊆ D1 × D 2.
Ad esempio:

l’insieme vuoto è una relazione grado 0 cardinalità 0


{(0,a),(1,b)} è una relazione grado 2 cardinalità 2
{(0,a),(0,a)} non è una relazione, perché non è un insieme.
il prodotto cartesiano (nella sua totalità) è una relazione grado 2 cardinalità 6

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.

Una relazione è un insieme, quindi:


- non è definito alcun ordinamento fra le tuple,
- le tuple di una relazione sono distinte l’una dall’altra.
{(0,a),(1,b)} ne deriva:

1a colonna elementi di D1, 2a colonna elementi di D2.

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

Le tuple, per ogni posizione ci restituiscono un valore appartenente al dominio corrispondente.

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

in questo esempio t[1] = 0

Quindi ogni tupla è una funzione e la notazione è quella naturale.

Nella notazione posizionale lo schema è costituito da:


- Nome di relazione R,
- D1 , … , Dn

Notazione per Nome:


Per maggiore leggibilità è stata introdotta la notazione per nome, basata su attributi; in essa si
associa un nome di attributo ad ogni componente delle tuple in una relazione.

L’attributo è quindi la coppia (nome di attributo , dominio).


L’uso degli attributi permette di:
- denotare le componenti di ogni tupla per nome piuttosto che per posizione,
- fornire maggiori informazioni semantiche sulle proprietà che ogni componente delle tuple
in una relazione modella.

Useremo quindi questa notazione.

Schema:
Se R nome di relazione e{ A 1 , A2 , .. . , A n } un insieme di nomi di attributi.

dom :{ A1 , A 2 , . . ., An }→ D è una funzione totale che associa ad ogni nome di attributo in


{ A 1 , A2 , .. . , A n } il corrispondente dominio.

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).

nelle colonne abbiamo i nomi degli attributi: A1 e A2

quindi al posto di scrivere t(1) scriveremo t(A2)

Stiamo dando il contesto interpretativo per le nostre relazioni.

La coppia (R( A 1 , A2 , .. . , A n), dom) è uno schema di relazione.

U R denota l’insieme dei nomi di attributi di R : U R={ A 1 , A2 , . .. , A n }

esempio:

La tuple della tabella seguente sono funzioni: {A1, A2, A3} -> char U integer

Schema di Base di Dati:


Siano S1 , S 2 , . .. , Sm schemi di relazioni, con nomi di relazione distinti.

S={S 1 , S2 , . .. , S m } è detto schema di base di dati.

Tupla e Relazione:
Sia S=( R( A 1 , A2 , .. . , A n) , dom) uno schema di relazione.

Una tupla t definita su S è una funzione totale:


{t :U R → dom ( A 1) ∪… ∪ dom( A n )∨t (Ai )∈ dom( Ai) ∀ i∈[1 , n]}
Una tupla t su S può essere rappresentata come:
[ A 1 : v 1 , A2 :v 2 , .. . , A n : v n ] con vi =t [ Ai ]∈ dom( A i) , i=1 ,. . . , n

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

Relazione (= istanza dello schema) ∈ string × string × int × string × real

Tupla: [titolo:‘ed wood’,regista:‘tim burton’,anno:1994, genere:’drammatico’, valutaz:4.00]

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.

Nello schema specificheremo l’attributo opzionale con la notazione:


R( A 1 , ... , A n )O
il circoletto in AnO ci indica la possibilità di avere un valore NULL (?)

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à.

Sia R( A 1 , ... , A n) uno schema di relazione.


Un insieme X ∈U R di attributi di R è chiave di R se verifica entrambe le seguenti proprietà:

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.

2. Minimalità: nessun sottoinsieme proprio di X verifica la proprietà 1.

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).

Se il titolo è la chiave, non possono esistere 2 film con lo stesso titolo.


Il problema è che esistono alcuni film con lo stesso titolo (remake dello stesso film).
Possiamo quindi individuare la chiave (titolo, regista).

2. Insegnamenti(Codice, Nome, CdS)


Una chiave potrebbe essere il codice dell’insegnamento ma potrebbe esserlo anche la
coppia (Nome, CdS); ci sono 2 chiavi candidate.

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.

Le chiavi delle relazioni vengono individuate mediante


esame del dominio applicativo e dei relativi vincoli.
Una relazione ha sicuramente almeno una chiave U Rsoddisfa sempre la proprietà (1).
Dobbiamo scegliere tra le chiave candidate la chiave primaria: non deve avere valori nulli e
deve essere la più semplice possibile, ovvero quella con il dominio più ridotto.
Chiavi Primarie e Alternative:
● Una tupla è identificata dal valore di una qualunque chiave candidata,
● La chiave primaria viene utilizzata dal DBMS per ottimizzare le operazioni,
● Criteri di scelta della chiave primaria:
○ Chiave candidata contenente il minor numero di attributi,
○ Chiave candidata più frequentemente utilizzata nelle interrogazioni,
○ Non può assumere valori nulli,
● Le chiavi alternative (quelle non primarie) possono invece assumere valori nulli.

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.

A1 chiave primaria, A2 e A3 chiavi alternative.

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à.

Rappresentazione delle Associazioni:


Il meccanismo delle chiavi serve anche per legare tuple diverse, attraverso rappresentazioni per
valore e facendo in modo che tuple in relazioni diverse siano associate dal condividere gli stessi
valori di attributi corrispondenti.

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 ’.

Quindi X ={f (Y 1 ), … , f (Y k ) }∈ U R è una chiave esterna di R su R ' se:


qualsiasi siano gli stati di R ed R ' per ogni tupla t di R esiste una tupla t ' di R ' tale che
t [f (Y i )]=t ' [Y I ] per ogni i∈[1 , k ].

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

R’(F,G,H) è un’altra relazione; poniamo f(D) = F e f(E) = G

Allora le due relazioni sono legate dalla 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.

Prendendo in considerazione un altro esempio:


Data una relazione R con chiave A, B, una relazione R’ che si riferisce ad R deve avere almeno
due attributi, ovvero quelli che si riferiscono ad A e B;
Questi due campi non devono però necessariamente chiamarsi nello stesso modo.

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:

Studenti ¿MatrS, … . , Mat rP prof ¿ Prof ¿MatrP, … . ¿

Violazioni Integrità Referenziale


L’integrità referenziale può essere violata:
- nella relazione referente da inserimenti e modifiche del valore della chiave esterna;
- nella relazione riferita da cancellazioni e modifiche del valore della chiave.

I linguaggi per basi di dati quali SQL:


- garantiscono l’integrità referenziale,
- permettono all’utente, nella definizione di chiavi esterne, di specificare come agire, in
caso di operazioni conflittuali.

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à.

3. Se relazione referente = la relazione riferita, cioè la chiave esterna contiene un


riferimento alla relazione stessa, gli attributi devono avere nomi diversi.

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.

6. Le chiavi esterne possono assumere valore nullo.

Lezione 4 Marzo - Modello ER

Modello Entità-Associazione
Inventato da Peter Chen nel 1976, il modello ER (entity relationship) è uno dei modelli più
utilizzati nell'ambito della progettazione concettuale;

Esso serve per la modellazione astratta dei dati.


E’ quindi utile per utilizzare una notazione schematica, grafica e astratta, utile proprio in fase di
progettazione.

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

Manca dei costrutti per descrivere singole istanze;


Ritroveremo vari aspetti simili al modello ER quando studieremo UML class diagram in
ingegneria del software.

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.

Rappresentate graficamente da rombi contenenti il nome dell’associazione e connesse da linee


alle entità da mettere in corrispondenza:

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.

Un’associazione n-aria ha grado n:


- In teoria n alto a piacere
- In un modello sensato le associazioni sono quasi tutte binarie (grado 2) ed grado
massimo non è molto alto (~3).

esempi:
Associazione unaria: grado 1

Associazioni binarie: grado 2

Associazione ternaria: grado 3

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).

visti come funzione: f cognome c1 -> la corte


f dataN c1 -> 21/12/99

Per ciascuna istanza, un attributo associa un insieme di valori, anche se nella maggior parte
dei casi si parla di un solo valore.

Si possono esprimere vincoli sulla cardinalità di questo insieme:


- cardinalità minima (c_min): può essere un naturale;
- cardinalità massima (c_max): può essere un naturale oppure n (arbitrario).

Nei diagrammi ER si scrive la coppia (c_min,c_max) sull’asta del lollipop.

Vincoli di Cardinalità per Attributi


Si parla di attributi:
- opzionali: se la cardinalità minima è 0,
- obbligatori: se la cardinalità minima è 1,
- mono-valore: se la cardinalità massima è 1,
- multi-valore: se la cardinalità massima è > 1.

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.

Esempio: residenza ha sotto-attributi città, via, numero, cap; graficamente:

Spesso difficile capire se modellare qualcosa:


- come attributo composito,
- come entità associata.

L’idea è che se lo stesso tipo dell’attributo si riusa meglio introdurre un’entità.


Dominio di un Attributo
Le informazioni sui domini di un attributo non sono direttamente rappresentabili in un diagramma
ER; li possiamo scrivere però da parte, nel dizionario dei dati, con la seguente notazione:
dom ( A , E)=Dominio ex . dom( titolo , film)=String

Sono quindi fondamentali per una corretta modellazione, poiché:


- Sono parte integrante del modello ER,
- Fanno parte della documentazione a corredo del diagramma ER (dizionario dei dati).

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.

Intervalli di valori, ad esempio di interi o di caratteri:


Notazione: [min,max] rappresenta {v | min ≤ v ≤ max}

Insiemi di valori (per attributi multi-valore):


Notazione: set_of(D) rappresenta {X| X ⊆ D}

Domini definiti per enumerazione dall'utente:


Notazione: {v1, ... ,vn} rappresenta l’enumerazione {v1, ..., vn}

Domini Composti: Servono per associare un dominio agli attributi composti.


Prodotto cartesiano dei domini componenti.

Notazione D = D1 × D2 × … × Dn rappresenta { | di ∈ Di, i = 1, ..., n

Semantica delle Associazioni


La semantica di un'associazione consiste di:
- un sottoinsieme A del prodotto Cartesiano delle semantiche delle entità che partecipano
all'associazione.
- per ciascun attributo dell’associazione è presente una funzione da A nel dominio
dell’attributo.
- per ciascuna istanza dell’associazione, ovvero per ciascuna tupla di istanze di
entità poste in relazione, ogni attributo ha un unico valore.

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à.

Anche i domini degli attributi sono vincoli di integrità.


Vincoli di Cardinalità per Associazioni
Vi è un vincolo per ciascuna entità coinvolta nell’associazione.

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)

Valori più comuni:


- c_min: 0, 1
- c_max: n, ovvero qualunque intero > 1

Data un’entità E ed un'associazione A:


- c_min = 1 ⇒ ogni istanza di E deve partecipare ad almeno una istanza di A
- la partecipazione di E ad A è obbligatoria

- c_min = 0 ⇒ ogni istanza di E può non partecipare ad alcuna istanza di A


- la partecipazione di E ad A è opzionale

- c_max = 1 ⇒ ogni istanza di E può partecipare a non più di una istanza di A

- c_max = c_min=1 ⇒ ogni istanza di E partecipa ad una ed una sola istanza di A

- c_max = n ⇒ non esiste limite al numero massimo di istanze di A a cui ogni istanza di
E può partecipare

- c_min = 0, c_max = n ⇒ ogni istanza di E può partecipare ad un numero qualsiasi di


istanze di A, anche nessuna

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.

Un'associazione binaria A si dice:


- uno a uno: se c_max di entrambe le entità rispetto ad A è 1
- uno a molti: se c_max di una entità rispetto ad A è n e c_max dell’altra entità rispetto ad
Aè1
- molti a molti: se c_max di entrambe le entità rispetto ad A è n

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.

La partecipazione di POLIZZA a copertura ha vincolo di cardinalità: (1, n) per lo stesso motivo.

La partecipazione di FAMILIARE a copertura ha vincolo di cardinalità: (0, 1), perché un familiare


può partecipare ad una sola tupla, ovvero quella corrispondente alla polizza.

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.”

In generale, sempre meglio usare associazioni binarie, come questa:

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).

Un identificatore è minimale se qualsiasi sottoinsieme proprio non è un identificatore (analogo a


chiave).

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.

L'entità identificata in questo modo viene detta entità debole.

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)

● Misti: sia con componenti interni che esterni.

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.

Identificatore esterno semplice:


Non vogliamo usare una proprietà dell’entità per distinguere le sue istanze, ma la partecipazione
ad un’associazione.
La nazionale viene identificata attraverso il nome della nazione che la rappresenta.
Nazionale è un’entità debole perché dipende da Nazione.

Se E2 (nazionale) è identificata esternamente da E1 (nazione) attraverso l'associazione A


(gioca), a ogni istanza di E2 può essere collegata a una e una sola istanza di E1.

Identificatore esterno composto:


Ad esempio le olimpiadi: ogni nazionale rappresenta una certa nazione in un certo sport; la
combinazione disciplina+nazione ci identifica la squadra nazionale.
La indichiamo con due stanghette che sorpassano quelle che collegano le entità per poi unirle a
loro volta e annerirne il pallino.

Identificatore misto (composto):


Una videoteca che decide di consorziarsi con altre videoteche e di creare un unico catalogo.
A questo punto devo fornire informazione sulla videoteca (nome e città); le collocazioni sono
state impostate per essere uniche in una videoteca ma possono risultare uguali in altre
videoteche.
Abbiamo quindi un'associazione (esterno) ed un attributo (interno).
Anche in questo caso identifico video solo se so la videoteca (entità debole).

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.

Non ha senso un identificatore (annerire i pallini) nelle associazioni.

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:

1. L'insieme delle istanze di E2 deve essere contenuto in quello delle istanze di E1


2. Ogni attributo di E1è anche un attributo di E2
3. Ad ogni associazione cui partecipa E1 partecipa anche E2
Generalizzando, E può essere generalizzazione di E1 , … , E n:

Tutte le istanze di E1 , … , E n sono quindi istanze di E :


- contenimento di istanze,
- ereditarietà di proprietà e associazioni.

Può essere una generalizzazione:

- Totale: ogni istanza di E è istanza di almeno un'entità Ei

- Parziale: esiste almeno un'istanza di E che non è istanza di alcuna entità Ei


Esempio 1: Cliente - Standard, VIP
- se esistono clienti che possono effettuare un noleggio senza registrarsi al
programma di fidelizzazione è parziale
- altrimenti è totale e standard e VIP sono le uniche tipologie di clienti previste
dalla videoteca

Esempio 2:

E’ parziale perché esistono laureati che non siano né ingegneri o medici, ad esempio i
laureati in informatica.

- Esclusiva: ogni istanza di E è istanza di al più un'entità Ei


es. Cliente - Standard, VIP

- Condivisa: esiste almeno un'istanza di E che è istanza di più di un'entità Ei


Esempio 1: Film - FilmAnimazione, FilmEssay
Esempio 2: ci sono laureati che hanno due lauree: una in ingegneria ed una in medicina.

Le due classificazioni precedenti sono ortogonali.

Le generalizzazioni possono quindi essere di 4 tipi:


- Totali esclusive, anche dette partizionamenti,
- Totali condivise,
- Parziali esclusive,
- Parziali condivise.
Le informazioni sulle tipologie di gerarchie di generalizzazione presenti in uno schema ER vanno
inserite nella documentazione a corredo dello schema.

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 .

esempio: un FilmBn è un caso particolare di Film.

esempio: un telaio non è un veicolo ma è parte di esso (sarebbe una associazione di


aggregazione).

Recap Modello ER - Principali Simboli Grafici

Componente Simbolo

Entità

Relazione

Attributo

Attributo Composito
Gerarchia di Generalizzazione

Relazione di Sottoinsieme

Identificatore

Vincolo di Cardinalità (c_min, c_max)

Materiale Aggiuntivo - Progettazione di Basi di Dati

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.

L’attività di progettazione viene quindi strutturata in fasi di modellazione della


realtà per approssimazioni successive.

Un modello è una rappresentazione formale della base di dati:


- astrae da aspetti non rilevanti per l’analisi che si vuole fare,
- permette di verificare specifiche,
- facilita comunicazione con gli utenti.

Ogni fase prevede verifiche di qualità.

Se la qualità non è sufficiente si effettuano:


- perfezionamenti o totali riscritture del modello generato in quella
fase;
- ripetizioni parziali o totali della fase
precedente.
Raccolta ed Analisi dei Requisiti
La prima fase è la raccolta e l’analisi dei requisiti, che si traduce nelle operazioni descritte in
figura, che rappresentano le caratteristiche della base di dati e che ci forniscono un documento
di partenza per le fasi successive: la specifica dei requisiti.

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).

L’output di questa fase è:


● Lo schema concettuale sviluppato utilizzando il modello concettuale prescelto;
● Documentazione a corredo dello schema:
○ Vincoli d’integrità non rappresentabili nello schema,
○ Scelte progettuali,
○ Altre informazioni che vedremo in seguito.

Progettazione Logica
Traduzione dello schema concettuale nel modello dei dati del DBMS target.
L’output è lo schema logico nel DDL del DBMS target.

Gli aspetti considerati durante la progettazione logica sono:


● Integrità e consistenza,
● Sicurezza,
● Efficienza.

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.

Raccolta ed Analisi dei Requisiti


È una attività difficilmente standardizzabile.
Di solito condotta da personale esperto, in stretta collaborazione con gli utenti finali.
Una volta ottenuto il documento di specifica, è però possibile effettuare alcune operazioni su di
esso, che faciliteranno le successive fasi di progettazione.

Operazioni sul Documento di Specifica


1. Leggerlo attentamente evidenziando:
a. tutte le possibili fonti di ambiguità ed imprecisione,
b. sinonimi usati per lo stesso concetto.

2. Chiarire i dubbi con i committenti del progetto,

3. Scegliere per ogni concetto il termine che meglio lo modella e usare esclusivamente
quello,

4. Separare le frasi che riguardano i dati da quelle che riguardano le operazioni,

5. Riorganizzare le frasi per concetti.

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à.

Scelta dei Costrutti


Leggere attentamente il documento di specifica e per ogni concetto individuato, scegliere il
costrutto del modello ER più idoneo alla sua rappresentazione.
Esistono alcune linee guida per effettuare questa scelta;

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

Per modellare proprietà con sotto-proprietà si usano:


Es. residenza dei clienti della videoteca
● attributi composti ⇒ duplicazione di eventuali dati uguali in entità diverse (più clienti con
stesso indirizzo).
● oppure entità ⇒ eventuali dati uguali in entità diverse in copia unica.
● la seconda si usa quando lo stesso valore dell’attributo si ripete in più entità.

Dal documento di specifica si deducono anche i vincoli di cardinalità.

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.

Sulla base di questo si decompone in sotto-problemi.

Ciascun sotto-problema viene:


● raffinato
● espanso

Le soluzioni dei sotto-problemi vengono integrate per arrivare allo schema finale.

Documentazione di Supporto
La documentazione di supporto deve contenere:

1. Domini degli attributi.

2. Vincoli imposti dal dominio non rappresentabili tramite vincoli di identificazione e


cardinalità nel diagramma ER.

3. Vincoli di autorizzazione, se necessario.

4. Tipologie di gerarchie di generalizzazione.

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.

Lezione 15 Marzo - Progettazione Logica

Fasi della Progettazione


Progettazione Concettuale: ci concentriamo su cosa rappresentare.
- input: specifica informale dei dati

- 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.

- obiettivo primario: rappresentazione non ambigua dei dati

Progettazione Logica: ci concentriamo su come rappresentare lo schema.

- 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

Fasi della Progettazione Logica

1. Ristrutturazione:
Schema Concettuale -> Schema Concettuale Ristrutturato

2. Traduzione:
Schema Concettuale Ristrutturato -> Schema Logico (relazionale)

3. Verifica di Qualità.

Introduzione alla Fase di Ristrutturazione


E’ un’attività di progettazione logica.
Genera lo schema ER ristrutturato, ovvero uno schema ER semplificato equivalente a quello di
partenza.
Anche se è descritto più o meno allo stesso modo di uno schema concettuale, esso non lo è.

Scopo: semplificare la traduzione successiva:

1. Eliminazione dallo schema ER dei costrutti non direttamente rappresentabili nel


modello relazionale:
- attributi composti,
- attributi multivalore,
- gerarchie di ereditarietà.

2. Ristrutturazioni guidate da aspetti prestazionali identificati dall'analisi del carico di


lavoro (workload) e dal volume dei dati (mole di istanze attese per le varie entità o
associazioni).

La traduzione non sempre è univoca:


- esigenze contrastanti,
- scelte del progettista sulla base della rilevanza attribuita alle singole esigenze.

Introduzione alla Fase di Traduzione


Traduce lo schema ER ristrutturato in uno schema relazionale equivalente:
- la traduzione è basata su regole predefinite,
- in molti casi si possono applicare più regole.

Traduzione non sempre univoca, ma in generale le scelte devono essere guidate da


considerazioni prestazionali.

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à).

Ristrutturazioni per migliorare le prestazioni, suggerite dall'analisi del workload:


● analisi della ridondanza,
● partizionamento/accorpamento di entità.

Analisi della Ridondanza


Una ridondanza indica la presenza ripetuta della stessa informazione; essa:
- viene rappresentata esplicitamente nello schema,
- può essere derivata da altre informazioni presenti nello schema.

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).

Eliminare la ridondanza permette di semplificare lo schema ER ed il corrispondente schema


relazionale.

Bisogna chiedersi, analizzando il workload, se dal punto di vista dell’efficienza mi conviene


memorizzare o no l’attributo o l’associazione ridondante.

Svantaggi della ridondanza:


- maggiore occupazione di spazio,
- appesantimento delle procedure di aggiornamento.

Vantaggi della ridondanza:


- può rendere più efficienti alcune interrogazioni descritte nel carico di lavoro se:
1. le interrogazioni vengono eseguite molto più frequentemente degli
aggiornamenti,
2. le informazioni ridondanti sono di dimensione contenuta può essere giustificata.

Morale: la ridondanza deve essere:


- limitata ai casi in cui sia possibile ottenere un significativo beneficio in termini di tempo di
esecuzione di interrogazioni eseguite frequentemente,
- esplicitata nella documentazione,
- gestita automaticamente durante gli aggiornamenti.

Le valutazioni da effettuare per decidere se eliminare entità o associazioni ridondanti si basano


su stime inerentemente soggettive.

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.

situazione di arrivo: tre entità; partizioniamo verticalmente, in base alle entità.

Potremmo anche partizionare orizzontalmente, in base alle istanze: ad esempio quando


partiziono gli studenti in laureati e laureandi.

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.

Partizionamento e Accorpamento si possono eseguire già nella fase di ristrutturazione, spesso


però si rimandano alla fase di progettazione fisica per disponibilità di ulteriori informazioni relative
all'esecuzione delle interrogazioni.

Eliminazione degli Attributi Composti


Eliminazione di un attributo composto A da un'entità E; tre soluzioni:

1) merge dei sotto-attributi di A in un unico attributo semplice:


- diventa responsabilità delle applicazioni garantire che il nuovo attributo contenga
valori coerenti con la semantica dell'attributo composto ristrutturato e fare merge
e unmerge.

2) promuovere i sotto-attributi, ovvero considerare tutti i sotto-attributi di A come attributi


di E:
- ridefinizione del dominio dell'attributo;
- si perde la relazione tra i sotto-attributi.
- diventa molto inefficente, se ci accedo molte volte perdo molto efficienza; può
essere utile quando vi accedo poche volte.
esempio da “indirizzo” composto ad attributi a valore singolo “via, numero, cap, ..”.

3) introdurre un’entità per rappresentare il tipo di A e associarla ad E; utile se l’attributo


oltre che composto è anche multivalore.

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.

Eliminazione degli Attributi Multi-valore


Data un’entità E con un attributo multivalore A :
- si definisce una nuova entità E A con un attributo monovalore A ,
- si collegano E ed E A tramite un'associazione R A

- Vincoli di cardinalità rispetto alla nuova associazione:


- per E stesso vincolo di cardinalità dell'attributo multi-valore,
- per E A può essere in generale posto uguale a (1 , n).

Eliminazione delle Gerarchie di Generalizzazione


Poniamo il caso di avere un’entità E generalizzazione di entità E 1 , … , En .

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.

L'eliminazione di entità padre è applicabile solo se la gerarchia è totale.


L'eliminazione di entità figlie è conveniente solo nel caso in cui le operazioni non fanno
distinzione tra le varie sotto-entità.
L'eliminazione di entità figlie causa spreco di memoria per la presenza dei valori nulli.
Se si decide di mantenere sia entità padre che entità figlie, l'entità figlia è identificata
esternamente dall'associazione che la lega all'entità padre.

Eliminazione Entità Figlie


Poniamo il caso che le entità figlie E 1 , … , En vengono eliminate.

Attributi:
- gli attributi di E 1 , … , En vengono inseriti in E come opzionali,

- si aggiunge ad E un attributo tipo che specifica da quale entità figlia Ei proviene


l’istanza dell'entità padre nello schema ristrutturato:
- nel caso di generalizzazioni totali, non può mai essere nullo,
- nel caso di generalizzazioni parziali, un valore nullo indica un'istanza di E che,
nello schema originario, non era istanza di nessuna Ei ,
- nel caso di generalizzazioni condivise, sarà multi-valore.

- bisogna aggiungere un vincolo di integrità per garantire che se tipo = Ei , gli attributi
obbligatori di Ei siano non nulli,

- bisogna aggiungere un vincolo di integrità per garantire che se un attributo di Ei è non


nullo, allora tipo = Ei (se la generalizzazione è condivisa, Ei è uno dei valori assunti da
tipo).

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.

Eliminazione Entità Padre


Applicabile solo nel caso di generalizzazione totale.

Entità: eliminazione dell'entità padre E .


Attributi: inserimento degli attributi di E in ciascuna delle entità figlie.
Associazioni: ogni associazione a cui partecipava E viene sostituita con n nuove associazioni,
una per ogni Ei .

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.

Sostituzione della Generalizzazione con Associazioni


Entità: non modificate.

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à.

Eliminazione entità padre:


- solo per generalizzazione totale,
- conveniente soprattutto nel caso in cui esistano operazioni che si riferiscono alle istanze
di una specifica entità figlia.

Sostituzione con associazioni:


- preferibile alla soluzione di eliminare le entità figlie per quanto riguarda la quantità di
memoria utilizzata;
- conveniente quando esistono delle operazioni che discriminano tra entità padre ed entità
figlie.

In alcune situazioni, può essere conveniente adottare soluzioni ibride:


- eliminazione di un sottoinsieme delle entità figlie, mantenendo le altre nello schema.

Generalizzazione a più livelli:


- si applicano le strategie proposte partendo dalle foglie della gerarchia complessiva;
- lo schema risultante dipenderà dal tipo della ristrutturazione applicata ad ogni livello.
Fase di Traduzione
Traduce lo schema ER ristrutturato in uno schema relazionale equivalente.

Si basa su un insieme di regole di traduzione:


- traduzione delle entità
- traduzione delle associazioni
- traduzione dei vincoli di integrità
- ottimizzazioni finali
In pratica, le traduzioni principali sono:

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 .

Vincolo di obbligatorietà per A 1 , A 3 , A 4 .

Chiavi candidate: A 1 ,(A 3 , A 4); sceglieremo la chiave con meno attributi e con dominio più
semplice possibile.

altro esempio:

Videoteca avrebbe potuto non essere sottolineata, poiché sottolineare


tutto può equivalere a non sottolineare nulla; per inciso queste 3
indicazioni sono equivalenti:

L’unica eccezione la vedremo in SQL, in cui c’è effettivamente differenza


tra sottolineare tutto o niente perché la relazione non ha il vincolo di non
duplicazione (come nel modello relazionale).
Entità con identificatore misto o esterno:
Diventa: E 1( A 1 , A 2O , A 3 , A 4 , B 1 E 2)

E 2(B 1)

Con domini estratti dalla documentazione generata dalla progettazione concettuale.

Vincolo di obbligatorietà per A 1 , A 3 , A 4 , B 1.

Chiavi candidate per E 2 :B 1

Chiavi candidate per E 1:( A 1 , B 1) ,( A 3 , A 4)

A viene automaticamente rappresentata dall’inserimento della chiave esterna.

altro esempio:

Entità con Identificatori Esterni/Misti:


Diventa: E 1( A 1 , A 2O , A 3 , A 4 , , B 1 E 2 , C 1)
ci portiamo dietro C1 (attributo
dell’associazione).

Domini estratti dalla documentazione generata dalla


progettazione concettuale.

Vincolo di obbligatorietà per

A 1 , A 3 , A 4 , B 1 ,C 1

Chiavi candidate per E 2 :B 1


Chiavi candidate per E 1: B 1 ,( A 3 , A 4)
A viene automaticamente rappresentata.
Il processo di eliminazione degli identificatori esterni o misti parte dalle foglie (le entità che hanno
solo identificatori interni).
Se E 2 è parte di un identificatore esterno/misto di E 1 a sua volta identificato esternamente
bisogna eliminare
- prima l’identificatore esterno/misto di E 2 in modo da avere la chiave da usare nel passo
successivo,
- poi quello di E 1.

Scelta Chiave Primaria


Ciascuna relazione potrebbe essere caratterizzata da più di una chiave; è necessario
selezionare una di queste come chiave primaria.
La scelta dipende da criteri di efficienza.
Analoghi criteri esistono per determinare quale chiave di una relazione R 2usare come chiave
esterna in una relazione R 1; è preferibile definire una chiave esterna sulla base di una chiave
primaria.
Gli identificatori che contengono attributi opzionali non possono essere selezionati come
chiave primaria; sono preferibili:
- identificatori composti da pochi piuttosto che molti attributi,
- che si stima vengano utilizzati da molte operazioni per accedere alle entità.

Se nessuna chiave candidata soddisfa i requisiti precedenti si aggiunge un ulteriore attributo


codice come chiave primaria; ad esso vengono assegnati valori speciali generati.

Traduzione Associazioni
Un’associazione può essere tradotta come relazione o chiave esterna, a seconda della
molteplicità e del grado della nostra associazione.

Molteplicità: vincoli di cardinalità sulla partecipazione; un’associazione può essere:

1) Molti a Molti: quando non ci sono vincoli di cardinalità massima:

2) Uno a Uno: i vincoli di cardinalità massima sono ambi posti ad uno:

3) Uno a Molti: da un lato il vincolo di cardinalità massima è uno e dall’altro è n.

Grado: numero di entità coinvolte.


esempio: associazione binaria coinvolge due entità.
Per semplicità useremo entità con una sola chiave semplice; ometteremo
la rappresentazione del dominio degli attributi.
Per verificare di aver capito riformulate gli schemi usando entità con più chiavi candidate; non
dovrebbe cambiare niente, riformulate gli schemi usando entità con la chiave primaria composta;
completate con i domini degli attributi.

Associazione Binaria Molti a Molti:

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)

L’associazione diventa una relazione indipendente.

Attributi:
- chiavi delle entità coinvolte, come chiavi esterne verso le relazioni che rappresentano le
entità.
- quelli propri dell’associazione.

Un altro esempio:

Associazione Ternaria Molti a Molti:


Diventa: E 1( A 1 , A 2 , A 3 , A 4)
E 2(B 1, B2 , B3)
E 3( D 1 , D 2)
E1 E2 E3
A( A 1 , B 1 , D1 ,C 1)

Associazione Binaria Uno a Molti:

Diventa:

E 2(B 1, B2 , B3)

In una uno a molti traduciamo l’associazione con una


chiave esterna, che andiamo a porre dove il vincolo di
cardinalità massima è 1.
Mettere A 1E 1 in E 2 non sarebbe quindi possibile.

Associazione Binaria Uno a Uno:

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)

Le due traduzioni sono analoghe poiché c_min in


ambi i casi è 1; se in uno dei due lati fosse stato 0,
allora sarebbe stato meglio mettere la chiave
esterna nel lato di partecipazione obbligatoria.

Recap - Traduzione di Associazioni


Casi standard:
1. con una molti a molti traduciamo sempre con una nuova relazione,
2. con un’identificazione mista traduciamo sempre con la chiave esterna,
3. se la cardinalità massima è 1, ovvero in una uno a uno, è meglio usare la chiave
esterna, a meno di casi particolari.
.
Quando ho una uno a uno vado a vedere le cardinalità minime per capire dove mettere la
chiave esterna:
- se entrambe sono poste a 0 o ad 1 posso mettere la chiave esterna in entrambe
senza alcuna differenza; nel primo caso potrei addirittura modellarlo come
relazione.
- nel caso di partecipazione opzionale (c_min = 0) da uno dei due lati conviene
mettere la chiave esterna nella relazione per cui la partecipazione è
obbligatoria (c_min = 1).

esempio:

la chiave esterna è meglio metterla in tesi.

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.

Lezione 22 Marzo - Algebra Relazionale 1

Operazioni nel Modello Relazionale


Si possono esprimere in due formalismi di base, equivalenti sotto opportune ipotesi:

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.

Modello alla base della semantica di SQL:


- Linguaggio più semplice;
- più facile ragionare sul significato, ad esempio sui casi limite;
- si capisce meglio come si valutano le espressioni
- più facile ragionare su complessità e possibili ottimizzazioni,

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:

Operazione Algebra SQL


Indicare (estrarre) il contenuto R SELECT * FROM R
di una relazione
oppure

SELECT DISTINCT A1, …,


An FROM R

(estrai dalla tabella R tutte le


sue colonne)

Ridenominazione

Operazione Algebra SQL

Ridenominazione ρ A 1, ..., Ak ← B 1 ,..., Bk (R) SELECT DISTINCT


A1 AS B1
Sostituisco A 1 ,... , Ak …..
con i corrispondenti Ak AS Bk
B1 , ... , Bk tutti distinti. FROM R

(estrai le colonne
In algebra è utile per
rinominandole)
garantire l’applicabilità di
alcune operazioni.

esempio:

rinomino A con G (la ridenominazione agisce solo


sull’intestazione)

non potrei fare queste operazioni perché violano delle


regole della ridenominazione.

Vincoli di Applicabilità di ρ A 1, ..., Ak ← B 1 ,..., Bk ( R):


- A 1 ,... , Ak devono stare in R,
- B1 , ... , Bk non possono già essere in R.

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 ,

- una lista A 1 ,... , An di nomi dei suoi attributi distinti ¿):


- ovvero{ A 1 , ..., An}⊆U R
- ovvero ∀ i ∈[1, n]∃ j ∈[1 , k ] t . c . Ai=Cj

- una lista B1 , ... , Bn di nomi


- distinti fra loro
- distinti da tutti i nomi in U R−{ A 1 , ... An }

- La ridenominazione ρ A 1, ..., An ← B 1 ,..., Bn (R) cambia i nomi degli attributi Ai in Bi cioè se


R ’=ρ A 1 , ..., An ← B 1 ,..., Bn ( R)

- R ’ ha nomi di attributi C ’ 1 , C ’ 2 , . .. , C ’ k dove C ' i=Bj se Aj=Ci altrimenti


C ’ i=Ci
- U R ’=U R −{ A 1 ,... An}∪ {B 1 , ... , Bn}⇒ Rango di R ’=Rango di R

- Il dominio dom ’(C ’ i)=dom(Ci)

- R ’ ha le stesse tuple di R ⇒ Cardinalità di R ’ = Cardinalità di R

Proiezione

Operazione Algebra SQL

Proiezione π A 1 ,... , Ak ( R) SELECT


(estrarre una fetta verticale A1, … , Ak
della relazione) Proietto le colonne FROM R
A 1 ,... , Ak .
(Per definizione senza Senza eliminazione di
duplicati) duplicati:
efficiente ma se ci sono
duplicati non ottengo una
relazione.
Da usare quando so che non
ci sono duplicati (estrazione
di una chiave).

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.

La proiezione riduce il numero di colonne (perché ne seleziona alcune) ma anche il numero di


righe, se ci sono duplicati.

Vincoli di Applicabilità di π A 1 ,... , Ak ( R):


- A 1 ,... , Ak devono stare in R.

Formalizzazione
Dati:
- una relazione R con nomi di attributi C 1 , C 2 ,. . . ,Ck e dominio dom ,

- una lista A 1 ,... , An di nomi distinti di suoi attributi,

- La proiezione Π A 1 , ..., An (R) tiene solo le colonne Ai se R ’=Π A 1 ,... , An ( R):


- R ’ ha nomi di attributi A 1 ,... , An

⇒ Rango di R ’=n

- Il dominio dom ’( Ai)=dom( Ai)

- t ’ è una tupla di R ’ se e solo se ∃una tuplat di R t . c .t ’ [ Ai]=t [ Ai] per ogni i


- Cardinalità di R’ <= Cardinalità di R
- eliminazione dei duplicati.
Selezione
Operazione Algebra SQL

Selezione σ F (R) SELECT * FROM R


(estrazione di tuple tramite un WHERE F
determinato filtro) Restituisco l’insieme delle
tuple T che appartengono a R Specifichiamo le condizioni
per cui la formula F(T) è vera. usando i big-six:
> >=
< <=
= ≠

Esempio:
prendo le tuple con attributo A uguale ad ‘a’.

Vincoli di Applicabilità diσ F ( R) :


- la formula F deve essere corretta e coinvolgere attributi esistenti di R.

Formalizzazione
Dati:
- una relazione R
- una formula F

- La selezione σ F (R) tiene solo le tuple di R su cui F vale true.


se R ’=σ F ( R):

- R ’ ha gli stessi attributi di R con lo stesso dominio:


Rango di R’ = Rango di R

- t è una tupla di R ’ se e solo se è una tupla t di R che soddisfa F :


Cardinalità di R’ <= Cardinalità di R

Formule
Una formula (predicato) F su una relazione R ha una delle seguenti forme:

- Atomo (predicato semplice): tutte le formule atomiche sono formule.

- ha la forma e op e’ dove:

- op è un operatore relazionale di confronto:


> < >= <= = ≠

- e, e’ sono o nomi di attributi di R o costanti, non entrambe costanti.

- ha la forma A is null dove A è un attributo.

- Combinazione booleana di formule mediante i connettivi:


∧ (AND), ∨ (OR), ¬ (NOT)

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.

- se t [ e] e t [ e ’ ] sono entrambi non nulli


- t [ e op e ’]=true se t [e]op t [e ’ ]
- t [ e op e ’]=false altrimenti

- t [ e is null]=true se t [e] è il valore nullo, false altrimenti.

- La validità degli operatori booleani è quella usuale, estesa su ?

Prodotto Cartesiano

Operazione Algebra SQL

Prodotto Cartesiano RXS SELECT * FROM R, S

Devo prendere due relazioni


disgiunte; se hanno degli
attributi in comune li devo
prima rinominare.

esempio:

combino le tuple delle relazioni.

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 }=∅

Il prodotto cartesiano R=R 1 × R 2 :

- 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.

Cardinalità di R = Cardinalità di R1 × Cardinalità di R2

caso degenere: se R 1=∅ (oppure R 2=∅), allora R=∅

Unione

Operazione Algebra SQL

Unione R ∪S SELECT *
FROM R
Le due relazioni devono UNION
avere lo stesso schema. SELECT *
FROM S

Posso prendere due relazione


con nomi di attributi diversi,
basta che il grado della
relazione sia lo stesso e i
domini siano compatibili.

I nomi risultanti saranno quelli


del primo argomento: R.

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

- una tupla t appartiene ad R se e solo se:


- t appartiene ad R1 oppure
- t appartiene ad R2

Cardinalità di R <= Cardinalità di R1 + Cardinalità di R2.

- tuple che compaiono in entrambi appartengono una sola volta al risultato


- caso degenere: se R 1⊆ R 2 , allora R=R 2 e viceversa .

Differenza

Operazione Algebra SQL

Differenza R−S opp. R ¿ SELECT *


FROM R
Le due relazioni devono EXCEPT
avere lo stesso schema. SELECT *
FROM S

oppure

SELECT *
FROM R
MINUS
SELECT *
FROM S

Posso prendere due relazione


con nomi di attributi diversi,
basta che il grado della
relazione sia lo stesso e i
domini siano compatibili.

I nomi risultanti saranno quelli


del primo argomento: R.

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 :

- ha nomi di attributi A1,A2, . . . ,An


Rango di R = Rango di R1 = Rango di R2

- Dominio dom

- una tupla t appartiene ad R se e solo se:


- t appartiene ad R1 e
- t non appartiene ad R2

Cardinalità di R <= Cardinalità di R1.

- tuple che compaiono in entrambi vengono eliminate


- caso degenere: se R1∩ R2 = ∅, allora R = R1

Intersezione

Operazione Algebra SQL

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.

E’ un operatore derivato, infatti l’intersezione di due relazioni R, S è definita da:


R ∩ S = R - (R - S)

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

Operazione Algebra SQL

Join R ⋈ AθA' S SELECT *


FROM R
E’ un operatore derivato da JOIN S
Prodotto Cartesiano + ON F
Selezione.
Con F condizione di join.

Per questo devo prendere Se F è l’uguale il join prende il


due relazioni disgiunte; se nome di equi-join.natural
hanno degli attributi in
comune li devo prima
rinominare.

Esempio:

prodotto cartesiano:
applico la condizione C=E.

Nella totalità vuol dire fare il join di R ed S applicando la condizione C=E:

[Theta-] Join di due relazioni R ed S:


R ⋈ AθA' S=σ AθA ’ (R × S)
Predicato di join: AθA’

- θ è un operatore relazionale (nell’esempio di prima era l’uguale di C=E)


- A ∈U R e A ’∈ US

Semantica statica: U R ∩ U S =∅ (per la correttezza del prodotto cartesiano)


Semantica dinamica: derivata dalla definizione.
Rango di R ⋈ AθA ' S=Rango di R+ Rangodi S
Cardinalità di R ⋈ AθA ' S ≤Cardinalità di R ×Cardinalità di S
Se θ è un’uguaglianza il theta-join prende il nome di equi-join.

Join Naturale

Operazione Algebra SQL

Join Naturale R⋈ S SELECT *


FROM R
NATURAL JOIN S
Non c’è alcun Vincolo di Applicabilità nel Join Naturale.

Non c’è una condizione; la condizione predefinita sarà: “gli attributi con lo stesso nome che
contengono lo stesso valore”.

E’ una semplificazione del join:


● si ‘incollano’ le colonne con lo stesso nome,
● non serve ridenominare,
● non bisogna specificare il predicato di join,
● non si hanno colonne duplicate nel risultato.

Ha senso solo nella notazione con nome del modello relazionale.


Se le due relazioni non condividono alcun attributo con lo stesso nome equivale ad un prodotto
cartesiano; più in generale corrisponde ad una serie di equijoin.

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

siano { A 1 , … , Ak }=UR ∩US nomi di attributi comuni a R1


e R 2{B 1 , … , Bk } nomi ‘nuovi’, cioè{B 1 , … , Bk }∩(UR ∪US)=∅

allora:
R 1 ⋈ R 2=Π schema σ F (R 1 × ρ A 1, … , Ak ←B 1 , …, Bk( R 2) )

Per come è definito il join naturale:


- dom 1(Ai)=dom 2( Ai) affinché sia corretto ρ

- 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

Fa un equijoin sulla colonna C; non viene richiesta l’uguaglianza sulla colonna A.


E’ utile quando vogliamo fare un Join Naturale, ma solo su alcuni attributi in comune, ad esempio
se due attributi con stesso nome vogliono dire cose diverse;

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.

Usiamo USING facendo un Join Naturale ignorando DataN:

SELECT * FROM CLIENTE


JOIN VIDEO USING CodCli

Outer Join
Ci permette di mantenere nel risultato le tuple che non hanno alcun match.

Consideriamo:
● R, S Relazioni,
● F Formula (Predicato).

L’OUTER JOIN è definito come:

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.

Frammento SPJ e Query Monotone


Il frammento SPJ comprende le operazioni di Selezione, Proiezione, Join, ed è quindi
rappresentabile come:

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

∀ D B1 , D B2 con D B1 ⊇D B2 allora Q( D B1 )⊇Q(D B2)

Applicata ad input più grandi mi fornisce risultati più grandi;


Se ho fornito un input ed ottenuto un output, se aggiungo all’input una tupla ottengo un risultato
almeno grande come quello precedente.

esempio:

hanno girato solo film horror

Anche l’unione è monotona; al contrario la differenza non lo è, in particolare:


● l'operatore di differenza è monotono rispetto al primo argomento (R in R\S)
● l'operatore di differenza non è monotono rispetto al secondo argomento (S in R\S)

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

Operatore LIKE con Wildcard % e _


L'operatore LIKE viene utilizzato in una clausola WHERE per cercare un pattern particolare in
una colonna.
Esistono due wildcards spesso utilizzate insieme all'operatore LIKE:

● Il segno di percentuale (%) rappresenta zero, uno o più caratteri;


● Il segno di sottolineatura (_) rappresenta un carattere singolo.

Senza le wildcards il LIKE sarebbe inutile (sarebbe equivalente al ‘=’).

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.

SELECT id FROM Professori


WHERE Nome LIKE '%te%'
AND Stipendio>12500 AND Stipendio<16000;

Recap

Operazione Algebra SQL

Operatori Unari

Ridenominazione ρ A 1, ..., Ak ← B 1 ,..., Bk SELECT DISTINCT


A1 AS B1
Sostituisco A1, …, Ak con B1, …..
…, Bk tutti distinti. Ak AS Bk
FROM R

(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

Con eliminazione di duplicati:


Da usare la maggior parte dei
casi.

Selezione σ F (R) SELECT * FROM R


(estrazione di tuple tramite un WHERE F
determinato filtro) Restituisco l’insieme delle
tuple T che appartengono a R Specifichiamo le condizioni
per cui la formula F(T) è vera. usando i big-six:
> >=
< <=
= ≠

Operatori Binari

Prodotto Cartesiano RXS SELECT * FROM R, S

Devo prendere due relazioni


disgiunte; se hanno degli
attributi in comune li devo
prima rinominare.

Unione R ∪S SELECT *
FROM R
Le due relazioni devono UNION
avere lo stesso schema. SELECT *
FROM S

Posso prendere due relazione


con nomi di attributi diversi,
basta che il grado della
relazione sia lo stesso e i
domini siano compatibili.

I nomi risultanti saranno quelli


del primo argomento: R.

L’unione, essendo
insiemistico, rimuove a
prescindere duplicati, a meno
che non usiamo:

...
UNION ALL
...

Differenza R−S opp. R ¿ SELECT *


FROM R
Le due relazioni devono EXCEPT
avere lo stesso schema. SELECT *
FROM S

oppure

SELECT *
FROM R
MINUS
SELECT *
FROM S

Posso prendere due relazione


con nomi di attributi diversi,
basta che il grado della
relazione sia lo stesso e i
domini siano compatibili.

I nomi risultanti saranno quelli


del primo argomento: R.

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.

Join R ⋈ AθA' S SELECT *


FROM R
E’ un operatore derivato da JOIN S
Prodotto Cartesiano + ON F
Selezione.
Con F condizione di join.

Per questo devo prendere Se F è l’uguale il join prende il


due relazioni disgiunte; se nome di equi-join.
hanno degli attributi in
comune li devo prima
rinominare.
Join Naturale R⋈ S SELECT *
FROM R
NATURAL JOIN S

Lezione 23 Marzo - Linguaggio SQL 1

SQL: Structured Query Language


E’ un linguaggio per la definizione e la manipolazione dei dati, supportato dalla totalità dei
DBMS relazionali.
Il suo antesignano è SEQUEL (IBM, anni ‘70); SQL è standard dal 1986 (SQL-1986); la versione
più recente è SQL:2008 mentre due grosse revisioni precedenti sono state SQL2 (o SQL-92) e
SQL:1999 (o SQL3), con caratteristiche object-relational.

E’ il primo linguaggio dichiarativo progettato specificatamente per accesso e manipolazione di


collezioni di dati.
Il risultato di un’operazione viene specificato mediante condizioni sul contenuto dei dati; il tutto
viene eseguito basandosi su calcolo (e algebra) relazionale.
Vi è però una grossa differenza con l’algebra relazionale: in SQL la semantica di una tabella
non è un insieme, e può infatti contenere duplicati (è un multinsieme).
Differenziamo dunque DDL e DML:
- DDL: istruzioni per la definizione della struttura dei dati;
- DML: istruzioni per l’interrogazione e l’aggiornamento dei dati.

Tutte le implementazioni di SQL prevedono inoltre comandi SDL (non standardizzati).

Comandi principali:

Ci concentreremo principalmente sul sottoinsieme relazionale puro di SQL, senza costrutti ad


oggetti.
SQL cerca di adattarsi ad ogni DBMS, spesso ci riesce; i DBMS relazionali più recenti, come
PostgreSQL, a volte si staccano dal linguaggio standard.

DDL: Linguaggio di Definizione dei Dati

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.

Ci concentreremo sui tipi predefiniti.

Tipi Numerici Esatti: Valori Interi


Dal meno al più preciso i valori interi sono rappresentati dai tipi:

SMALLINT 16 bit; per risparmiare spazio quando si sa che i valori sono limitati.
INTEGER 32 bit
BIGINT64 bit; per memorizzare valori più grandi.

Tipi Numerici Esatti: Valori Reali


Rappresentati da:

- NUMERIC [(precisione[,scala])]
La precisione indica il numero complessivo di cifre;
La scala indica il numero di cifre dopo la virgola.

ex. 4 cifre in tutto, una dopo la virgola: NUMERIC (4,1) = [-999.9,999.9]


ex. 4 cifre in tutto, nessuna dopo la virgola: NUMERIC (4) = [-999,999]

- DECIMAL [(precisione[,scala])]
Può essere implementato con precisione maggiore di quella richiesta
ex. DECIMAL(4,1) ⊇ [-999.9,999.9]

Tipi Numerici Approssimati: Valori Reali in Virgola Mobile


I valori reali sono rappresentati dai tipi:

REAL singola precisione


DOUBLE PRECISION doppia precisione
FLOAT [(precisione)] precisione indicata.
la precisione minima specificabile è 1, quella di default e la
massima dipendono dalle specifiche implementazioni di SQL.
La precisione dipende dalla specifica implementazione di SQL.
La precisione singola deve in ogni caso essere minore della doppia.

Tipi di Dato Carattere


CHARACTER/CHAR:
CHAR [(n)] stringhe di esattamente n caratteri

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.

I valori di tipo CHAR/VARCHAR vanno racchiusi tra singoli apici.


es. ‘pulp fiction’, ‘pippo & sua zia’, ‘123’
case sensitive: ‘pippo’≠ ‘Pippo’.

Tipi di Dato Temporali: Istantanei


DATE Risoluzione di un giorno;
es. DATE ’27-Ott-1969’ o DATE ‘1969-10-27’

TIME[(p)] p è l’eventuale numero di cifre frazionarie cui si è interessati.


Fino a 6: microsecondo;
Formato dei valori TIME: ‘hh:mm:ss[.nnnnnn]’
es. TIME ’21:56:32.5’

TIMESTAMP[(p)] = DATE+TIME[(p)] Formato dei valori TIMESTAMP:


‘dd-mm-yy hh:mm:ss[.nnnnnn]’
es. TIMESTAMP ’27-Ott-1969 21:56:32.5’

Tipi di Dato Temporali: Intervalli


INTERVAL Rappresenta una durata temporale.
Formato dei valori INTERVAL <stringa che esprime durata> <unità di misura>
es. INTERVAL ‘3’ YEAR

INTERVAL <stringa che esprime durata> <unità di misura> TO <unità di misura>


es. INTERVAL ‘36 22:30’ DAY TO MINUTE

Se la granularità minima è il mese si parla di intervalli year-month.


Altrimenti si parla di intervalli day-time (memorizzati in secondi).

Tipo di Dato Boolean


BOOLEAN Consiste di {TRUE, FALSE, UNKNOWN}.

UNKNOWN rappresenta totale mancanza di conoscenza, equivale a NULL


si usa per la gestione dei confronti con valori nulli.
Se exp si valuta a null, i confronti con exp e i predicati atomici su di exp si
valutano a UNKNOWN.
Altri Tipi di Dato
CHARACTER LARGE OBJECT (CLOB)
Per rappresentare sequenze di caratteri di elevate dimensioni (testo)

BINARY LARGE OBJECT (BLOB)


per rappresentare sequenze di bit di elevate dimensioni (immagini)

Creazione di Relazioni
Sintassi base: CREATE TABLE <nome relazione>
(<specifica colonna> [, specifica colonna]*)

specifica colonna ::= <nome colonna> <dominio>


[DEFAULT <valore default>]

ex. CREATE TABLE R


(nome VARCHAR(20),
cognome VARCHAR(20));

Posso anche specificare il valore di default:

se non inserisco nulla nella colonna B


ho il valore di default ‘b’.
Può essere utile ad esempio nel noleggio: di base, se non metto niente, posso specificare di
voler inserire la data odierna.

Vincoli
In SQL, al contrario dell’algebra relazionale, è permesso avere duplicati, per efficienza, ma è da
evitare.

Dobbiamo specificare tramite vincoli il fatto di evitare duplicati.


Ad esempio, se specifichiamo una chiave il sistema ci garantirà che i valori di chiave siano
univoci.
I vincoli di integrità costituiscono quindi un aspetto importante nella definizione dello schema di
una base di dati.
in SQL è possibile specificare nella definizione di una relazione:
- obbligatorietà di colonne (NOT NULL),
- chiavi (UNIQUE e PRIMARY KEY),
- chiavi esterne (FOREIGN KEY),
- vincoli CHECK (su colonna o su tupla): li vedremo dopo aver presentato il linguaggio di
interrogazione.

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.

Chiavi alternative indicate dalla parola chiave UNIQUE:


- non esistano due tuple che condividono gli stessi valori non nulli per gli attributi, ma
alcune colonne UNIQUE possono contenere valori nulli.

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, ...)

2) Indicare le chiavi alla fine della definizione della tabella:


PRIMARY KEY (<lista nomi di colonne>) oppure
UNIQUE(<lista nomi di colonne>)

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:

CREATE TABLE VIDEO


(colloc NUMERIC(4) PRIMARY KEY, …)

CREATE TABLE NOLEGGIO


(colloc NUMERIC(4) REFERENCES 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:

FOREIGN KEY(A1, …, An) REFERENCES R2


con A1, …, An attributi di R1 che fanno riferimento alla chiave di R2

Quando dichiaro una chiave esterna:


● non necessariamente gli stessi nomi
● i domini degli attributi corrispondenti devono essere compatibili
● si può (deve in caso di ambiguità) indicare quali sono le colonne riferite

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).

Le opzioni possibili sono:


● NO ACTION: t viene cancellata solo se non esiste alcuna tupla in R_NTE che vi fa
riferimento.
E’ l’opzione di default.
● CASCADE: la cancellazione di t implica la cancellazione di tutte le tuple in R_NTE che vi
fanno riferimento.
● SET NULL: la cancellazione di t implica che in tutte le tuple in R_NTE che vi fanno
riferimento la chiave esterna viene posta a NULL (se ammesso).
● SET DEFAULT: la cancellazione di t implica che in tutte le tuple in R_NTE che vi fanno
riferimento la chiave esterna viene posta al valore di default specificato per le colonne
che costituiscono la chiave esterna.

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.

L’opzione di default è NO ACTION anche per la modifica.


Nel caso di inserimento o modifica nella tabella referente non è possibile specificare alcuna
opzione e viene applicata sempre NO ACTION.
Cancellazione di Relazioni
Sintassi: DROP TABLE <nome relazione> {RESTRICT|CASCADE};

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

utilizziamo nel WHERE una funzione.

Con il SELECT possiamo quindi creare colonne virtuali:


SELECT colloc, dataNol, (DataRes - DataNol) Day

Possiamo anche nominarle usando AS:


SELECT colloc, dataNol, (DataRes - DataNol) Day AS durata

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:

Esiste anche un’altra funzione, che vedremo in seguito, ovvero:


4. COUNT.

Esempio:
Determinare il numero di film presenti in catalogo, il numero dei loro registi e la loro valutazione
minima, media e massima.

Non vogliamo estrarre informazioni da singole tuple in Film,


Vogliamo invece:
○ Contare quante tuple ci sono nella relazione,
○ Calcolare funzioni matematiche sull’insieme di valori contenuti nella colonna
valutaz di tutte le tuple della relazione.

Soluzione:

SELECT COUNT(*), MIN(valutaz), AVG(valutaz), MAX(valutaz) FROM Film;

Funzioni di Gruppo Numeriche


Si applicano solo a insiemi di valori semplici (non tuple).
Sull’insieme vuoto restituiscono NULL.

MAX (massimo) e MIN (minimo) si applicano a insiemi di valori ordinati;


- usare o meno DISTINCT non cambia il risultato.
SUM (somma) e AVG (average = media) si applicano a insiemi di valori numerici.

Ci sono altre funzioni che non presentiamo, fra cui ad esempio STDEV (deviazione standard) e
VAR (varianza).

Funzione di Gruppo Count


Conta il numero di tuple, quindi la cardinalità dell’insieme viene passata come argomento;
● sull’insieme vuoto restituisce 0,
● è l’unica che si applica anche a insiemi di tuple,
● può avere anche la forma: COUNT([DISTINCT] *)
○ la funzione restituisce il numero di tuple [(distinte)] che soddisfano la clausola di
selezione.

Esempio:
Count(*) Restituisce il numero di tuple

Count(A) Restituisce il numero di valori non nulli per l’attributo A;

Count(DISTINCT A) Restituisce il numero di valori distinti e non nulli per l’attributo A;

Funzioni di Gruppo - Limitazione


Le funzioni di gruppo possono comparire solo nella clausola di proiezione, ovvero nel
SELECT e nel HAVING.
Quindi non possono essere usati per filtrare le tuple nella clausola di selezione.
Avremo bisogno di una sotto-interrogazione (vedi seguito).
Esempio:
Determinare il numero di film presenti in catalogo, il numero complessivo di registi che li hanno
girati e la valutazione minima, media e massima di tali film.

SELECT COUNT(*), COUNT(DISTINCT regista),


MIN(valutaz), AVG(valutaz), MAX(valutaz)
FROM Film;

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.

La clausola di proiezione di un’interrogazione contenente la clausola GROUP BY può solo


includere:
● una o più tra le colonne che compaiono nella clausola GROUP BY,
● funzioni di gruppo.

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:

SELECT codCli, regista, COUNT(*) AS NumN,


MAX((dataRest-dataNol) DAY) AS durataM
FROM Noleggio NATURAL JOIN Video
GROUP BY codCli, regista;
Un altro esempio:
Mettiamo insieme tutti i noleggi di un cliente nella stessa data:
raggruppiamo quindi in base a CodCli e DataNol:

L’interrogazione:

Ci restituisce il numero di video noleggiati da quel cliente in quel giorno.

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:

SELECT CodCli, Nome, Cognome, Count(*)


FROM Noleggio NATURAL JOIN Cliente
WHERE DataNol > ‘29/03/2021’
GROUP BY CodCli, Nome, Cognome

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

SELECT regista, COUNT(*) AS numF,


COUNT(DISTINCT genere) AS numG,
MIN(valutaz) AS minV, AVG(valutaz) AS avgV, MAX(valutaz) AS maxV
FROM Film
WHERE anno < 2000
GROUP BY regista
HAVING COUNT(*) >= 2;

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

SELECT colloc FROM Noleggio


WHERE codCli = 6635 AND (dataNol > DATE '20-Mar-2012' OR dataRest > DATE '20-Mar-
2012');
restituisce anche noleggi in corso, purché siano iniziati dopo il 20 Marzo

SELECT colloc FROM Noleggio


WHERE codCli = 6635 AND NOT dataRest < DATE '15-Mar-2024';
non restituisce i noleggi in corso
Le interrogazioni:
SELECT colloc FROM Noleggio
WHERE dataRest = CURRENT_DATE OR NOT dataRest = CURRENT_DATE;
SELECT colloc FROM Noleggio WHERE dataRest = dataRest;

non restituiscono tutti i noleggi, ma solo i noleggi terminati, cioè


SELECT colloc FROM Noleggio
WHERE dataRest IS NOT NULL;

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).

- una funzione di gruppo può comunque restituire NULL se applicata a un insieme di


valori vuoto o contenente il solo valore NULL.

Esempio:

SELECT SUM(A+B) FROM R;


SUM(3,NULL,NULL) = 3;

SELECT SUM(A) + SUM(B);


SUM(1,NULL,4) + SUM(2,3,NULL) = 5 + 5 = 10;

Se e1 ed e2 sono NULL e1=e2 restituisce UNKNOWN

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

Verifica dei Valori Nulli


Non è possibile testare i valori NULL con operatori di confronto, come = < <>
Dovremo invece usare gli operatori IS NULL e IS NOT NULL.
Sintassi di IS NULL:
SELECT nome_colonna
FROM nome_tabella
WHERE nome_colonna IS NULL;

Sintassi di IS NOT NULL:


SELECT nome_colonna
FROM nome_tabella
WHERE nome_colonna IS NOT NULL;

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’:

SELECT titolo FROM Film


WHERE valutaz = ( SELECT valutaz FROM Film
WHERE titolo = 'le iene‘ and regista = ‘quentin tarantino’);

La sotto-interrogazione restituisce 4.00, usato nel predicato dell’interrogazione esterna.


- la query:
SELECT titolo FROM Film
WHERE valutaz = 4

produce lo stesso risultato ma non è equivalente:


- richiede di formulare ed eseguire due query (meno efficiente),
- non si propagano le modifiche,
- particolarmente pericoloso in caso di accessi concorrenti, cosa che accade
sempre.

- l’interrogazione restituisce ‘nightmare before christmas’ , ‘ed wood’ , ‘la fabbrica di


cioccolato’ e ‘le iene’.

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.

Le sottointerrogazioni permettono inoltre di esprimere le interrogazioni di massimo e di minimo


discusse in precedenza.
Esempio: determinare il titolo ed il regista del film drammatico di valutazione minima;

SELECT titolo, regista FROM Film


WHERE genere = ‘drammatico’ AND
valutaz = (SELECT MIN(valutaz) FROM Film WHERE genere = ‘drammatico’);

Una sotto-interrogazione può avere al suo interno:


- un'altra sotto-interrogazione,
- predicati di join,
- clausole group by e
- tutti i predicati visti.

La clausola di qualificazione di una interrogazione può contenere una qualsiasi combinazione di


condizioni normali e condizioni con sottointerrogazioni.

Le sottointerrogazioni possono essere:


- semplici,
- correlate.

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).

Per usarle in un atomo serve un quantificatore:


- ANY quantificatore ∃
- ALL quantificatore ∀

ANY:

WHERE A θ ANY (sq)

Con A→ v
sq→ v1 , ... , v n

La condizione è vera se l’operatore di confronto θ fra il valore dell’espressione e uno qualsiasi


dei valori dell’espressione restituisce vero, ovvero:

Se ∃ vi ∈{v 1 ,... , v n }t .c . vθ v i allora restituisco true

ALL:

WHERE A θ ALL (sq)

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.

Se ∀ v i ∈ {v 1 , ... , v n }t . c . vθ v i allora restituisco t

Esempio:
Determinare titolo, regista ed anno dei film più vecchi di almeno un film di Quentin Tarantino (si
può fare anche con MAX).

SELECT titolo, regista, anno


FROM Film
WHERE anno < ANY ( SELECT anno FROM Film WHERE regista = 'quentin tarantino');
Risultato: l’ultimo film di Quentin Tarantino è del 1994.

Sotto-Interrogazioni: uso di ALL/ANY oppure MAX/MIN

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:

SELECT titolo, regista FROM Film


WHERE genere = ‘drammatico’ AND
valutaz >= ALL ( SELECT valutaz FROM Film WHERE genere = ‘drammatico’);

equivale a:

SELECT titolo, regista FROM Film


WHERE genere = ‘drammatico’ AND
valutaz = SELECT MAX(valutaz) FROM Film WHERE genere = ‘drammatico’;

In assenza di valori nulli; altrimenti:


- quella con ALL si valuta in FALSE,
- quella con MAX, giustamente, potrebbe ritornare TRUE.

La stessa cosa accade con NOT IN.


L’unico modo per evitare che interrogazioni con MAX e NOT IN restituiscano erroneamente
FALSE è mettere nella sotto-interrogazione una condizione ulteriore nel where, dove
specifichiamo:
AND A IS NOT NULL

Esempio:
Max restituisce 3, quando andrò a fare t3[B] = 3 restituirà true.
Il risultato sarà quindi a3.

Se invece uso ALL:

Quando andrò a fare t3[B] = 3 farà tutti i confronti e quello con NULL risulterà UNKNOWN.

Il risultato sarà quindi l’insieme vuoto.

Lo stesso discorso vale per l’interrogazione:

SELECT titolo, regista FROM Film


WHERE genere = ‘drammatico’ AND
valutaz >= ANY ( SELECT valutaz FROM Film WHERE genere = ‘drammatico’);

equivale a:

SELECT titolo, regista FROM Film


WHERE genere = ‘drammatico’ AND
valutaz = SELECT MIN(valutaz) FROM Film WHERE genere = ‘drammatico’;

In generale conviene quindi usare MIN/MAX.


Vedremo però che le funzioni di gruppo non possono essere annidate, quindi in quei casi
ci serviranno ALL e ANY.
Sotto-Interrogazioni: Appartenenza
Usando ANY ed ALL si può definire la (non) appartenenza insiemistica.

- exp IN Q definito come exp = ANY Q

- exp NOT IN Q definito come exp <> ALL Q

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');

Risultato: ‘pulp fiction’ (uscito nel 1994).

esempio:
Restituire il titolo dei film di Quentin Tarantino usciti in un anno in cui non sono usciti film di Tim
Burton.

SELECT titolo FROM Film


WHERE regista = 'quentin tarantino'
AND anno NOT IN ( SELECT anno FROM Film WHERE regista = 'tim burton');

Risultato: le iene (1992).

Sotto-Interrogazioni – Appartenenza Tuple


È possibile selezionare più di una colonna tramite una sottointerrogazione.
È necessario racchiudere tra parentesi la lista delle colonne a sinistra dell'operatore di
confronto.

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');

L'interrogazione non restituisce alcuna tupla.

Modello di Esecuzione
Costruire in ordine:

1. FROM: si definisce la relazione di partenza (con possibili manipolazioni come il join).


2. WHERE: si applica la condizione di ricerca specificata nella clausola WHERE a tutte le
tuple delle relazioni oggetto dell’interrogazione.
La valutazione avviene tupla per tupla.
3. GROUP BY: si applica il partizionamento specificato dalla clausola GROUP BY.
4. HAVING: si applica la condizione su gruppi specificata nella clausola HAVING.
I gruppi che soddisfano tale condizione sono i gruppi di tuple che verificano
l’interrogazione.
5. SELECT: si calcolano le funzioni di gruppo specificate nella clausola di proiezione
dell’interrogazione.

Nel modello di esecuzione un passo importante per le sotto-interrogazioni è costruire prima la


sotto-interrogazione, per poi passare a quella principale.

Lezione 12 Aprile - Linguaggio SQL 3

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

Quello che mi manca a livello sintattico è la parte denotata in grassetto.


Questo perché il regista all’interno della sotto-interrogazione nasconde quello della
interrogazione principale.
Dobbiamo quindi trovare un modo per fare riferimento al ‘regista’ della interrogazione principale.

Strategia Naive e Irrealistica (per l'esecuzione per illustrarne il significato)


Quando l’interrogazione esterna valuta se una tupla candidata appartiene al risultato:

1. chiama la sotto-interrogazione sul regista x del film in esame; x gioca il ruolo di un


parametro nella ‘‘chiamata’’.

2. la sotto-interrogazione calcola la media m delle valutazioni dei film di x,


3. l’interrogazione esterna confronta la valutazione del film in esame con m.

In questo tipo di interrogazioni ogni esecuzione della sotto-interrogazione è correlata al valore di


uno o più attributi delle tuple candidate nella interrogazione principale.
Valgono le usuali regole di scope:
- nella subquery si può fare riferimento a quanto definito nella query esterna ma non
viceversa,
- eventuali nomi uguali nella subquery coprono quelli della query esterna.

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:

SELECT X.titolo, X.regista, X.anno FROM Film AS X


WHERE X.valutaz > ( SELECT AVG(valutaz) FROM Film
WHERE regista = X.regista);

Altri Utilizzi
Gli alias di relazione sono utili quindi anche per abbreviare la scrittura di query:
SELECT X.a FROM LaMiaTabellaConNomeSignificativoQuindiLungo X

Esempio: SELECT C.denominazione FROM CorsiDiLaurea C

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:

SELECT DISTINCT X.regista FROM Film X, Film Y


WHERE X.genere = Y.genere
AND X.regista = Y.regista
AND X.titolo <> Y.titolo;
EXISTS e NOT EXISTS
Le sotto-interrogazioni correlate sono spesso usate in combinazione con l’operatore EXISTS
(eventualmente in forma negata NOT EXISTS).
EXISTS si applica ad una sottoquery e restituisce vero se la subquery restituisce almeno
una tupla, falso se restituisce l’insieme vuoto.

Quindi, data una interrogazione il predicato EXISTS(sq)


● restituisce il valore Booleano TRUE se sq restituisce almeno una tupla,
● restituisce il valore Booleano FALSE altrimenti,
● non restituisce mai il valore UNKNOWN.

NOT EXISTS è la negazione di EXISTS.

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:

SELECT DISTINCT X.regista FROM Film X


WHERE EXISTS ( SELECT * FROM Film
WHERE regista = X.regista AND anno = X.anno
AND titolo <> X.titolo);

Un modo alternativo sarebbe stato l’uso di GROUP BY ed HAVING.

EXISTS e NOT EXISTS in sostituzione a Intersezione e Differenza


Intersezione e differenza si possono definire in SQL anche tramite EXISTS e NOT EXISTS.
In sostanza, si possono fare usi analoghi di:
- INTERSECT vs. EXISTS vs. IN,
- EXCEPT/MINUS vs. NOT EXISTS vs. NOT IN.

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

Andiamo ad analizzare il primo caso (il secondo è analogo).


Esempio: determinare gli anni in cui sono usciti sia film di Tim Burton sia film di Quentin
Tarantino:

SELECT anno FROM Film


WHERE regista = 'tim burton‘
INTERSECT
SELECT anno FROM Film
WHERE regista = 'quentin tarantino‘

può essere scritta come:

SELECT DISTINCT anno FROM Film


WHERE regista = 'tim burton'
AND EXISTS ( SELECT * FROM Film F
WHERE regista = 'quentin tarantino' AND anno = F.anno);

Altra soluzione usando IN:

SELECT DISTINCT anno FROM Film


WHERE regista = 'tim burton‘
AND anno IN (SELECT anno FROM Film WHERE regista = 'quentin tarantino‘);

Divisione

Operazione Algebra SQL

Divisione R÷S Non esiste in SQL.

Ha senso solo se lo schema Possiamo esprimerla:


di S è incluso nello schema 1. attraverso il NOT
di R e restituisce come EXISTS;
2. mediante l’uso di
funzioni di gruppo.
schema:
e come tuple i pezzi di tuple
che compaiono in R abbinati
a tutte le tuple di S.

La divisione è un operatore derivato: le sotto-interrogazioni correlate e l'operatore di NOT


EXISTS permettono infatti di esprimere l'operazione di divisione.
SQL non prevede un operatore apposito per questo operatore; in effetti esso non ha un livello
d’uso come ad esempio quello del join.

La specifica della divisione in SQL richiede di ragionare in base al concetto di controesempio, in


base alla tautologia:

∀ z (∃ y p ( z , y ))⇔¬ ∃ z (¬∃ y p(z , y))

La divisione non è monotona, in particolare:


● l'operatore di divisione è monotono rispetto al primo argomento (R in R:S)
● l'operatore di divisione non è monotono rispetto al secondo argomento (S in R:S)

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.

Restituisce a livello di schema la differenza tra gli schemi di R ed S:


Restituisce a livello di tuple i pezzi di tuple (gli attributi che stanno in R ma non in S) che
compaiono in R abbinati a tutte le tuple di S.

Quindi, formalizzando, la divisione:


- Si chiama così perché è inversa del prodotto cartesiano:
R = (R×S) ÷ S

Se R, S e T hanno lo stesso schema, allora:


● R:S=S:R commutativa
● (R:S):T = R: (S:T) transitiva

- 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.

- Si definisce togliendo le tuple per cui manca almeno un completamento.

Esempio 1: poniamo le due tabelle R e S.

k, j appaiono in R abbinati a tutte le tuple di S; m al contrario non appare in R abbinato ad ‘a b’.

La relazione risultante conterrà solo l’attributo A1 con le tuple k, j.

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.

Dividiamo l’affermazione in due parti:


a) Trovare i “pezzi di tupla a cui manca un abbinamento”: proiettiamo sullo schema finale,
troviamo tutti gli abbinamenti possibili (con il prodotto cartesiano) e poi togliamo da essi
(con la differenza) gli abbinamenti presenti in R.

se prendiamo l’esempio 1 ecco cosa risulta.

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.

Quindi in generale la divisione può essere riscritta come:

π ¿ ¿(R){π ¿¿ ¿ ¿

Divisione in SQL
In SQL possiamo esprimere la divisione:
● attraverso il NOT EXISTS;
● mediante l’uso di funzioni di gruppo.

Il primo metodo funziona sempre.


Il secondo metodo funziona se siamo sicuri che T non possa essere abbinata ad altro che a tuple
di S; per farlo funzionare sempre possiamo aggiungere una condizione nel WHERE, dove
restringiamo le tuple abbinabili.
1) Scrivere la divisione attraverso il NOT EXISTS;
In pratica non deve esistere una tupla in S per cui manchi un abbinamento, ovvero per
cui le componenti di S non stiano nell’insieme delle componenti di R per la tupla
correlata.
Esempio 2 in SQL:
Determinare i codici dei clienti per cui non è possibile determinare un film di Tim Burton che il
cliente non ha mai noleggiato.

SELECT DISTINCT X.codCli FROM Noleggio X


WHERE NOT EXISTS (SELECT * FROM Film F WHERE regista = 'tim burton'
AND
NOT EXISTS (SELECT * FROM Noleggio
NATURAL JOIN Video
WHERE codCli = X.codCli
AND titolo = F.titolo AND regista = F.regista));

La sottointerrogazione più interna contiene la tabella dividendo, mentre la prima


sottointerrogazione contiene la tabella divisore; infine l’interrogazione principale conterrà il
risultato (quoziente) della divisione.

2) Esprimere la divisione 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:

Andiamo poi a completare con il resto:

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:

SELECT codCli FROM Noleggio NATURAL JOIN Video


WHERE regista = 'tim burton' GROUP BY codCli
HAVING COUNT(DISTINCT titolo) =
(SELECT COUNT(DISTINCT titolo)
FROM Film WHERE regista = 'tim burton');
Lezione 19 Aprile - Linguaggio SQL 4
Modifiche all’istanza di una relazione.

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.

Inserimento con Valori Espliciti

Esempio:
Inserire un nuovo film, con:
● titolo → ``La tigre e la neve'‘
● regista → Roberto Benigni
● anno di produzione → 2005
● genere → commedia
● valutaz → 3

INSERT INTO Film(titolo, regista, anno, genere, valutaz)


VALUES ('la tigre e la neve', 'roberto benigni’, 2005, 'commedia’, 3);

oppure

INSERT INTO Film(anno, titolo, genere, regista, valutaz) -- ordine diverso


VALUES (2005, 'la tigre e la neve', 'commedia', 'roberto benigni’, 3);

Eccezione 1
Se la lista delle colonne manca, equivale alla lista di tutte le colonne di R nell’ordine dato dal
comando CREATE TABLE.

Inserire un nuovo film, con:


● titolo → ``La tigre e la neve'‘
● regista → Roberto Benigni
● anno di produzione → 2005
● genere → commedia
● valutaz → 3
INSERT INTO Film VALUES ('la tigre e la neve', 'roberto benigni’, 2005, 'commedia’, 3);

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

INSERT INTO Film(anno, titolo, genere, regista)


VALUES (2005,'la tigre e la neve', 'commedia', 'roberto benigni');

Manca valutaz: valutazione non è specificata quindi assume il valore di default, se presente,
oppure il valore nullo.

Equivalentemente:

INSERT INTO Film


VALUES ('la tigre e la neve', 'roberto benigni', 2005,'commedia‘, NULL);

Non rispecchia perfettamente la specifica perché assegna esplicitamente NULL alla


valutazione.
Se nello schema valutaz è dichiarato con vincolo NOT NULL e non c’è default da errore.

Inserimento Mediante Query

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.

INSERT INTO Noleggio(colloc, codCli)


SELECT colloc, 6635 FROM Video
WHERE regista = 'gabriele salvatores'
AND (colloc,6635) NOT IN (SELECT colloc, codCli FROM Noleggio);

Le tuple inserite sono:


(1124, CURRENT_DATE, 6635, NULL) e
(1126, CURRENT_DATE, 6635, NULL)

I valori di default vengono stabiliti al momento della creazione della tabella.

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”:

DELETE FROM Film


WHERE titolo = 'la tigre e la neve' AND regista = 'roberto benigni’;

Cancellare i clienti che non hanno effettuato noleggi nell'ultimo anno:

DELETE FROM Cliente


WHERE codCli NOT IN ( SELECT codCli FROM Noleggio
WHERE dataNol > (CURRENT_DATE - 1 YEAR));

Modifica
Esempi:
1) Raddoppiare le valutazioni dei film (ad esempio in corrispondenza ad un cambio di scala
da 0-5 a 0-10):

UPDATE Film SET valutaz = valutaz * 2;

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

UPDATE Film SET valutaz = valutaz * 1.1


WHERE genere = 'horror' AND anno > 1999
AND (titolo, regista) IN ( SELECT titolo, regista FROM Noleggio
NATURAL JOIN Video
GROUP BY titolo, regista
HAVING COUNT(DISTINCT codCli) >= 2);

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:

UPDATE Film X SET valutaz = ( SELECT 1.1 * AVG(valutaz) FROM Film


WHERE anno = X.anno
AND genere = X.genere)
WHERE EXISTS (
SELECT * FROM Noleggio NATURAL JOIN Video
WHERE dataNol > CURRENT_DATE - 1 MONTH
AND titolo = X.titolo AND regista = X.regista )

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.

E’ quindi necessario discutere l’ordine di valutazione nella modifica di tuple.

Modifica di più Tuple: Ordine di Valutazione


Le modifiche in SQL sono eseguite in modo set-oriented:

1. la clausola WHERE e il valore dell’espressione SET vengono valutati un’unica volta


sull’istanza della base di dati prima della modifica.

2. gli assegnamenti vengono effettuati su tutte le tuple contemporaneamente.

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:

UPDATE Film X SET valutaz = ( SELECT 1.1 * AVG(valutaz) FROM Film


WHERE anno = X.anno
AND genere = X.genere)
WHERE EXISTS (
SELECT * FROM Noleggio NATURAL JOIN Video
WHERE dataNol > CURRENT_DATE - 1 MONTH
AND titolo = X.titolo AND regista = X.regista )

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.

Abbiamo già discusso la specifica di:


- Vincoli di obbligatorietà di colonne (NOT NULL)
- Vincoli di chiave (UNIQUE e PRIMARY KEY)
- Vincoli di integrità referenziale (chiavi esterne, FOREIGN KEY).

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?

I vincoli di integrità possono essere classificati in 2 tipi di gruppi:

1. Statici: relativi ad uno stato della base di dati.


ex. la valutazione di un film deve essere compresa tra 0 e 5.

2. Di transizione: mettono in relazione stati diversi della base di dati.


ex. non è possibile modificare la data di restituzione di un video assegnandogli una data
precedente a quella memorizzata.
Li introdurremo nel seguito, poiché serve il concetto di trigger.

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).

Vincoli Check su Colonna


CREATE TABLE Film ( ... valutaz DECIMAL(3,2), ...);

Vincolo su valutazione (singolo attributo):


la valutazione è un numero compreso tra 0 e 5 -> Vincolo su singolo attributo

CREATE TABLE Film ( ...


valutaz DECIMAL(3,2)
CHECK (valutaz BETWEEN 0.00 AND 5.00),
...);

La condizione dopo CHECK è una formula, della stessa forma di quelle che potrebbero essere
inserite in una clausola WHERE.
Essa può anche includere sottointerrogazioni.

Esempio con IN:


CREATE TABLE Video ( ...
tipo CHAR NOT NULL DEFAULT 'd’
CHECK (tipo IN ('d','v’)), -- il tipo può assumere solo i valori ‘d’ e ‘v’
...);

I vincoli CHECK si possono utilizzare contemporaneamente ad altri vincoli su singolo attributo.

Vincoli Check su Tabella


La clausola, riferendosi a più attributi, deve essere specificata alla fine delle varie specifiche
degli attributi della tabella.

Esempio: La data di restituzione di un noleggio deve seguire la data di noleggio:

CREATE TABLE Noleggio ( ...


CHECK (dataRest >= dataNol) );

Vincoli Check con Sotto-Interrogazioni


La specifica dei vincoli può includere sottointerrogazioni che possono coinvolgere altre relazioni:

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)),
...);

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.

In postgreSQL i vincoli CHECK non possono contenere sottointerrogazioni.

Vincoli Check: Verifiche e Violazioni


Il vincolo viene verificato solo quando si inserisce o modifica una tupla della relazione (check su
relazione) o una colonna (check su colonne).
Modifiche ad elementi diversi NON attivano la verifica il vincolo può essere violato!!!!
In caso di errore l’operazione non viene eseguita e si ha una segnalazione di errore.

Esempio:
CREATE TABLE Film ( ... valutaz DECIMAL(3,2)
CHECK (valutaz BETWEEN 0.00 AND 5.00), ...);

Il vincolo viene verificato:


- Ogni volta che si inserisce una nuova tupla in Film.
- Ogni volta che viene modificata la valutazione di un Film.

Esempio:
CREATE TABLE Noleggio ( ...
CHECK (dataRest >= dataNol) );

Il vincolo viene verificato:


- Ogni volta che si inserisce una nuova tupla in Noleggio,
- Ogni volta che viene modificata la data di restituzione o la data di noleggio, il vincolo
viene verificato sulla tupla modificata.

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),
...);

Il vincolo viene verificato:


- Ogni volta che si inserisce una nuova tupla in Film,
- Ogni volta che viene modificata la valutazione di un Film, il vincolo viene verificato

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).

CONSTRAINT <nomeC> CHECK (<condizione>)

il nome dei vincoli serve a farvi riferimento per


● aggiungerli:
ALTER TABLE <nomeT> ADD CONSTRAINT <nomeC> CHECK (<condizione>)

● eliminarli:
ALTER TABLE DROP CONSTRAINT <nomeC>

Esempio:
Consideriamo la tabella video e supponiamo di attribuire un nome ad ogni vincolo in essa.

CREATE TABLE Video( colloc DECIMAL(4) CONSTRAINT PKey PRIMARY KEY,


titolo VARCHAR(30) CONSTRAINT Tnn NOT NULL,
regista VARCHAR(20) CONSTRAINT Rnn NOT NULL,
tipo CHAR CONSTRAINT Snn NOT NULL
DEFAULT 'd'

CONSTRAINT Tok CHECK (tipo IN ('d','v'))

CONSTRAINT FK FOREIGN KEY (titolo,regista)


REFERENCES Film ON DELETE RESTRICT);

Cancellazione vincolo Rnn:


ALTER TABLE Video DROP CONSTRAINT Rnn;

Modifica vincolo Tok: prima cancelliamo il vincolo e poi lo dichiariamo di nuovo:


ALTER TABLE Video DROP CONSTRAINT Tok;
ALTER TABLE Video ADD CONSTRAINT Tok CHECK (tipo IN ('d','v','x’));

Asserzioni
Vincoli su tuple multiple o relazioni multiple possono essere specificati utilizzando le asserzioni.
Elementi dello schema, manipolate da appositi comandi del DDL.

CREATE ASSERTION <nomeAsserzione> CHECK (<condizione>)

● <nomeAsserzione>: è il nome che viene assegnato all’asserzione

● <condizione>: è un predicato o una combinazione booleana di predicati


(come nel caso dei vincoli CHECK)
In genere specifica che l’insieme delle tuple che NON soddisfano
il vincolo è vuoto -> uso del predicato NOT EXISTS

Esempio, su tuple multiple della stessa relazione:


Uno stesso video non può essere noleggiato contemporaneamente da due clienti, ovvero:
l’insieme dei video correntemente noleggiati da più di un cliente è vuoto.

CREATE ASSERTION SoloUno


CHECK (NOT EXISTS ( SELECT * FROM Noleggio
WHERE dataRest IS NULL
GROUP BY colloc HAVING COUNT(*) > 1));

Esempio, su tuple multiple della stessa relazione:


Un cliente non può avere più di tre video in noleggio contemporaneamente, ovvero:
l’insieme dei clienti che hanno più di 3 video in noleggio è vuoto.

CREATE ASSERTION Max3


CHECK (NOT EXISTS ( SELECT * FROM Noleggio
WHERE dataRest IS NULL
GROUP BY codCli HAVING COUNT(*) > 3));

Esempio, su più relazioni:


Un video non può essere noleggiato prima dell'uscita del film che lo contiene, ovvero:
l’insieme dei video noleggiati prima dell’uscita del film contenuto nel video noleggiato è vuoto.

CREATE ASSERTION DateOk


CHECK (NOT EXISTS ( SELECT * FROM Noleggio
NATURAL JOIN Video NATURAL JOIN Film
WHERE YEAR(dataNol) < anno));

Verifiche e Violazioni di Asserzioni


Le asserzioni trattano le modifiche in modo diverso rispetto al vincolo check.
Le asserzioni infatti vengono verificate ogni volta che viene modificata l’istanza di una
delle tabelle citate nell’asserzione.
Quindi il processo di modifica è molto costoso, ed è per questo che alcuni DBMS, come Postgre,
non supportano le asserzioni.

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:

1. Interagiamo con il sistema attraverso applicazioni per basi di dati arbitrariamente


complesse utilizzando linguaggi general purpose.

2. Interagiamo con il sistema attraverso sequenze di comandi SQL.


Quali garanzie sul risultato dell’esecuzione complessiva il sistema offre a chi offre queste
sequenze? Ad esempio rispetto a vincoli di integrità e problemi ulteriori in presenza di
guasti.

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?

Attraverso una transazione, ovvero:


● un’unità logica di elaborazione;
● che corrisponde a una serie di operazioni fisiche elementari (letture/scritture su tuple)
sulla base di dati;
● a cui viene garantita un’esecuzione che soddisfa alcune buone proprietà;

Gli esempi sono come quelli sopracitati:


- Bonifico bancario
- acquisto biglietto
- prenotazione aerea
Specificare che le sequenze di operazioni che li compongono sono transazioni fa si che il
sistema garantisca ad esse buone proprietà.

Una transazione è quindi costituita da operazioni elementari; ognuna di queste, se è


un’operazione di scrittura (aggiornamento, modifica, cancellazione) eseguita dalla transazione
cambia lo stato della base di dati:

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.

Esempio: Quante operazioni elementari vengono eseguite dal comando:


UPDATE Film SET valutaz = valutaz * 2

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.

Le operazioni in memoria centrale (valutaz = valutaz*2) non sono elementari.

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

Presenti proprio nella componente chiamata Transaction Manager:


Atomicità
E’ detta anche proprietà tutto-o-niente; tutte le operazioni di una transazione devono essere
trattate come una singola unità:
● vengono eseguite tutte, oppure non ne viene eseguita alcuna.
● in caso di fallimento (abort) della transazione lo stato risultante della base di dati è
quello precedente l’inizio della transazione (viene eseguito rollback)
● l'atomicità delle transazioni è assicurata dal gestore del ripristino (recovery).

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.

Una transazione flat viene iniziata:


- implicitamente all’esecuzione del primo comando SQL;
ogni volta che eseguiamo un comando inizia una transazione flat.
- esplicitamente con il comando BEGIN [TRANSACTION] o START TRANSACTION
Può avere solo 2 esiti, in accordo con la proprietà di atomicità:
- Terminare correttamente;
- Terminare non correttamente (in seguito a malfunzionamenti).

Andiamo a distinguere i due casi;

1) Terminazione Corretta:
Una transazione termina correttamente quando, dopo aver eseguito tutte le proprie
operazioni, esegue una particolare istruzione SQL, detta COMMIT [WORK]

Essa comunica “ufficialmente” al Transaction Manager (gestore delle transazioni) il termine


delle operazioni;

E’ una transazione flat poiché vi sono:


- BEGIN all’inizio;
- COMMIT alla fine.

2) Terminazione non Corretta:

Possiamo indicare una terminazione non corretta esplicitamente:


la transazione decide di “abortire” eseguendo l’istruzione SQL ROLLBACK [WORK];
Il DBMS deve “disfare” (UNDO) le eventuali modifiche apportate al DB dalla transazione,
riportando lo stato della BD a quello prima dell’inizio della transazione.

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.

Casi di terminazione non corretta esplicita:

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.

ll sistema esegue in autonomia ROLLBACK, abortisce la


transazione ed effettua UNDO, ritornando allo stato esistente
prima dell’inizio della transazione.

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.

Transazioni e Vincoli di Integrità


Ritornando sulla proprietà di consistenza:
Lo stato iniziale e finale di una transazioni devono sempre soddisfare tutti i vincoli di integrità
esistenti sulla base di dati; tuttavia questa proprietà ammette la possibilità che gli stati
intermedi possono violare la consistenza.

Esistono due diverse modalità di controllo che stabiliscono quando effettuare la valutazione del
vincolo e ripristinare la consistenza:

1. Valutazione immediata: viene effettuata dopo ogni comando di manipolazione dei


dati; ci possono essere anche tanti controlli all’interno della stessa transazione.
2. Valutazione differita: il controllo viene effettuato un solo controllo al termine
dell’esecuzione della transazione.

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.

Specifica dei Vincoli:


Consideriamo lo schema:

FOREIGN KEY (titolo, regista) REFERENCES Film


Valutiamo il caso di inserimento di due tuple; una si riferisce al Film ‘A Star is Born’, l’altra al
corrispondente video.

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

La transazione sarà la seguente:

BEGIN

INSERT INTO Video VALUES (4000, ‘A Star is Born’, ‘Bradley Cooper’,…);


INSERT INTO Film VALUES (‘A Star is Born’, ‘Bradley Cooper’,…);

COMMIT;

2) Valutazione immediata con DEFERRABLE e INITIALLY IMMEDIATE:


FOREIGN KEY (titolo, regista) REFERENCES Film
DEFERRABLE INITIALLY IMMEDIATE

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.

La transazione sarà la seguente:

BEGIN

INSERT INTO Video VALUES (4000, ‘A Star is Born’, ‘Bradley Cooper’,…);


INSERT INTO Film VALUES (‘A Star is Born’, ‘Bradley Cooper’,…);

COMMIT;

3) Valutazione differita con SET CONSTRAINTS ALL DEFERRED:


FOREIGN KEY (titolo, regista) REFERENCES Film
DEFERRABLE INITIALLY IMMEDIATE

Nonostante vi sia IMMEDIATE nel vincolo andiamo a rendere la transazione differita inserendo in
essa:

BEGIN

SET CONSTRAINTS ALL DEFERRED;

INSERT INTO Video VALUES (4000, ‘A Star is Born’, ‘Bradley Cooper’,…);


INSERT INTO Film VALUES (‘A Star is Born’, ‘Bradley Cooper’,…);
COMMIT;

4) Valutazione differita con INITIALLY DEFERRED:


FOREIGN KEY (titolo, regista) REFERENCES Film
DEFERRABLE INITIALLY DEFERRED

In questo caso poniamo già nella chiave esterna la dichiarazione differita della transazione;

BEGIN

INSERT INTO Video VALUES (4000, ‘A Star is Born’, ‘Bradley Cooper’,…);


INSERT INTO Film VALUES (‘A Star is Born’, ‘Bradley Cooper’,…);

COMMIT;

5) Valutazione immediata con SET CONSTRAINTS ALL IMMEDIATE:


FOREIGN KEY (titolo, regista) REFERENCES Film
DEFERRABLE INITIALLY DEFERRED

Anche se il vincolo la dichiara come differita, andiamo a renderla immediata attraverso il codice:

BEGIN

SET CONSTRAINTS ALL IMMEDIATE;

INSERT INTO Video VALUES (4000, ‘A Star is Born’, ‘Bradley Cooper’,…);


INSERT INTO Film VALUES (‘A Star is Born’, ‘Bradley Cooper’,…);

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.

a) INITIALLY IMMEDIATE: di base tutte le transazioni sono dichiarate immediate;


Il vincolo può essere sovrascritto all’interno della transazione attraverso:
SET CONSTRAINTS ALL DEFERRED;
In questo caso si parlerà quindi di transazione differita.

b) INITIALLY DEFERRED: di base tutte le transazioni sono dichiarate differite;


Il vincolo può essere sovrascritto all’interno della transazione attraverso:
SET CONSTRAINTS ALL IMMEDIATE;
In questo caso si parlerà quindi di transazione immediata.
AUTOCOMMIT
Abbiamo detto che una transazione implicitamente inizia all’esecuzione del primo comando SQL.

Le transazione che cominciano implicitamente possono anche quindi terminare implicitamente.

E’ prevista una modalità di terminazione implicita, gestita da una proprietà chiamata


AUTOCOMMIT, che può essere o meno abilitata:

- Se AUTOCOMMIT = TRUE, ogni comando costituisce implicitamente una


transazione a se stante (= eseguire COMMIT dopo ogni comando SQL);

- Se AUTOCOMMIT = FALSE, tutti i comandi eseguiti all’interno di una sessione


costituiscono implicitamente una transazione (= eseguire COMMIT dopo l’ultimo
comando della sessione).

Lezione 22 Aprile - Linguaggio SQL 5

Concorrenza
Sommario:
● Obiettivi gestione della concorrenza
● Anomalie
● Protocolli di locking per la gestione della concorrenza
● Livelli di isolamento

Obiettivi Gestione della Concorrenza


Finora abbiamo considerato transazioni singole.
In generale però nello stesso momento più transazioni che accedono una stessa base di dati
potrebbero essere in esecuzione.
Come è possibile eseguire concorrentemente un insieme di transazioni?
Quali problemi possono sorgere e come si possono risolvere?

Esecuzione Seriale di Transazioni


Un DBMS, dovendo supportare l’esecuzione di diverse transazioni che accedono a dati condivisi,
potrebbe eseguire tali transazioni in sequenza, attraverso quindi una serial execution.

Schedule dell’esecuzione di due transazioni:


Esecuzione Concorrente di Transazioni
Un DBMS potrebbe anche eseguire più transazioni in concorrenza, alternando l’esecuzione di
operazioni di una transazione con quella di operazioni di altre transazioni, questa è detta
interleaved execution.

Essa porta ad un miglioramento prestazionale:


Mentre una transazione è in attesa del completamento di una operazione di I/O, un’altra può
utilizzare la CPU, aumentando il throughput del sistema, ovvero il numero transazioni elaborate
nell’unità di tempo.
E’ molto utile con I/O oppure se si ha una transazione “breve” e una “lunga”; l’esecuzione
concorrente porta a ridurre il tempo medio di risposta del sistema:

Riduzione del Tempo Di Risposta


T1 è “lunga”, T2 è “breve”, ogni riga della tabella è un’unità di tempo; consideriamo un
esecuzione seriale (a sinistra) e una concorrente (a destra):

Notiamo che il throughput della concorrente è quasi la metà.

Controllo della Concorrenza


Come abbiamo visto, l’esecuzione concorrente migliora il tempo medio di risposta del sistema.
L’esecuzione concorrente potrebbe però generare anomalie.

Lo scopo del gestore della concorrenza è garantire consistenza e isolamento delle


transazioni eseguite concorrentemente, evitando possibili anomalie.

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.

I dati su disco devono essere quindi copiati ed elaborati in memoria centrale;


Modificare un dato (Y per esempio) vuol dire leggere il suo blocco da disco, modificarlo e riporre
il blocco su disco.

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.

Se ciò non avviene, si possono avere 4 tipi base di problemi:

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.

Abbiamo quindi una perdita degli aggiornamenti;


Entrambe le transazioni quindi entrambe vedono (e comprano) l’ultimo biglietto.
2) Dirty Read

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.

Un buon compromesso sono gli schedule serializzabili:


● schedule concorrenti, ovvero non seriali (garanzie prestazionali),
● che non causano anomalie (garanzie di correttezza).

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.

Protocolli di Locking per la Gestione della Concorrenza


La serializzabilità di un insieme di transazioni in esecuzione è garantita da protocolli di
controllo della concorrenza, eseguiti dal gestore delle transazioni.

Un protocollo di controllo della concorrenza esegue le transazioni concorrenti secondo un


qualche ordine, grazie ad uno schedule serializzabile.

Esistono molteplici protocolli, tra i più noti:


● Protocolli basati su lock;
● Protocolli basati su timestamp.

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.

La richiesta di lock è implicita, e quindi non è visibile a livello SQL;


I lock sono di vario tipo, quelli di base sono:
● S-lock (Shared): un lock condiviso è necessario per leggere;
● X-lock (eXclusive): un lock esclusivo è necessario per scrivere/modificare.
Compatibilità tra Lock
Il Lock Manager è una componente del transaction manager che gestisce i lock.

Quando la transazione T vuole operare (leggere/scrivere) su un dato Y invia (implicitamente) una


richiesta di acquisizione del lock corrispondente al Lock Manager:
- S-lock(Y),
- X-lock(Y).

Quando T richiede un lock sul dato esso viene accordato a T in funzione della seguente tabella
di compatibilità:

Se T richiede un lock share:


- se un'altra transazione ha richiesto un altro lock share il lock viene accordato; quindi
posso avere due o piu lock share, che tanto si occupano solo di lettura;
- se un'altra transazione ha richiesto un lock exclusive il lock non viene accordato, poiché
se no ci sarebbe rischio di anomalie tra letture e scritture.

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.

Se la richiesta non viene accordata, T rimane in attesa.

Quando T ha terminato di usare Y, può rilasciare il lock tramite l’istruzione unlock(Y).

Protocollo Strong 2 Phase Locking (Strong 2 PL)


Il sistema crea schedule serializzabile usando il meccanismo dei lock, infatti si può dimostrare
che se una transazione:
- prima acquisisce tutti i lock necessari e
- li rilascia solo al termine dell’esecuzione (al momento del COMMIT o ROLLBACK),

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.

Quando si verificano situazioni di deadlock, vengono abortite le transazioni.


Vediamo come questo protocollo garantisce l’assenza delle 4 anomalie sopracitate:

1) Assenza di Lost Update


Passiamo dalla situazione a sinistra (senza Strong 2 PL) a quella di destra (con Strong 2 PL):

Né T1 né T2 riescono ad acquisire il lock per poter modificare X; restano quindi entrambi in


attesa (wait), in una situazione di attesa circolare o deadlock.

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.

2) Assenza di Dirty Read


Passiamo dalla situazione a sinistra (senza Strong 2 PL) a quella di destra (con Strong 2 PL):

In questo caso l’esecuzione corretta richiede che T2 aspetti la terminazione di T1 prima di poter
leggere il valore di X.

3) Assenza di Unrepeatable Read


Passiamo dalla situazione a sinistra (senza Strong 2 PL) a quella di destra (con Strong 2 PL):
Anche in questo caso T2 viene messa in attesa, e T1 ha quindi la garanzia di poter leggere
sempre lo stesso valore di X.

4) Assenza Di Phantom Row


Un'anomalia si presenta quando si leggono più volte dalla stessa tabella, aggiungendo o
rimuovendo righe in essa.

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.

E’ possibile trovare un compromesso:


● si può operare in modo tale che solo alcune anomalie si possano presentare;
● si passa quindi da isolamento totale a isolamento parziale;
● per ciascuna transazione possiamo quindi definire un livello di isolamento.

Lo standard definisce 4 livelli di isolamento:


Nessun livello può generare anomalie di lost update.
Il livello SERIALIZABLE evita tutte le anomalie.
Cerchiamo di capire quali meccanismi di locking vengono adottati in questi livelli di isolamento.

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.

Se T legge un insieme di valori basandosi su una qualche condizione di ricerca (SELECT),


questo insieme non viene modificato da altre transazioni fino a che T non ha terminato

Nessuna anomalia si può verificare.

REPEATABLE READ
Protocollo di locking:
- Strong 2PL
- senza lock su tabelle complete.

T legge solo modifiche effettuate da transazioni che hanno effettuato il commit;


Nessun valore letto o scritto da T viene modificato da altre transazioni prima che T abbia
terminato.

L’anomalia di phantom row è possibile.

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.

T legge solo modifiche effettuate da transazioni che hanno effettuato il commit.


Nessun valore scritto da T viene modificato da altre transazioni prima che T abbia terminato
Un valore letto da T può però essere modificato da altre transazioni prima che T abbia
terminato.
Anomalie di letture non ripetibili e phantom row possibili.

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).

Livelli di Isolamento In SQL


Per default è impostato su READ COMMITTED nella maggior parte dei DBMS in commercio.
Il livello di isolamento può essere specificato o modificato contestualmente al comando di inizio
transazione.

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.

SET TRANSACTION ISOLATION LEVEL


{SERIALIZABLE | REPEATABLE READ | READ COMMITTED | READ UNCOMMITTED}

Livelli di Isolamento: Quale Scegliere?


I livelli più safe diminuiscono la concorrenza e quindi l’efficienza.
I livelli più efficienti aumentano il rischio di anomalia.

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.

Multiversione – Snapshot Isolation


Questa variante dei livelli di isolamento è implementata da molti sistemi commerciali.

Rilassa la gestione dei lock in lettura limitando le anomalie possibili.


Analogamente a READ UNCOMMITTED nessuna transazione chiede lock in lettura.
Diversamente da READ UNCOMMITTED ogni lettura non viene eseguita sulla copia del dato al
momento della lettura ma su una copia sicura, ovvero sulla versione dei dati esistente al
momento dell’inizio della transazione, generata da transazioni che hanno terminato con
commit.
Non si vedono quindi valori sporchi, generati da transazioni non ancora terminate.

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.

Livelli di Isolamento di PostgreSQL


Per ogni livello di isolamento, lo standard specifica solo i requisiti minimi.

I sistemi possono quindi ulteriormente restringere quanto richiesto.


In particolare, PostgreSQL implementa alcuni livelli di isolamento di SQL con snapshot isolation.
Tre soli livelli di isolamento in PostgreSQL (a partire da 9.1):
● SERIALIZABLE
● REPEATABLE READS
● READ COMMITTED, che viene implementato come READ UNCOMMITTED e coincide
con esso.

Ripristino
Sommario:
● Obiettivi gestione del ripristino,
● Ripristino basato sui file di log,
● Problemi prestazionali ripristino,
● Ripristino per media failure.

Obiettivi della Gestione del Ripristino


Sappiamo già che ogni transazione o termina con commit o con abort.
Dopo un abort o un commit non può più “cambiare idea”.
Questo comportamento atomico deve essere garantito anche in caso di malfunzionamenti:
● Gli effetti delle transazioni committed devono essere permanenti (Durability).
● Gli effetti delle transazioni aborted non devono lasciare tracce (Atomicity).

Durability e Atomicity vengono assicurati da una particolare componente del Transaction


Manager: il gestore del ripristino.

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).

Modello Astratto di Esecuzione


E’ opportuno introdurre un modello astratto di esecuzione di una transazione per descrivere i
possibili malfunzionamenti:

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.

Ripristino Basato sui File di log


Le attività di ripristino eseguite a valle del verificarsi di un transaction o di un system failure
vengono genericamente indicate con il termine ripresa a caldo; essa si basa su i file di log.
Durante l'esecuzione di una transazione tutte le operazioni di scrittura sono registrate in un file di
log, memorizzato su memoria stabile.
Memoria stabile:
- è un’astrazione teorica di una memoria che teoricamente non è mai coinvolta in failures.
- se ne implementano approssimazioni, duplicando le informazioni in diverse memorie
non volatili con probabilità di fallimento indipendenti.

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.

Protocollo Wal - Write-Ahead Logging


Affinché il Log possa essere utilizzato per ripristinare lo stato del DB a fronte di
malfunzionamenti, è importante che venga applicato il cosiddetto protocollo WAL (Write-ahead
Logging).

Prima di scrivere su disco una pagina P modificata, il corrispondente record di Log deve essere
già stato scritto nel file di Log.

Se il protocollo WAL non viene rispettato è possibile che:


- una transazione T modifichi il DB aggiornando una pagina P,
- si verifichi un system failure prima che il Log record relativo alla modifica di P sia stato
scritto nel 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.

L’architettura del sistema nell’ambito del ripristino è la seguente:


Vi sono quindi 4 passi per costruire un log che possa garantire un possibile ripristino:
1) Una transazione esegue una modifica (update) su una pagina P,
2) a valle di questo aggiornamento il sistema deve generare l’opportuno record del file di log
in memoria centrale,
3) in base al protocollo WAL questo record viene conservato in memoria stabile,
4) a quel punto la pagina P può essere scritta su disco.

Lo stato corrente della base di dati su disco riflette solo le azioni delle transazioni il cui
effetto è già stato reso persistente;

Inoltre esso, in memoria volatile:


● può non riflettere alcune azioni di transazioni committed (alcuni blocchi devono ancora
essere forzati su disco);
● può riflettere azioni di transazioni non committed (alcuni blocchi sono stati forzati su
disco ma devono essere riportati allo stato iniziale).

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).

Protocollo Wal e Modello Astratto di Esecuzione

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 il passo 4 rispetto al passo 1, ovvero quando la pagina P viene


modificata nel buffer?
2) Quando eseguire il passo 4 rispetto a quando la transazione entra nello stato committed?

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.

Questo offre una migliore gestione del buffer.

2) Quando eseguire il Passo 4 rispetto a quando la transazione entra nello stato


committed?

Politica Force: Prima di scrivere il record di COMMIT sul Log si forza la scrittura su disco di
tutte le pagine modificate da T.

A quel punto si scrive il record di COMMIT sul file di Log.


Quando T entra nello stato committed, tutte le modifiche sono state rese persistenti su disco.
Questo porta a molti I/O inutili: se una pagina P è frequentemente modificata, deve essere
scritta su disco ad ogni COMMIT.
Non forzare subito la scrittura su disco permette di accumulare alcune modifiche per poi
registrarle tutte insieme

Politica No-Force: Si scrive subito il record di COMMIT sul file di Log.


Le pagine del buffer si copiano su disco successivamente (appena si può).
Quando T entra nello stato committed, alcune delle sue modifiche possono ancora non essere
state rese persistenti.
Migliora gli accessi a disco: una pagina P viene scritta su disco solo se deve essere rimpiazzata
nel buffer.
Transaction Failure
Accade quando T esegue ROLLBACK (entra in stato failed)

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.

Politica No-Steal: Nessuna pagina modificata è già stata copiata su disco;


Non si fa quindi mai UNDO di T; è quindi inutile mantenere before(P) nel log; viene quindi
chiamato log a modifiche differite.

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.

2) La transazione T non è nello stato committed.


La transazione entra nello stato failed; da questo punto in avanti si procede come per
Transaction Failure.

Attenzione: REDO e UNDO devono essere idempotenti.

Più esecuzioni in sequenza devono essere equivalenti ad un’esecuzione singola.


Si assicura un comportamento corretto anche in presenza di malfunzionamenti durante
l’esecuzione della procedura di ripristino:
● REDO da eseguire quando REDO è già in esecuzione,
● UNDO da eseguire quando UNDO è già in esecuzione.
Problemi Prestazionali Ripristino
La politica Steal è quella più utilizzata perché non richiede di mantenere nel buffer
necessariamente tutti i blocchi modificati da transazioni che non hanno effettuato il commit; essa
è preferita a no-steal.
La politica Force è quella meno utilizzata a causa dei costi; si preferisce no-force.
Quindi la combinazione steal-no force è la più utilizzata dai DBMS.

Questa combinazione è costosa se le transazioni sono tante e lunghe:


● In caso di guasto, UNDO di transazioni non committed,
● Al COMMIT, copia su disco di tutte le pagine modificate dalle transazioni.

Possiamo quindi introdurre i checkpoint per renderle meno lunghe.

Checkpoint
Per migliorare le prestazioni, periodicamente si può eseguire un checkpoint, ovvero una
scrittura forzata su disco delle pagine modificate.

Il sistema periodicamente (al checkpoint):


1. forza tutte le pagine di log nel buffer su memoria stabile,
2. forza tutte le pagine dati nel buffer su disco,
3. forza il record (CKP) sul log in memoria stabile.

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.

Ripristino per Media Failure


L’approccio della Ripresa a caldo (Recovery con log), usato per System Failure e Transaction
Failure non basta per il media failure.
Il sistema deve adottare una strategia per affrontare i failure che riguardano memoria
persistente.
Il media failure viene quindi affrontato combinando recovery con log e dump.
Un dump (backup) è una copia completa della base di dati, memorizzata in memoria stabile.
La creazione del dump viene registrata nel file di log, con indicazione del file e del device su cui
è stato effettuato il dump; a questo punto si passa alla ripresa a freddo:
- si accede al dump e si ripristina il contenuto della base di dati su memoria non volatile,
- si effettua una ripresa a caldo, accedendo al file di log come discusso in precedenza.

Lezione 29 Aprile - Dati Derivati e Viste


Sommario:
- Dati derivati:
- colonne derivate,
- relazioni derivate,
- viste.

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.

Relazioni Derivate da Query


Possiamo derivare una relazione anche a partire da una query, utilizzando l’istruzione:
WITH DATA AS

oppure

AS …. [WITH DATA / WITH NO DATA]

Esempio:

Decidiamo di memorizzare in due relazioni separate i noleggi conclusi ed i noleggi in corso:

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 è:

● Una relazione virtuale che permette di accedere diversamente ai dati memorizzati


nelle relazioni “reali” (di base), ovvero guardando al contenuto della base di dati
attraverso un interrogazione.

● Si definisce tramite un'interrogazione su relazioni di base e viste.


● Non corrisponde a dati memorizzati secondo il suo schema ma al risultato
dell’interrogazione.
● Può essere usata a quasi tutti gli effetti come una relazione di base;
● Il meccanismo delle viste è utile per:
○ semplificare l'accesso ai dati,
○ fornire indipendenza logica,
○ garantire la privatezza dei dati.

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:

CREATE VIEW Nol3gg AS


SELECT codCli, dataNol, colloc
FROM Noleggio WHERE dataRest IS NULL
AND (CURRENT_DATE - dataNol) DAY > INTERVAL '3' DAY;

I nomi delle colonne della vista sono codCli, dataNol e colloc.

Un'alternativa è:

CREATE VIEW Nol3gg (codiceCliente, dataNoleggio,Video)


AS SELECT codCli, dataNol, colloc FROM Noleggio
WHERE dataRest IS NULL
AND (CURRENT_DATE - dataNol) DAY > INTERVAL '3' DAY;
I nomi delle colonne della vista sono codiceCliente, dataNoleggio, Video.

Definizione di piu Viste


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?

V1 = SELECT * FROM R NATURAL JOIN S WHERE C < 18

Prende il FROM dalla vista riferita e combina le condizioni del WHERE.

Perché le Viste Semplificano l’Accesso ai Dati?


Vi sono varie ragioni; vediamo i casi più comuni.

Casi Comuni – join


Le viste vengono utilizzate per semplificare l’accesso agli utenti che lavorano sempre/spesso con
particolari associazioni tra entità:
- una sola tabella invece di un join esplicito,
- si possono prefiltrare eventuali dati superflui.

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).

CREATE VIEW InfoNol3gg AS


SELECT telefono, dataNol, titolo, 5 * ((CURRENT_DATE - dataNol) DAY) AS importo
FROM Cliente NATURAL JOIN Noleggio NATURAL JOIN Video
WHERE dataRest IS NULL
AND (CURRENT_DATE - dataNol) DAY > INTERVAL '3' DAY;

I nomi delle colonne della vista sono telefono, dataNol, titolo e importo.

Casi Comuni – Group By


Serve per astrarre i dati e presentarli agli utenti in modo aggregato:
- permette di garantire riservatezza,
es. si vogliono dare informazioni sul numero di noleggi dei singoli film alle ditte di
distribuzione, senza divulgare chi ha noleggiato cosa per ragioni di privacy.

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:

CREATE VIEW InfoCli (nome, residenza, annoN, numNol, durataM) AS


SELECT nome, residenza, YEAR(dataN),
COUNT(*), MAX((dataRest - dataNol) DAY)
FROM Cliente NATURAL JOIN Noleggio
GROUP BY codCli, nome, residenza, dataN;

I nomi delle colonne della vista sono nome, residenza, annoN, numNol, durataM.

Interrogazioni e Aggiornamenti su Viste


L’utente percepisce le viste come tabelle vorrebbe manipolarle come le relazioni di base.

Su una vista si possono eseguire:


- interrogazioni,
- inserimenti,
- aggiornamenti.

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.

Su una vista ad esempio è possibile:


● effettuare proiezioni,
● specificare condizioni di ricerca,
● effettuare dei join con altre relazioni o viste,
● effettuare raggruppamenti,
● calcolare funzioni di gruppo,
● definire altre viste.

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:

CREATE VIEW InfoNol3gg AS


SELECT telefono, dataNol, titolo, 5 * ((CURRENT_DATE - dataNol) DAY) AS importo
FROM Cliente NATURAL JOIN Noleggio NATURAL JOIN Video
WHERE dataRest IS NULL
AND (CURRENT_DATE - dataNol) DAY > INTERVAL '3' DAY;

SELECT telefono FROM InfoNol3gg


GROUP BY telefono
HAVING SUM(importo) >= ALL ( SELECT SUM(importo)
FROM InfoNol3gg GROUP BY telefono);

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.

Questa restrizione richiede che l’utente finale:


- sia conscio di stare usando una vista,
- ne conosca la definizione.

Si perde quindi la trasparenza.


Esempio:

CREATE VIEW InfoCli (nome, residenza, annoN, numNol, durataM) AS


SELECT nome, residenza, YEAR(dataN),
COUNT(*), MAX((dataRest - dataNol) DAY)
FROM Cliente NATURAL JOIN Noleggio
GROUP BY codCli, nome, residenza, dataN;

SELECT AVG(durataM) FROM InfoCli


WHERE annoN BETWEEN 1970 AND 1976 AND residenza LIKE '%genova‘;
AVG(durataM) non è ammesso, sarebbe come scrivere:

SELECT AVG(MAX((dataRest - dataNol) DAY)


FROM Cliente NATURAL JOIN Noleggio
WHERE YEAR(dataN) BETWEEN 1970 AND 1976 AND residenza LIKE '%genova‘
GROUP BY codCli, nome, residenza, dataN;

E AVG(MAX(..)) non è ammesso.

Aggiornamenti su Viste
SE possibile, l'esecuzione di un'operazione di aggiornamento su una vista viene
propagata alla relazione su cui la vista è definita.

Creo una vista V su R: CREATE VIEW V AS SELECT * FROM R WHERE B>=2

Voglio eseguire: INSERT INTO V VALUES (2,8,10).


V è definita in termini di R -> devo inserire (2,8,10) in R.
In questo caso semplice tutto va bene; analogamente per le operazioni di update e delete.
In casi complicati le operazioni potrebbero non andare a buon fine, quindi sono proibite.

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:

CREATE VIEW InfoNolCosti AS


SELECT codCli, dataNol, titolo, 5 * ((dataRest - dataNol) DAY) AS importo
FROM Cliente NATURAL JOIN Noleggio NATURAL JOIN Video
WHERE dataRest IS NOT NULL;

UPDATE InfoNolCosti importo = importo + 2


- modifica dataNol o dataRest?
- ammesso di scegliere arbitrariamente di modificare dataRest, che valore scelgo?

In questi casi la modifica verrà rifiutata.

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;

INSERT INTO ClientiConNoleggiAperti VALUES (6635, CURRENT_DATE)


Vi è ambiguità su che colloc usa per inserire la tupla in Noleggio, quindi l'inserimento verrà
rifiutato.

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

Voglio eseguire DELETE FROM VV WHERE A=1


Posso:
- cancellare in R,
- cancellare in S,
- cancellare in entrambe,
- modificare la tupla (1,2,3) in R.

Il non determinismo è un problema perché è indice di incertezza sulle intenzioni dell’utente e


potrebbe portare la base di dati in uno stato incomprensibile all’utente soprattutto ad uno che
veda le relazioni base e non la vista.
Per questi vari problemi vengono inserite restrizioni per rendere deterministico lo stato di una
base di dati dopo un aggiornamento.

Recap delle Restrizioni

Delete su Viste: Restrizioni


È possibile eseguire il comando DELETE su V se la query di definizione Q è su una singola
relazione R e non contiene costrutti che riorganizzano la struttura dell BD:
- GROUP BY,
- DISTINCT,
- operatori insiemistici,
- funzioni di gruppo,
- eventuali sotto-interrogazioni in Q che fanno riferimento ad R.

Update su Viste: Restrizioni


È possibile eseguire il comando UPDATE su una colonna C di V se:
- la query di definizione Q soddisfa tutte le restrizioni richieste per il DELETE,
- la colonna aggiornata C non è definita nella vista tramite un’espressione o
funzione (quindi la colonna è una di quelle di base).

Insert in Viste: Restrizioni


È possibile eseguire il comando INSERT in V se la query di definizione Q soddisfa tutte le
restrizioni richieste per il DELETE, inoltre:
- tutte le colonne di V soddisfano le restrizioni richieste per l’UPDATE,
- tutte le colonne di R su cui vale il vincolo di NOT NULL e per cui non è specificato
valore di default sono incluse in V.

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?

V1 = SELECT * FROM R NATURAL JOIN S WHERE C < 18

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;

Si consideri adesso la seguente interrogazione:

SELECT *
FROM V
WHERE D > 5

Quale interrogazione verrà generata dall'espansione dell'interrogazione precedente?

SELECT D, MAX(A)
FROM R NATURAL JOIN S
WHERE D > 5 AND E = 6
GROUP BY D;
Ulteriore problema
Consideriamo:

CREATE VIEW Nol3gg AS


SELECT codCli, dataNol, colloc
FROM Noleggio
WHERE dataRest IS NULL AND
(CURRENT_DATE - dataNol) DAY > INTERVAL '3' DAY;

Supponiamo di voler inserire nella vista Nol3gg la seguente tupla:


(6635, CURRENT_DATE, 1128)

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:

CREATE VIEW Nol3gg AS SELECT codCli, dataNol, colloc


FROM Noleggio WHERE dataRest IS NULL
AND (CURRENT_DATE - dataNol) DAY > INTERVAL '3' DAY
WITH CHECK OPTION;

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.

Se specifichiamo CASCADED, indipendentemente da CHECK OPTION su V2, sia la condizione


di V1, sia quella di V2 vengono verificate.

Lezione 29 Aprile - Controllo dell’Accesso


Sommario:
● Introduzione,
● Il modello per il controllo degli accessi,
● Controllo degli accessi in SQL:
○ Principi generali,
○ Comandi,
○ Gestione dell’accesso,
○ I ruoli,
○ Controllo dell’accesso basato sul contenuto.

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.

Il modello per il Controllo degli Accessi


Il controllo dell’accesso si basa sulla specifica di opportune politiche di sicurezza, che
dipendono dal dominio considerato.
Esse sono regole e principi secondo cui l'organizzazione vuole che siano protette le proprie
informazioni, ovvero un insieme di direttive ad alto livello che esprimono le scelte compiute da
un’organizzazione in merito alla protezione dei propri dati.
Esempio di politica di sicurezza: “le valutazioni dei film possono essere viste solo dal
responsabile della videoteca”.
Vediamo come esse possono essere rappresentate;

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.

La concessione dell’autorizzazione può essere:


- totale: l’accesso richiesto può essere totalmente eseguito,
- parziale: sono una parte dell’accesso richiesto può essere eseguito (vedremo meglio in
seguito),
- negata: l’accesso richiesto non può essere eseguito.

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.

L'amministrazione dei privilegi è decentralizzata mediante ownership:


- l'utente che crea una relazione, riceve tutti i privilegi su di essa ed anche la possibilità di
delegare ad altri tali privilegi.

La delega dei privilegi avviene mediante grant option.


Se un privilegio è concesso con grant option l'utente che lo riceve può non solo esercitare il
privilegio, ma anche concederlo ad altri.
Un utente può quindi concedere un privilegio su una determinata relazione solo se è il
proprietario della relazione o ha ricevuto tale privilegio con grant option.

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.

GRANT {<lista privilegi> | ALL PRIVILEGES} -- componente P


ON <nome oggetto> -- componente O
TO {<lista utenti> |PUBLIC} -- componente S
[WITH GRANT OPTION];

● <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.

● <lista utenti>: indica l'insieme degli utenti a cui il comando si applica

La clausola opzionale WITH GRANT OPTION consente la delega dell'amministrazione dei


privilegi.
La parola chiave PUBLIC consente di specificare le autorizzazioni implicate dal comando per tutti
gli utenti del sistema.

Privilegi del Creatore Della Risorsa


Alla creazione di una risorsa, il sistema concede tutti i privilegi su tale risorsa all’utente che ha
creato la risorsa.

I comandi di GRANT corrispondenti vengono eseguiti automaticamente dal sistema:


- solo il creatore della risorsa ha il privilegio di eliminare una risorsa (DROP) e modificarne
lo schema (ALTER),
- il privilegio di eliminare e modificare una risorsa non può essere concesso a nessun altro
utente.

Privilegi del DBA


L’amministratore della base di dati possiede tutti i privilegi su tutte le risorse.

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.

REVOKE [GRANT OPTION FOR] <lista privilegi>


ON <nome oggetto>
FROM <lista utenti>
{RESTRICT | CASCADE};

<lista privilegi>: indica l'insieme di privilegi oggetto del coman,,,do di revoca.

<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.

Un grafo delle interrogazioni per il privilegio p sulla tabella R contiene:


- un nodo per ogni utente che possiede il privilegio p su R,
- un arco dal nodo u1 al nodo u2 se l'utente u1 ha concesso il privilegio p su R a u2;
- l'arco è etichettato con la lettera g se il privilegio è delegabile (concesso con
GRANT OPTION).

Esempi:

La radice del grafo sarà dunque l’owner.

Esecuzione dei Comandi di Grant


Il gestore dell’accesso, durante l’esecuzione di un comando di GRANT:
● verifica che l’utente che esegue il comando abbia i diritti per poterlo eseguire, leggendo i
cataloghi (comando SELECT),
○ se l’utente può eseguire il comando di GRANT, i nuovi privilegi concessi vengono
memorizzati nei cataloghi (comando INSERT), modificando di conseguenza i
cataloghi;
In alcuni casi il comando può essere eseguito solo parzialmente ovvero quando
vi sono solo alcune autorizzazioni tra quelle richieste vengono inserite.

Esempio:
barbara: GRANT select ON Film TO matteo;

Barbara ha acquisito il privilegio con grant option, quindi lo può delegare.


Il nuovo grafo (che corrisponderà ad un nuovo contenuto delle tabelle dei cataloghi è:

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).

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 con RESTRICT:

barbara: REVOKE insert ON Clienti FROM paolo RESTRICT;

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:

Se invece avessi eseguito:


barbara: REVOKE insert ON Clienti FROM marina RESTRICT;

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.

Esempio con CASCADE:


Se invece eseguiamo:
barbara: REVOKE insert ON Clienti FROM marina CASCADE;

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).

Se però usiamo cascade:


Supponiamo che Luca revochi il privilegio SELECT concesso a Barbara, con clausola
CASCADE:

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.

RBAC - Role-based Access Control:


Un ruolo rappresenta una funzione che gli utenti ricoprono all'interno della realtà organizzativa in
cui operano.

Esempi di ruolo per il dominio della videoteca: cliente, commesso, direttoreVideoteca.


Gli utenti possono essere abilitati a ricoprire uno o più ruoli, in base alle mansioni che devono
svolgere.

I privilegi possono essere concessi ai singoli utenti ma anche ai 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.

Comandi per la gestione dei Ruoli


Creazione di un ruolo: CREATE ROLE <NomeRuolo>;

Eliminazione di un ruolo: DROP ROLE <NomeRuolo>;

Associazione dinamica di un ruolo all’utente della sessione attiva:


SET ROLE <NomeRuolo>

I comandi GRANT e REVOKE possono essere utilizzato anche per gestire autorizzazioni riferite
ai ruoli.

Comando GRANT esteso ai Ruoli

1. Concessione privilegi ai ruoli:


Nella clausola TO, al posto di specificare una lista di utenti, specifichiamo una lista di ruoli.

Esempio:
Concessione privilegi ai ruoli con concessione dei privilegi.

CREATE ROLE direttoreVideoteca;


GRANT delete ON Clienti TO direttoreVideoteca WITH GRANT OPTION;

2. Autorizzare un utente a ricoprire un ruolo;

Esempio:
Il ruole direttoreVideoteca è concesso a Luca, che a sua volta può delegarlo (ADMIN OPTION).

GRANT direttoreVideoteca TO Luca WITH ADMIN OPTION;

Gerarchie Tra Ruoli


SQL permette anche di definire i ruoli gerarchicamente.
Una gerarchia sui ruoli definisce un ordinamento parziale ≥ tra di essi:
● induce un'ereditarietà dei privilegi tra ruoli nella gerarchia,
● stabilisce una relazione tra gli utenti abilitati a ricoprire i vari ruoli.

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:

Ruoli: direttoreVideoteca, commesso con direttoreVideoteca ≥ commesso

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;

Comando REVOKE esteso ai Ruoli


1. Revoca di privilegi a ruoli:

Specifichiamo nella clausola from i ruoli a cui vogliamo revocare i privilegi.

Esempio:
REVOKE delete ON Clienti FROM direttoreVideoteca;

2. Revoca di ruoli a utenti o ruoli:

Se rimuoviamo un ruolo rimuoviamo un arco nella nostra gerarchia.


Esempio:
Revoca l’ADMIN OPTION riferita al ruolo di direttore; luca rimane direttore ma non può delegarlo:
REVOKE ADMIN OPTION FOR direttoreVideoteca FROM Luca;

Revoca del ruolo:


REVOKE direttoreVideoteca FROM Luca;

Elimina il ruolo:
REVOKE direttoreVideoteca FROM amministratoreVideoteca;

Controllo dell’Accesso Basato sul Contenuto


Le viste sono un importante meccanismo attraverso cui è possibile fornire forme più
sofisticate di controllo dell'accesso.
La sintassi del comando GRANT non consente di concedere privilegi solo su alcune tuple di una
tabella.

È 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?)

CREATE VIEW Commedie AS SELECT * FROM Film WHERE genere = ‘commedia’;

Barbara concede il privilegio di SELECT sulla vista Commedie a Marina (quando lo può fare?)

GRANT SELECT on Commedie TO MARINA

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.

Se Barbara ha in privilegio di SELECT su Film può creare la vista Commedie:


CREATE VIEW Commedie AS SELECT * FROM Film WHERE genere = ‘commedia’;

L’owner della vista quali privilegi può esercitare sulla vista?

L’insieme dei privilegi esercitabili dipende da:


● P1 le autorizzazioni che l'utente possiede su tutte le relazioni/viste su cui la vista è
definita,
● P2 le operazioni che possono essere eseguite sulla vista in base alla sua semantica.

I privilegi esercitabili sulla vista sono quelli contenuti nell’intersezione di P1 e P2.


Un privilegio sulla vista è delegabile solo se il creatore della vista ha il diritto di delegare tale
privilegio su tutte le relazioni o viste componenti.

Esempio:
Barbara ha solo il privilegio SELECT su Film, con GRANT OPTION

Barbara può eseguire il comando:


CREATE VIEW Commedie AS
SELECT * FROM Film WHERE genere = ‘commedia’;

Privilegi esercitabili da Barbara sulla vista Commedie:


P1 = {SELECT con GRANT OPTION}
P2 = {SELECT, INSERT, DELETE, UPDATE }

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.

Elena può eseguire il comando:


CREATE VIEW NumNoleggi AS
SELECT codCli, COUNT(*) AS NumNol FROM Noleggi
GROUP BY codCli;

Privilegi esercitabili da Elena sulla vista NumNoleggi


P1 = { SELECT, INSERT, UPDATE}
P2 = { SELECT }

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.

La concessione di privilegi su una vista è molto simile a quella su relazioni di base;


I privilegi che un utente può concedere ad altri su una vista sono quelli che possiede con GRANT
OPTION.
Se l’owner di una vista V perde il privilegio di SELECT sulle relazioni o viste che definiscono
V, in accordo alla semantica della revoca ricorsiva, si cancella la vista.

Lezione 4 Maggio - Sviluppo di Applicazioni per Basi di Dati


Per scrivere un’applicazione bisogna essere in grado di eseguire elaborazioni arbitrariamente
complesse.

Serve un linguaggio che possa:


● Effettuare aggiornamenti,
● Effettuare interrogazioni,
● Effettuare I/O,
● Controllare il flusso in un modo che dipenda dal risultato dell’interrogazione.
Gli ultimi due punti non sono garantiti da SQL, che infatti non è completo:
● computazionalmente:
○ non è in grado di esprimere tutte le computazioni teoricamente possibili,
○ mancano costrutti quali scelta o iterazione.
● operazionalmente:
○ non è in grado di comunicare direttamente con il sistema operativo e, attraverso
di esso, con l’hardware e le periferiche,
○ mancano costrutti per scrivere in un file, stampare, sviluppare un’interfaccia
utente.

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.
ex. PL/pgSQL.
- Accoppiamento Esterno: Un linguaggio di programmazione esistente (es. C, Java)
viene integrato con SQL; il codice viene eseguito in un ambiente esterno al DBMS.
Due approcci principali, di lunga tradizione:
- Librerie di funzioni: ad hoc per eseguire comandi SQL, come ad esempio
JDBC.
- SQL ospitato: comandi espliciti poi tradotti in chiamate di funzione a librerie;
ex. SQL <comando> EXEC SQL

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.

Permette di garantire la completezza computazionale.


Non sempre garantisce la completezza operazionale.
Adottato soprattutto per nuove applicazioni che richiedono una forte interazione con il DBMS.
Lo standard SQL propone un’estensione procedurale (SQL/PSM), ma la sintassi varia
sensibilmente tra i vari DBMS; noi vedremo PostgreSQL: PL/pgSQL

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>

Dove modo rappresenta il tipo di parametro che si sta usando:


- Input: IN,
- Output: OUT,
- Input/Output: INOUT.

E dove il tipoRitorno rappresenta il tipo del valore restituito dalla funzione.


Stessi tipi ammissibili rispetto ai parametri; void se la funzione non restituisce nulla.
Se almeno un parametro è OUT o INOUT, la clausola RETURNS può essere omessa.
Se è presente, deve coincidere con il tipo dei parametri di output; se ci sono più parametri OUT o
INOUT, il tipo di ritorno è un record, ovvero una tupla.

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:

CREATE FUNCTION AggiornaVal1 (IN ilGenere CHAR(15) )


RETURNS void AS
$$ <aggiorna la valutazione dei film di genere uguale ad ilGenere> $$
LANGUAGE plpgsql;
Restituisce tupla vuota, poiché è una procedura.

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;

Restituisce tupla formata da un unico elemento, che rappresenta la valutazione media.


SELECT AggiornaVal2(‘drammatico’);
aggiorna la valutazione dei film di genere drammatico e restituisce una tabella di grado 1,
costituita da un’unica tupla contenente la valutazione media dei film di genere ‘drammatico’.

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;

Restituisce tupla formata da un unico elemento, che rappresenta la valutazione media


SELECT AggiornaVal3(‘drammatico’);
aggiorna la valutazione dei film di genere drammatico e restituisce una tabella di grado 1,
costituita da un’unica tupla contenente la valutazione media dei film di genere ‘drammatico’.
Siccome manca il parametro out nella chiamata il risultato ha una colonna denominata val.

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;

Restituisce tupla formata da due valori.


Due soluzioni di interrogazione:
1) SELECT AggiornaVal4(‘drammatico’);
Aggiorna la valutazione dei film di genere drammatico e restituisce una tabella di
grado 1, costituita da un’unica tupla contenente come unico valore un record,
composto dalla valutazione media dei film di genere ‘drammatico’ e dall’anno del film
drammatico più vecchio record come tipo di dato non visto a lezione
2) SELECT * FROM AggiornaVal4(‘drammatico’);
Aggiorna la valutazione dei film di genere drammatico e restituisce una tabella di
grado 2, costituita da un’unica tupla contenente la valutazione media dei film di
genere ‘drammatico’ e dall’anno del film drammatico più vecchio

Corpo di una Routine


Il corpo di ogni programma/routine è composto da due sezioni:
● sezione di dichiarazione: è opzionale e contiene le dichiarazioni di tutte le variabili del
programma;
● sezione di esecuzione: è obbligatorio e contiene il codice da eseguire, ovvero costrutti
procedurali (istruzioni) e statement SQL.

Sezione di Dichiarazione
Sintassi:
DECLARE <listaDichiarazioni>

Sintassi di una singola dichiarazione di variabile/costante:

<nome> [ CONSTANT ] <tipo>


[ NOT NULL ]
[ { DEFAULT | := } <espressione> ];

Dove:
● CONSTANT: <nome> non può essere riassegnata (costante).

● NOT NULL: non si può assegnare NULL a <nome>

All’entrata nel blocco a <nome> viene assegnato


- se è presente DEFAULT o :=, il valore ottenuto dalla valutazione di <espressione>.
- altrimenti NULL.

Le assegnazioni vengono eseguite in conformità del tipo e dei vincoli.


Esempio:
DECLARE
valutaz NUMERIC(3,2) DEFAULT 2.50;
regista VARCHAR(20) := ’quentin tarantino’;
genere CONSTANT CHAR(15) := ‘drammatico’;
codCli DECIMAL(4) NOT NULL :=0000;
Possibili usi corretti:
● assegnare ‘pinco palla’ a regista
● assegnare 1234 a codCli

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>

Dove <nome var> ed <espressione> devono avere lo stesso tipo.

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;

Esecuzione Comandi SQL


I comandi che non restituiscono tuple (INSERT, DELETE, UPDATE, CREATE TABLE …) sono
istruzioni.
È sufficiente scrivere il comando nel corpo della funzione.

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.

IF/ELSE: Costrutti di Scelta


Sintassi:
IF <expr booleana>
THEN <istruzioni>
[ELSE <istruzioni>]
END IF;

oppure:

IF <expr booleana>
THEN <istruzioni>
[ELSEIF <expr booleana> THEN <istruzioni> ]
END IF;

WHILE: Cicli con Iterazione Incerta


Sintassi:
[WHILE <expr booleana>] LOOP <istruzioni> END LOOP;

La clausola WHILE è opzionale:


● se assente equivale a WHILE true
● se presente il ciclo termina se la condizione diventa falsa
Nel blocco LOOP si possono utilizzare:
● EXIT [WHEN <expr booleana> ] per uscire dal ciclo
● CONTINUE [WHEN <expr booleana> ] per passare all’esecuzione dell’iterazione
successiva.
in entrambi i casi se è presente la clausola WHEN <expr booleana> l’azione viene eseguita solo
se l’espressione si valuta a true.
In caso di cicli annidati EXIT/CONTINUE fanno riferimento al ciclo
più interno; si può fare in modo di far riferimento anche ad un altro ciclo più esterno usando
le label, se vi servisse guardate il manuale.

FOR: Cicli con Iterazione Certa


FOR <var indice> IN [ REVERSE ] <expr> .. <expr>
[ BY <expr> ] LOOP <istruzioni> END LOOP;

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.

La clausola BY permette di definire il passo se manca, il passo di default è 1.

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.

Operazioni sui Cursori


Dichiarazione:
<nome cursore> CURSOR FOR <select statement>

Associa il cursore all’interrogazione; da inserire nella sezione di dichiarazione.

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;

WHILE FOUND LOOP


BEGIN
… elaborazione di ilTitolo e ilRegista …
FETCH valElevata INTO ilTitolo, ilRegista;
END;
END LOOP;
CLOSE valElevata;
END;

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).

La stringa contenuta può anche essere costruita a tempo di esecuzione.


La clausola INTO si comporta come nel comando SELECT INTO, se la stringa è un comando di
SELECT.

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.

Esistono due tipi di eccezioni:


● di sistema, definite e sollevate direttamente dal sistema,
● utente, definite e sollevate dal programma applicativo (non le vediamo).

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.

Si sceglie quale handler usare sulla base del valore dell’eccezione.


Dopo un’eccezione in SQL la variabile globale e predefinita SQLSTATE assume un valore nella
lista dei codici di errore.

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.

E’ necessario generare un flusso di esecuzione attraverso 3 passi fondamentali:


1. Connessione alla base di dati:
● per specificare il server a cui connettersi servono
○ stringa di locazione
○ credenziali di accesso (nome utente e password)

2. Esecuzione dei comandi SQL: avviene in tre passi:


a. Preparazione del comando:
● generazione delle strutture dati necessarie per la comunicazione con il
DBMS,
● eventuale compilazione ed ottimizzazione del comando da parte del
DBMS.
b. Esecuzione del comando sul DBMS a seguito di una chiamata dell’applicazione
spesso disaccoppiata dalla fase di preparazione.

c. Manipolazione del risultato tradotto nelle strutture del linguaggio di


programmazione

3. Chiusura della connessione, da fare quando l'interazione con la base di dati è


terminata.
○ i DBMS hanno un massimo di connessioni contemporaneamente attive,
○ chiudere una connessione libera risorse,
○ in genere, la terminazione di un'applicazione comporta la chiusura automatica di
tutte le connessioni aperte.
Anche nel caso di accoppiamento esterno si può eseguire sia codice statico che dinamico.

Lezione 6 Maggio - Trigger

DBMS Passivi e Attivi


I DBMS tradizionali sono passivi: eseguono delle operazioni solo su richiesta.
Spesso si ha la necessità di avere capacità reattive, ovvero quando il DBMS reagisce ad eventi
eseguendo operazioni definite dal progettista.
DBMS con queste funzionalità sono conosciuti come DBMS attivi (ADBMS).

Negli ADBMS si possono definire regole attive o trigger.

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

I DBMS attivi iniziano ad affermarsi a partire dagli anni ‘90;


I maggiori DBMS commerciali (Oracle, IBM DB2, Microsoft SQL Server) sono stati estesi con la
possibilità di specificare trigger.
A partire da SQL:1999 anche lo standard recepisce tali estensioni.
Attualmente i DBMS non sono completamente allineati allo standard attuale;
I trigger in PostgreSQL sono infatti abbastanza diversi da quanto prescrive lo
standard.

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:

Un DBMS attivo si comporta in questo modo:


Usi di un DBMS attivo
Ecco un elenco di usi di un DBMS attivo:
● Monitoraggio (come nella slide precedente)
● Vincoli di integrità
● Alerting
● Auditing
● Sicurezza
● Statistiche
● Eccezioni

Approcci Architetturali dei DBMS


DBMS passivi, approccio 1:

Problemi
● minore efficienza (controlla anche se non è cambiato nulla)
● determinare la frequenza ottima di polling

DBMS passivi, approccio 2:

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

1. Al verificarsi dell’evento si valuta la condizione


2. Se la condizione è soddisfatta si esegue l’azione
Eventi
Possibilità di definire trigger che possono essere attivati before o after un evento:

BEFORE: il trigger viene eseguito prima di eseguire l’evento


● utile per trigger che verificano pre-condizioni
● se non sono vere -> abort, si previene l’esecuzione dell’evento

AFTER: il trigger viene eseguito dopo l’esecuzione dell’evento


● utile per mantenere consistenti i dati, monitoraggio, alerting etc.
Possibilità di combinare gli eventi (eventi composti) tramite, ad esempio:
● Operatori logici: and, or, ecc.
● Sequenza: seleziono un trigger se due o più eventi accadono in un certo ordine.

Evento Condizione Azione - Esempio:


Relazione Film.
Trigger T: descrizione informale
assegna un valore di default all’attributo valutaz, se questo non è specificato al momento
dell’inserimento della tupla il valore di valutaz è posto uguale alla media delle valutazioni,
calcolata su tutte le tuple presenti nella relazione Film, aumentata del 10%.

Trigger T: descrizione ECA:


● Evento: INSERT INTO Film,
● Condizione: valutaz IS NULL,
● Azione: assegnamento del valor medio di valutaz moltiplicato per 1.1 all’attributo valutaz
delle tuple inserit.

Evento Condizione Azione


Perché è vantaggioso avere l’evento?
● valutare una condizione è costoso,
● rilevare l’accadere di un evento è immediato.

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.

Se si specifica UPDATE OF a1,…,an, il trigger viene attivato solo da un evento che


modifica tutti e soli gli attributi a1,…,an.

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.

SQL: 200N - Condizione ed Azione:


Condizione: Espressione booleana SQL arbitraria.
Azione:
● Un singolo comando SQL
● Una sequenza di comandi SQL
● Non possono contenere parametri di connessione
● Nel caso di trigger di tipo BEFORE, SQL sconsiglia l’esecuzione di comandi di
aggiornamento dei dati nel contesto dell’azione, ma non lo vieta
● Un trigger di tipo BEFORE potrebbe aggiornare alcuni dati prima dell’esecuzione
dell’evento che ha attivato il trigger, generando comportamenti anomali; soprattutto se vi
sono più trigger BEFORE per lo stesso evento.

SQL:200N - Modalità di Esecuzione:


Due modalità:
● Orientata all’istanza (instance oriented): il trigger viene eseguito una volta per ogni tupla
coinvolta nell’evento che attiva il trigger e soddisfa la condizione.
FOR EACH ROW
● Orientata all’insieme (set oriented): il trigger viene eseguito una sola volta per tutte le
tuple coinvolte nell’evento.
FOR EACH STATEMENT

Possono esserci differenze nel risultato.

Esecuzione orientata all’istanza:


Valutazione condizione ed esecuzione azione, vengono eseguiti una volta per ogni tupla
coinvolta nell’evento che ha attivato il trigger.
La tupla coinvolta nell’evento viene chiamata variabile o tupla di transizione.

Esecuzione orientata all’insieme:


Valutazione condizione ed esecuzione azione vengono eseguiti una sola volta, per l’insieme
di tuple coinvolte nell’evento che ha attivato il trigger.
L’insieme delle tuple aggiornate dall’evento viene chiamato tabella di transizione.

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

Si possono usare nella condizione e/o nell’azione.


È possibile assegnare alle tabelle/tuple di transizione un alias.

Creazione e Cancellazione Trigger


Creazione:

CREATE TRIGGER <nome trigger>


{BEFORE | AFTER} <evento> ON <tabella soggetto>
[REFERENCING { OLD [ROW] AS <variabile> |
NEW [ROW] AS <variabile> |
OLD TABLE AS <variabile> |
NEW TABLE AS <variabile> }]
[FOR EACH {ROW | STATEMENT}]
[WHEN <condizione>]
{<comando SQL> |
BEGIN ATOMIC <sequenza di comandi SQL> END} ;

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

Operazione scatenante: Comando SQL che inserisce 5 tuple in Film.

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

Visibilità di Tuple/Tabelle di Transizione


Quali tuple sono visibili durante la valutazione della condizione e l’esecuzione dell’azione?

Dipende:
● Dal tipo di trigger (before/after)
● Dal tipo di esecuzione (row/statement)
● Dall’evento che ha attivato il trigger

FOR EACH ROW FOR EACH STATEMENT

BEFORE Tuple Sì Tabelle No Tuple No Tabelle No

AFTER Tuple Sì Tabelle Sì Tuple No Tabelle Sì

Insert
Non si possono specificare clausole REFERENCING OLD; le tuple inserite dall’evento non
esistevano prima della sua esecuzione.

Se il trigger è di tipo BEFORE le tuple inserite


● non sono visibili nella tabella soggetto
● ma possono essere accedute una alla volta usando la tupla di transizione NEW

Se il trigger è di tipo AFTER le tuple inserite


● sono visibili nella tabella soggetto
● e possono essere accedute mediante la tupla o la tabella di transizione NEW

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.

Possono esistere vincoli specificati per la tabella soggetto.


L’esecuzione degli eventi può violare i vincoli; nel determinare l’ordine di esecuzione è quindi
necessario considerare anche il controllo dei vincoli.
I trigger vengono selezionati secondo il seguente ordine:

1) Trigger BEFORE, FOR EACH STATEMENT

Per ogni tupla oggetto del comando che rappresenta l’evento:

a) Trigger BEFORE, FOR EACH ROW


Esecuzione evento per la singola tupla
Verifica dei vincoli di integrità sulla tupla, con valutazione immediata.

b) Trigger AFTER, FOR EACH ROW


Verifica dei vincoli con valutazione immediata sulla tabella.

2) Trigger AFTER, FOR EACH STATEMENT

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.

Implementazione dei Trigger


I trigger sono più flessibili dei vincoli di
integrità; essi permettono di stabilire come reagire ad una violazione di un vincolo arbitrario.
I trigger possono specificare anche vincoli di transizione.
La flessibilità non sempre è un vantaggio.
A volte definire dei vincoli è più vantaggioso:
● Migliore ottimizzazione
● Meno errori di programmazione
● I vincoli sono parte dello standard da lungo tempo, i trigger no

Esprimere Vincoli di Integrità


Esempio:
Ogni cliente non può noleggiare più di tre video contemporaneamente.

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;

Possiamo anche cancellare solo il noleggio appena inserito:

CREATE TRIGGER VerificaNoleggi


AFTER INSERT ON Noleggio
REFERENCING NEW TABLE AS NT
FOR EACH STATEMENT
WHEN EXISTS
(SELECT *
FROM Noleggio
WHERE Noleggio.dataRest IS NULL AND
Noleggio.codCli IN (SELECT codCli FROM NT)
GROUP BY codCli
HAVING COUNT(*) > 3)
DELETE FROM Noleggio
WHERE (colloc,dataNol) IN (SELECT colloc, dataNol FROM NT)
AND codCli IN (SELECT codCli
FROM Noleggio
WHERE Noleggio.dataRest IS NULL AND
Noleggio.codCli IN (SELECT codCli FROM NT)
GROUP BY codCli
HAVING COUNT(*) > 3);
VINCOLI DI INTEGRITÀ

Se un noleggio causa la violazione del vincolo deve essere impedito tutto il noleggio, non solo i
video eccedenti.

Calcolo di Dati Derivati


Aggiornamento automatico attributo ptiMancanti nella tabella Standard; ad ogni nuovo noleggio
si attribuiscono:
● Per ogni VHS 1 punto
● Per ogni DVD 2 punti

CREATE TRIGGER CalcolaPtiMancanti


AFTER INSERT ON Noleggio
REFERENCING NEW ROW AS NR
FOR EACH ROW
BEGIN ATOMIC
UPDATE Standard
SET ptiMancanti = ptiMancanti - 1
WHERE codCli = NR.codCli AND
NR.colloc IN (SELECT colloc
FROM Video
WHERE tipo = 'v');
UPDATE Standard
SET ptiMancanti = ptiMancanti - 2
WHERE codCli = NR.codCli AND
NR.colloc IN (SELECT colloc FROM Video
WHERE tipo = 'd');
END;

Definizione di Regole Operative


Quando il valore dell’attributo ptiMancanti per un cliente standard diventa 0, il cliente è rimosso
dalla tabella Standard ed inserito nella tabella VIP con bonus pari a 5 euro.

CREATE TRIGGER OrganizzaClienti


AFTER UPDATE OF ptiMancanti ON Standard
REFERENCING NEW ROW AS NR
FOR EACH ROW
WHEN NR.ptiMancanti <= 0
BEGIN ATOMIC
INSERT INTO VIP
VALUES (NR.codCli,5.00);
DELETE FROM Standard
WHERE codCli = NR.codCli;
END;

Trigger in PostGreSQL

CREATE TRIGGER Nome


{ BEFORE | AFTER| INSTEAD OF} {Evento [OR Evento ]*}
ON Relazione
* [ REFERENCING { OLD | NEW } TABLE [ AS ] <variabile>]
[FOR EACH {ROW | STATEMENT } ]
** [WHEN (Condizione)]
EXECUTE FUNCTION NomeFunzione (Argomenti)

La modalità di default è FOR EACH STATEMENT.

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

* dalla versione 10, non usabile per UPDATE su specifici attributi


** dalla versione 9

È inoltre possibile specificare trigger appositi per i vincoli:

CREATE CONSTRAINT TRIGGER


{DEFERRABLE {INITIALLY IMMEDIATE |INITIALLY DEFERRED} | NOT DEFERRABLE}

Il momento di attivazione del trigger può essere:


● immediato (alla fine del comando che ha generato l’evento),
● differito, alla fine della transazione che contiene il comando.

La modalità di attivazione si modifica con SET CONSTRAINTS, come già visto per i vincoli:
● Solo FOR EACH ROW
● No tabelle di transizione

Altri comandi possibili sono:


● DROP TRIGGER
● ALTER TRIGGER (permette solo di modificare il nome)

E le clausole:
● DISABLE TRIGGER [NomeTrigger | ALL | USER]
● ENABLE TRIGGER [NomeTrigger | ALL | USER]
del comando di ALTER TABLE

Azione in un Trigger PostGreSQL


L’azione deve essere l’invocazione di una funzione definita dall’utente
● deve essere definita senza parametri
● deve restituire tipo trigger

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.

Trigger Function PL/PGSQL


PL/pgSQL può essere utilizzato per definire trigger function.
Una trigger function è una funzione senza parametri e con tipo di ritorno trigger creata con il
comando CREATE FUNCTION.
La funzione deve essere dichiarata senza parametri anche se ci si aspetta che riceva gli
argomenti specificati nel CREATE TRIGGER.

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

Non esistono variabili di transizione a livello di tabella.

● TG_NAME: contiene il nome del trigger correntemente attivato


● TG_WHEN: contiene la stringa AFTER o BEFORE a seconda della definizione del trigger
● TG_LEVEL: contiene la stringa ROW o STATEMENT a seconda della definizione del
trigger
● TG_OP: contiene la stringa INSERT, UPDATE o DELETE a seconda dell’operazione che
ha attivato il trigger
● TG_TABLE_NAME: contiene il nome della relazione su cui è definito il trigger
● TG_NARGS: contiene il numero degli argomenti passati alla trigger function nel comando
CREATE TRIGGER
● TG_ARGV[ ]: array di stringhe, che contiene gli argomenti passati alla trigger function nel
comando CREATE TRIGGER; l’indice parte da 0 accessi all’array con indici invalidi
risultano in valore NULL

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:

● trigger BEFORE STATEMENT


per ogni tupla oggetto del comando
○ trigger BEFORE ROW
○ comando e verifica dei vincoli di integrità
○ trigger AFTER ROW

verifica dei vincoli che richiedono di aver completato ilcomando

● trigger AFTER STATEMENT


● se esistono più trigger dello stesso tipo: ordine alfabetico

Possibilità di esecuzione differita per CONSTRAINT TRIGGER

PostgreSQL - Tabella Riassuntiva

Postgresql vs. SQL200N


Lezione 11 Maggio - Qualità di una Base di Dati
Ci concentriamo su «come farlo bene», introducendo il concetto di qualità.
Qualità dei dati: assenza di comportamenti indesiderati durante l’esecuzione degli
aggiornamenti.
Qualità del servizio: come migliorare le prestazioni del sistema nell’eseguire le interrogazioni e
nell’eseguire le transazioni.
La qualità di una base di dati si può definire in termini di proprietà che gli schemi (= le singole
relazioni) possono soddisfare o meno.
Introduciamo quindi strategie per verificare se le proprietà sono soddisfatte e, se non lo sono,
per trasformare gli schemi.
Quest’ultime hanno due applicazioni:
1. Come ultima fase di progettazione (ma se seguiamo la metodologia, potremmo non
averne bisogno),
2. Come tecnica di verifica dei risultati della progettazione di una base di dati (magari
ottenuti non seguendo la metodologia che abbiamo introdotto in precedenza).

Normalizzazione
Introduciamo quindi la normalizzazione, legata fortemente al concetto di qualità:

Dobbiamo quindi introdurre 3 concetti nuovi e fondamentali:


1) Forma Normale,
2) Dipendenza Funzionale,
3) Scomposizione.

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:

1) Ridondanza: Le informazioni di ciascuno studente (esempio nome, cognome, anno di


corso) sono ripetute in tutte le tuple relative allo studente.
2) Anomalia di Aggiornamento: Se l’anno di iscrizione di uno studente varia, è necessario
andarne a modificare il valore in diverse tuple (se non lo faccio, dati inconsistenti).
3) Anomalia di Inserimento: Un nuovo studente che non abbia ancora dato esami non può
essere inserito.
4) Anomalia di Cancellazione: Se cancelliamo tutti gli esami registrati per un corso (ad es.
perché gli studenti si laureano), perdiamo anche le informazioni sul corso.

Questo perché abbiamo usato un'unica relazione per rappresentare informazioni


eterogenee:
- gli studenti con i relativi anni di corso,
- i corsi con i relativi crediti,
- gli esami per i corsi sostenuti dagli studenti con le relative votazioni.

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.

R(X) schema di relazione, X = {A1,A2,....An}:


● Y e Z sottoinsiemi non vuoti di X,
● r istanza di R(A1,A2,....An).

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.

Y determina funzionalmente Z in R(X), indicato con Y → R(X) Z (o con Y → Z , in assenza di


ambiguità) se qualunque istanza r di R(X) soddisfa Y → Z.

Y → Z è un vincolo di integrità (vale per qualunque istanza di R) ed è chiamato dipendenza


funzionale per R.

Inoltre Y → A è non banale se:


- A non appartiene a Y,
- nessun attributo in Z appartiene a Y.
Se le parti sinistre delle dipendenze funzionali non corrispondono a chiavi, esse causano
anomalie.

Forma Normale di Boyce e Codd


Andiamo ad analizzare come definire le buone proprietà di uno schema partendo dalle
dipendenze funzionali.

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

MatricolaStudente(1) = MatricolaStudente NomeStudente CognomeStudente AnnoIscr

MatricolaStudente(2) = MatricolaStudente(1) È il nostro risultato.

Verifica di una BCNF


Vi sono quindi 3 fasi:
● Per ogni relazione R nello schema, individuo dipendenze funzionali a partire dal
documento di analisi requisiti (sono vincoli).
● Determino la chiave.
● Verifico la condizione forma normale di Boyce Codd.

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.

Bisogna inoltre considerare un caso particolare: se nessuna relazione ottenuta contiene la


chiave, va aggiunta una relazione corrispondente alla chiave.
Questo serve a permetterci di ricostruire senza perdita il contenuto della relazione originale
combinando (join) il contenuto delle relazioni in cui l’abbiamo scomposta.
Una decomposizione dovrebbe sempre soddisfare:
1. la decomposizione senza perdita (lossless join), che garantisce la ricostruzione delle
informazioni originarie.
2. la conservazione delle dipendenze, che garantisce il mantenimento dei vincoli di
integrità originari

Decomposizione Senza Perdita


Una relazione R si decompone senza perdita su X1 e X2 se il join delle proiezioni di R su X1 e
X2 è uguale a R stessa (cioè non contiene tuple spurie).
Una decomposizione con schemi disgiunti è certamente con perdita.
Avere degli attributi in comune però non basta a garantire che la decomposizione sia senza
perdita: essa è garantita se gli attributi comuni alle relazioni contengono una chiave per
almeno una delle relazioni decomposte.

Conservazione delle Dipendenze


Una decomposizione conserva le dipendenze se ciascuna delle dipendenze funzionali dello
schema originario coinvolge attributi che compaiono tutti insieme in uno degli schemi
decomposti.

Problematiche della Decomposizione


Possono esistere relazioni non normalizzate per cui non è possibile trovare una decomposizione

che rispetti i due punti fondamentali; ad esempio:


City Street → Zip coinvolge tutti gli attributi e quindi nessuna decomposizione può preservare
tale dipendenza.
Quindi in alcuni casi la BCNF non è raggiungibile; per alcuni schemi che hanno almeno due
chiavi non esiste una decomposizione in BCNF che sia senza perdita (lossless join) preservi le
dipendenze.

Terza Forma Normale


Una relazione R è in terza forma normale se, per ogni FD (non banale) X → Y definita su R, è
verificata almeno una delle seguenti condizioni:
- X contiene una chiave K di R (soddisfa BCNF),
- ogni attributo in Y è contenuto in almeno una chiave di R: Y contiene solo attributi
primi.

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.

L’approccio alla scomposizione visto prima:


1. si crea una relazione per ogni gruppo di attributi coinvolti in una dipendenza funzionale,
2. si verifica che alla fine una relazione contenga una chiave della relazione originaria.

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.

Progetto Extra - Modello Nested Relational


Dobbiamo considerare una contrapposizione frequente:
- avere uno schema in forma normale, ovvero senza ridondanze, ma dove spesso alcune
interrogazioni sono costose poiché richiedono join;
- avere uno schema denormalizzato, ridondante, ma più efficiente su alcune
interrogazioni.

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).

Relazioni Nested Relational


Una relazione nested relational è una relazione che non è in prima forma normale (1NF) cioè
può contenere gruppi di attributi (potenzialmente annidati) multi-valore.

Un altro esempio:
Vediamo anche il caso in cui volessi gestire anche più indirizzi; la ristrutturazione standard è la
seguente:

La cui possibile denormalizzazione è 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.

Abbandonando la prima forma normale:


Possiamo anche specificare un nome per gruppi di attributi multivalore:
CLIENTE(codCli,nome,cognome,dataN,
indirizzi: (città,via,no,cap),
(telefono) )

In SQL i gruppi sono dichiarati con la parola chiave SET:


CREATE TABLE cliente
(codCli DECIMAL(4),
nome VARCHAR(20),
dataN DATE,
telefono SET (CHAR(15)),
indirizzi SET (ROW (citta VARCHAR(20), via VARCHAR(20),
no VARCHAR(10), cap INTEGER)))

Workload come Fondamento della Scelta


La scelta dipende dal workload, ovvero dalle query più utilizzate sulla BD.
Se la nostra esigenza fosse determinare per ogni film il suo genere, l’anno e i giudizi ricevuti in
modo efficiente, con lo schema:

l’interrogazione non richiederebbe join e potrebbe essere eseguita efficientemente.

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:

l’interrogazione non richiede join e può essere eseguita efficientemente.

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

Cosa vuol dire progettare lo schema logico in base a un workload?


Per ogni interrogazione abbiamo uno schema logico che ne rende più efficiente l’esecuzione.
Dato un workload, guardiamo l’insieme di tutte le interrogazioni e individuiamo uno schema
che permetta di eseguirle in modo ragionevolmente efficiente (rappresenta il miglior
compromesso), eventualmente introducendo ridondanza.

Lezione 18 Maggio - Qualità del Servizio


Come migliorare le prestazioni del sistema nell’eseguire le interrogazioni e transazioni.
Le prestazioni del sistema nell’eseguire le interrogazioni possono essere migliorare agendo:
● a livello logico/esterno,
● a livello fisico,
● al livello di specifica di interrogazioni SQL.

Le prestazioni vengono analizzate in riferimento a un certo carico di lavoro – workload,


attraverso un’attività detta tuning, in cui si analizzano le prestazioni del sistema e si
cercano di adottare correttivi.
Il Tuning è svolto in fase di progettazione, quando il sistema non è ancora in produzione.
Il workload indica quali sono le interrogazioni o gli aggiornamenti più importanti e con che
frequenza vengono eseguiti e quali sono le prestazioni desiderate/necessarie per tali
interrogazioni e aggiornamenti e per il sistema in generale; le informazioni sul workload possono
essere determinate:
● dal documento di analisi dei requisiti (in progettazione),
● dal monitoraggio del sistema (dopo la produzione).
Entrano in gioco in questa fase Elaboratore delle interrogazioni (Query Processor) e Gestore
delle strutture di memorizzazione.

Gestore delle Strutture di Memorizzazione


Le informazioni di una BD sono memorizzate su disco.
Il disco comunica con altre memorie, tipicamente sempre più piccole, ma sempre più veloci; un
esempio di gerarchia potrebbe essere:
disco -- memoria -- cache

Le prestazioni di una memoria si misurano in tempo di accesso:

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.

Analizziamo meglio il primo:

Gestore dei File

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 leggere un settore:


- il braccio si posiziona sopra la traccia,
- i dati vengono letti o scritti quando il settore passa sotto la testina.

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.

Le prestazioni delle memorie possono essere classificate in:


- Interne: dipendono da caratteristiche meccaniche e tecniche di memorizzazione e
codifica dei dati;
- Esterne: dipendono da tipo di interfaccia e file system.

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.

file → pagine → record → campi


In PostgreSQL:
L’insieme di tutti i database gestiti da un server PostgreSQL si chiama database cluster.
Ogni database contiene tabelle e altri oggetti; ogni tabella è memorizzata in un file separato.
PostgreSQL organizza lo spazio fisico in tablespace, ciascuno corrispondente a una posizione
su disco in cui vengono memorizzati i file corrispondenti a oggetti dei database.
Il file corrispondente ad una tabella è memorizzato in un singolo tablespace, ma un tablespace
può contenere più relazioni.
L’uso dei tablespace permette al sistema di decidere dove memorizzare i vari oggetti, ad
esempio memorizzando sullo stesso cilindro dati che vengono acceduti insieme.
L’uso dei tablespace permette quindi di raggiungere le migliori prestazioni nel caso di DB di
grandi dimensioni.
cluster → database → tabelle = file (organizzati attraverso tablespace)

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).

Consideriamo adesso la relazione tra:


● Livello Logico: organizzato in relazioni e tuple.
● Livello Fisico: organizzato in file e record.

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.

file → pagine → record → campi


campi dei record →definiscono→ tipi di record → schema fisico

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.

File con Record a Lunghezza Fissa


Ogni record ha gli stessi campi e la lunghezza dei campi è fissa.
Si possono identificare:
● la posizione di partenza del record,
● la posizione di partenza di ogni campo rispetto alla posizione di partenza del record.
L’accesso ai campi del record è quindi facilitato.

File con Record a Lunghezza Variabile


Un file può contenere record a lunghezza variabile per varie ragioni:
1. campi di dimensione variabile (ad esempio valori di tipo VARCHAR),
2. campi opzionali,
3. se il file è file eterogeneo: il file contiene record di tipi differenti, e quindi di dimensioni
differenti; esempio: le informazioni di un cliente memorizzate nello stesso file vicino ai
noleggi da lui effettuati.
Nel caso di record a lunghezza variabile si hanno diverse alternative, che devono considerare
anche i problemi legati agli aggiornamenti che modificano la lunghezza dei campi (e quindi dei
record).
Una soluzione consolidata consiste nel memorizzare prima tutti i campi a lunghezza fissa, e
quindi tutti quelli a lunghezza variabile; per ogni campo a lunghezza variabile si ha un “prefix
pointer”, che riporta l’indirizzo del primo byte del campo.

Organizzazione dei Blocchi su Disco


Esistono vari modi per organizzare i blocchi su disco; è un tipico problema affrontato anche dai
sistemi operativi.

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.

Organizzazione dei Record nei File


I file che contengono i record dei dati costituiscono l’organizzazione primaria dei
dati.
Il termine organizzazione primaria di un DBMS indica quindi l'insieme di tutti i file contenenti i
record corrispondenti alle tuple contenute nelle tabelle rappresentate a livello logico.
Non riguarda il come vengono organizzati (ma solo il cosa viene organizzato).
Il modo con cui i record vengono organizzati nei file incide sull’efficienza delle
operazioni e sull’occupazione di memoria.
Poiché i dati sono trasferiti in blocchi tra la memoria secondaria e la memoria principale, è
importante assegnare i record ai blocchi in modo tale che uno stesso blocco contenga record tra
loro interrelati.
Poiché la lettura di blocchi vicini riduce il tempo di seek, è importante memorizzare blocchi
contenenti record interrelati il ’’più vicino possibile’’ su disco.
Le informazioni sull’organizzazione primaria dei dati sono contenute in cataloghi di sistema, che
dipendono dallo specifico DBMS considerato e in generale contengono per ogni relazione:
● Numero tuple,
● Numero blocchi nel file,
● Informazioni su strutture ausiliarie per l’accesso ai dati.

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.

Gestore del Buffer


Un aspetto significativo per minimizzare gli accessi a disco è mantenere più blocchi possibile nel
buffer della memoria principale.
Il gestore del buffer si occupa di dirigere questi aspetti, ad esempio decidendo quale blocco
sostituire in caso di buffer pieno; la gestione del buffer è fondamentale dal punto di vista
prestazionale.
Il buffer è organizzato in pagine, che hanno la stessa dimensione delle pagine/blocchi su
disco.
Terminologia alternativa: Buffer → buffer pool; Pagina nel buffer → buffer.
A fronte di una richiesta di una pagina, ad esempio durante una operazione di lettura (SELECT)
il Buffer Manager (BM) opera come segue:
- Se la pagina è già nel buffer, viene fornito al programma chiamante l’indirizzo della
pagina corrispondente.
- Se la pagina non è in memoria:
Il BM seleziona una pagina nel buffer per la pagina richiesta:
● Se la pagina è libera viene portata dal disco alla memoria centrale.
● Se la pagina prescelta dal buffer è occupata, questa viene riscritta su disco solo
se è stata modificata e non ancora salvata su disco e se nessuno la sta usando.
A questo punto il BM può leggere la pagina da disco e copiarla nella pagina del
buffer selezionata, rimpiazzando così quella prima presente

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).

Vi sono vari diversi tipi di condizioni:


- uguaglianza su chiave di ricerca primaria, ad esempio il video con collocazione 1001,
- uguaglianza su chiave di ricerca secondaria, ad esempio i film di genere drammatico,
- range, ad esempio i film con valutazione tra 2 e 5,
- combinazioni delle precedenti, ad esempio i film di genere drammatico con valutazione
tra 2 e 5.
Un indice è un multi-insieme di coppie del tipo (k i , r i ) dove:
● k i è un valore per la chiave di ricerca su cui l'indice è costruito,
● r i può assumere diverse forme, per il momento assumiamo che sia un puntatore a un
record con valore di chiave k i.

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 Primari e Secondari


Se la chiave di ricerca di un indice I è anche chiave per la relazione, una sola coppia può
condividere lo stesso valore k per la chiave di ricerca; in questo caso l’indice si chiama
primario.
Se la chiave di ricerca di un indice I non è chiave per la relazione, anche più di una coppia può
condividere lo stesso valore k per la chiave di ricerca; in questo caso l’indice si chiama
secondario.
Per evitare di duplicare inutilmente i valori di chiave, la soluzione più comunemente adottata per
gli indici secondari consiste nel raggruppare tutte le coppie con lo stesso valore di chiave in una
lista di puntatori.
Le diverse tecniche di indice differiscono nel modo in cui organizzano l'insieme di coppie (k i , r i )
● Indici ordinati (anche chiamati indici ad albero): le coppie(k i , r i ) vengono mantenute
esplicitamente.
Le coppie vengono salvate in un file, ordinate rispetto ai valori di chiave k i.

● 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.

Indici Ordinati/Ad Albero

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.

Vi sono due possibili casi di ricerca:


● Ricerca per uguaglianza
● Ricerca per intervallo (range)

Ricerca per Uguaglianza


Ha due possibili risvolti:
- Non trovato: il valore non esiste nell’indice, quindi non esistono tuple che soddisfano la
condizione; non si accede il file dei dati.
- Trovato: si naviga il puntatore al file dei dati e si recuperano le tuple (anche più di una,
indice secondario, numero arbitrario di puntatori, inseriti in un blocco puntato dal
puntatore nell’indice).

Ricerca Per Intervallo


Si esegue la ricerca dell’estremo sinistro dell’intervallo.
Una volta raggiunta una foglia, ci si sposta nella lista dei nodi foglia fino a superare l’estremo
destro dell’intervallo.
La ricerca per intervallo è quindi molto efficiente.

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.

Esempio di Indice Clusterizzato Secondario:

Su Singolo Attributo e Multi-Attributo


Indice su singolo attributo: indice la cui chiave di ricerca è costituita da un solo attributo.
Indice multiattributo: indice la cui chiave di ricerca è costituita da più di un attributo.
Esempio: per Noleggio:
● indice su (colloc,dataNol) è indice multiattributo.
● indice su codCli è indice a singolo attributo.

Un indice multiattributo è definito su una lista di attributi:


- diversi ordinamenti degli attributi chiave corrispondono a diversi indici
→ indice su (dataNol,colloc) è 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.
- le tuple che soddisfano condizioni di uguaglianza sui primi i attributi A1,…, Ai e condizioni
di intervallo sugli attributi Ai+1,…, Aj, j < n.

Un indice multiattributo può supportare più interrogazioni di un indice a singolo attributo.


Deve però essere aggiornato più di frequente ed è di dimensione maggiore rispetto ad
indice su singolo attributo.

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.

Gli indici hash al contrario mantengono le associazioni (k i , r i ) in modo implicito, tramite


l’uso di una funzione hash, definita sul dominio della chiave di ricerca.

Ad ogni valore della funzione hash corrisponde un indirizzo in area primaria:

L’associazione tra i valori della funzione Hash e gli indirizzi su disco avviene tramite un bucket
index:

Ricerca per Uguaglianza


Supponiamo di cercare le tuple della relazione R(K,B,C) con B = w, con indice hash su attributo
B:
1. Si calcola H(w),
2. Se H(w) = 1, tramite il bucket index, si determina l’indirizzo corrispondente al
bucket (supponiamo 1)
3. Si accede il bucket e, record per record, si cercano le tuple t con t.B = w
4. Si deve analizzare sia la parte del bucket in area primaria sia (in caso) quella in area di
overflow.

Ricerca per Intervallo


Un indice hash non supporta ricerche per intervallo.
La funzione hash infatti non mantiene l’ordine; tuple con valori contigui per la chiave di ricerca
possono essere memorizzati in bucket diversi.
Inserimento e Trabocchi
Il numero di record che possono essere allocati nello stesso bucket determina la capacità c dei
bucket; i bucket hanno in genere la stessa capacità.
Se il bucket individuato per l’inserimento ha ancora spazio (la sua capacità è inferiore a c), si
inserisce il record nel bucket; se il bucket non ha più spazio (la sua capacità è pari a c), si genera
un trabocco (overflow): la presenza di overflow può richiedere l’uso di un’area di memoria
separata, detta area di overflow.

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).

Creazione e Funzione di Hash


La creazione di un indice hash richiede di specificare:
● la funzione H per la trasformazione della chiave,
● il metodo per la gestione dei trabocchi,
● il fattore di caricamento d: valore tra 0 e 1 che indica quanto si intendono mantenere
“pieni” i bucket.

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ò).

Una funzione hash è un’applicazione suriettiva H dall’insieme delle possibili chiavi


all’insieme 0, . . . , M - 1 dei possibili indirizzi, che verifichi le seguenti proprietà:
● distribuzione uniforme delle chiavi nello spazio degli indirizzi (ogni indirizzo deve
essere generato con la stessa probabilità),
● distribuzione casuale delle chiavi (eventuali correlazioni tra i valori delle chiavi non
devono tradursi in correlazioni tra gli indirizzi generati).

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.

Metodo della divisione:


La chiave numerica viene divisa per M e l’indirizzo è ottenuto considerando il resto:
H (k)=k mod M

→ dove mod indica il resto della divisione intera


Affinché H distribuisca bene, M deve essere primo oppure non primo con nessun fattore primo
minore di 20.
Test sperimentali eseguiti con file con caratteristiche molto diversificate mostrano che, in
generale, il metodo della divisione è il più adattabile ed è quello più utilizzato dai DBMS (incluso
PostgreSQL).
Una funzione hash è detta perfetta se per un certo numero di record non produce trabocchi.
Una funzione perfetta può sempre essere definita disponendo di un’area primaria con capacità
complessiva pari al numero dei record da memorizzare.

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.

Clusterizzati e non Clusterizzati


Un indice hash è clusterizzato se i record che condividono lo stesso valore per la chiave di
ricerca sono memorizzati in posizioni adiacenti nel file dei dati.
In caso contrario, l’indice è non clusterizzato.

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;

In presenza di un indice hash clusterizzato (e quindi in presenza di un file organizzato a hash)


l’organizzazione primaria corrisponde ai record memorizzati nell’area primaria + i record
memorizzati nell’area di overflow.
Esiste al più un indice clusterizzato per tabella (indipendentemente dal tipo).

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.

Confronto tra Indici ad Albero e Hash


Per selezioni con condizioni di uguaglianza del tipo:
SELECT A1,A2,......An
FROM R
WHERE Ai=C
un indice hash su Ai è preferibile.
Per selezioni con condizioni di tipo range del tipo:
SELECT A1,A2,......An
FROM R
WHERE C1 < Ai < C2

un indice ad albero su Ai è preferibile.

Gli indici hash infatti non mantengono l’ordine, infatti:


- la scansione di un indice ha un costo proporzionale al logaritmo del numero di valori in R
per Ai.
- in una struttura hash il tempo di ricerca è indipendente dalla dimensione della base di
dati.

Progetto Extra - Metodo di Progettazione Nested Relational

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.

Esempio: cliente aggrega indirizzi e telefono.

Metodologia per arrivare allo schema Nested Relational


Input: schema ER e Workload,

Step 1: Modellare le query secondo un certo formalismo.


Step 2: Annotare lo schema ER con informazioni sulle query (sul workload).
Step 3: Generare lo schema Nested Relational a partire dallo schema ER annotato.
Step 1
Per ogni query individuiamo:
1. rispetto a quale entità E vogliamo aggregare,
2. rispetto a quali entità LS la query vuole applicare delle selezioni (clausole FROM e
WHERE di una query) e come sono collegate ad E.
→ se aggreghiamo rispetto a un’entità con selezione, E deve essere inclusa in LS.
3. quali informazioni LP restituiamo e come sono collegate ad e.

Otteniamo quindi: Q → (E, LS, LP)

Enunciamo vari esempi:

_ indica il cammino.
! indica il cammino vuoto.

Caso 2:

L’entità di aggregazione è clienti.


Siccome selezioniamo tutti i clienti in LS ci sarà solo Cliente + cammino vuoto.
Abbiamo però diverse informazioni da restituire: la lista di proiezione ha 2 entità:
- Cliente, con gli attributi da restituire e cammino vuoto,
- Film, con gli attributi da restituire e cammino che porta a cliente (C iniziale
dell’associazione consiglia).

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:

Abbiamo 2 selezioni: Video e noleggio con i loro cammini.

Caso 7:

Da cliente a Video → due associazioni: Effettua + Relativo.


Step 2
Annotiamo lo schema con le informazioni relative a ciascuna query Q.

Esempio Q1:

Esempio Q2:

Esempio Q4:
Esempio Q7:

Output Secondo Passo:

Step 3
1) Creiamo una relazione Nested Relational R_E per ogni entità aggregante.

2) Nella relazione R_E:


a) Traduciamo gli attributi di E annotati con query Q associati ad E in attributi
semplici in R_E.
b) Traduciamo i cammini entranti in E annotati con query associati ad E (le scritte
dentro Q) come attributi semplici o nested in R_E, a seconda della cardinalità
delle associazioni coinvolte nel cammino (lato E).

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:

Lezione 23 Maggio - Elaborazione di Interrogazioni


Vediamo come il sistema esegue una interrogazione, sfruttando le strutture ausiliarie di accesso
e la struttura attribuita ai file del livello fisico per determinare l’algoritmo di esecuzione più
efficiente.

Consideriamo la seguente interrogazione in SQL:


SELECT B,D
FROM R,S
WHERE R.A = “c” ∧ S.E = 2 ∧ R.C=S.C

La stessa query si può rappresentare in algebra (linguaggio operazionale) con la seguente


espressione:

Query Processor
Passi nell’esecuzione di una interrogazione:

1) Parsing (eseguito dal parser)


● input: interrogazione SQL
● output: parse tree (o abstract syntax tree)
In questo passo viene controllata la correttezza sintattica della query SQL e ne viene
generata una rappresentazione interna (in termini di parse tree).

2) Traduzione (effettuata dal translator):


● input: parse tree
● output: espressione algebrica canonica

In questo passo viene generate una espressione algebrica corrispondente a:


● prodotto cartesiano delle relazioni in clausola FROM seguita da
● condizioni di selezione, da clausola WHERE seguita da
● condizioni di proiezione, da clausola SELECT

3) Ottimizzazione Logica:
● input: espressione algebrica canonica
● output: piano di esecuzione logico ottimizzato (logical query plan – LQP)

Piano di esecuzione logico è un’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.

4) Ottimizzazione Fisica:
● input: piano di esecuzione logica ottimizzato,
● output: piano di esecuzione fisico ottimale (physical query plan - PQP).

Il piano di esecuzione fisico è l’algoritmo di esecuzione dell’interrogazione, rappresentato


come albero, sul livello fisico.
Si seleziona il piano di esecuzione fisico più efficiente, che riduce quindi gli accessi a disco.
Si determina in modo preciso come la query sarà eseguita (per esempio si determina che indici
si useranno); la scelta avviene considerando tutti i possibili piani fisici che realizzano il piano
logico scelto, valutando il costo di ognuno di essi e scegliendo il piano fisico di minor costo.

Ottimizzazione Logica e Piano Logico


Piano di esecuzione logico: gli operatori manipolano tabelle e tuple, elementi del livello logico.
Tornando all’esempio precedente, un primo algoritmo di esecuzione di alto livello deriva
dall’espressione algebrica, quindi l’algoritmo logico consiste nel:
- eseguire il prodotto Cartesiano,
- selezionare le tuple,
- effettuare la proiezione.

L’espressione algebrica rappresenta un algoritmo (logico) di esecuzione che opera su tabelle e


si può rappresentare come un albero:

Un'altra possibile strategia corrispondente ad un piano logico alternativo (quindi a espressione


algebrica alternativa):

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.

I piani logici forniscono alcune indicazioni su come eseguire l’interrogazione ma non


corrispondono ad algoritmi utilizzabili dal sistema per l’esecuzione.
I dati sono memorizzati su disco quindi gli algoritmi di esecuzione delle interrogazioni devono
operare su record memorizzati nei file.
Si deve quindi passare dal piano di esecuzione logico al piano di esecuzione fisico, dove ogni
operatore viene implementato tramite un algoritmo che manipola record.
Per interrogazioni complesse esistono molteplici strategie logiche possibili, ovvero molti piani di
esecuzione logici.
Scelto un piano logico e dato uno schema fisico, esistono molteplici strategie fisiche che lo
realizzano, quindi esistono anche più piani fisici per uno logico, poiché per ogni operatore
algebrico esistono diversi possibili algoritmi.
Il costo di determinare la strategia ottima può essere quindi elevato ma comunque
vantaggioso, poiché il vantaggio in termini di tempo di esecuzione che se ne ricava è tuttavia
tale da rendere preferibile eseguire l'ottimizzazione.

Ottimizzazione Fisica e Piano Fisico


I passi da svolgere in questa fase sono i seguenti:

1) Stima delle dimensioni dei risultati:


Vi è la necessità di determinare la dimensione (fisica!) delle relazioni corrispondenti ai sottoalberi
perché diventano input per gli operatori padre, importanti per calcolare il costo totale del piano,
cioè il numero totale di accessi a disco.
Questa stima è effettuata utilizzando statistiche mantenute dal sistema nei cataloghi.

2) Costruzione ed Enumerazione dei Piani Fisici:


Ogni nodo dell’albero di esecuzione logico corrisponde a un nodo dell’albero di esecuzione fisico
in cui si specifica:
● si sceglie un algoritmo per l’esecuzione dell’operatore,
● si specificano i parametri necessari all’esecuzione dell’operatore con l’algoritmo scelto, a
livello fisico.

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.

Proiezione: permette di gestire cascate di proiezioni e vale se {A1,.....,An} ⊆ {B1,.....,Bm}.

Commutazione di selezione e proiezione: se una selezione con predicato P coinvolge solo gli
attributi A1,......,An, allora:

Commutazione di selezione e prodotto Cartesiano:


● se una selezione con predicato P coinvolge solo gli attributi di e1, allora

● come conseguenza, se P=P1 AND P2 dove P1 coinvolge solo gli attributi di e1 e P2


quelli di e2

● inoltre se P1 coinvolge solo attributi di e1, mentre P2 coinvolge attributi di e1 e di e2

Commutazione di proiezione e prodotto Cartesiano:


Sia A1,....,An una lista di attributi di cui gli attributi B1,.....,Bm siano attributi di e1, e i rimanenti
C1,.....,Ck siano attributi di e2, allora:
Selezioni, prodotto cartesiano e join; i può trasformare una selezione ed un prodotto
cartesiano in un join, in accordo alla definizione di join:

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 1: Eseguire le operazioni di selezione (σ) il più presto possibile.

Euristica 2: Eseguire le operazioni di proiezione (π) il più presto possibile.

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.

Si seleziona il piano di esecuzione fisico più efficiente, che riduce quindi


gli accessi a disco; per farlo:
- si determina in modo preciso come la query sarà eseguita (per esempio si determina che
indici si useranno);
- la scelta avviene considerando tutti i possibili piani fisici che realizzano il piano logico
scelto, valutando il costo di ognuno di essi e scegliendo il piano fisico di minor costo.

Servono in questa fase:


Algoritmi:
● per l’elaborazione dei singoli operatori logici,
● per l’elaborazione complessiva del piano fisico.

Scelta del piano fisico:


● stima della dimensione dei risultati e del costo di un piano di esecuzione,
● identificazione dello spazio di ricerca dei piani,
● trattamento delle sottointerrogazioni.

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.

Accesso alle Relazioni di Base


Le foglie dei piani di esecuzione fisici rappresentano sempre tabelle di base.
L’accesso alle relazioni di base nell’ambito dell’esecuzione di una interrogazione avviene tramite
cammini di accesso, che descrivono come accedere al file dei dati di una relazione per ritrovare
le tuple di interesse e tengono conto di eventuali operazioni di selezione definite sulle relazioni di
base, per le quali esista un indice utilizzabile per la loro esecuzione.
Un cammino di accesso per una relazione di base può essere:
1. una scansione sequenziale,
2. un indice + una condizione di selezione (detta predicato di ricerca) per la quale esiste un
indice utilizzabile per individuare le tuple che soddisfano la condizione.

3. Selezione con Condizione Composta: In presenza di condizioni composte il sistema


preferisce scegliere un PQP che contenga un cammino di accesso basato su una
condizione con indice che, se falsa, rende falsa tutta l’interrogazione (fattore
booleano).

SELECT * FROM R WHERE (R.A = 5 OR R.B = 6) AND R.C >10


R.C>10 fattore booleano (il sistema sceglierà questo cammino di accesso per il PQP)
INDEX SCAN ( I R (C),C > 10) Filter (A=5 OR B=6)

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

2. Accesso con indice: il costo dipende da:


● tipo di indice (ordinato, hash),
● numero di tuple che soddisfano la condizione di selezione.
costo totale =
costo di determinare i riferimenti ai dati che soddisfano la condizione:
- 1 per hash,
- h (log N) per indice ad albero.
+
costo per accedere ai corrispondenti blocchi dei dati (tipicamente maggiore) e influenzato anche
dalla clusterizzazione.
Indice clusterizzato: ogni blocco dati viene visitato al più una volta (sia per condizione di
uguaglianza che per intervallo); se viene utilizzato un cammino di accesso basato su indice non
clusterizzato, è possibile evitare di visitare lo stesso blocco più volte per lo stesso valore
della chiave di ricerca.
Indice non clusterizzato: Lo stesso blocco può essere acceduto più volte (sia per uguaglianza
che per intervallo).

Elaborazione dei Nodi Interni del Piano

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:

1) Il mantenimento dei duplicati:


Per implementare la proiezione senza eliminazione dei duplicati, per ogni tupla in input, è
necessario rimuovere gli attributi che non compaiono nella lista di proiezione; lo si fa attraverso
un approccio iterativo (scansione sequenziale della relazione in input), non servono gli indici; il
costo è pari al numero di blocchi della relazione in input.

2) L’eliminazione di duplicati (proiezione algebrica):


Per implementare la proiezione con eliminazione di duplicati è necessario:
● rimuovere gli attributi che non compaiono nella proiezione (si può eseguire come per
proiezione senza eliminazione dei duplicati);
● eliminare i duplicati, attraverso tre algoritmi possibili approcci, che sfruttano:
○ ordinamento (approccio di partizionamento),
○ indici (approccio index-based),
○ hashing (approccio di partizionamento).

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).

Si può ottimizzare integrando il terzo passo nel secondo.

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.

1) Merge sort esterno a 2 fasi:


Supponiamo di dover ordinare una relazione R in input che consiste di un file di B(R)
blocchi e di avere a disposizione solo NP+1 < B(R) pagine di buffer in memoria centrale.

Si ordinano separatamente porzioni di dati che stanno in memoria e poi se ne effettua il


merge.

Fase 1 (sort interno):


1. si leggono NP pagine alla volta dal file dei dati al buffer,
2. i record delle NP pagine vengono ordinati facendo uso di un algoritmo di sort
interno (es. Quicksort),
3. le NP pagine così ordinate, vengono quindi scritte su disco in un file temporaneo.

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.

Consideriamo quindi R ⋈ PJ S e definiamo:


- R : relazione esterna (outer relation, per convenzione a sinistra),
- S: relazione interna (inner relation),
- PJ =R . A θ S . B

Nested Loop Join - Iterazione Semplice


Si accede ad una tupla di R (outer relation) e si confronta con ogni tupla di S (inner relation).
Costo: B(R) + T(R) * B(S)
L’ordine con cui vengono generate le tuple del risultato coincide con l’ordine delle tuple nella
relazione esterna.

Conveniente se la relazione inner può essere mantenuta in MM, in questo caso il costo è di B(R)
+ B(S)

Quindi: conviene considerare come relazione outer la relazione più grande.

Block Nested Loop - Iterazione Orientata ai Blocchi


Variante del Nested Loop molto usata.
Rinunciando a preservare l’ordine della relazione esterna, risulta più efficiente in quanto esegue
il join di tutte le tuple in memoria prima di richiedere nuove pagine della relazione interna;
Costo: B(R) + B(R)*B(S)
L’ordine con cui vengono generate le tuple del risultato non coincide con l’ordine delle tuple nella
relazione esterna.
Index Nested Loop
Supponiamo che la relazione interna sia una relazione di base ed esista un indice
sull'attributo A della relazione S che supporta ricerche rispetto a una condizione di join:
PJ =R . A θ S . B

Consideriamo la strategia di iterazione semplice:


Data una tupla tr di R non è più necessario scandire l'intera relazione S, ma è sufficiente
eseguire una ricerca sull'indice di S con condizione A θ tr . A

Costo: B( R)+T (R)∗Costo di ricerca con accesso( I S ( A ), A θ tr . A )

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.

Idea di base algoritmo:


● R e S vengono partizionate sulla base dei valori di H,
● la ricerca delle tuple in join avviene solo tra partizioni relative allo stesso valore di H.
Il costo risulta essere dell’ordine di B(R) + B(S).

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.

Valutazione per Materializzazione - Costosa


Un semplice modo di eseguire un piano di accesso composto da diversi operatori consiste nel
procedere bottom-up, secondo il seguente schema:
● Si accede alle relazioni di base, eventualmente integrando l’accesso con le operazioni di
selezione e proiezione, e si memorizzano tali risultati in relazioni temporanee.
● Si procede quindi in modo analogo per gli operatori del livello sovrastante, fino ad
arrivare alla radice

Tale modo di procedere è detto “valutazione per materializzazione” ed è altamente inefficiente e


costoso, poiché:
➔ comporta la creazione, scrittura e lettura di molte relazioni temporanee,
➔ se la dimensione dei risultati intermedi eccede lo spazio disponibile in memoria centrale, i
risultati devono essere scritti/ letti su/da disco.

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.

Un piano può contenere alcuni operatori valutati per materializzazione (input


materializzato) e altri eseguiti in pipeline (input una tupla alla volta).

Sintesi Elaborazione degli Operatori Relazionali tramite Operatori Fisici


Stima del Costo di un Piano di Esecuzione
Per stimare il costo di un piano di esecuzione si usa un approccio bottom-up:
● Si stima il costo di accesso alle relazioni di base.
● Per ogni nodo nell’albero:
○ Si stima il costo di effettuare l’operazione corrispondente,
○ Si stima la dimensione del risultato (che sarà l’input di operatori successivi) e si
determina se è ordinato.
● Si sommano poi i costi parziali ottenuti.

Per la determinazione della stima dei costi delle varie operazioni e della dimensione del risultato,
si utilizzano dati contenuti nei cataloghi di sistema.

Per ogni relazione R:

Per ogni indice I:

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.

Stima della Dimensione del Risultato della Selezione


Il numero di tuple restituito da una selezione sP(R) dipende da quante tuple di R soddisfano il
predicato P.
Si stima dunque un Fattore di selettività F(P), che stima la probabilità che una tupla di R
soddisfi il predicato P.
Assunzione: uniformità di distribuzione dei valori di ogni attributo; si assume quindi che ogni
valore appaia con la stessa probabilità.
Tanto più il fattore di selettività è minore di uno, tanto più è selettivo il predicato a cui si riferisce
(minore è il numero di tuple che soddisfano P).

Si stima poi che sP(R) selezioni un numero di tuple pari a T(R) * F(P).

Identificazione dello Spazio di Ricerca dei Piani


Data un’interrogazione un ottimizzatore dovrebbe enumerare tutti i possibili piani per eseguirla e
valutarne il costo, per poi selezionare quello di costo minimo.
Questo è un problema complesso e costoso; se ad esempio consideriamo il join, per N join,
esistono almeno N! ordinamenti di essi.
Per ogni ordinamento poi vi sono diversi piani di esecuzione in relazione a: scelta relazione outer
e inner, algoritmo di join.

Pruning dello Spazio di Ricerca


La soluzione sta nell’utilizzo di euristiche (analogamente a ottimizzazione logica) per evitare di
considerare piani di accesso che sicuramente non possono risultare ottimali, in un processo
detto pruning dello spazio di ricerca.

Euristica per Join


Per restringere lo spazio della ricerca non vengono considerate permutazioni di join che
implicano dei prodotti cartesiani; si considerano inoltre solo piani left-deep, in cui il figlio
destro di ogni nodo etichettato con un join è una relazione di base.
Essi permettono di generare piani di esecuzione fully-pipelined, in cui tutti gli operatori
vengono eseguiti in pipeline; in questo modo le relazioni inner sono già materializzate.
Se il piano non è left-deep, un piano in cui la relazione inner sia il risultato di un join forza a
materializzare il risultato del join.
Tuning: Livello Fisico
Dato un carico di lavoro, l’attività di tuning fisico si preoccupa di progettare uno schema fisico,
in termini di insieme di indici creati, che permetta di rendere il più possibile efficiente
l’esecuzione delle operazioni contenute nel workload.

Vengono quindi effettuate scelte ottimizzate riguardo a:


● su quali tabelle e attributi creare indici,
● che tipi di indici creare:
○ albero/hash,
○ clusterizzato/non clusterizzato,
○ chiave di ricerca: singolo attributo/multiattributo.
Una scelta impropria degli indici può portare a
● spreco di tempo su interrogazioni e aggiornamenti (ad es. indici che vengono
mantenuti dal sistema ma mai usati),
● spreco di spazio disco.
E’ opportuno quindi cercare di scegliere indici che siano utilizzabili nel maggior numero possibile
di interrogazioni.

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.

Scelta degli Indici

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.

Indici Ordinati e Hash


Interrogazioni supportate da indici ordinati:
● Selezioni (e join) con condizioni di uguaglianza o intervallo,
● Ordinamento,
● Raggruppamento.
Interrogazioni supportate da indici hash
● Selezioni (e join) con condizioni di uguaglianza,
● Raggruppamento.

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 Ordinati Clusterizzati e Non Clusterizzati


Se su una tabella è necessario creare un solo indice, conviene crearlo clusterizzato (sempre più
conveniente di indice non clusterizzato).

.Scansione sequenziale: B(R) accessi.


Accesso con indice ad albero: h accessi a disco + accesso a k blocchi contenenti le tuple
risultato; in generale k è maggiore per indici non clusterizzati.
L’indice è preferibile alla scansione sequenziale se B(R) > h + k;
k è piccolo per interrogazioni ad alta selettività (quindi con una bassa frazione di record
acceduti).
Se su una tabella è necessario creare più di un indice, è necessario ricordare che un solo indice
per relazione può essere clusterizzato.
In questo caso è opportuno:
● clusterizzare l’indice con chiave di ricerca coinvolta in una condizione della
interrogazione a selettività più bassa (frazione record restituiti più alta).
● favorire l’indice che permette di ottimizzare più di una interrogazione.

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.

Indice (ordinato o hash) I S ( B) permette al sistema di considerare l’index nested loop.


Indici ordinati I R ( A) e I S ( B) clusterizzati sono utili per la strategia merge join.
In assenza di indici, spesso il Sistema sceglie l’hash join (spesso più efficiente di nested loop)
Non equijoin: R join S on R.A θ S.B.
Indice ordinato I S ( B) permette al sistema di considerare l’index nested loop.
Merge join e hash join non indicate (mai scelti dal Sistema).

Tuning delle Interrogazioni


L’obiettivo è riscrivere una query contenuta nel carico di lavoro in modo da permettere al
sistema di scegliere piani di esecuzione fisici più efficienti.
Il tuning a livello fisico e logico hanno side effect:
● Modifica dello schema fisico (es. aggiungiamo un indice)
● Modifica dello schema logico

Il tuning delle interrogazioni non comporta side-effect, ma solo benefici.


E’ quindi la prima attività da svolgere se le prestazioni di una interrogazione non sono
soddisfacenti.
Perché non bastano gli ottimizzatori?
- Lo spazio di ricerca dei possibili piani di esecuzione non viene analizzato in modo
esaustivo,
- Il costo di ciascun piano di esecuzione viene solo stimato.

Riscrivi le query “troppo lente”:


● Troppi accessi a disco,
● Scansione totale di una tabella,
● Indici non utilizzati.

Ovvero le query con:


1. Condizioni su espressioni,
2. Uso di viste,
3. Clausola DISTINCT,
4. Sottointerrogazioni.

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.

ad esempio: SELECT * FROM Film WHERE 100*valutaz/5 >= 30;

diventa: SELECT * FROM Film WHERE valutaz >= 1,5;

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.

Salvare (Materializzare) il Risultato della Vista su Disco


Le viste materializzate (non presenti in tutti i sistemi) sono viste il cui risultato viene calcolato,
memorizzato su disco e mantenuto aggiornato dal sistema.

CREATE MATERIALIZED VIEW NomeVista AS <QUERY>

Esempio:
CREATE MATERIALIZED VIEW Noleggi_Commedie AS
SELECT dataNol, codCli FROM Noleggi NATURAL JOIN Video
NATURAL JOIN Film WHERE genere = ‘commedia’

Porta un vantaggio durante la specifica e l’esecuzione delle interrogazioni perché non vi è


alcuna espansione della query, l’interrogazione infatti può essere direttamente eseguita sul
contenuto materializzato della vista; abbiamo precalcolato il join e abbiamo «salvato» il risultato
nella vista materializzata.
Questo porta all’introduzione di un livello fisico anche per la vista; il contenuto materializzato
della vista deve essere mantenuto «allineato» con il contenuto delle tabelle di base; ad
esempio, se aggiungiamo un impiegato, il contenuto della vista cambia e deve quindi cambiare la
sua materializzazione su disco.

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.

ENABLE QUERY REWRITE:


L’ottimizzatore può riscrivere le interrogazioni utilizzando eventuali viste materializzate presenti
nello schema esterno.
Quindi il sistema, che è sempre consapevole dell’esistenza delle viste, se la vista è definita con
la clausola ENABLE QUERY REWRITE, proverà però a riscrivere le interrogazioni utilizzandola.
Rispetto allo standard, le viste materializzate in PostgreSQL vengono gestite con le seguenti
opzioni:
- BUILD: immediate,
- REFRESH: never (nessun aggiornamento automatico).
- QUERY REWRITE non attivato.

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.

Potrebbero piacerti anche