Sei sulla pagina 1di 401

30-03-2021

Quando si parla di sistemi informativi e database si fa riferimento a società, aziende o industrie per le quali
avere la conoscenza di informazioni è dal punto di vista strategico molto importante.

Sistema informativo e sistema informatico

Un sistema informativo si occupa della gestione a 360 dell’informazione. È caratterizzato da componenti


che si occupano di:

- Acquisire
- Archiviare: le informazioni acquisite vengono memorizzate su particolari supporti
- Recuperare: le informazioni vengono recuperate con il linguaggio SQL che consente di interrogare le
basi di dati
- Elaborare
- Condividere
- Trasmettere

Devo acquisire una certa conoscenza. Essa può essere memorizzata su vari supporti: file, documenti
cartacei, supporti verbali.

Fatto ciò, le informazioni vengono memorizzate nelle basi di dati. Vengono poi elaborate dal sistema
informatico. Infine, la conoscenza viene trasformata in informazione che viene condivisa agli utenti finali.

La parte di aggiornamento mette in luce il fatto che la conoscenza non è statica ma varia nel tempo. Le
informazioni ritornano all’inizio della catena, cioè, vengono aggiornate.

Il sistema informatico è una porzione del sistema informativo: rappresenta la parte automatizzata, cioè
quella che viene elaborata in maniera automatica da un calcolatore. È tutto ciò che ha a che fare con la
tecnologia software e hardware a supporto del sistema informativo:

- Macchine hardware
- Software
- Reti di comunicazione
- Tutto ciò che si attiene all’Information and Communication Technology (ITC)
Esempio

Del sistema informatico fanno parte macchine hardware, programmi software, apparati di rete per gestire la
condivisione e acquisizione delle informazioni e sensori per rilevare l’inquinamento.

Alcune aziende per motivi legati all’età del personale o all’abitudine utilizzano ancor’oggi documenti
cartacei per memorizzare le informazioni di interesse oltre ai sistemi informatici. In tal caso, per acquisire le
informazioni dalla conoscenza memorizzata su supporti di natura differente, risulterà più macchinoso.

I dati devono essere poi interpretati per dare luogo alle informazioni, perché sono dati grezzi.
Se contestualizzo le due parole “Ferrari” e “8” potranno assumere un significato, ma scritte così come sono
su un foglio esse sono prive di significato.

Se ad esempio il foglio su cui sono scritte è quello di un ordine associato ad un ristorante di un hotel posso
capire che la stringa “Ferrari” fa riferimento ad una bottiglia e la stringa “8” fa riferimento al numero di
camera.

Se contestualizzo le due stringhe nel campo automobilistico, posso assumere che “Ferrari” fa riferimento
all’autovettura e “8” fa riferimento alla posizione.

A seconda del contesto le parole (i dati) possono assumere un significato differente, ma così come sono non
hanno significato.

I dati vanno contestualizzati per poter ottenere l’informazione. L’informazione è un dato elaborato.
Un’informazione è definita da tre fattori: tipo, attributo, valore.

Visti come colonna rappresentano un dato, visti come riga rappresentano un’informazione.

L’approccio per la gestione dei database usa i files per memorizzare i dati sulle memorie di massa. All’inizio
essi potevano essere ad esempio fogli Excel memorizzati in locale, oggi potrebbero essere database remoti
quindi file memorizzati su server.
I metadati sono dati che descrivono altri dati. Sono informazioni associate ad altre informazioni per
arricchire la conoscenza.

La base di dati viene gestita dal Data Base Management System (DBMS)
È un sistema in grado di gestire una grande collezione di dati che deve essere grande, condivisa e
persistente e deve assicurare caratteristiche di affidabilità e privatezza.

Il DBMS deve garantire la privatezza, cioè per accedervi bisogna autenticarsi. Il DBMS deve riconoscere la
persona che in un determinato istante sta operando sul database. Utilizza dei meccanismi di logging che
consentono inoltre di regolare che tipo di operazioni può eseguire l’utente che vi sta accedendo.

Il DBMS potrebbe anche garantire l’accounting, cioè, tenere traccia in un file di log di ciò che sta accadendo
nel DBMS, come ad esempio chi o cosa (un altro programma) sta accendendo al database.
Il DBMS deve definire lo schema del database. Inoltre, deve definire i vincoli sui dati come, ad esempio, il
tipo di dato che è possibile memorizzare.

Il compito principale del DBMS è quello di gestire la base di dati, cioè, manipolarla mediante l’inserimento,
cancellazione, aggiornamento e recupero delle informazioni.

Inoltre, deve controllare la base di dati, cioè, vedere chi ha fatto cosa assicurando la protezione da guasti e
accessi indesiderati.

Il DBMS si interpone tra programmi applicativi (quelli che utilizza l’utente finale) e la base di dati. Supporta
le interrogazioni e offre meccanismi per la comprensione dei dati memorizzati nel database.

31-03-2021
Il livello esterno è caratterizzato da una serie di viste.

Il livello concettuale è quello intermedio, cioè il livello logico.

Il livello interno si interfaccia direttamente con la base dati.

La possiamo vedere come una piramide con la punta verso il basso che rappresenta come vengono
fisicamente organizzati i dati all’interno della memoria. L’unico punto di accesso per il DBMS da parte
dell’utente è attraverso il livello esterno.

Il livello interno rappresenta il modo in cui il DBMS vede i file: è la parte che si interfaccia direttamente con
il sistema operativo e riguarda i file in cui vengono memorizzati i dati. Quindi non interessa in maniera
diretta l’utente.

Il livello esterno è il punto di accesso e si interfaccia direttamente con l’utente e le applicazioni.


Rappresenta il modo in cui le informazioni vengono viste dall’utente e dalle applicazioni che accedono alla
base di dati.

Il livello logico è un livello intermedio che si interpone fra l’utente e il livello interno.
Cosa significa avere differenti viste del livello esterno?

Consideriamo le due tabelle “studenti” e “facoltà”.

Abbiamo diversi tipi di facoltà di uno stesso ateneo (ad esempio la Vanvitelli): ingegneria, economia,
giurisprudenza. Si vengono a creare delle viste logiche in quanto le informazioni riguardanti gli studenti di
ingegneria non interesseranno a quelli di economia e viceversa. Abbiamo quindi una vista “studenti di
economia” e una vista “studenti di ingegneria”.

Entrambe le viste appartengono alla medesima base dati. Le viste sono come delle porzioni di un medesimo
database.
Gli schemi sono relativi a ciascun livello di un DBMS.

Lo schema logico può fa riferimento alle colonne di un DB: rappresenta come sono suddivise logicamente le
tabelle. Fa riferimento alla loro struttura.

Lo schema interno riguarda la memorizzazione fisica dei dati.

Lo schema è una componente invariante nel tempo: una volta definite le colonne del DB è definito lo
schema. È la struttura del database (matricola, nome, cognome, indirizzo).

L’istanza o stato di un DB rappresenta i valori assunti dallo schema (dalle colonne del DB) ed è una
componente variabile nel tempo.

Lo schema può però essere modificato, aggiungendo ad esempio ulteriori informazioni (una nuova
colonna).

A volte si dice che lo schema è la componente intensionale mentre l’istanza è la componente estensionale.

La componente intensionale fa riferimento al significato della base dati: quando definisco lo schema
definisco il significato della base dati.

La componente estensionale fa riferimento ai valori che la base dati può assumere.


L’indipendenza logica dei dati riguarda il fatto che le modifiche del livello esterno non influenzano il livello
logico.

Allo stesso modo deve esserci indipendenza fra livello interno e livello logico.

In altri termini, una modifica di un determinato livello del DBMS non deve influenzare il livello
sottostante/sovrastante.

Un DB è una collezione di dati logicamente correlati tra loro. Tali dati vanno strutturati, cioè le informazioni
memorizzate in una tabella non sono scorrelate fra loro (ad esempio matricola, nome, cognome, ecc).
Modello gerarchico: organizzazione dei dati ad albero.

Modello reticolare: organizzazione dei dati in grafi.

Modello ad oggetti: dati organizzati in oggetti.

Modello relazionale: è il modello attualmente più diffuso. I dati vengono organizzati in record (in righe) di
lunghezza fissa. Se una tabella è costituita da n colonne, ciascuna riga avrà 5 elementi, anche se una
determinata informazione (cella) è assente.

Un albero è un modello gerarchico: in alto a tale gerarchia c’è il nodo radice, nella parte terminale
dell’albero ci sono i nodi foglia. I nodi intermedi sono i nodi padri. I legami tra i vari nodi avvengono
mediante puntatori.

Da un nodo padre possono nascere più nodi figli.

Si parte dal nodo radice per arrivare alle foglie.


Nel modello reticolare si hanno una serie di nodi, la cui associazione fra nodi avviene mediante archi dando
luogo a quello che viene detto grafo orientato.

Nel modello relazionale c’è il concetto di tabella. I record sono le righe di una tabella.

La realtà di interesse viene modellata in tabelle.


La realtà di interesse viene modellata in classi costituita da una serie di attributi e metodi. Il problema di tale
metodologia è che è poco performante su grosse quantità di dati.
La proprietà più importante è l’indipendenza dei tre livelli di un DBMS.

Il controllo della ridondanza è una proprietà per la quale il DBMS elimina righe duplicate di una tabella.

Il DBMS gestisce l’inconsistenza: se ho un legame tra due colonne di tabelle differenti, nel momento in cui
viene modificata una viene modificata anche l’altra (esempio: modifica del codice di un corso universitario –
vedi esempi precedenti).

Integrità dei dati: il DBMS evita che i dati vengano corrotti o che siano mancanti.

Gestione della concorrenza: il DBMS gestisce gli accessi simultanei. Se ad esempio compro un biglietto
aereo e qualcuno lo sta comprando, bisogna decrementare il numero di biglietti disponibili.

Con affidabilità si intende backup e recovery. Il DBMS in caso di malfunzionamenti deve sempre ripristinare
uno stato consistente soprattutto durante le transazioni.

Un DBMS deve garantire la sicurezza: deve proteggere il DB da eventuali attacchi o accessi proibiti
(autorizzazione).

Il gestore degli accessi gestisce gli accessi alla base dati. Vengono creati file di log che tengono traccia degli
accessi

Il gestore delle query (interrogazioni) si occupa di eseguire le interrogazioni per estrapolare le informazioni
di interesse.

Il gestore della memoria si occupa della memorizzazione dei dati su supporti di massa.

Il gestore dei file gestisce l’organizzazione dei file nel file system.

Il gestore della concorrenza si occupa degli accessi simultanei.


Gli utenti di un DBMS sono di diverse tipologie:

- Progettisti e programmatori: non è detto che siano la medesima persona. Il progettista può ad
esempio definire gli schemi e le relazioni fra tabelle. Il programmatore invece può definire lo
strumento software che accede alla base dati
- Amministratore: è colui che interagisce in prima linea col DBMS e si occupa della parte di controllo,
gestione e amministrazione e quindi di tutto ciò che ha a che fare con i vari gestori visti prima.
- Utenti finali: sono utenti che utilizzano il DBMS in modalità predefinite, cioè sempre allo stesso
modo.
- Utenti casuali: accedono al database non in maniera standard.

I dati sono una risorsa comune perché possono essere condivisi.

Il controllo centralizzato fa riferimento al fatto che l’interfaccia d’accesso al DBMS è univoca: posso accedere
da qualsiasi parte del mondo mediante url.
L’SQL comprende tre “sotto-linguaggi”: DDL, DML, DCL.

Il DDL viene utilizzato per la definizione dei dati, cioè per la creazione dello schema delle tabelle.

Il DML consente di inserire informazioni (istanze), aggiornarle ed eseguire interrogazioni.

Il DCL riguarda gli utenti e la loro profilazione (privilegi).


Per evitare di trovarmi in uno stato inconsistente (ad esempio prenotazione biglietto aereo su server che
crasha) una transazione deve terminare con successo (commit) oppure deve riportare il sistema nello stato
precedente (abort).

L’operazione di SELECT è di sola lettura.

INSERT è un’operazione in scrittura così come UPDATE e DELETE.

L’operazione di SELECT non causa problemi circa la concorrenza e crash improvvisi.


Una transazione deve rispettare le proprietà ACID.

09-04-2021

Modello relazionale

Si basa su due concetti fondamentali:

- Relazione
- Tabella

Una delle caratteristiche fondamentali del DBMS è l’indipendenza dei tre livelli (esterno, logico, interno o
fisico). A noi interessa l’indipendenza del livello fisico dal livello logico e tale modello relazionale mantiene
questa indipendenza.

È l’insieme di tutte le coppie possibili dove il primo elemento appartiene all’insieme A e il secondo
elemento appartiene all’insieme B.
Il punto di forza del modello relazionale è che è molto intuitivo dal punto di vista della lettura.

Una relazione matematica su due insiemi generici, detti domini della relazione, è un sottoinsieme del loro
prodotto cartesiano, e la si può rappresentare come una tabella.

Quando si compie un prodotto cartesiano, alcune coppie hanno un significato rilevante rispetto ad altre.
Consideriamo i due insieme A1 e A2.

Facendone il prodotto cartesiano otteniamo tutte le possibili coppie ordinate. A partire dal prodotto
cartesiano possiamo ottenere una serie di relazioni, cioè una serie di sottoinsiemi.

Osserviamo come alcune relazioni sono più significative di altre. Ad esempio, il sottoinsieme costituito dalle
coppie (4,2) e (9,3) potrebbe essere una relazione chiamata “quadrato di”.

Il numero n di tutte le coppie ottenute dal prodotto cartesiano è detto grado.

Il numero di elementi di un elemento (di una n-upla) del prodotto viene detta cardinalità della relazione.
In una relazione (che è una tabella) è irrilevante l’ordine delle righe. Può essere invece rilevante l’ordine
delle colonne (esempio relazione “quadrato di”).

In una relazione non possiamo avere due elementi uguali, cioè due n-uple uguali.

Consideriamo la tabella, cioè la relazione, in figura. Ogni suo elemento è una quadrupla.

In questo caso è importante l’ordine interno di ciascuna n-upla (cambierebbe il risultato della partita),
mentre è invece irrilevante l’ordinamento tra le n-uple.

Questo tipo di rappresentazione non è soddisfacente perché in ambito informatico si cerca di utilizzare
notazioni che non sono posizionali, cioè il cui significato non dipende dall’ordine.

La soluzione è utilizzare delle intestazioni per ciascuna colonna.

In questo modo, anche scambiando l’ordine delle colonne, la semantica della relazione non cambia.

L’intestazione di ciascuna colonna, cioè il nome, viene detto attributo.


L’attributo dal punto di vista informatico potrebbe essere visto come una variabile.

La tabella è una relazione.

Una tupla è una riga (un record) della relazione.


Una tupla è una funzione t che a ciascun attributo (intestazione della tabella) associa il relativo valore della
riga considerata.

Una tupla può essere definita anche su più variabili.

Ricapitolando:

- Ogni tabella è una relazione, cioè un sottoinsieme di un certo prodotto cartesiano


- Ogni colonna è un attributo della relazione
- Ogni riga della relazione è una tupla

Abbiamo tre relazioni: studenti, corsi ed esami. Ognuna è costituita da 3 attributi (3 colonne).

Posso stabilire dei legami tra relazioni mediante valori comuni sulle tuple. Ad esempio, tra la relazione
Studenti e la relazione Esami c’è un legame fra le loro prime tuple, e fra l’ultima tupla di entrambe. Allo
stesso modo, c’è un legame fra la relazione Esami e la relazione Corsi come mostrato in figura.
Lo schema di una relazione è dato dal nome della relazione e dall’insieme dei suoi attributi. Ad esempio:
Studente(Matricola, Cognome, Nome).

Lo schema di una base dati è dato dall’insieme degli schemi di tutte le sue relazioni.

Nota: le relazioni di una stessa base dati devono avere nomi diversi onde evitare ambiguità.

Una istanza di una relazione è un insieme di tuple. È un po’ l’analogo della programmazione ad oggetti:
possiamo vedere una relazione come una classe, e le istanze della classe (gli oggetti) sono l’insieme delle
tuple della relazione.

Una istanza di una base di dati è l’insieme delle istanze delle sue relazioni.
Consideriamo lo schema della relazione in figura.

Non tutte le persone potrebbero avere un telefono. Il problema è però che l’assenza di informazione non è
contemplata perché tutte le tuple di una relazione devono essere omogenee fra loro.

Potrei allora pensare di dare un valore standard a coloro che non dispongono di un numero telefonico,
attribuendo ad esempio il valore “0”.

Tale soluzione però non sempre può essere utilizzata.

Ciò che si utilizza è un valore speciale in caso di assenza di informazione: tale valore è il valore “NULL”.

Il valore NULL può essere utilizzato in casi differenti, ma alcune volte non ha significato, cioè, è
inconsistente.

Ad esempio, non ha senso il valore null utilizzato nella relazione “Esami”. Ho un voto, ma un’informazione
assente riguardo lo studente che ha sostenuto l’esame e il codice del corso: non ha senso.

Il valore NULL deve essere usato con cautela: utilizzato erroneamente può generare ambiguità.
Il valore NULL viene automaticamente inserito dal DBMS quando l’informazione non è fornita e non è stato
definito un valore di default in tale evenienza.

I vincoli di integrità fanno riferimento all’omogeneità delle informazioni.

I dati vengono inseriti o dall’utente oppure da un’applicazione software.

Nell’esempio i due studenti hanno Verdi Fabio e Bruno Dario hanno la medesima matricola. Ciò
teoricamente non rappresenta un problema perché in una relazione basta che non ci sia una tupla
duplicata, cioè due righe perfettamente uguali. Però in questo caso due matricole uguali rappresentano un
problema.

Anche nella relazione Esami c’è un problema: abbiamo uno studente “200765” che non è però definito nella
relazione “Studenti”. Chi ha sostenuto l’esame?

Un altro problema in tale relazione riguarda la prima tupla: un voto pari a “35” non ha senso nel contesto di
esami universitari.

Un’altra ambiguità riguarda la seconda tupla: è stata attribuita una lode ad uno studente il cui voto è
inferiore a 30.
Un vincolo di integrità serve a far sì che una relazione mostri solo informazioni corrette.

È un predicato che associa all’istanza della relazione il valore vero o falso. Se il predicato è vero allora
l’istanza soddisfa il vincolo

Una istanza di un DB è lecita se soddisfa tutti i vincoli della base di dati.

Un vincolo intrarelazionale è un vincolo all’interno di una relazione, mentre un vincolo interrelazione è un


vincolo fra due o più relazioni.

Un vincolo intrarelazionale si suddivide in:

- Vincolo di tupla: coinvolge le righe della relazione (tabella)


- Vincolo su valori: il suo soddisfacimento riguarda i singoli valori della tupla.

Esempio
I vincoli di chiave sono i vincoli più importanti perché consentono di identificare una tupla di una relazione.
Anch’essi si suddividono in intrarelazionali e interrelazionali.

Un vincolo di chiave può essere ad esempio che all’interno della relazione Studenti non devono esistere due
valori dell’attributo matricola uguali. È inoltre un vincolo intrarelazionale.

Ad esempio, nella relazione Studenti l’attributo Matricola è una chiave perché consente di identificare in
maniera univoca una tupla della relazione Studenti.

Una superchiave è uno o un insieme di attributi di una relazione tali che in tale relazione i valori di
quell’attributo sono tutti distinti.

Una superchiave minimale è una superchiave tale che gli attributi di cui è costituita non contengono
un’altra superchiave. Una superchiave minimale si dice chiave.
L’attributo matricola è una superchiave perché i suoi valori sono tutti distinti all’interno della relazione e
quindi identifica in maniera univoca ciascuna tupla. È inoltre una superchiave minimale perché coinvolge un
unico attributo (Matricola), cioè non esiste un suo sottoinsieme in grado di identificare univocamente le
tuple.

L’insieme di attributi Cognome Nome e Nascita è anch’esso una superchiave perché sono in grado di
identificare in maniera univoca ciascuna tupla della relazione senza ripetizioni se presi tutti e tre insieme. È
inoltre una superchiave minimale perché i suoi sottoinsiemi (ad esempio solo Cognome e Nome) non
identificano in maniera univoca ciascuna tupla.

Quindi:

- Una superchiave è un insieme di attributi che identifica in maniera univoca ciascuna tupla della
relazione, cioè non esistono due tuple distinte tali che esse assumano lo stesso valore sui suddetti
attributi. Essa però potrebbe contenere un sottoinsieme di attributi che a loro volta identificano in
maniera univoca ciascuna riga della tabella, cioè, potrebbe contenere un’altra (o più) superchiave.
- Una superchiave minimale è invece una superchiave per la quale non esiste un suo sottoinsieme di
attributi che sia a sua volta una superchiave, cioè, è una superchiave che non contiene altre
superchiavi.

{Matricola, Corso} è una superchiave ma non è minimale perché contiene la superchiave “Matricola”.
In tale esempio {Corso, Cognome} si dice casualmente una chiave per la relazione in questione, in quanto
potrebbe ad esempio iscriversi un altro “Rossi Dario” che frequenta ingegneria informatica.

Ogni relazione ha sempre una superchiave in quanto l’insieme di tutti i suoi attributi lo è sempre. Questo
perché in una relazione non possono mai esserci due tuple identiche, e quindi tutti gli attributi della
relazione identificheranno sempre in maniera univoca ciascuna sua tupla.

Il valore speciale NULL non deve mai essere assegnato ad un attributo che è anche chiave per una relazione.
13-04-2021

Una chiave primaria è una superchiave minimale, cioè una chiave (per definizione). Non bisogna usare
valori nulli per la chiave primaria perché altrimenti non riuscirei a identificare univocamente ciascuna tupla
della relazione.

La chiave primaria in una relazione viene identificata mediante una sottolineatura.

La chiave primaria è unica.

Consideriamo un vincolo interrelazionale, cioè che coinvolge due o più relazioni. Il più importante vincolo
interrelazionale è il vincolo di integrità referenziale: consente di associare le tuple di due o più relazioni
differenti.

Abbiamo un legame tra l’attributo “Matricola” e l’attributo “Studente”. L’attributo “Studente” prende il
nome di chiave esterna: ogni suo valore deve essere contemplato tra i valori dell’attributo “Matricola” che è
chiave primaria per la tabella esterna. Tale chiave primaria viene detta chiave interna

La chiave esterna appartiene alla tabella interna.

I DBMS sono molto rigidi riguardo i controlli dei vincoli di integrità referenziale: questo perché tali vincoli ci
consentono di individuare i legami fra le relazioni.
Cosa accade se rimuovo lo studente Rossi Mario dalla relazione Studenti? Si ha una violazione in quanto il
primo record della relazione Esami non “punta” più a nessun altro record della relazione Studenti.

Si ha un vincolo di integrità referenziale solo se i due insiemi di attributi coinvolti della tabella esterna e
interna hanno tutti i valori in comune.

A e B sono gli attributi coinvolti nel vincolo di integrità referenziale.

Questo secondo caso la superchiave minimale X della tabella esterna è costituita da più di un attributo.

Possono esistere anche altre tipologie di vincoli come, ad esempio, il fatto che lo stipendio di un impiegato
semplice debba essere minore o uguale allo stipendio di un capo.
Sulle tabelle è possibile eseguire operazioni per estrapolare le informazioni di interesse.

Le operazioni possono essere di tipo insiemistico perché le relazioni non sono altro che un insieme
costituito da una serie di attributi.

Abbiamo altri operatori specifici e di join.


L’unione può essere applicata quando abbiamo due relazioni definite sugli stessi attributi. È una relazione
costituita ancora sugli stessi attributi e costituita dalle tuple che appartengono alla prima relazione, alla
seconda relazione, oppure a entrambe senza ripetizioni.

Il grado, cioè, è il numero di attributi, è il medesimo delle due relazioni di partenza. La cardinalità, ovvero il
numero di righe, è maggiore o uguale a quella delle due relazioni iniziali.

L’intersezione di due relazioni è a sua volta una relazione che contiene le tuple che appartengono sia alla
prima che alla seconda relazione di partenza. Anche in questo caso le due relazioni di partenza devono
essere definite sugli stessi attributi.
La differenza di due relazioni (R1-R2) definite sugli stessi attributi è una relazione definita ancora sugli stessi
attributi che contiene le tuple che appartengono alla prima relazione ma non alla seconda. In questo caso
l’ordine all’interno dell’operazione è significativo.

Andiamo ora a vedere alcuni esempi di operatori specifici.

Se volessimo ottenere tutte le coppie genitore-figlio, cioè se volessi sapere chi è il padre e la madre, dovrei
adoperare l’unione. Le due relazioni però hanno un attributo con nome differente (Padre e Madre) e quindi
non posso adoperare l’unione così com’è. Posso allora rinominare i due attributi in “Genitore” e applicare
poi l’unione.
Il simbolo di tale operatore è rho con un pedice costituito da una freccia: a destra della freccia è indicato il
nome vecchio dell’attributo, mentre a sinistra quello nuovo. Infine, tra parentesi abbiamo l’argomento, cioè
la relazione alla quale viene applicata la ridenominazione.

Esempio

In tale esempio viene eseguita l’unione delle relazioni Paternità e Maternità avendo prima ridenominato i
due attributi Padre e Madre in “genitore”.

Esempio: ridenominazione su più attributi


La selezione opera una decomposizione orizzontale, ovvero genera un sottoinsieme di tuple su tutti gli
attributi della relazione e cioè un sottoinsieme di righe.

La proiezione opera invece una decomposizione verticale, cioè, genera un sottoinsieme di attributi su tutte
le tuple ovvero un sottoinsieme di colonne.
La selezione produce un’altra relazione sugli stessi attributi della relazione di partenza e che contiene
soltanto le tuple che rendono F vera.

Si indica col simbolo sigma.

La selezione è un operatore unario, cioè opera su una singola relazione.

Tale selezione si legge come: seleziona le righe della tabella “Impiegati” la cui età sia maggiore di 30 e lo
stipendio sia maggiore di 4000.
In questo caso si stanno selezionando le righe della tabella “Cittadini” il cui valore dell’attributo
“CittàDiNascita” coincida con il valore dell’attributo “Residenza”.

È una formula proposizionale che confronta due attributi (A e B).

L’operatore proiezione viene indicato con il simbolo “pi”: è un operatore specifico di tipo unario.

Dato l’insieme X di attributi della relazione r(x), la proiezione è un sottoinsieme Y di X. Tira fuori un
sottoinsieme di colonne della relazione r.
In questo esempio si vanno a selezionare le colonne “Cognome” e “Nome” della relazione “Impiegati”.

Vengono selezionati un sottoinsieme di attributi su tutte le righe della tabella.

In questo caso cognome e nome sono anche una superchiave.

In questo esempio si mette in luce il fatto che nel risultato non vengono considerati i duplicati.

In questo caso reparto e capo non sono una superchiave.

Possiamo allora osservare che, quando si opera una proiezione su attributi che sono una superchiave per la
relazione di partenza, la relazione risultante avrà cardinalità pari a quella di partenza.

Invece, quando ciò non è vero la cardinalità sarà sempre minore o uguale.
Possiamo osservare come gli attributi Cognome e Corso non rappresentino una superchiave: difatti la
cardinalità della tabella risultante è inferiore a quella di partenza.

Rossi compare due volte perché sono associati a corsi differenti.


In questo caso Cognome e Corso rappresentano una superchiave perché tali attributi hanno sempre valori
differenti nella tabella studenti: la cardinalità della tabella risultante alla proiezione è massima.

Osserviamo però che tali attributi sono casualmente una superchiave, perché potrebbero iscriversi nuovi
studenti tali da avere duplicati di tali attributi nella tabella studenti.

Join

Combina le tuple di due relazioni differenti sulla base dei valori contenuti negli attributi in comune.

Bisogna avere almeno un attributo in comune fra le due relazioni affinché si possa applicare l’operatore
(binario) di join.

Va ad unire le tuple delle due relazioni iniziali sulla base dei valori uguali sull’attributo comune.

Si considera l’unione delle tuple delle due relazioni iniziali sulla base dell’intersezione dell’attributo comune.
L’attributo comune è “CodCorso”.

Si va a fare il “matching” delle tuple delle due relazioni sulla base dei valori coincidenti dell’attributo
comune.
14-04-2021

Ricapitolazione

La selezione permette di filtrare le tuple, mentre la proiezione consente di filtrare le colonne. Difatti si parla
di decomposizione orizzontale e verticale rispettivamente. Entrambi sono operatori unari.

Per la selezione la cardinalità è inferiore o uguale a quella di partenza, mentre il grado è lo stesso di quello
di partenza. Per la proiezione vale il contrario.

La join (giunzione) è un operatore binario, cioè, coinvolge almeno due relazioni. La giunzione è attuata sulla
base di un attributo comune fra due o più relazioni. Nasce una nuova relazione in cui le righe sono l’unione
delle righe delle due relazioni originali laddove c’è un matching sull’attributo comune.

Un join si dice completo se partecipano al risultato tutte le tuple di entrambe le relazioni. In caso contrario
si dice non completo e le tuple che non partecipano vengono dette dangling (dondolanti). Il caso limite è
quello in cui nessuna tupla partecipa al join e quindi si dirà vuoto.
Alla join non partecipano la prima tupla di r1 e la seconda tupla di r2 perché per esse non c’è un matching
sull’attributo comune “Dipartimento” con la relazione coinvolta nell’operazione.

Nessun valore dell’attributo “Dipartimento” trova un matching nelle due relazioni.

La cardinalità del risultato nel caso di join completo è il prodotto delle due cardinalità di partenza.
Con |R| si indica la cardinalità di una relazione.

Serve a risolvere il problema delle tuple dondolanti (dangling).


Abbiamo due tuple dondolanti in r1 e r2 perché per esse non c’è una corrispondenza nell’altra relazione
sull’attributo comune.

Il left join privilegia le tuple del primo operando: esse vengono inserite comunque nella relazione risultate e
in corrispondenza dell’attributo rimanente dell’altra relazione (capo in questo esempio) viene imposto il
valore NULL.

Il right join al contrario privilegia le tuple del secondo operando: queste vengono inserite comunque nella
relazione risultate, e in corrispondenza dell’attributo rimanente della prima relazione (Impiegato in questo
caso) viene impostato il valore NULL.

Il full join comprende sia il left join che il right join.

Consideriamo le due relazioni di partenza in alto. In tale esempio è stata prima eseguita una join naturale.
Le ultime due tabelle sono rispettivamente il risultato di una proiezione e il risultato di una proiezione alla
quale è stata applicata una selezione.

Delle due tabelle a sinistra è stato fatto dapprima il join naturale e poi una selezione sulla tabella risultante.

- Date due relazione R1 e R2 su due insiemi di attributi X1 e X2, la proiezione sugli attributi X1 della
giunzione tra le due relazioni è sempre un sottoinsieme della prima relazione
- Una relazione R definita sull’unione degli insiemi di attributi X1 e X2 , la giunzione delle proiezioni
sui due insiemi di attributi non è contenuta in R.
La join può essere eseguita in generale su N relazioni: si parla di join N-ario.

R1 si “aggancia” a R2 sulla base dell’attributo comune “Dipartimento” e R2 si “aggancia” a R3 sulla base


dell’attributo comune “Divisione”. Devono essere soddisfatte le corrispondenze su tutti gli attributi comuni
affinché la riga entri a far parte del risultato.
Osserviamo che tra le relazioni Impiegati e Progetti non c’è nessun attributo comune. In questo caso la join
dà come risultato il prodotto cartesiano delle due tabelle iniziali.

Nella pratica non viene quasi mai utilizzata la join nel caso in cui le due relazioni non abbiano attributi
comuni in quanto non viene ottenuta nessuna informazione rilevante ed inoltre la relazione risultante può
avere dimensioni piuttosto elevate.

Una cosa più sensata in questo caso è eseguire una selezione in seguito all’operazione di join. Si introduce
quello che viene chiamato theta-join.

Il theta join si utilizza per risolvere il problema dell’operazione di join in assenza di attributi comuni fra le
relazioni coinvolte.

Il theta join è un join sul quale viene applicata una selezione.

L’equi-join è invece un join seguito da una selezione che viene applicata sulla base di una uguaglianza.
In questo esempio viene eseguita prima l’operazione di join fra le relazioni “Impiegati” e “Progetti” (che non
avendo attributi comuni sarà un prodotto cartesiano) e dopodiché viene eseguita una selezione: vengono
selezionate le tuple per la quale i valori dell’attributo “Progetto” sono uguali ai valori dell’attributo “Codice”.

Si tratta dunque di un equi-join.

Il join naturale è un qualcosa di più teorico: così com’è viene poco utilizzato. Nella pratica è solitamente
utilizzato il theta-join, cioè un join seguito da una selezione.
Una interrogazione è un’operazione che estrare un’altra relazione sulla base di una formula proposizionale.

A volte può essere utile applicare una rappresentazione ad albero per le interrogazioni.

La radice dell’albero è l’operazione più esterna, cioè la selezione. Il nodo è rappresentato dall’operazione più
interna, ovvero la giunzione, e le foglie sono gli operandi.

A volte è utile utilizzare delle viste, cioè, assegnare un nome ad una certa operazione per poi poterla
riutilizzare in altre operazioni.
Consideriamo le tre relazioni in figura. Tali relazioni rappresentano per noi la nostra base dati.

C’è un legame tra la relazione impiegato e la relazione sedi mediante l’attributo Sede. Un altro legame è
presente fra la relazione progetti e la relazione Sedi mediante l’attributo città.

1) Eseguo prima una selezione sulla relazione Impiegati che tira fuori tutte le tuple per cui lo stipendio
è maggiore di 1300. Dopodiché applico la proiezione, tirando fuori solo le colonne Nome, Sede e
Stipendio. Inoltre, assegno un nome al risultato di tale operazione: “ImpFac” (Impiegati Facoltosi).
Posso anche eseguire prima la proiezione e poi la selezione.
2) Nel secondo esempio osserviamo che l’attributo città si trova nella relazione “Sedi”. Dovrò allora
eseguire una join fra la relazione Impiegati e la relazione Sedi mediante l’attributo comune “Sede”.
Sulla relazione “Imp” vado prima a selezionare le tuple per la quale lo stipendio è maggiore di 1300.
Infine, applico la proiezione, per ottenere le colonne Sede, Responsabile e Città. Un’alternativa è
eseguire prima la join fra “Sedi” e “ImpFac” cioè con gli impiegati che guadagnano più di 1300 e poi
posso applicare la proiezione.
Nota: i valori duplicati compaiono solo una volta.

Seleziono prima le tuple della tabella impiegati in cui il ruolo è sistemista. Dopodiché vado a selezionare
solo le sedi. A questo punto sulla relazione sedi vado a considerare solo la colonna “Sede”. Andiamo poi a
sottrare le due relazioni ottenute: in questo modo otteniamo solo la sede in cui non lavorano sistemisti
(S03). Infine, si fa la giunzione con sedi e si seleziona la colonna responsabile.

Il tutto è rappresentato anche con uno schema ad albero. Lo si legge dal basso verso l’alto.

Vedi altri esempi da pag. 61 delle slide numero 4.

La presenza di valori nulli introduce un nuovo stato diverso da VERO o FALSO.


Se vado a selezionare dalla tabella impiegati le colonne Nome ed Ufficio, osserviamo come la tupla (Verdi,
NULL) viene riportata una sola volta.

Se eseguiamo l’unione tra impiegati e responsabili, osserviamo come l’ultima riga non fornisce nessuna
informazione utile.

La selezione è quella che causa più problemi in presenza di valori nulli: la selezione applica un filtraggio sulle
righe. Il predicato NULL è da considerare come vero o falso?

Ad esempio, voglio selezionare le tuple in cui età>40. L’ultima tupla la devo prendere oppure no?

Ciò che si fa è estendere l’algebra relazionale con un terzo valore indefinito.


Nel caso della join, se l’attributo comune ha da entrambi i lati il valore nullo, la join non lo considera.
16-04-2021

Le interrogazioni che faremo col linguaggio SQL la maggior parte dei DBMS le traduce nella relativa algebra
relazionale.

Abbiamo visto che per giungere ad un medesimo risultato possiamo adoperare più query equivalenti.

Il DBMS cerca sempre di adoperare la soluzione meno costosa, cioè quella dal punto di vista
computazionale meno onerosa, cioè più ottimizzata.

Per ottimizzare le query il DBMS adopera una serie di trasformazioni che rendono più veloci le
interrogazioni.

La trasformazione più semplice è quella di rendere atomica la selezione: se abbiamo una congiunzione su
una selezione (una selezione con una AND), ciò equivale alla cascata delle selezioni.

Un’altra trasformazione che si può applicare è quella di idempotenza della proiezione: una proiezione può
essere trasformata in una cascata di proiezioni che vanno man mano ad eliminare delle colonne.
La pushing selections down è tra le trasformazioni più importanti. Consiste nell’anticipare le selezioni
rispetto all’operazione di join naturale.

Supponiamo di avere una join naturale (un prodotto cartesiano) tra due relazioni e di voler poi applicare
una selezione per filtrare delle righe. Per diminuire il tempo di calcolo posso pensare prima di applicare la
selezione sulla seconda relazione e poi eseguo il prodotto cartesiano.

È l’analogo del caso visto precedentemente. Prima di eseguire la join tra le relazioni si va prima a filtrare
sulle colonne della seconda selezione. Questo, come prima, solo se gli attributi sulla quale è definita la
proiezione appartengono alla seconda relazione.

Supponiamo si voglia fare una selezione su una giunzione. Se possibile si sposta la selezione sulla giunzione,
cioè il filtraggio sulle tuple viene inglobato nel prodotto cartesiano.
Matricola è chiave primaria per impiegati mentre in supervisione la chiave primaria è impiegato. Abbiamo
anche un vincolo di integrità referenziale perché Impiegato “punta” a matricola.

Supponiamo di voler ottenere i numeri di matricola dei capi con meno di 30 anni.

Posso eseguire la join tra impiegati e supervisione. Si tratterà di un prodotto cartesiano perché non hanno
attributi comuni. Fatto ciò, filtriamo sulle tuple considerando quelle in cui il campo matricola è uguale al
campo impiegato e dove l’età è minore di 30. Infine, filtriamo sulle colonne, in particolare tiriamo fuori solo
la colonna capo.

Osserviamo come tale operazione sia poco efficiente in quanto viene eseguito il prodotto cartesiano.

Per ottimizzare possiamo applicare delle trasformazioni.

Abbiamo una formula proposizionale con una congiunzione: posso atomizzarla eseguendo in cascata le due
proposizioni di cui è composta. Cerco prima gli impiegati con età minore di 30, anticipiamo cioè la
proiezione rispetto alla join

Posso poi inglobare la selezione “Matr=Imp” nella join.


Abbiamo già detto che con l’utilizzo del valore NULL i problemi maggiori si hanno nella selezione. Se ho un
predicato che prevede un attributo il cui valore è null lo devo considerare oppure no?
A tal proposito si introduce una logica a 3 valori.

Per lavorare con i valori NULL si introduce la clausola “IS”.


Abbiamo visto che possiamo prendere il risultato di una interrogazione intermedia e assegnargli un nome,
così come nei linguaggi di programmazione utilizziamo le variabili.

L’utilizzo delle viste ci consente di dare una visione differente per ciascun utente.

Un altro utilizzo può essere quello di “spezzettare” una interrogazione più o meno complessa oppure di
riutilizzare una stessa interrogazione per altre interrogazioni.

Le viste possono trovarsi in diverse relazioni tra loro.

Le relazioni derivate sono quelle viste che al loro interno contengono il risultato di un’altra relazione. Le
posso innestare nelle modalità più svariate.

Le viste materializzate è come se fossero una tabella vera e propria mantenuta in memoria. Il problema di
tali viste è che bisogna tenerle aggiornate nel momento in cui viene aggiornata la relazione da cui derivano.
Tale viste non vengono memorizzate in maniera fisica nella base dati.

In tale esempio viene creata una vista “Supervisione” al cui interno vengono memorizzati gli impiegati con i
loro capi.

Le viste vengono poi trattate come delle relazioni a tutti gli effetti.
Le viste possono essere utilizzate per riutilizzare una medesima query (la vista è il risultato di una query che
viene memorizzato).

Possono essere utilizzate per fornire appunto viste differenti della base di dati a diversi tipi di utenti.

La vista consente di rendere più leggibile una query.


La vista deve essere aggiornata ogni volta che vengono aggiornate le relazioni dalla quale discende.

Supponiamo di aggiungere una tupla nelle relazioni Afferenza e Direzione.

La prima cosa da fare e ricalcolare la vista Supervisione.

In generale l’aggiornamento può dar luogo a delle ambiguità. Potrei ad esempio aggiornare la vista
supervisione ma lasciare invariate le due viste Afferenza e direzione.
NomeRelazione.Attributo: è la notazione che viene utilizzata per far capire al DBMS che stiamo facendo
riferimento all’attributo di una determinata relazione.

Questo perché ad attributi di relazioni differenti può essere assegnato lo stesso nome.
Accenni:

Consideriamo una relazione Supervisione i cui attributi sono Impiegato (chiave) e Capo.

È come se avessimo una sorta di meccanismo ricorsivo: Rossi ha come capo Lupi che a sua volta ha come
capo Falchi. Ma allora il superiore di Rossi è Falchi.

Esercizi a fine slide.


Abbiamo detto inizialmente che il linguaggio SQL mette a disposizione a sua volta una serie di sotto-
linguaggi:

- DDL: creazione della base dati, schemi, tabelle, viste


- DML: manipolazione delle relazioni
- DCL: è la parte di controllo della base dati. Creazione di utenti e loro profilazione.

Serve a creare la base dati.

CREATE SCHEMA:
Esempio

Crea una tabella impiegato.

L’attributo matricola è una stringa di lunghezza 6 ed è chiave primaria.

L’attributo nome è una stringa di 20 elementi: con la clausola NOT NULL si indica che tale dato non può
essere vuoto, cioè, è obbligatorio.

Quando NOT NULL non viene specificato, quel campo può rimanere vuoto.

Stipendio è un attributo numerico che contiene massimo 9 valori e di default è 0, cioè se non viene dato di
default viene impostato il valore 0.

FOREIGN KEY serve a specificare un vincolo di integrità referenziale: Dipart è una chiave esterna e fa
riferimento all’attributo NomeDip della relazione Dipartimento. L’attributo Dipart della tabella impiegato
punta all’attributo NomeDip della relazione Dipartimento.

Con la clausola UNIQUE si impone il fatto che Nome e Cognome non possono avere lo stesso valore, cioè,
devono essere diversi.
Per gli attributi possono quindi essere definiti dei domini di appartenenza, cioè dei tipi di dato predefiniti
oppure dei tipi di dato definiti da noi.

Esempio: numeric(6,4) rappresenta numeri su 6 cifre totali con un numero di cifre dopo la virgola pari a 4.
Rappresenta i numeri da -99.9999 a +99.9999
Viene creato un dominio chiamato Voto di tipo smallint con valore di default NULL.

Con la clausola “CHECK” si va ad imporre un determinato requisito per le istanze di quel dominio. In tale
esempio deve essere un valore compreso fra 18 e 30.

20-04-2021
I vincoli intrarelazionale sono riferiti all’interno della relazione.

I campi “NOT NULL” sono campi obbligatori.

La clausola “UNIQUE” viene utilizzata per far sì che non ci siano valori uguali o sul singolo attributo oppure
su una più attributi differenti: in tal caso vuol dire che quegli attributi non possono assumere lo stesso
valore.

Serve a specificare la chiave primaria di una relazione e può essere applicato una sola volta: questo perché
in una relazione la chiave primaria deve essere unica.
I vincoli interrelazionali occorrono fra due o più relazioni e rappresentano l’integrità referenziale. Viene
specificata mediante la clausola foreign key: crea un legame fra due attributi di tabelle differenti. Una
tabella sarà definita come tabella interna e l’altra come tabella esterna. Per la tabella esterna l’attributo
coinvolto deve essere definito come unique: questo perché altrimenti non riuscirà a puntare in maniera
univoca ad un record dell’altra tabella.

References viene utilizzato quando il legame che si vuole creare coinvolge un unico attributo, mentre
foreign key quando sono coinvolti più attributi.
La tabella interna punta ad un attributo della tabella esterna.

La tabella esterna è quella “puntata” mentre la tabella interna è quella che “punta”.

Se nella tabella interna c’è un valore dell’attributo coinvolto nel legame che non compare nell’altra tabella, si
ha una violazione sulla tabella interna. Il DBMS in tal caso impedisce tale operazione.
La tabella esterna è quella che viene puntata.

Se nella tabella esterna modifico il valore dell’attributo coinvolto nel vincolo di integrità, chiaramente la
tabella interna punterà ad un valore errato.

Stesso discorso se cancello una riga nella tabella esterna: la tabella interna punterà ad una riga inesistente.

Si possono allora adottare politiche differenti.

Con la clausola “Cascade” tutte le righe della tabella interna (quella che punta) che corrisponderanno ad
una riga cancellata nella tabella esterna (quella puntata), verranno a loro volta eliminate.

Con la clausola “Set null” se viene cancellato un attributo della tabella esterna coinvolto nel vincolo, i relativi
attributi della tabella interna saranno impostati al valore NULL.

In maniera analoga, con “Set Default” nella tabella interna viene impostato un valore di default per
l’attributo coinvolto nel vincolo nel caso in cui viene cancellato quello nella tabella esterna.

Con la clausola “No Action” si impedisce invece la cancellazione.


Con “alter domain” è possibile modificare un dominio precedentemente definito da noi.

Con la clausola “drop” possiamo cancellare dei vincoli, regole ecc...

Con “alter table” possiamo invece modificare una relazione.

Con drop possiamo eliminare uno schema, un dominio, una tabella, una vista, ed una asserzione.

“Restrict”: specifica che il comando viene eseguito solo in presenza di oggetti vuoti.

“Cascade”: vengono eliminati oggetti anche se non sono vuoti.


Un indice consente di rendere più rapida e agevole la ricerca su determinati attributi di una tabella.

Se ad esempio voglio sapere tutti i numeri di telefono che cominciano con “081” posso creare un indice
specificando il nome dell’indice e la tabella a cui fa riferimento con gli attributi coinvolti.

Tutti i DBMS gestiscono un dizionario dei dati, cioè una descrizione delle tabelle presenti nella base dati
mediante il modello relazionale. Sono dei metadati, cioè dati che descrivono altri dati.

L’insieme delle tabelle definiscono il catalogo della base dati.


Abbiamo visto che in algebra relazionale una medesima interrogazione è possibile formularla con
espressioni differenti. Anche con l’SQL è possibile ciò.

Con la clausola SELECT si specifica quali attributi deve restituire la relazione.

Nota: il risultato di una interrogazione è sempre una nuova relazione!

La clausola FROM serve a specificare da quali relazione bisogna prelevare quegli attributi.

Con la clausola WHERE si va a specificare le condizioni.


Con la clausola “as” andiamo a chiamare Stipendio in “Salario” nel risultato della query. È un alias.

Con from indichiamo da quale tabella dobbiamo estrarre l’attributo “Stipendio”.

Con “Where” andiamo a specificare la condizione che in questo caso è “cognome=’Rossi’”.

Nota: se si vogliono estrarre tutte le colonne di una relazione si utilizza “Select *” cioè, l’operatore asterisco.
Stiamo presupponendo che ci sia un legame tra la tabella impiegato e la tabella dipartimento dove
quest’ultima ha per chiave primaria l’attributo “Nome”.

Nella tabella impiegato non ho informazioni riguardo le città in cui lavorano gli impiegati ma ho informazioni
circa il dipartimento di appartenenza.

Nella tabella dipartimento ho invece le città di appartenenza dei vari dipartimenti.

Allora possiamo eseguire una giunzione (una join) tra la tabella impiegato e la tabella dipartimento:

Con la clausola “from” andiamo a specificare le tabelle coinvolte nella giunzione, mentre con la clausola
“where” si specifica l’uguaglianza fra gli attributi delle due tabelle sulla base della quale bisogna eseguire la
giunzione.

Con la “select” infine andiamo a estrarre le informazioni che ci interessano dalla giunzione, e cioè il nome,
cognome e le città.

Il risultato della query sarà:


Nota: nella select non andiamo a specificare l’attributo città a quale tabella appartiene, cioè non andiamo a
scrivere “Dipartimento.Città”, perché fra le due tabelle questo attributo compare una sola volta. Stessa cosa
potrei fare per l’attributo “Cognome”. Invece, per l’attributo nome è obbligatorio specificare a quale tabella
facciamo riferimento, perché entrambe le tabelle hanno un attributo denominato “Nome”.

La stessa query può essere scritta in maniera più compatta:

Dove si fa utilizzo di alias con la clausola “as”.

Nota: gli alias hanno vita solo nell’ambito della query.

Nella clausola where si vanno a specificare le formule proposizionali viste nell’algebra relazionale, cioè
quelle che ci permettono di impostare delle condizioni attraversi connettivi logici and, or e not.
In algebra relazionale tale interrogazione si scrive come:

Abbiamo prima eseguito una selezione (sigma) dalla tabella impiegato sulla base della formula
proposizionale per la quale deve risultare “Cognome=Rossi” and “Dipart=Amministrazione OR
Dipart=”Produzione”, ed infine viene eseguita una proiezione (Pigreco) sulla colonna Nome.

L’operatore like viene utilizzato specialmente nelle ricerche.

Nell’esempio vuol dire che stiamo prendendo tutti i cognomi che hanno come lettera iniziale un qualsiasi
carattere (underscore) come secondo carattere la lettera “o”, seguito da una stringa qualsiasi (%) e che
termina con la lettera “i”.
Considerando la tabella impiegato, supponiamo di voler estrarre gli impiegati il cui stipendio e maggiore di
40.

Nel caso di valori NULL dobbiamo specificare cosa deve fare il DBMS.

Se voglio prendere anche i valori nulli dovrò scrivere “where stipendio>40 and stipendio IS NULL”, viceversa
utilizzerò “IS NOT NULL”.

Nelle from una lista di tabelle equivale ad una join, la clausola where condizione si traduce in una selezione,
e la clausola select si traduce in una proiezione.
Nelle tabelle ci possono essere delle righe in cui un attributo assume lo stesso valore.

Possiamo fare in modo di prendere solo una volta i duplicati attraverso la clausola distinct: si antepone tale
clausola al nome dell’attributo che si sta selezionando.

Il modo più semplice per eseguire una join in SQL è con la virgola nella clausola from, che indica un join
naturale.

Un’alternativa è la seguente:

from t1 tipojoin join t2 on condizione

L’esempio in questo lucido è l’analogo di:


Il join esterno può essere destro, sinistro o “intero”.

Esempio: LEFT JOIN

Osserviamo che sull’attributo comune NroPatente le due tabelle non hanno una corrispondenza su tutti i
valori.

Il left join manterrà tutte le righe della prima tabella “Guidatore”, comprese quelle che non hanno una
corrispondenza sull’attributo comune con l’altra tabella. In questo caso sarà la riga relativa al numero
patente “AP 45444442R”. Le righe della seconda tabella che non hanno invece corrispondenza con quelle
della prima non saranno contemplate nel risultato.

Saranno dunque impostati valori “NULL” sugli attributi della seconda tabella, cioè su “Targa” “Marca” e
“Modello”.
Nel caso del full join, vengono messe nel risultato sia le righe della prima che le righe della seconda tabella
che non trovano corrispondenza sull’attributo comune, impostando a NULL i valori degli attributi
complementari.

Gli alias possono essere utilizzati sia nella select che nella from: nel primo caso saranno applicati ad un
attributo, mentre nel secondo caso ad una tabella.

È una ridenominazione che vale solo nell’ambito della query.


In tale esempio è come se sdoppiassi la tabella impiegato in due copie I1 e I2 e le “attaccassi” l’una accanto
all’altra in un’unica tabella.

A questo punto si vanno a scegliere quelle tuple il cui cognome è uguale, mentre i nomi sono diversi e il
dipartimento è “Produzione”.
21-04-2021

Abbiamo istruzioni che consentono di ordinare le righe di una tabella.

Si utilizza la clausola “order by” seguita dall’attributo sul quale si vuole applicare l’ordinamento ed infine si
specifica “asc” o “desc” se si vuole un ordine crescente o decrescente rispettivamente.

Nell’esempio vengono selezionate tutte le righe della tabella automobili e ordinate sull’attributo “Marca” in
maniera decrescente.

Gli operatori aggregati permettono di calcolare dei valori.

Count permette di contare il numero di elementi.

Sum consente di calcolare la somma.

Max e min individuano il valore massimo e minimo di un attributo.

Avg viene utilizzato per calcolare la media su un attributo.


Distinct serve ad eliminare le tuple duplicate in una selezione. All invece va a prendere tutte le tuple tranne i
valori nulli.

Nota: in questo caso viene estratto un valore e non una relazione.

Max e min possono essere utilizzati anche per stringhe, mentre sum e avg ha senso applicarli solo su valori
numerici o intervalli temporali.
La prima query restituisce il valore massimo dell’attributo stipendio associato all’impiegato la cui età è
inferiore a 35 anni.

La seconda query restituirà un errore: questo perché nella clausola select non è possibile “mischiare” un
attributo normale con un operatore aggregato.

Per poter fare questo tipo di operazione si deve utilizzare una clausola di raggruppamento group by:

La query nell’esempio la possiamo pensare costituita da 3 passi differenti:

- Nel primo step vengono selezionate le colonne dipartimento e stipendio dalla tabella impiegato.
- Nel secondo step vengono raggruppate le tuple in base al tipo di dipartimento
- Nell’ultimo step viene eseguita la somma degli stipendi sulle tuple aggregate

Nota: tali step intermedi noi non li vediamo! Difatti non possiamo avere una relazione in cui una tupla è
costituita da più tuple (la tabella in mezzo).
Il group by può essere utilizzato anche senza operatori di aggregazione, per raggruppare ad esempio
determinati risultati.

Nel primo esempio stiamo selezionando la colonna ufficio dalla relazione impiegato e stiamo raggruppando
per dipartimento.

Tale query è però priva di senso. Vediamo il perché.

Il problema è che ad ogni valore di dipartimento corrispondono diversi valori di ufficio.


Viene introdotta la clausola having che viene utilizzata sempre con group by.

La clausola “having” consente di selezionare il singolo sottoinsieme.

La query in esempio la si può ancora dividere in 3 step:

- Seleziona le colonne “Dipart” e Stipendio dalla tabella “Impiegati”.


- Dopodiché le tuple vengono raggruppate per dipartimento.
- Vengono poi calcolate le somme degli stipendi sulle tuple aggregate.
- Infine, con la clausola “Having” vengono selezionate le tuple il cui stipendio è maggiore di 100

Tutto ciò fra parentesi quadre è opzionale.


Except viene utilizzato per fare la differenza, union per l’unione e intersect per l’intersezione.

Viene sempre eseguita una operazione di eliminazione dei valori duplicati, a meno che non venga utilizzata
la parola chiave “all”.

L’unione opera sulle righe: nell’esempio vengono unite in un’unica colonna nomi e cognomi degli impiegati.

La prima query seleziona i nomi, la seconda query i cognomi ed infine vengono presi solo i valori comuni
(l’intersezione appunto).

La query equivalente richiama due volte la stessa tabella considerandole come due tabelle differenti
assegnandogli due alias I1 e I2.
Tale query la possiamo vedere costituita da due query.

Vengono prima selezionati i nomi e i cognomi degli impiegati, ed infine viene adoperata la differenza, cioè,
vengono presi i valori diversi di nome e cognome.
Consideriamo la base dati in figura.

Abbiamo un vincolo di integrità referenziale, cioè un riferimento, tra la tabella Imp e la tabella Sedi
mediante l’attributo Sede, e un riferimento tra Sedi e Prog mediante l’attributo città.

Supponiamo di voler ottenere gli impiegati delle sedi di Milano.

Va a selezionare i valori di CodImp dalla tabella Imp per i quali i valori di sede sono contenuti nel risultato
della query nidificata: tale query fornisce i codici delle Sedi (dalla tabella Sedi) che si trovano a Milano.

Quella in alto a destra è il risultato della query nidificata. Da tale risultato vengono presi i relativi codici degli
impiegati che lavorano in quelle sedi.
Le query nidificate possono restituire uno o più valori. Alcuni operatori di confronto (=, >,<) possono essere
utilizzati solo se la subquery restituisce un’unica tupla. In tal caso si parla di subquery scalare.

Quando il risultato di una subquery produce più tuple, vengono utilizzati oltre gli operatori di confronto
tradizionali le parole chiave “ALL”, “ANY”, “IN”, “EXISTS” e “NOT EXISTS”.
“op” rappresenta un operatore di confronto.

Nel primo esempio la query interna seleziona la sede degli impiegati il cui stipendio è maggiore di 1500.

Tale query interna restituisce quindi due tuple.

A questo punto la query più esterna seleziona i responsabili delle Sedi la cui Sede appartiene almeno ad uno
dei due valori restituiti dalla query interna.

Nel secondo esempio la query interna seleziona tutta la colonna stipendio della tabella Imp, e poi seleziona
i codici degli impiegati il cui stipendio è minore-uguale di tutti i valori restituiti dalla query interna.
La query interna seleziona i nomi dei dipartimenti situati nella città di Firenze.

La query esterna seleziona tutte le tuple della tabella Impiegato tale per cui il dipartimento di appartenenza
è almeno uno fra i dipartimenti della città di Firenze.

La query esterna seleziona i nomi dei dipartimenti diversi da tutto ciò che restituisce la query interna. La
query interna Seleziona i dipartimenti dalla tabella impiegato il cui cognome associato è Rossi.

Quindi complessivamente vengono resistititi i nomi dei dipartimenti in cui non lavorano persone di
cognome rossi.
Le variabili di range sono gli alias definiti nell’interrogazione esterna. Se tale alias non viene utilizzato nella
subquery allora abbiamo una interpretazione semplice, altrimenti si parlerà di query con correlazione.
Si ha una correlazione quando nella query interna si ha un riferimento al contesto dell’interrogazione
esterna.

La query interna utilizza una variabile di range P che viene utilizzata nella query esterna.

In tale esempio vengono estratte le tuple dalla relazione Persona che hanno stesso nome e cognome ma
codice fiscale differente.

“Exists” restituisce un valore vero solo se la query restituisce una relazione non vuota.

Per ogni riga della query esterna viene eseguita la query interna nel caso delle interrogazioni con
correlazione.
La correlazione è mediante la variabile di range “P”.

Per ogni riga esaminata nell’ambito dell’interrogazione esterna deve essere valutata la query interna.

La query interna è costituita dalla disgiunzione (OR) di due query.

Tale query è sintatticamente errata e produce un errore: una variabile di range dichiarata in una query
interna non può essere utilizzata in una query dello stesso livello. Non posso utilizzare D1 nella seconda
subquery (quella dopo l’OR) perché non esiste in quel contesto. Questo perché sono in disgiunzione
mediante l’operatore logico OR.

È come se fossero due funzioni separate.

L’ultima query (Blu) seleziona il nome dalla relazione autore dove l’attributo canzone assume lo stesso
valore di C.Canzone dove C è un alias di Cantante. Seleziona l’autore di una canzone che è anche cantante di
quest’ultima.

Nota: in questo caso posso utilizzare l’alias C nell’ultima query perché essa si trova ad un livello più basso
rispetto alla query in cui esso è definito. Nell’esempio di prima invece avevamo due query allo stesso livello
per via dell’operatore OR.
Nel secondo esempio stiamo inserendo in maniera automatica dei valori.

La differenza tra delete e drop è che delete cancella il contenuto delle righe ma mantiene la struttura della
tabella, mentre drop cancella sia lo schema che il contenuto. Non esisterà più quella determinata relazione.
23-04-2021

Tali vincoli vengono specificati in alternativa ai vincoli di default offriti da SQL.

Viene utilizzato nel momento in cui viene creata una tabella.

Dopo la clausola check viene specificata la condizione basata su un predicato, così come viene fatto con la
clausola where.

La clausola check agisce su un vincolo della tabella, e in fase di inserimento di un valore di quell’attributo la
clausola check deve essere sempre verificata.
Il vincolo che deve essere applicato sull’attributo cognome è che esso inizi con la lettera “C”.

Il secondo vincolo è che lo stipendio annuale deve essere compreso fra 1 e 999.

Verranno accettate dal DBMS soltanto le tuple che rispettano entrambi i vincoli.

La stessa cosa vale anche in fase di aggiornamento.

I vincoli di check vengono valutati non in fase di inserimento, ma immediatamente dopo. Viene prima
inserita la tupla, e poi si effettua la verifica.

Se il vincolo non viene rispettato si esegue una operazione di rollback parziale.


Il cognome dell’impiegato non inizia con la lettera “C”.

L’inserimento viene dapprima effettuato ma successivamente il DBMS controlla le condizioni e quindi andrà
ad eliminare la tupla: viene eseguito un rollback parziale.

È un costrutto molto potente perché ci consente di creare un vincolo qualsiasi nelle tabelle.

La condizione può riferirsi anche ad attributi di tabelle differenti.


La keyword check non deve essere necessariamente posto subito dopo il nome dell’attributo, ma anche alla
fine.

In tale esempio il cognome deve iniziare con “C” e inoltre il dipartimento che si va ad inserire nell’attributo
“Dipart” deve essere contenuto nella tabella dipartimento (che è un’atra tabella!).

In questo caso il vincolo controlla se nella tabella impiegato il distinct di dipartimento + il numero di nomi
del dipartimento sono inferiori a 100. In altri termini i record della tabella impiegato vengono accettati solo
se il numero di dipartimenti contenuti nella tabella dipartimento è inferiore a 100.

La tabella dipartimento non è legata a quella impiegato: se però inserisco il 101-esimo dipartimento nella
tabella dipartimento da quel momento in poi non posso inserire più impiegati perché il vincolo di check sarà
sempre falso. Ciò rappresenta una anomalia.

Quello che si fa è utilizzare una asserzione con la clausola assertion.

Le asserzioni non fanno riferimento al contenuto delle tabelle ma al loro schema.

Consentono di specificare dei vincoli.


L’asserzione consente di scorporare un vincolo dalla definizione della tabella: non viene definito nella
tabella ma direttamente nella base dati. Fa parte dello schema della base dati.

Il vantaggio è che in questo caso il vincolo viene verificato in fase di inserimento, mentre con la clausola
check definita nella tabella subito dopo l’inserimento.

L’attributo supervisore punta alla tabella impiegato (alla sua chiave primaria).

Viene creata un’asserzione denominata EtàSupervisore.

Con la clausola check vado a verificare che l’età dei supervisori sia inferiore di 30: seleziono l’età relativa agli
impiegati che sono supervisori (I.ID=D.SupervisoreID).
Le politiche di controllo vengono applicate sia sulle asserzioni che su vincoli generici con la clausola check.

I vincoli immediati vengono controllati immediatamente all’inserimento/modifica di una tupla. Se non è


soddisfatto, anche in caso di asserzioni, si ha il rollback parziale.

I vincoli differiti sono verificati al termine di una serie di operazioni: se le operazioni sono più di una si tratta
di transazione. Tale controllo differito viene utilizzato per far sì che la base dati non si trovi mai in uno stato
inconsistente.

Impiegato punta a dipartimento e dipartimento punta a impiegato mediante vincoli di integrità referenziale.

Se il controllo del vincolo fosse immediato, quando inizialmente le due tabelle sono vuote, ogni qualvolta
provo ad inserire una tupla tale operazione genera un errore perché il vincolo non verrà mai rispettato in
questo modo. Un attributo non vuoto punterà ad uno vuoto e viceversa.
Nel caso di vincolo differito non è possibile individuare l’operazione che ha causato la violazione: ciò che
accade è che, a differenza dell’immediato che esegue un rollback parziale, si ha un rollback totale dell’intera
transazione, cioè, vengono annullate tutte le operazioni che sono state eseguite contemporaneamente.

Le viste si distinguono in materializzate e virtuali.

Quelle materializzate sono effettivamente memorizzate nella base dati, mentre quelle virtuali
(maggiormente utilizzate) non vengono memorizzate ma vengono calcolate ogni qual volta che viene
utilizzata.

Il DBMS fa in modo che la vista venga aggiornata.

Il “SelectSQL” indica una interrogazione SQL.

Ciò che è fra parentesi quadre è opzionale.

La parola chiave with serve a specificare un check, e se la vista deve essere di tipo “Locale” o a “Cascata”.
Una vista può contenere altre viste così come le interrogazioni possono essere nidificate.

La vista è come una nuova tabella. Vengono quindi specificati i nomi degli attributi.

Dopo la parola chiave “as” viene specificata la query SQL.

In tale esempio la vista ImpiegatiAmmPov fa utilizzo della vista creata precedentemente.

“With check option” consente di modificare la vista a condizione che la n-pla continui ad appartenere alla
vista, cioè tale che lo stipendio sia ancora inferiore a 50.
Nel primo riquadro viene creata una vista che ha due colonne NomeDip e NroUffici. Il numero di uffici viene
ottenuto mediante l’istruzione count(distinct Ufficio) per contare il numero di Uffici senza ripetizioni.
Posso modificare una vista solo se viene rispettato il vincolo di integrità con la tabella/tabelle da cui deriva,
e solo se posso immaginare di apportare tale modifica alle tabelle da cui deriva.

La vista viene trattata esattamente come una relazione.

La tupla viene correttamente inserita nella vista e viene altresì inserita nella relazione Impiegato da cui
deriva, in maniera tale da mantenere la base dati coerente.

Nel secondo esempio la tupla viene inserita nella tabella sottostante ma non nella vista che accetta soltanto
stipendi maggiori di 10: ciò solo se c’è un check option nella vista.

SQL ci consente di utilizzare delle funzioni che possono essere:

- Predefinite
- Definite dall’utente, così come si fa nei linguaggi di programmazione
Nell’esempio la funzione coalesce esegue una ricerca nell’attributo Dipart della stringa ‘Ignoto’.

La funzione Nullif accetta due parametri: il primo è il nome dell’attributo e il secondo è il valore che si vuole
in esso ricercare. A differenza del coalesce, se l’espressione è uguale al valore costante restituisce il valore
null, altrimenti restituisce il valore dell’espressione. Restituisce “Null” quando DIpart assume il valore
‘Ignoto’, altrimenti restituisce il valore dell’espressione.
Della funzione case ne esistono due versioni riportate in figura.

Esempio: Case prima versione

“end as Tassa” fa sì che il risultato di tale funzione condizionale (dei vari when e else) venga memorizzato
nell’alias “Tassa”.

Esempio: case seconda versione


L’attributo stipendio viene aggiornato sulla base dei vari when – then (quando – allora).

Con “else Stipendio” restituisce lo stipendio così com’è, cioè non apporta alcuna modifica e poi termina.

Sono funzioni che vengono memorizzate nel database come se fossero delle tabelle.

La sintassi di tali procedure varia al variare del DBMS, ma il funzionamento logico è lo stesso.

Consente di arricchire le funzionalità offerte dal DBMS.


27-04-2021

Le basi dati senza costrutti dinamici sono delle basi dati passive, cioè le modifiche vengono fatte solo da un
amministratore.

Possiamo rendere però una base dati attiva, cioè in grado di agire ad eventi interni al DBMS definiti
dall’utente: tale meccanismo viene detto trigger.

Il trigger segue delle regole particolari basate sul paradigma Evento Condizione Azione: quando si verifica
un certo evento quando la condizione è vera verrà intrapresa una certa azione.
Un evento che scatena l’azione del trigger potrebbe essere una modifica dei dati nel database: inserimento,
aggiornamento e cancellazione.

Quando si verifica l’evento scatenante il trigger si attiva ma l’azione specificata nel trigger viene eseguita
sulla base della condizione: essa viene valutata e il trigger si dice che è considerato. Il trigger però viene
sempre attivato dopo l’evento scatenante.

Quando l’azione è eseguita il trigger si dice eseguito.

La tabella di destinazione sulla quale viene azionato il trigger viene detta target.

L’istanza della tabella viene congelata prima e dopo l’evento: questo perché possono nascere dei problemi
durante l’esecuzione di un trigger e quindi può essere necessario eseguire un ripristino.

Con evento si intende quello scatenante.

Il timestamp di creazione serve per dare la priorità ad un trigger quando più di uno deve essere eseguito
simultaneamente.
La parte con “as” è la parte di aliasing.

Con for each si specifica la granularità.

Con “statement procedurale SQL” si intende ciò che deve essere fatto dal trigger. Tutto ciò che c’è prima
definisce il trigger.
Primo esempio:

Impiegato è la tabella target. “Before” è la modalità, “update” è l’evento scatenante, “of Stipendio” specifica
la colonna da modificare, “for each row” specifica la granularità del trigger che è in questo caso a livello di
riga, “when” specifica la condizione di attivazione, e l’ultima riga è lo statement procedurale, cioè, ciò che
deve essere eseguito.

Siccome c’è la clausola “before” tale trigger agisce prima dell’update e della verifica di integrità. Condiziona i
valori usati da un’operazione di modifica: esegue una limitazione su un attributo.

Secondo esempio:

In questo caso viene utilizzata la clausola “after”. Il trigger reagisce ad una modifica (update in questo caso):
prima faccio l’aggiornamento e poi il trigger viene azionato.

Nell’esempio precedente viene azionato invece prima della modifica (dell’update).

Nel primo esempio viene sostanzialmente verificata prima dell’update la condizione specificata col “when”:
se è vera allora viene eseguita l’azione specificata dal trigger ancor prima della modifica (l’ultima riga).
Condiziona l’update.

Nel secondo esempio viene invece prima eseguito l’update e poi verificata la condizione del trigger ed
eventualmente intrapresa la relativa azione.
Di default la granularità è a livello di statement: significa che il trigger viene considerato ed eseguito una
sola volta per ogni statement, cioè per ogni istruzione che lo ha attivato. Se ad esempio stiamo eseguendo
due update consecutivi, il trigger viene eseguito una sola volta per ciascuno di essi indipendentemente dal
numero di tuple modificate.

Nella modalità row-level il trigger viene considerato ed eseguito per ciascuna tupla modificata,
indipendentemente dall’istruzione che stiamo eseguendo (update, delete ecc..)

La clausola referencing dipende dalla granularità.

Se è di tipo row-level ci sono due variabili di transizione: la riga vecchia e la riga nuova.

Se facciamo riferimento agli statement ci sono due variabili di transizione: la tabella vecchia e la tabella
nuova.

Tali variabili di transizione vengono “congelate” in caso di eventuali recovery.


DB2 è il DBMS di IBM, che è stato il primo a definire questa tipologia di struttura.

Con “signal sqlstate ‘70005’ si genera un messaggio di errore.

E’ un trigger di tipo before che impedisce la modifica dell’attributo “Fornitore” se non ponendolo al valore
NULL. Se il valore è diverso da null viene sollevata una eccezione ed eseguito un rollback, cioè, torna allo
stato precedente.

La variabile N si riferisce alla nuova riga, cioè a quella modificata.

In questo caso il trigger è di tipo after: registra nella tabella audit il numero di tuple modificate nella tabella
Parte.
Siccome in un DBMS possono esserci più trigger associati ad uno stesso evento, vengono eseguiti prima i
trigger BEFORE statement-level, poi BEFORE row-level, successivamente gli AFTER row-level ed infine gli
AFTER statement-level.

Cosa importante da ricordare è che in caso di errore viene eseguito un rollback parziale o totale.
La parte che si differenzia dai trigger DB2 è la parte procedurale (al di sotto di for each row) nella quale c’è
la parte SQL: la sintassi SQL è inglobata in un linguaggio procedurale (PLSQL).

L’evento sul quale viene verificato il trigger è l’update, la condizione è specificata dalla when e la condizione
è specificata da insert into.

Viene dichiarata una variabile di tipo numerica che va a selezionare il numero di ordini esterni per i quali
l’attributo parte è uguale a new.Parte.

Se X=0 (se il numero di ordini esterni è 0) allora va inserire all’interno un ordine esterno (un ordine
pendente).

La granularità è a livello di tupla (for each row), ed è di tipo after quindi viene valutato subito dopo la
modifica.

Inizialmente la tabella OrdiniEsterni è vuota.


Quando parte = 1 va a selezionare la prima tupla di magazzino e alla quantità disponibile va a sottrarne 70.
La nuova quantità disponibile è dunque 130.

Ciò che accade è che è verificata la condizione del trigger e quindi viene attivato: il trigger allora va a porre
una nuova tupla in ordini esterni.

In questa seconda transazione la modifica viene eseguita su tutte le tuple di Magazzino (where part<=3).

La condizione del trigger viene verificata per tutte le tuple alla quale è applicato l’update, e sarà eseguito in
questo caso solo per la prima e per la terza tupla in quanto per esse si verifica che QtaDisp<QtaSoglia.
Terminazione: il trigger deve sempre terminare in uno stato finale corretto,

Confluenza: deve terminare in un unico stato corretto e consistente.

Determinismo delle osservazioni: il comportamento del trigger non deve essere aleatorio ma deve eseguire
sempre la stessa azione

Una base dati attiva reagisce dinamicamente a degli eventi provenienti dall’interno o dall’esterno. Sulla base
di tali trigger si realizzano dei diagrammi detti grafi di attivazione del trigger.

Ogni nodo rappresenta il trigger (la regola) e ogni arco la transizione che può portare da un trigger all’altro,

Tale grafo deve risultare aciclico: se ci sono dei cicli significa che il grafo potrebbe non terminare mai.
R1 rappresenta il trigger in esempio. L’unico attributo che viene considerato è lo stesso attributo che
determina l’attivazione del trigger: non si passerà mai ad un'altra regola ma viene richiamato sempre sé
stesso (è una regola ricorsiva)

Tale grafo potrebbe non terminare mai.

Se però lo si progetta in maniera corretta allora possiamo garantire la sua terminazione.

Con set Stipendio = 0.9 * Stipendio ogni volta lo si va a ridurre.

Se ad esempio lo stipendio è 1000, moltiplicato per 0,9 sarà 900. Ogni volta sarà diminuito fino a quando
non risulterà più maggiore di 100 e quindi il trigger non verrà più eseguito, terminando.

Se invece avessimo set Stipendio = 1.1 * Stipendio, lo stipendio aumenterà sempre e la condizione sarà
sempre verificata portando difatti il trigger a non terminare mai.
La clausola instead of è un ulteriore modalità dei trigger.

(Accenni)
Vediamo ora com’è possibile creare una profilazione e quindi autorizzare utenti ad eseguire determinate
operazioni rispetto ad altri utenti.
I ruoli sono una sorta di contenitori di privilegi, cioè una sorta di profilo al quale è associato una serie di
privilegi, e tale profilo può essere poi assegnato agli utenti.

28-04-2021

Consente di rendere una serie di operazioni indivisibile, cioè atomica.

Viene dichiarata con “start transaction” seguita dalle operazioni che si vogliono effettuare per poi terminare
con “commit work”;
Anche per le transazioni bisogna avere l’opportunità di compiere un rollback in caso di errori.

Una transazione può concludere con commit o rollback.

Nel caso di “commit” la transazione viene eseguita e c’è un salvataggio delle operazioni compiute, mentre
nel caso di rollback si ritorna allo stato prima della transazione.

Nell’esempio in figura abbiamo una transazione eseguita da 4 operazioni. La terza operazione però fallisce:
per evitare di trovare il DB in uno stato consistente si compie un rollback che consente di tornare allo stato
precedente del DB.
Una transazione è nello stato attivo fintanto che vengono eseguite operazioni di lettura/scrittura. Quando
termina si va in uno stato in cui si verifica che tutto sia andato a buon fine. Si esegue quindi un salvataggio
oppure un rollback e poi si termina.
Esempio di una transazione bancaria: prelievo da sportello automatico.

Se si verifica una anomalia bisogna tornare nello stato precedente (rollback), altrimenti si termina con
successo. O tutto o niente.

Se c’è un rollback l’utente non se ne accorge: vede tutto fermo perché l’utente vede o lo stato S0 o lo stato
S1. Le operazioni intermedie non sono visibili all’utente. Lo stato S1 sarà visibile all’utente solo dopo il
commit.

La proprietà di consistenza consiste nel fatto che lo stato in cui la transazione termina sia coerente con la
base dati progettata. Non devono essere violati i vincoli di integrità referenziali, di dominio, quelli fatti con la
clausola check, asserzioni ecc.

La violazione di vincoli si può avere in 2 modalità:

- Immediata: se violato un vincolo immediato il comando corrispondente deve ritornare subito un


codice di errore
- Ritardata: il controllo viene effettuato solo dopo il commit e quindi l’ultima operazione sarà il
rolback.
Tale proprietà agisce sui tempi di risposta.

Le transazioni possono accedere in parallelo al database, cioè contemporaneamente. Ad esempio,


prenotazione biglietti treni: più persone possono acquistare contemporaneamente dei biglietti. Uno stato
inconsistente potrebbe essere che vendo più biglietti di quelli disponibili se non aggiorno in maniera
opportuna il numero di biglietti disponibili.

Bisogna fare in modo che l’effetto delle transazioni eseguite contemporaneamente sia simile a quello che si
avrebbe se fossero eseguite in maniera sequenziale. Bisogna isolarle.

Qualsiasi risultato di una transazione deve essere mantenuto permanentemente nel database.

ESERCIZI A FINE PDF


Quando si sviluppa un software si hanno una serie di fasi. Prima della fase di scrittura di un codice c’è una
fase di progettazione e modellazione.

Abbiamo dunque un modello a cascata, cioè il susseguirsi di varie fasi. A partire da una fase si può sempre
tornare a quella precedente se necessario.

La prima fase è lo studio di fattibilità: a partire da un problema si cerca di capire i costi per risolverlo e a
quali fasi bisogna dare la priorità. Si può fare oppure no?

Dopodiché c’è una fase di raccolta e analisi dei requisiti nella quale si studiano le proprietà che dovrà avere
il sistema che stiamo sviluppando. Si raccolgono i requisiti che il sistema dovrà avere: possono essere
funzionali oppure no. I requisiti funzionali sono ad esempio le funzionalità del sistema. Un requisito non
funzionale potrebbe essere uno di tipo temporale come, ad esempio, la velocità del sistema nel compiere
determinate operazioni. Un altro requisito non funzionale potrebbe essere l’occupazione di memoria.

Successivamente c’è la fase di progettazione in cui si progetta appunto la base dati.

La fase di implementazione è la vera e propria fase di implementazione del sistema informativo e degli
eventuali sistemi web: scrittura del codice.

C’è poi una fase di validazione e collaudo in cui si verifica che ciò che è stato realizzato effettivamente
rispetti i requisiti preposti, ed infine c’è la messa in esercizio del prodotto. Anche se il prodotto è messo in
funzionamento, in caso di eventuali problemi, si ritorna alla fase precedente per risolverli.
Nello specifico, per la progettazione di una base dati si progetteranno i dati e le applicazioni.

Decomposizione: divide et impera. Si scompone il problema in sotto-problemi per avere una organizzazione
più accurata.

Generalità: se progetto un DB devo cercare di dare un criterio di generalità, cioè il database deve funzionare
indipendentemente dalla applicazione che lo utilizza e da linguaggio in cui è scritta.

La fase di progettazione è suddivisa in:

- Progettazione concettuale: risponde alla domanda “Cosa deve fare il sistema?”


- Progettazione logica
- Progettazione fisica

Nella progettazione logica e fisica ci si preoccupa invece di “Come farlo”.


In tale fase vengono inoltre specificati i vincoli di integrità referenziale, di dominio, di tupla ecc.

In questa fase viene prodotto uno schema concettuale che rappresenta una formalizzazione della realtà di
interesse. Ciò deve essere indipendente dal DBMS.
In tale fase si inizia un po’ a perdere l’indipendenza dal DBMS perché la progettazione logica la si fa in base
al modello dei dati utilizzato dal DBMS.
Parto dalla realtà di interesse con un’analisi dei requisiti. Svolgo una progettazione concettuale (modello E-
R) ottenendo uno schema concettuale. Tale schema viene dato in input alla fase di progettazione logica che
fornisce uno schema logico costituito da una serie di tabelle (relazioni). Tale schema logico lo si dà come
input alla progettazione fisica e si ottiene una rappresentazione sotto forma di file e di indici, cioè come
vengono memorizzate le tabelle nella memoria.

Ci consente di partire dalla realtà di interesse e di astrarla ottenendo uno schema concettuale costituito da
una serie di costrutti.
Il nome di una entità può essere costituito anche da più di un sostantivo.
La relazione ha una regola di lettura bidirezionale: vale sia in un verso che nell’altro.

Uno studente sostiene l’esame di un corso, così come in un corso gli esami sono sostenuti dagli studenti.

Una relazione binaria fa riferimento a due entità, ternaria a 3 entità, n-aria a n entità.
Una occorrenza è una coppia di valori che si verifica per ciascuna entità coinvolta. In una stessa relazione
non possono esserci relazioni che si ripetono.

Rappresentano il modo in cui le relazioni sono legate fra loro.

Nel secondo esempio due entità sono legate da due relazioni differenti.
Un impiegato è un collega di un altro impiegato. La relazione è simmetrica e ricorsiva.

Nel secondo esempio invece bisogna inserire anche dei ruoli per la relazione, in quanto letta in un verso ha
un significato e nell’altro un altro significato.

Un fornitore fornisce un prodotto e un prodotto viene fornito al dipartimento.

Letto più ad alto livello: un fornitore fornisce un prodotto al dipartimento.


La cardinalità indica quante occorrenze di una certa entità possono essere legata ad altre occorrenze di
un’altra entità mediante una relazione.

La cardinalità fornisce anche la partecipazione obbligatoria o meno


30-04-2021

Uno studente può aver sostenuto o mene l’esame di un corso così come per un corso può essere che
nessuno studente abbia sostenuto il relativo esame.

La cardinalità massima (N) dal lato studente si legge come: uno studente può sostenere esami per più corsi.

Dal lato corso la cardinalità massima si legge come: per un corso possono esserci N studenti che sostengono
quell’esame.

Una montagna può essere scalata da un alpinista fino ad N alpinisti (da sinistra verso destra), mentre
l’alpinista va a scalare almeno una montagna fino ad N montagne (da destra a sinistra).

Un macchinista è abilitato a guidare un locomotore fino ad N locomotori, mentre un locomotore è abilitato


ad essere guidato da almeno un macchinista fino ad N macchinisti.
Una persona può essere impiegata in un’azienda oppure no, mentre un’azienda può dare impiego a nessuno
fino ad N persone.

Il cinema è ubicato in un’unica località, mentre in una località possono essere ubicati più cinema o nessuno.

Un comune è ubicato in un’unica provincia mentre in una provincia possono essere ubicati almeno un
comune fino ad N.

Un professore può essere titolare di una cattedra, mentre una cattedra può essere titolata ad un professore.
Un professore di ruolo è titolare di un’unica cattedra, mentre la cattedra può essere titolata ad un
professore di ruolo al massimo.

Un professore di ruolo è titolare di una cattedra coperta, ed una cattedra coperta è titolata da un unico
professore di ruolo.

Un attributo può essere associato sia ad una entità che ad una relazione: è un valore appartenente al suo
dominio di riferimento che contiene valori ammissibili per quell’attributo.
Un attributo che è anche chiave primaria viene annerito, quindi avremmo dovuto colorare in nero sia la
matricola che il codice.
Un identificatore interno è sostanzialmente la chiave primaria.
L’automobile è identificata univocamente dalla targa, mentre la persona viene identificata univocamente da
nome, cognome e data di nascita (nella nostra realtà di interesse, ma in generale possono esserci persone
che hanno gli stessi valori su tali attributi).

In questo caso l’identificatore di “Studente” è legato con l’identificatore di “Università”.

Se ad esempio stessi rappresentando una base dati che riguarda università differenti, potrebbe essere che ci
siano numeri di matricola uguali. Pertanto, per identificare univocamente lo studente ho bisogno anche del
suo nome.

Dipartimento ha un identificatore esterno costituito dal nome del dipartimento e dalla città della sede.

Un impiegato può afferire ad un dipartimento e può dirigere un dipartimento (ed un solo). Ad un


dipartimento possono afferire da 0 a N impiegati, ma un dipartimento è diretto da un unico impiegato (c’è
un solo direttore).
Un impiegato può partecipare ad un progetto fino ad N progetti, mentre ad un progetto deve partecipare
almeno un impiegato fino ad N impiegati.

Un dipartimento appartiene ad un’unica sede, mentre ad una sede possono appartenere più dipartimenti.

Le entità E1… En sono specializzazioni di E così come E è generalizzazione di E1, …, En.

E è l’entità padre che generalizza le entità figlie.


È una sorta di gerarchia.

È l’analogo dell’ereditarietà della programmazione ad oggetti.


Ereditarietà: ogni attributo dell’entità padre (Dipendente) sarà attributo anche per le entità figlie.

Se specifico attributi per le entità figlie, questi NON appartengono all’entità padre.

In questo esempio, l’attributo Stipendio dell’entità lavoratore non appartiene all’entità persona.
Con la freccia piena si indica la generalizzazione totale ed esclusiva, mentre con quella vuota si indica quella
parziale ed esclusiva.

Uomo e Donna generalizzano Persona. In questo caso abbiamo una generalizzazione totale perché ogni
occorrenza di persona è almeno una occorrenza di Uomo o Donna, cioè una persona o è uomo o è donna. È
esclusiva perché non c’è una sovrapposizione, cioè una persona o è uomo o è donna.

In questo caso abbiamo una generalizzazione parziale perché un veicolo non è detto che sia
necessariamente una automobile o una bicicletta. È sempre esclusiva perché non possiamo avere una
sovrapposizione fra le entità figlie, cioè un veicolo non può essere contemporaneamente una automobile e
una bicicletta.

In questo esempio abbiamo una generalizzazione parziale e sovrapposta in quanto le persone non si
suddividono soltanto in studenti e lavoratori ed inoltre uno studente può essere anche un lavoratore.

Se considero una terza entità chiamata “Studente lavoratore” allora passo da una generalizzazione
sovrapposta ad una esclusiva.
Nota: gli attributi dell’entità Personale vengono ereditati dalle entità figlie Medico, Paramedico, e a sua volta
dalle entità figlie di Medico.

Un reparto afferisce ad uno ed un solo personale, mentre il personale può afferire ad uno o più reparti.

Per un reparto può esserci al più un primario, mentre tra il personale c’è uno ed un solo primario di un
reparto.

In un reparto possono esserci ricoverati nessuno o più pazienti, mentre un paziente può essere ricoverato in
uno o più reparti.
Persona è specializzata in uomo e donna ed è totale ed esclusiva, mentre è altresì specializzata in impiegato
e studente, ma in tal caso è parziale è sovrapposta.

Anche impiegato è specializzato in Segretario, Direttore e Progettista e stiamo assumendo che sia una
specializzazione totale ed esclusiva.
Esempio
Nota: a noi interessa e utilizzeremo il modello E-R.

Le entità vengono sostituite dalle classi nelle quali è riportato il nome e gli attributi.
Le associazioni (relazioni) vengono rappresentate da delle linee che uniscono le classi con il nome della
relazione.

L’asterisco serve a rappresentare la molteplicità “N”.

Una persona risiede in una città mentre in una città risiedono più persone (*).

Un turista può prenotare da 1 a molti viaggi, mentre un viaggio può essere prenotato da più turisti.

Gli identificatori (le chiavi) vengono evidenziati con la clausola {id}

Esercizi a fine slide.


Esempi di query SQL

Risultato:
04-05-2021
Le fonti per la raccolta dei requisiti possono essere eterogenee, cioè di diverso tipo: fogli cartacei,
informazioni verbali, regolamenti aziendali, normative ecc...
L’output di tale fase è lo schema concettuale: devo capire come tradurlo in un modello E-R.
Vengono utilizzati per rappresentare ed ottimizzare i modelli E-R, quindi parliamo sempre di progettazione
logica.

Sono strategie utilizzate per creare un modello E-R il più preciso possibile.
Supponiamo che voglio memorizzare in quale azienda lavora l’entità impiegato.

Posso rappresentare l’attributo “Azienda” di impiegato come una vera e propria entità legata ovviamente
all’impiegato da una relazione.

La reificazione consiste quindi nel trasformare l’attributo di una entità in una seconda entità legata a quella
da cui proviene mediante una relazione.
Con l’approccio Top-Down si parte da uno schema E-R completo che viene via via raffinato per ottenere uno
schema sempre più preciso.

Nell’approccio bottom-up si usa la strategia del divide et impera, cioè si vanno a creare delle porzioni dello
schema E-R che verranno poi unite ed integrate fra loro per ottenere lo schema E-R finale.

Nella strategia inside-out lo schema si sviluppa a macchia d’olio: si costruisce uno schema scheletro che
contiene le entità fondamentali e via via lo si arricchisce.
A partire da una entità la si sdoppia in due entità legate da una relazione.

Posso ad esempio sdoppiare l’entità impiegato nell’entità impiegato e l’entità capo legate dalla relazione
supervisione.

Ad esempio, a partire dall’entità “dipendente” posso specializzarla nelle entità “addetto produzione”,
“addetto vendite” ecc.

Consiste nello scomporre una entità in più entità che non sono però legate da alcuna relazione.
Ad esempio, potrei avere una entità “Sportivo” che posso decomporre in entità “Calciatore”, “Tennista”,
“cestista” ecc che per la nostra realtà di interesse non sono legate fra loro.

Consiste nel trasformare una relazione in più relazioni che sono legate comunque alle medesime entità di
partenza.

Una singola relazione viene sdoppiata in due relazioni che fanno riferimento ad una medesima entità.
Consiste nel raffinare una relazione andando a sviluppare degli attributi su di essa.

Stesso discorso per le relazioni.


A partire da un problema lo si scompone in frammenti via via più semplici fino a quando non sono più
frazionabili. Il problema diventerà quindi generare qualcosa.
Consiste nel generare una relazione che leghi due o più entità.

A partire da delle entità devo trovare l’entità padre da cui discendono.


Ho degli attributi, cioè delle proprietà significative che non hanno esistenza autonoma, ma che posso legare
attraverso una entità.

Ad esempio, posso immaginare di avere gli attributi “DataInizioRicovero” e “DataFineRicovero” che posso

legare mediante la relazione ricovero.


Si parte da uno schema E-R “scheletro” che contiene i concetti fondamentali e poi lo si arricchisce degli
aspetti secondari.
Sintattica: utilizzare solo i costrutti standard del modello E-R

Semantica: devono essere utilizzati propriamente.

Completezza: lo schema E-R deve rappresentare tutti i concetti della realtà di interesse.

Leggibilità: lo schema deve risultare facilmente leggibile.

Minimalità: assenza di ridondanze, ma non sempre è desiderata.

Lo schema E-R deve essere leggibile altrimenti diventa complesso tradurlo nello schema logico.
(Per questo capitolo vedi anche le slide, ulteriori esempi presenti - esercizi a fine slide).
A partire dello schema concettuale, cioè lo schema E-R, a seguito della progettazione logica si ottiene lo
schema logico che fa riferimento a delle tabelle.

Non si ha una descrizione concettuale ma una rappresentazione più concreta: si rappresenta il modo in cui
andiamo ad organizzare i dati, indipendentemente da come saranno fisicamente memorizzati.
Una entità con attributi è intuitivo pensare che si trasformi in una tabella dove ciascun attributo è una sua
colonna.

Vediamo come è possibile calcolare il carico di lavoro di una base dati a partire da uno schema E-R.
Chiaramente si tratterà di un calcolo approssimativo.
Si analizzerà il volume di dati, cioè il numero di occorrenze di ogni entità e relazione, e la dimensione di
ciascun attributo.

Inoltre, si analizza il tipo di operazioni, la frequenza delle operazioni, e il numero di dati coinvolti in ciascuna
operazione.

Faremo riferimento a tale schema E-R per il calcolo del carico di lavoro.

Il tipo indica se si tratta di una entità o di una relazione.

Il volume indica il numero di occorrenze per quella entità o per quella relazione. Ad esempio, il volume di
Sede è circa 10, cioè nella base dati saranno caricate circa 10 sedi.

In maniera analoga per le altre entità e relazione, ragionando sulla nostra realtà di interesse (ad esempio
una azienda di medie dimensioni).
La relazione di composizione ha un volume parti a quello dei dipartimenti perché dallo schema E-R vediamo
che un dipartimento appartiene ad un’unica sede. Siccome i dipartimenti sono circa 80 allora la relazione
“Composizione” sarà istanziata circa 80 volte.

Un impiegato afferisce al più ad un dipartimento (ma può anche non appartenere a nessuno). Siccome gli
impiegati sono circa 2000 allora il volume di afferenza possiamo ipotizzare essere circa 1900, andando a
togliere quei 100 impiegati che probabilmente non afferiscono a nessun dipartimento, e che quindi non
coinvolgono tale relazione.

La relazione “partecipazione” coinvolge le entità impiegato e progetto. Se consideriamo che in media un


impiegato partecipa a 3 progetti, allora su 2000 impiegati la relazione di partecipazione avrà 2000*3=6000
occorrenze.

A partire da impiegato vogliamo ottenere tutte le informazioni riguardanti il dipartimento nel quale lavora e
i progetti ai quali partecipa.

Uno schema di navigazione è la parte dello schema E-R coinvolta nell’operazione di interesse.

Impiegato è l’entità coinvolta, cioè quella di partenza.


L’operazione di scrittura è quella più onerosa.

Nella tabella vengono innanzitutto riportate le entità e le relazioni coinvolte nell’operazione di interesse (nel
nostro esempio ottenere tutti i dati dell’impiegato a partire dal suo codice in termini di progetti ai quali
partecipa e dipartimenti ai quali afferisce).

La relazione direzione non è coinvolta. (?)

A partire da un codice voglio ottenere tutti i dati dell’impiegato: chiaramente per l’entità impiegato eseguirò
un solo accesso in lettura.

Siccome un impiegato può afferire al più ad un dipartimento, allora per la relazione afferenza avrò al più un
accesso in lettura.

Stesso discorso vale per l’entità dipartimento.

Siccome un impiegato in media afferisce a 3 progetti (perché abbiamo 6000 istanze della relazione
partecipazione, e 2000 impiegati: 6000/2000 = 3 progetti in media) allora avremo 3 accessi all’entità
progetto e 3 accessi alla relazione partecipazione tutti in lettura.
SI parte dallo schema E-R tenendo conto del carico applicato, dopodiché si eseguono le seguenti operazioni:

- Analisi delle ridondanze: vediamo se ci sono dati ripetuti.


- Eliminazione delle generalizzazioni, perché non siamo in grado di rappresentarle sotto forma di
tabella
- Vediamo se ci sono entità e relazione che devono essere accorpate o divise
- Infine, andiamo a definire le chiavi primarie.

Si fa riferimento a dati che possono essere derivati da altri dati.

Il vantaggio di averli è che possono semplificare le query, mentre lo svantaggio è l’appesantimento degli
aggiornamenti e una maggiore occupazione di memoria.
L’importo netto è derivabile dall’IVA e dall’importo lordo mediante una operazione matematica.

Facendo riferimento alla sintassi SQL, la sintassi di INSERT si semplifica perché abbiamo un attributo in
meno. Stesso discorso per le operazioni di UPDATE.

Avere tale attributo può però semplificare una query: se volessi sapere l’importo netto potrei accedere in
maniera diretta ad esso, senza calcoli intermedi.

L’importo totale lo posso anche calcolare moltiplicando la quantità per il prezzo del prodotto di cui si
compone l’acquisto. In questo modo avremmo meno spazio occupato in memoria.
La relazione Docenza forma un ciclo perché posso passare da studente a professore mediante le entità e
relazioni intermedie.

Per l’analisi di una ridondanza ci serve la tavola dei volumi e la tavola delle operazioni.

Città e persona sono legate dalla relazione “Residenza”.

Per ciascuna operazione nella tavola delle operazioni sono riportate le stime della loro frequenza.
La ridondanza è rappresentata dall’entità città.

Delle operazioni a noi interessa la loro frequenza.

Siccome l’accesso in scrittura è più oneroso rispetto ad un accesso in lettura, consideriamo l’accesso in
scrittura con un costo doppio.

Con ridondanza l’occupazione in memoria è maggiore, però con ridondanza vengono eseguiti meno accessi
in memoria (in lettura e in scrittura).
Con identificatore primario si intende la chiave primaria.

Quando viene utilizzata nell’ottica di foreign key essa va a realizzare dei legami logici fra entità, pertanto la
scelta deve essere accurata.
Si va a scegliere sempre la superchiave minimale, cioè quella costituita dal minor numero di attributi.
Questo perché avere chiavi primarie basati al più su due valori ci porta a generare indici ridotti: gli indici
consentono di effettuare delle ricerche più veloci.

Quando è difficile trovare un identificatore possiamo introdurre un ID, ad esempio, di tipo


AUTO_INCREMENT.

Sono degli identificatori simili agli indici primari utilizzati per semplificare delle query.
Il problema delle gerarchie (generalizzazione – specializzazione) è che è difficile tradurle in tabelle. Quello
che si fa in tal caso è eliminarle sostituendole con entità e relazioni, sulla base di 3 possibili scenari.
L’accorpamento delle entità figlie nell’entità genitore consiste nell’accorpare gli attributi delle figlie tra quelli
delle entità padre, e le relazioni delle figlie saranno ora attribuite all’entità padre.

Si introduce poi un attributo TIPO che tiene conto delle specializzazioni dell’entità padre.

Laureando e Tirocinante vengono accorpate nelle entità padre che assorbirà i suoi attributi ed inoltre per
essa sarà introdotta una nuova variabile Tipo che tiene conto delle specializzazioni, cioè se è uno studente
laureando oppure uno studente tirocinante. Le relazioni nelle quali erano coinvolte le entità figlie vedranno
ora coinvolte l’entità padre.

Osserviamo che per gli attributi assorbiti dal padre viene introdotta una cardinalità, che può essere al più
pari ad 1 per indicare il fatto che lo studente può avere uno di quegli attributi in base al fatto che sia
Laureando o Tirocinante. Stesso discorso vale per le relazioni con le entità Docente e Azienda: non sono
obbligatorie perché il tutto dipende dall’attributo Tipo.
07-05-2021

In questo caso l’entità padre viene accorpata nelle entità figlie, e queste ultime vengono legate alla
relazione alla quale era legata l’entità padre mediante due relazioni R11 e R12. Infine, gli attributi dell’entità
padre vengono assorbiti dalle entità figlie.
L’entità padre studente viene accorpata alle entità figlie “Iscritto” ed “Erasmus”, le quali assorbono i suoi
attributi. Queste due relazioni figlie vengono poi legate, mediante due relazioni, all’entità alla quale era
legata l’entità padre.

Tale trasformazione è possibile solo se la generalizzazione è totale ed esclusiva. Se così non fosse
potrebbero esserci alcune occorrenze dell’entità padre che non sono presenti fra le occorrenze dell’entità
figlie, e quindi il collasso verso il basso non è applicabile.
La terza trasformazione prevede di legare l’entità padre e le entità figlie mediante delle relazioni per ognuna
di esse. Siccome le entità figlie specializzano l’entità padre, bisogna introdurre un identificatore esterno. La
cardinalità deve essere inoltre di tipo uno ad uno fra l’entità figlia e la nuova relazione, mentre deve essere
di tipo 0 a 1 fra la relazione e l’entità padre.
Dipende dal tipo di generalizzazione-specializzazione in questione, anche se la prima tipologia è sempre
applicabile.
In questo esempio andiamo ad accorpare l’entità E1 nell’entità padre (collasso verso l’alto), mentre per
l’entità E2 abbiamo eseguito la trasformazione 3, cioè, è stata legata al padre mediante la relazione RG2.

La cardinalità (0,1) di A11 dipende in base al valore di tipo, quindi non deve essere obbligatoria.

Iscritto viene assorbito dall’entità padre Studente, mentre Erasmus viene legata al padre mediante la
relazione “Erasmus” e trasformata in “StudenteErasmus”.
È possibile dividere o accorpare entità e relazioni, se queste sono omogenee fra loro.

Nel partizionamento verticale si dividono gli attributi. Creo due (o più) entità alle quali posso assegnare un
sottoinsieme di attributi a partire da quelli di partenza.

Tali entità sono legate fra loro da una relazione.

La cardinalità deve essere (1,1) per entrambe le entità che partecipano alla relazione. Questo perché siamo
partiti da una medesima entità e non avrebbe rendere facoltativa la partecipazione.
Per eliminare l’attributo telefono, lo vado a trasformare in una nuova entità “Telefono” la cui chiave
primaria è il numero. Tale entità è legata a quella di partenza mediante una relazione con partecipazione
obbligatoria. L’entità di partenza è coinvolta nella relazione con cardinalità (1, N).

Perché va fatta questa ristrutturazione? Perché, se un attributo ha una cardinalità maggiore di 1, vuol dire
che nella relativa tabella dovrei avere più colonne per uno stesso attributo, cosa non possibile.
Le due entità coinvolte nella relazione rimangono invariate, ma si sdoppia la relazione.

Ciò che cambia è la cardinalità minima. Per la composizione attuale la partecipazione è obbligatoria, mentre
per la composizione precedente della squadra è facoltativa (quel giocatore potrebbe non esserci più).

La fase di ristrutturazione è obbligatoria ai fini di ottenere lo schema logico, in quanto determinati tipi di
costrutti dello schema E-R non sono rappresentabili nello schema logico.

Nello schema logico dalle entità si ottengono delle tabelle. Per tener conto dei legami fra tabelle si
utilizzeranno gli identificatori esterni (foreign key), ovvero i vincoli di integrità referenziale.
Saranno trasformate in tabelle le entità e le relazioni dello schema E-R ristrutturato.

Le tabelle ingloberanno gli identificatori di tutte le entità collegate, cioè, diventeranno degli attributi per
quella tabella.
È il tipo di traduzione meno problematico.

Una relazione molti a molti si traduce in una tabella con eventuali attributi, nel caso in cui la relazione ne
abbia. Fra tali attributi rientrano le chiavi primarie delle entità coinvolte in tale relazione. Tali chiavi
primarie, prese entrambe, rappresenteranno la superchiave per questa nuova tabella.

Per le entità avremmo due tabelle i cui attributi sono i loro attributi nello schema E-R.

La relazione si trasforma anch’essa in una tabella i cui attributi sono i suoi attributi più le chiavi primarie
delle due relazioni che essa lega. La coppia di tali chiavi primarie sarà per questa nuova tabella una
superchiave.

Sulle chiavi primarie può essere anche eseguita una ridenominazione per far capire meglio a cosa si riferisce
quell’attributo (ad esempio: Matricola in MatricolaImpiegato).

La matricola della tabella Partecipazione è legata alla matricola della tabella impiegato, e l’attributo codice
della tabella partecipazione è legato all’attributo codice della tabella progetto. Sono dei vincoli di integrità
referenziale fra tali tabelle, cioè, sono degli identificatori esterni per la tabella Partecipazione.
In questo caso la ridenominazione delle chiavi primarie per la tabella relativa alla relazione è obbligatoria, in
quanto avremmo altrimenti due attributi col medesimo nome.

La chiave primaria di prodotto viene presa due volte, perché tale relazione si legge in due versi.
Nota: tutte e tre le chiavi primarie sono state ridenominate nella tabella Fornitura. Tutte e tre
rappresentano una superchiave per quest’ultima.

Un giocatore gioca in un’unica squadra, mentre in una squadra possono giocare fino ad N giocatori.

Per giocatore la chiave primaria è l’insieme degli attributi Data nascita e Cognome.

Le occorrenze della relazione o sono tante quante sono quelle dell’entità se la partecipazione è obbligatoria,
oppure al massimo quelle dell’entità se la partecipazione è opzionale.

La relazione Contratto si traduce in una nuova tabella che ha come attributi la chiave primaria di giocatore
(che è costituita da Cognome ridenominato in Giocatore e Data nascita ridenominato
DataNascitaGiocatore), di squadra e i suoi eventuali attributi.

Il vincolo di integrità referenziale sussiste fra la relazione Contratto e la relazione Giocatore mediante le loro
chiavi primarie.

Per la tabella relativa alla relazione la chiave primaria sarà la chiave primaria della relazione “a uno”
Studente è identificato dalla Matricola e dal Nome dell’università. C’è quindi una chiave esterna.

Adottando la soluzione compatta, prendo la chiave della relazione “a N” e la metto nella relazione “a 1”
diventando così una chiave esterna.
Nelle relazioni uno a uno posso inglobare la chiave di una delle due relazioni nell’altra relazione in maniera
indifferente.
La tabella direttore contiene, oltre che i suoi attributi, il nome del dipartimento che è chiave primaria e poi
l’attributo della relazione (sono state effettuate ridenominazioni per questi ultimi).

In questo caso nella tabella dipartimento inglobo la chiave primaria di Direttore (codice ridenominata in
Direttore) e l’attributo della relazione.
Nella terza soluzione creo una tabella anche per la relazione, la cui chiave primaria sarà rappresentata da
solo una delle chiavi primarie delle due relazioni (in questo caso Codice ridenominato in Direttore)

Nella quarta soluzione nella tabella della relazione scelgo come chiave primaria la chiave primaria dell’altra
relazione (Il nome del dipartimento ridenominato in Dipartimento).

La terza e quarta soluzione sono le soluzioni non compatte, mentre le prime due quelle compatte.
Le frecce indicano i vincoli di integrità referenziale.

Impiegato ha i suoi attributi e chiave primaria. Siccome ha anche una relazione uno a uno con direzione
allora posso mettere in impiegato la chiave primaria di dipartimento che è costituita dal suo nome e dalla
città della sede. Tali attributi sono chiavi esterne.

(Rivedere questo esempio se necessario)


11-05-2021

Possiamo utilizzare tale schema come riferimento per esercizi.

E3 ed E4 sono in relazione “molti a molti” e quindi le relative tabelle sono costituite solo dagli attributi
propri.

Per E2 invece abbiamo anche un identificatore esterno: la sua chiave prima e l’insieme delle chiavi primarie
di E1 ed E5.
L’asterisco denota che su determinati attributi potrebbero esserci valori nulli dovuti alla partecipazione
opzionale.

Anche per gli schemi logici è possibile scrivere una documentazione ad essa associata. Essa si basa sulla
rappresentazione degli schemi delle tabelle in cui vengono specificati i vincoli di integrità referenziale, le
chiavi primarie ecc.
Esempio

Esercizi a fine slide.

Dopo la traduzione del modello concettuale in modello logico, c’è un ulteriore analisi che è possibile
effettuare prima di implementare il database. Si tratta di una serie di ottimizzazioni: serve a risolvere una
serie di eventuali anomalie che potremmo generare quando realizziamo il nostro sistema informativo.
Possono portare alla comparsa di una serie di valori nulli che causano problemi nella gestione del sistema
informativo.
La presenza di ridondanze può essere un aspetto positivo quando si effettuano determinate query, ma può
rappresentare un aspetto negativo durante le operazioni di aggiornamento (aggiornamento, inserimento,
cancellazione).

La fase di normalizzazione può essere applicata o direttamente sul modello E-R oppure sul modello logico.

Nela tabella osserviamo la presenza di righe ripetute o, meglio, la presenza di uno stesso impiegato che
partecipa a diversi progetti.

La chiave primaria è però la coppia (Impiegato, Progetto) ed è sempre univoca.

Osserviamo che:

- Lo stipendio per ogni impiegato è unico: è indipendente dai progetti a cui partecipa un impiegato.
Lo stipendio è funzione dell’impiegato.
- L’attributo bilancio è sempre lo stesso per ogni progetto: se nella tabella abbiamo valori ripetuti del
progetto avremo anche valori ripetuti del bilancio. Il bilancio è funzione del progetto.
- Ciascun impiegato per ogni progetto svolge un’unica funzione: l’attributo “Funzione” dipende (è
funzione di) dall’impiegato e dal progetto.

Abbiamo quindi dei valori duplicati, cioè delle ridondanze.

Vediamo le anomalie che possono presentarsi nelle operazioni di aggiornamento.


Supponiamo di dover aggiornare lo stipendio dell’impiegato “Neri”.

Dovrò andare ad aggiornare 3 record: abbiamo una anomalia di aggiornamento, cioè, replicare uno stesso
valore su 3 record differenti.

Stesso discorso nella cancellazione di un Impiegato: supponendo che “Neri” non partecipi più al progetto
“Venere”, “Giove” e “Marte” dovremo allora cancellare 3 record. Abbiamo una anomalia di cancellazione.

Se volessi poi inserire un impiegato che non partecipa a nessun progetto, questo non potrei farlo in quanto
dovrei inserire un valore NULL per l’attributo “Progetto” che è parte della chiave primaria. Ho quindi una
anomalia di aggiornamento

Se tale relazione è stata ad esempio ottenuta mediante una serie di join che non viene memorizzata nel
database, allora non rappresenta un problema.

Se però tale relazione è stata però prevista dal modello E-R ed è stata tradotta in una tabella nella
progettazione logica, allora così non è.

Il problema è che è stata utilizzata un’unica tabella (un’unica relazione) che contiene informazioni che tra
loro non sono omogenee.
Gli impiegati vengono memorizzati con i relativi stipendi, i progetti con i relativi bilanci, e le partecipazioni
degli impiegati ai progetti con le relative funzioni. Tutte queste informazioni sono però eterogenee fra loro.

Un attributo si dice primo se appartiene alla chiave primaria, viceversa si dirà non primo.

La dipendenza funzionale è un vincolo che andiamo ad aggiungere e rappresenta i legami che esistono fra
gli attributi di una relazione.

Per l’esempio visto prima possiamo esprimere una serie di proprietà riportate in slide.
Lo Stipendio ha un legame con l’Impiegato.

Il Bilancio ha un legame con l’attributo Progetto.

La Funzione ha un legame con l’Impiegato.

Allora esiste una dipendenza funzionale (un legame logico) che associa ai valori dell’attributo Impiegato i
valori dell’attributo Stipendio.

Il valore dell’attributo progetto determina il valore dell’attributo bilancio.

Questa dipendenza funzionale sussiste però solo perché i valori di Impiegato e Progetto sono unici in quanto
chiavi primarie.

Se considero come Y l’attributo Impiegato e come Z l’attributo Stipendio, stiamo dicendo che, se considero
delle coppie di tuple che hanno lo stesso valore sull’attributo Impiegato (Y), esse avranno lo stesso valore
anche sull’attributo Stipendio (Z). Cioè, un Impiegato ha un unico stipendio. Quindi Y determina
funzionalmente Z, cioè l’attributo Impiegato determina funzionalmente l’attributo Stipendio.
La freccia si legge “determina”. Impiegato determina Stipendio, o anche, lo stipendio è determinato
funzionalmente dall’impiegato.

Per uno stesso valore dell’attributo Impiegato è associato un unico valore dell’attributo Stipendio.

Stesso discorso per le altre dipendenze funzionali.


Alcune dipendenze funzionali sono implicite, cioè banali.

Le dipendenze funzionali banali non le andremo a considerare.

La dipendenza funzionale non è commutativa (non è biunivoca).

Se ad esempio Impiegato determina Stipendio, ciò non vuol dire che Stipendio determina Impiegato.

Una dipendenza funzionale è piena se, rimuovendo un qualsiasi attributo da X (dove X determina Y), essa
non vale più.

Se X determina A, B in maniera minimale si scrive che X determina A e X determina B.


La dipendenza funzionale è legata allo schema della relazione, cioè dalla semantica della relazione.

Se K è una chiave primaria di una relazione, allora K determina qualsiasi altro attributo della relazione.
Questo perché la chiave primaria è un valore univoco. Ad esempio:
Quando in una dipendenza funzionale non sono coinvolte chiavi primarie possono verificarsi delle anomalie.

Questo perché una chiave primaria determina univocamente una tupla e quindi non genera ridondanza e
quindi anomalie.

Se invece non sono chiavi primarie, queste possono generare ridondanze e quindi anomalie: ad esempio
esistono impiegati con lo stesso stipendio.

Una base dati è in prima forma normale quando gli attributi contengono solo valori atomici, cioè, devo
evitare attributi multivalore e composti.

Gli attributi composti sono quegli attributi costituiti da altri attributi come ad esempio l’indirizzo:

È costituito da una via e un CAP.

Un attributo multivalore è un attributo che può assumere più valori per una stessa istanza di una relazione.
Pertanto, bisogna eliminare queste due tipologie di attributi ai fini di ottenere la prima forma normale.

12-05-2021

Uno schema è in seconda forma normale se è in prima forma normale, ed ogni attributo non primo (cioè,
che non sia una chiave) dipende in maniera piena dalla chiave della relazione, cioè, non dobbiamo avere
una dipendenza parziale. Ciò solitamente accade quando la chiave primaria è costituita da due attributi.

Se la chiave primaria è costituita da un solo attributo, allora si ha già la seconda forma normale per la
relazione.

Nell’esempio la chiave primaria è costituita da Articolo e Magazzino che determinano l’Indirizzo.

Inoltre, osserviamo che in realtà il Magazzino determina l’Indirizzo, ovvero, l’indirizzo è indipendente
dall’articolo.

Allora possiamo dire che l’Indirizzo dipende funzionalmente dal Magazzino. Ma siccome Magazzino è solo
una porzione della chiave primaria, tale tabella non è in seconda forma normale.

Bisogna allora effettuare una decomposizione sulla relazione di partenza.


Creo una tabella i cui attributi sono Articolo, Magazzino e Quantità in quanto l’attributo Quantità dipende in
maniera piena (cioè, da entrambi) gli altri due attributi. Questo perché ovviamente la quantità dipende oltre
che dall’articolo anche dal magazzino che si considera.

Creo poi una seconda tabella costituita dagli attributi Magazzino e Indirizzo, in quanto Indirizzo dipende in
maniera piena da Magazzino.

Tale relazione è in 1NF perché ho tutti attributi atomici.

Si ha una dipendenza funzionale tra Impiegato e Sede e tra Progetto e Sede.


La chiave primaria è costituita da Impiegato e Progetto.

Pertanto, non è in 2NF in quanto, ad esempio, la Sede è determinata dal Progetto e non da entrambi gli
attributi della chiave primaria, così come l’Impiegato determina la Sede perché esso lavora presso un’unica
sede.

Effettuiamo una decomposizione in due tabelle Impiegato, Sede e Progetto, Sede.

Se effettuo una giunzione di queste due tabelle sulla base dell’attributo comune Sede, si ottiene la relazione
a destra.

Ciò che succede è che si ottengono due tuple che nella relazione originaria non erano presenti: tali tuple
vengono dette spurie.

La decomposizione si dice in questo caso “con perdita”.

Sia R una relazione sull’insieme di attributi X costituita dagli insiemi di attributi X1 e X2

Sia X0 l’intersezione, cioè la parte comune, fra gli attributi X1 e X2 sulla quale eseguiamo l’equi-join.
Bisogna essere in grado di ricostruire la relazione di partenza R facendo in modo che la proiezione R1 equi-
join R2 sugli attributi in comune X0 deve essere uguale a quella di partenza, per avere una decomposizione
senza perdita.

Una condizione sufficiente affinché la relazione R si decomponga senza perdite in due relazioni R1(X1) e
R2(X2) è che l’attributo comune X0 sia una chiave primaria per R1 o per R2

Se a partire dalle due relazioni a sinistra faccio un equi-join sull’attributo impiegato che è anche chiave
primaria, allora sarò in grado di ricostruire la relazione di partenza.

Vediamo quando si ha una conservazione delle dipendenze funzionali.

Decomponiamo la tabella di partenza sfruttando la dipendenza Impiegato -> Sede.

Si ha una conservazione della dipendenza perché Impiegato è una chiave primaria per entrambe le relazioni
ottenute.
Tale relazione per come è stata decomposta presenta dei problemi.

Supponiamo di voler inserire una nuova Tupla che specifica la partecipazione dell’impiegato Neri al progetto
Marte.

Tale tupla va inserita solo nella relazione (Impiegato, Progetto).

Tale operazione è però illecita perché porta ad una violazione della dipendenza Progetto->Sede nella tabella
di partenza.

Questo perché, se inserissi la nuova tupla (Neri, Marte, Milano) nella relazione di partenza, si avrebbe un
illecito in quanto il progetto Marte viene svolto a Roma e non a Milano. Quindi la dipendenza funzionale
non viene preservata nella relazione originaria.
La forma normale di Boyce-Codd prevede di avere la prima forma normale e la decomposizione è eseguita
su un attributo che è anche chiave per la relazione di partenza.

Stipendio dipende funzionalmente da Impiegato, Bilancio da Progetto e Funzione da Impiegato e Progetto.

Decompongo secondo la forma normale di Boyce-Codd in due relazioni Impiegato, Stipendio e Progetto,
Bilancio tenendo conto delle due dipendenze funzionali.
Abbiamo una dipendenza funzionale fra Località e Prefisso.

L’Abbonato può dipendere dal Numero, e Indirizzo può dipendere dalla Località indipendentemente dal
numero.

Posso decomporre lo schema come riportato nella slide (in alto a dx) e la decomposizione risulterebbe
senza perdita.
A livello qualitativo è inferiore alla BCNF perché introduce delle ridondanze.
La relazione in alto a sx non soddisfa la terza forma normale ed ha come chiave Dirigente e Progetto. Questo
perché la dipendenza funzionale tra dirigente e stipendio coinvolge al primo membro lo stipendio che non è
chiave primaria, e il secondo membro non partecipa a nessuna chiave della relazione.

Posso pensare di fare le due decomposizioni a destra. Esse sono in terza forma normale perché Dirigente
rappresenterà una chiave primaria.

Tale entità non è normalizzata perché Indirizzo è un attributo composto ed inoltre Nome prodotto è un
attributo multi-valore perché posso fare la fornitura di diversi prodotti.

Tale entità va a violare la 2NF perché abbiamo delle dipendenze parziali rispetto la chiave primaria:
NomeFornitore e Indirizzo dipendono solo da PartitaIva e NomeProdotto e Prezzo solo da Codice, mentre la
chiave primaria è PartitaIVA e Codice.
Come decomporre l’entità ai fini di soddisfare la 2NF.

A partire dall’entità fornitura vado a creare due nuove relazioni Prodotto e Fornitore legate dalla relazione
Fornitura.

In questo modo Prezzo e NomeProdotto Dipendono solo dal Codice, così come NomeFornitore e Indirizzo
dipendono solo da PartitaIVA e quindi sono in 2NF.

Già in fase di ristrutturazione vengono eliminati gli attributi composti e multi-valore, quindi la 1NF è
implicita nel modello relazionale quando poi lo si va a tradurre.

Tutte le proprietà elencate sono implicite nella definizione di relazione.


La seconda forma normale si ha quando gli attributi sono atomici e tutti gli attributi che non sono una
chiave dipendono interamente dalla chiave, cioè da tutti gli attributi di cui è costituita.

La terza forma normale si ha in presenza di attributi atomici, dipendenza piena degli attributi non chiave
dagli attributi che costituiscono la chiave e tali attributi dipendono SOLO da quelli che costituiscono la
chiave.

Se ad esempio A2 determina A4 non si ha la 3NF perché A2 non è un attributo di una chiave primaria. Allora
eseguo la decomposizione riportata in figura, cioè, creo una relazione apposita per la dipendenza A2->A4
dove A2 deve essere inoltre chiave primaria in tale relazione.
Siccome c’è una relazione tra Professore e Dipartimento abbiamo una violazione della 3NF in quanto si ha
una dipendenza transitiva, cioè lo studente andrebbe a determinare il Dipartimento transitività.

Per risolvere tale problema posso fare nel seguente modo:

Vado a distaccare Tesi da Dipartimento mediante la nuova relazione Afferenza.


Familiari a Carico è un attributo multivalore, cosa impensabile in un DB perché si contrappone alla
definizione del modello relazionale.
A partire da Dipendenti creo due Relazioni Dipendenti e Familiari.

Mediante la MatricolaDip se eseguo un join con l’altra tabella posso ottenere le stesse informazioni della
tabella di partenza.

In questo caso Merci ha una chiave primaria costituita da Codice e Magazzino. Tale entità per definizione
non è in 2NF: c’è una dipendenza parziale tra Magazzino e LocalitàMagazzino.

Decompongo allora nelle due relazioni riportate in figura.

In terza forma normale bisogna eliminare le dipendenze transitive, cioè le dipendenze funzionali tra due
attributi non chiave.

In Studenti si ha una dipendenza tra Scuola e Telefono Scuola, cioè la scuola determina il Telefono della
Scuola; quindi, la relazione non è in 3NF.

È però in prima forma normale perché gli attributi sono atomici, ed è altresì in 2NF perché non ho
dipendenze parziali ovviamente. Per risolvere ciò decompongo nelle due tabelle a destra.
Siccome scuola determina Telefono Scuola, creo una relazione apposita in cui Scuola è chiave primaria.

Creo poi una relazione per la dipendenza funzionale tra la chiave primaria Nome e l’attributo Scuola.

Si ha la forma normale di Boyce-Codd quando si hanno delle dipendenze rispetto ad una chiave candidata.

Pagamenti ha per chiave primaria Codice Esame. Tale relazione presenta una dipendenza funzionale tra
Descrizione e Ticket oltre che tra codice Esame e Descrizione. L’attributo Descrizione potrebbe essere una
chiave candidata perché è determinante per il Ticket e quindi tale relazione non è in forma normale di
Boyce-Codd.

Allora creo due nuove relazioni: una che ha come attributi la chiave primaria e l’attributo Descrizione, l’altra
a come attributi la chiave candidata e il secondo attributo da essa determinato.

19-05-2021
Fino ad ora abbiamo considerato il DBMS come una “scatola nera” che ci consente di effettuare molteplici
operazioni, ma abbiamo tralasciato gli aspetti legati alla memorizzazione, alle interrogazioni ecc...

È un sistema di gestione dei dati. Tali dati rispettano una serie di proprietà: sono grandi in termini di
dimensioni, persistenti (cioè, non volatili) e condivisi.

La persistenza riguarda la memorizzazione su memorie secondarie, come ad esempio hard disk.

Essendo grande non è possibile caricare tutto in memoria principale (RAM).


Ciò che appare agli utenti è il modello relazionale.

Per l’utilizzo della memoria secondaria da parte del DBMS abbiamo una struttura organizzata su 4 livelli.

A partire dalle query SQL il DBMS ha una componente che si occupa delle query, cioè il gestore delle
interrogazioni, che le traduce ad un livello più basso.

Il gestore dei metodi di accesso trasforma ciò che ne deriva dalle interrogazioni in una serie di scritture e
letture che dovranno essere effettuate nella memoria fisica.

Il gestore del buffer mantiene la corrispondenza tra la base dati in memoria secondaria e la porzione in
memoria centrale. Questo perché l’accesso in memoria centrale è più rapido, e quindi si presenta la
necessità di caricare una parte di DB in tale memoria.

Quindi, il gestore della memoria secondaria traduce tutto ciò che arriva dal gestore del buffer in operazioni
di lettura e scrittura in memoria secondaria.
Bisogna garantire l’affidabilità, cioè la persistenza delle informazioni anche in presenza di malfunzionamenti.

Più applicazioni possono accedere contemporaneamente al DB, quindi nasce il problema della gestione
della concorrenza.

Se una applicazione modifica dei valori, la seconda applicazione dovrà leggere i valori corretti, cioè quelli
aggiornati.

Bisogna inoltre introdurre dei meccanismi di autorizzazione per determinate operazioni da parte di
determinati utenti.
In tale schema vengono introdotti 3 nuovi gestori (nella parte destra).

Il gestore delle transazioni comunica sia con il gestore della concorrenza che con quello dell’affidabilità.
Un programma in esecuzione fa riferimento a dati presenti in memoria centrale.

Una base dati però, per via delle dimensioni, non può essere caricata interamente in memoria centrale, e
quindi una sua porzione viene memorizzata in memoria secondaria. Alcuni dati devono quindi essere
trasferiti da memoria secondaria alla memoria centrale nel momento in cui sia necessario: di tale aspetto se
ne occupa il gestore del buffer.

Il tempo di trasferimento si riferisce al trasferimento dell’informazione.


La corrispondenza fra memoria centrale e memoria secondaria viene realizzata nel DBMS mediante una
zona di memoria centrale detta buffer.

In caso di scrittura, questa viene dapprima eseguita nel buffer, cioè in memoria centrale. A questo punto il
gestore del buffer può decidere se riportare questa scrittura immediatamente in memoria secondaria
oppure se farlo in maniera differita.

Nel buffer vanno inseriti quei dati del DB che vengono utilizzati più frequentemente (principio di località dei
dati).

Se un dato (una pagina nel buffer) non viene utilizzato da molto tempo, posso andarlo a sostituire con un
nuovo dato. Il gestore del buffer quindi si occupa di mantenere aggiornate le pagine in base ai loro utilizzi.
Possiede quindi due sottomoduli (direttorio e le due var di stato).
Fix richiede una pagina che non è presente nel buffer e che quindi deve prelevare dalla memoria centrale,
incrementa anche il contatore associato alla pagina perché un programma la sta utilizzando.

La primitiva fix è quindi utilizzata a seguito di una richiesta di lettura di una pagina da parte di una
transazione.

LRU: utilizzata meno recentemente; FIFO: il primo ad entrare è il primo ad uscire.

Steal: brutalmente toglie una pagina ad un’altra transazione detta vittima. In memoria secondaria preleva la
pagina di interesse e la va a copiare laddove è stata sottratta la pagina alla transazione vittima, ed infine
restituisce l’indirizzo al richiedente.
Il mantenimento della consistenza fra memoria secondaria e principale, cioè la corrispondenza delle
informazioni presenti nelle due, può avvenire in maniera sincrona o asincrona.

Con la primitiva force, in maniera immediata la modifica in memoria principale viene riportata in memoria
secondaria (in maniera sincrona).
Il block3 header e il block trailer contengono le informazioni di controllo utilizzate dal file system per
l’accesso alla pagina.

Page header e page trailer contengono le informazioni di controllo relative alle modalità di accesso alla
pagina.

Un bit di parità è utilizzato per eseguire controlli di integrità.

Il dizionario della pagina contiene una serie di puntatori a dati utili presenti nella pagina, come una sorta di
indice.

La parte utile della pagina contiene appunto i dati veri e propri.


I blocchi sono i componenti fisici di un file mentre i record sono i componenti logici, e in generale hanno
dimensioni differenti.

La dimensione del blocco dipende dal file system, mentre la dimensione del record dipende
dall’applicazione.

Anche il gestore delle pagine utilizza una serie di primitive sopra elencate.
C’è un ordinamento fisico ma non logico. I record vengono memorizzati nell’ordine in cui arrivano all’interno
dei file.
Si fa riferimento al valore assunto da un campo chiave e sulla base di tale valore si posizionano fisicamente i
record ne file.

La funzione di hashing trasforma tutti i possibili valori che può assumere la chiave in un indice di dimensione
inferiore. Fa una sorta di mappatura: è come se prendessi un codice fiscale (la chiave) e lo mappo su un
numero di caratteri inferiore.
Supponiamo di voler memorizzare 40 studenti ognuno dei quali ha una matricola individuata da 6 cifre.
Abbiamo quindi 40 record. Siccome con 6 cifre ho circa un milione di possibili chiavi, applico una funzione di
hashing.

Un esempio di funzione hash potrebbe essere ad esempio quella che divide il numero di matricola per 50 e
ne utilizza il resto come indice di un array di 50 elementi.

Il problema è che possono nascere collisioni, cioè a diverse matricole potrebbe essere associato uno stesso
indirizzo nell’array.

Inoltre, alcuni valori non vengono mai utilizzati, ovvero, alcune divisioni non forniscono mai determinati
valori per il resto.

La funzione vista in questo esempio non è quindi di buona qualità: dobbiamo ridurre il numero di collisioni e
ottimizzare la gestione dei file di hashing.
L’idea è di far sì che ogni blocco fisico possa contenere più record logici.

Anziché avere un unico blocco con 50 posizioni, proviamo a dividere le 50 posizioni in 5 blocchi dove ogni
blocco ha un fattore di blocco pari a 10.

Come algoritmo di hashing applicheremo il numero di matricola modulo 5 (cioè, il resto della divisione per 5
della matricola).

Nei blocchi 0 e 2 si verifica un overlfow, ovvero un elemento eccede la dimensione massima.


21-05-2021

Una pagina o un blocco coincide con un nodo. I legami fra nodi e foglie avvengono mediante puntatori.

Si deve fare in modo che la profondità dell’albero sia la stessa ovunque, in maniera da avere tempi di
accesso costanti.

Ogni arco rappresenta un puntatore da un nodo ad un altro, cioè da una pagina ad un’altra le quali
contengono le tuple.
In una struttura ad albero la ricerca è sequenziale e i valori K delle chiavi sono ordinati.

Ogni chiave è seguita da un puntatore.

Solo per la prima chiave K1 abbiamo un puntatore che lo precede P0. P0 punta al sottoalbero che contiene
tutte le chiavi minori di k1.
La copertura dell’albero deve essere almeno del 50% per evitare di avere rami troppo lunghi o troppo corti.

L’altezza dell’albero binario è H=2 pari al numero di indici. Se voglio trovare un record corrispondente, per
arrivare ad una foglia dovrò eseguire H+1 accessi.

Supponiamo di voler trovare un record con valore di chiave pari a 60.

Si parte dal nodo radice.

Ricordando la struttura di un nodo, nella porzione sinistra dell’albero avremo il sotto-albero che contiene
tutti i valori inferiori di 24. Nella porzione destra avremo il sottoalbero che contiene tutti i valori di chiave
maggiori di 72. Allora vuol dire che per ricercare la chiave con valore 60 dovrò far riferimento al puntatore
che sta in mezzo che punta al nodo centrale verso il basso.

In tale nodo farò riferimento al puntatore che si trova dopo la chiave con valore 48 (perché in un albero i
valori di chiave in ciascun nodo sono ordinati). A tal punto scenderò nell’ultimo nodo in basso nel quale
troverò il valore di chiave desiderato.
Albero di altezza pari a due, e quindi per inserire il record dovrò eseguire H+1=3 accessi.

Si parte dal nodo radice considerando il puntatore prima del valore di chiave 24, perché esso punterà ad un
sottoalbero con valori di chiave inferiori a 24.

Si reitera tale procedimento in ciascun nodo che si incontra scendendo verso il basso fino ad arrivare al
nodo desiderato in cui inserire il record.

Cosa accade se durante un inserimento si arriva in un nodo che non ha slot liberi?

Bisogna utilizzare la tecnica della divisione, cioè, bisogna splittare il nodo precedente a quello di arrivo (a
quello che precedeva il nodo che abbiamo trovato tutto occupato) siccome anch’esso è tutto occupato (il
nodo intermedio centrale ha già tutti i puntatori “utilizzati”, cioè tutti slot che puntano ad altri nodi)

Siccome abbiamo suddiviso il nodo intermedio centrale che è puntato solo dal nodo radice, bisogna
splittare anche il nodo radice, creandone poi uno nuovo. Questo perché anche il nodo radice ha tutti gli slot
occupati, cioè che puntano ad altri nodi.
Gli aggiornamenti vengono tradotti in cancellazione ed inserimento.

A sinistra abbiamo la situazione iniziale.

Supponiamo di voler inserire una coppia puntatore-chiave con chiave k3 nella foglia (cioè, nel nodo in
basso).

Siccome la pagina della foglia non ha spazio disponibile, bisogna effettuare uno split: devo suddividere in
due parti l’informazione presente nella foglia, cioè, devo dar luogo a due foglie.

Anche il nodo padre va aggiornato, inserendo la chiave k3 fra k1 e k6.

Infine, fra i due nodi figli appena creati, si sceglie il nodo di destra per l’inserimento di k3, perché a destra
troviamo sempre i valori maggiori o uguali rispetto alla chiave a cui si fa riferimento nel nodo padre. A
sinistra invece troviamo i valori strettamente minori della chiave a cui si sta facendo riferimento.

Supponiamo di voler ora cancellare la chiave k2. In questo caso eliminando k2, i due nodi figli vengono uniti
(merge) e vanno poi aggiornati i puntatori nel nodo padre.
Nei B+ tree le chiavi dei nodi intermedi sono comunque ripetute nelle foglie (nei nodi finali).

Nei Binary Tree invece le chiavi che compaiono nei nodi intermedi non sono ripetute nelle foglie.

In questo caso abbiamo dei puntatori ai dati. Si ha sempre un ordinamento di tali dati. Siccome i nodi in tale
struttura sono delle liste, abbiamo dei puntatori anche fra le foglie di uno stesso livello.
In un albero B semplice, abbiamo sempre una struttura gerarchica in cui ciascun nodo contiene le coppie
chiave-puntatore.

Abbiamo due puntatori:

- Uno utilizzato per la ricerca dei valori strettamente compresi tra Ki e Ki+1
- Uno che punta direttamente al blocco contenente la tupla associata a quel valore chiave
Analizziamo ora il gestore delle interrogazioni. Il Query processor è un modulo che si occupa di ottimizzare
le interrogazioni.

Tutto ciò viene eseguite in tempo di esecuzione: il risultato si ottiene dopo poche frazioni di secondo.

L’input è la query SQL. Tale query viene prima analizzata per verificarne la correttezza in combinazione con il
catalogo dei dati.

L’output è la relativa query in algebra relazionale, che deve essere poi ottimizzata dal punto di vista
algebrico.

Infine, si ha una ottimizzazione sui costi utilizzando i profili delle relazioni (che vedremo a breve cosa sono).

Il risultato è la rappresentazione della query SQL sotto forma di albero.

I nodi foglia saranno le tabelle e i nodi intermedi saranno operazioni dell’algebra relazionale.
Sono dei parametri utilizzati per il calcolo dei costi nell’ottimizzazione delle query.

La join è l’operazione più costosa dal punto di vista computazionale che nel caso peggiore corrisponde ad un
prodotto cartesiano tra tuple di due tabelle.

La cardinalità di una selezione è pari al rapporto fra la cardinalità della tabella, e il numero di valori distinti
assunti dall’attributo A.

La cardinalità della proiezione è il minimo valore fra la cardinalità della tabella e la produttoria dei valori
distinti assunti dagli attributi Ai.
Consiste nell’anticipare una selezione rispetto ad una operazione di join.

La query in esempio prevede di selezionare tutte le tuple per cui A = 10 dal risultato di una join fra le
relazioni R1 e R2 sulla base dell’uguaglianza degli attributi R1.B e R2.B.

Il primo punto rappresenta la traduzione della query in algebra relazionale.

Posso anticipare la selezione rispetto la join, cioè, eseguo la selezione su R2 delle tuple per cui A=10,
supponendo che A sia un attributo solo della relazione R2. In questo modo andrò ad eseguire una giunzione
con una delle due tabelle il cui numero di tuple è ridotto.
Si leggono dal basso verso l’alto.
Nella from la virgola denota una operazione di giunzione, cioè un prodotto cartesiano fra tre relazioni.

Si esegue prima il prodotto cartesiano fra R1 ed R2, e poi con R3. Dopodiché, si esegue la selezione sulle
tuple sulla base del predicato specificato, ed infine la proiezione delle colonne A ed E.

A seguito dell’ottimizzazione:

Primo esempio:

H ed I sono attributi solo di R3 e quindi ne vado ad anticipare la selezione, facendole in cascata.

Si va poi ad anticipare (rispetto la join) la selezione su R1 sulla base del predicato B>100. Successivamente si
esegue la join ed infine la proiezione.

Secondo esempio:

Atomizzo le selezioni ed anticipo la proiezione rispetto l’operazione di join (il lato destro dell’operazione).

Nel lato sinistro dell’operazione di join viene eseguita una anticipazione della selezione e della proiezione.
Quelle sopra elencate sono 4 operazioni elementari che il DBMS esegue nell’ottimizzazione delle query.
Una tabella è indicata come “esterna” e l’altra come “interna”.

Sulla tabella esterna si ricercano le tuple per trovare l’elemento associato all’attributo A. Viene prelevato il
valore di tale attributo, e sulla tabella interna si esegue una scansione per trovare le tuple che hanno una
corrispondenza su quell’attributo.
Le tabelle si distinguono in “sinistra” e “destra”.

Vengono eseguite due operazioni di scanning contemporaneamente sulle due tabelle.

Viene utilizzata una funzione di hashing utilizzata per generare tante partizioni delle due tabelle, e su tali
partizioni vengono eseguiti delle join “più semplici”.
Stiamo facendo riferimento all’ottimizzazione basata sui costi (quella della prima slide).

Ho una join di 3 tabelle.

Si hanno varie alternative, in base all’ordine in cui vengono eseguite tali giunzioni.
A partire da tali 3 ordinamenti, si possono eseguire 5 diversi ordinamenti delle join per svolgere l’operazione
(nested loop, hash join, merge-scan).

Alla fine, si ottengono 75 possibili alternative.


Noi non possiamo modificare la modalità di memorizzazione dei file che dipende dal DBMS in questione,
però per migliorare le prestazioni del sistema possiamo creare degli indici appropriati.
Deve terminare correttamente oppure se ci sono degli errori bisogna abortire.

Aggiorna la tabella ContoCorrente cambiando il valore del saldo di diversi conti correnti.

Bisogna vederla come una operazione unica, cioè atomica.


Abbiamo aggiunto un processo decisionale: abbiamo specificato che il conto non deve andare sotto lo zero.
Dobbiamo vedere le transazioni come se fossero eseguite in maniera isolata, e l’esito di una non deve
influenzare l’altra.

25-05-2021

Nel caso di transazioni contemporanee (concorrenti) devo ottenere un risultato come se queste fossero
eseguite in maniera seriale.

La parte a sinistra di tale schema è quello visto nel capitolo precedente. Analizziamo ora la parte destra.
La persistenza riguarda la memorizzazione in memoria secondaria (hard disk ecc.)

Se una transazione termina con abort, vuol dire che ci sono stati dei malfunzionamenti, e quindi bisognerà
eseguire un abort cioè un rollback allo stato precedente.

Con “B” faremo riferimento all’inizio della transazione (Begin), con “C” faremo riferimento al Commit e con
“A” ad abort.

Prima di ogni transazione deve quindi essere salvato lo stato del DB in un file di log.

Il gestore dell’affidabilità riceve, dal gestore delle transazioni e dell’accesso, delle richieste di lettura e
scrittura tramite le primitive riportate in figura.
Tali richieste vengono poi passate al gestore del buffer per eseguire l’accesso alla memoria principale, se
presenti in essa i dati, o secondaria viceversa.

Vengono generate altre richieste di lettura e scritta al gestore della memoria secondaria ed infine vengono
predisposti i meccanismi per il logging, cioè si fissa un backup (facendo il dump del DB) oppure un
checkpoint, ovvero una sorta di punti di ripristino.

Il gestore dell’affidabilità dispone della cosiddetta memoria stabile. Deve essere resistente ai guasti in
quanto essa contiene anche il file di log.

Ogni memoria può però guastarsi indipendentemente dalla tipologia.

Per far fronte a questa evenienza si introduce ridondanza (cioè, mirroring): vengono eseguite copie
identiche dell’informazione su più dischi.
Il log è un file che rappresenta il cuore del controllo dell’affidabilità. Viene scritto in memoria stabile e
contiene informazioni che consentono di ritornare nello stato precedente a seguito di un guasto generico
(anche un guasto hardware).

Ha una parte detta “top” che rappresenta l’ultimo blocco che è stato aggiunto.

Nel log possono essere inserite due tipi di righe (record):

- Record relative alle transazioni: operazioni di aggiornamento, di inserimento, e di cancellazione,


commit, abort e begin
- Record di sistema: checkpoint, dump

La “T” rappresenta la transazione alla quale stiamo facendo riferimento.

La “O” rappresenta un generico oggetto che si vuole modificare, cancellare o inserire.

“AS” sta per after state e “BS” before state

- Undo update, undo delete su un oggetto “O”: dal file di log viene prelevato il valore del “before
state”, cioè il valore dell’oggetto prima della modifica, e viene copiato nell’oggetto O.
- Undo insert: consiste nel cancellare l’oggetto O inserito

- Redo insert, redo update su un oggetto O: viene copiato il valore dell’after state (AS), cioè il valore
di O dopo la modifica, nell’oggetto O

- Redo delete: viene cancellato l’oggetto O.


Il top del log è l’ultimo elemento inserito.

Il dump consiste in una copia dell’intera base dati, o quanto meno delle tabelle cruciali. Nel log viene scritto
un record di dump.
Il checkpoint serve ad evitare che ad ogni guasto bisogna ripartire dal file di dump, in modo da ripartire da
uno stato più recente successivo alla creazione del file di dump.

Un record di checkpoint è del tipo CK(T1,T2,…,Ti) dove con Ti rappresenta l’identificatore della transazione
attiva della quale vado a salvare lo stato.

Tali 4 fasi vengono ripetute ogni volta che viene creato un record di checkpoint.

Se ci sono transazioni attive in esecuzione, il file di dump non può essere creato.
L’esito finale di unta transazione può essere commit o abort.

Nel caso di commit, devo memorizzare in maniera persistente le modifiche nel DB. Scrivo quindi un record
di commit nel log in maniera sincrona (force), cioè immediatamente.

Un guasto prima del commit porta ad un undo di tutte le azioni eseguite per la transazione.

Con la regola WAL deve essere scritta prima la parte before state nel file di log, e poi si può eseguire la
transazione.
Nella modalità immediata scrivo prima nel log e poi nella base dati, il commit (cioè, la conferma) viene
eseguito alla fine. Se terminasse con un abort, bisognerebbe eseguire un undo di tutte le operazioni. Quindi
in tale modalità il log contiene operazioni di una transazione uncomitted.

Nella modalità differita, vengono scritti i record solo nel file di log prima che sia abbia il commit. Solo dopo il
commit le modifiche vengono applicate al DB. In caso di abort non occorre fare nulla, perché nel DB non è
stato ancora scritto nulla.
Quando si verifica un guasto (soft o hard) viene adottato un modello di tipo fail-stop.

La prima cosa che viene fatta è uno stop di tutte le transazioni attive.

Viene poi ripristinato il corretto funzionamento del sistema operativo, cioè si ha l’avvio del sistema (boot).

Infine, viene eseguita la procedura di ripristino:

- Ripresa a caldo nel caso di guasto soft


- Ripresa a freddo nel caso di guasto hard

Si ritorna nello stato di funzionamento normale solo dopo il buon fine dell’esecuzione del ripristino.
La fase 4 è la più importante: consente di replicare tutte le transazioni.

Nel record di checkpoint vengono salvate solo le transazioni che sono ancora attive, cioè per le quali non è
ancora stato dato il commit.
A partire dal top, si va a ritroso fino all’ultimo record di checkpoint.

Si va poi ad inizializzare gli insiemi UNDO e REDO. In particolare, REDO all’insieme vuoto e UNDO viene
inizializzato con le transazioni che erano attive fino a quel checkpoint, cioè T2, T3 e T4.

A partire dal checkpoint si ripercorre in avanti il record di log e si aggiornano gli insiemi di UNDO e REDO.

Dopo il checkpoint si ha:

- Il commit di T4, che quindi è stato salvato prima del crash. Tolgo T4 dall’insieme UNDO e lo inserisco
in REDO.
- L’inizio di T5: all’interno dell’UNDO metto T5.
- Commit di T5: T5 viene tolta dall’UNDO e aggiunta nel REDO.

Questa prima fase è detta di SETUP: corrisponde al secondo step della ripresa a caldo in cui si decidono le
transazioni da rifare o disfare.
Si ripercorre indietro il log fino all’azione più vecchia che viene eseguita fra quelle in UNDO. Nel nostro caso,
sarà UPDATE T2. A partire da tale azione si esegue l’UNDO delle azioni, riportando ad uno stato precedente
la base dati.
Si introduce un nuovo parametro, cioè il numero di transazioni al secondo “tps”.

A seconda della tipologia di sistema informativo possiamo avere un tps differente.

Il gestore della concorrenza riceve le richieste di lettura e scritta dal gestore dei metodi di accesso e le
richieste di begin, commit e abort dal gestore delle transazioni.

Siccome abbiamo diverse transazioni attive, è necessario un dispositivo per decidere quali transazioni
autorizzare o meno. Tale dispositivo viene detto scheduler, che è il blocco che controlla la concorrenza.
T1 legge x, lo incrementa, e lo scrive. La stessa cosa fa t2 sullo stesso oggetto x.

Supponiamo che inizialmente X=2. Quindi r1=x ci restituisce il valore 2.

Se eseguo t1 e t2 in maniera seriale, t2 leggera 3 perché x viene incrementato prima da t1. Infine, t2
incrementa x, e quindi il risultato finale è x=4.

Il valore corretto è x=4 alla fine delle due transazioni.

Supponiamo di eseguire t1 e t2 in maniera concorrente, e cioè prima che una delle due transazioni termini,
viene eseguita anche l’altra.

Comincia t1, legge due e scrive 3.

Comincia poi t2, ma anch’essa legge 2 perché t1 non ha ancora scritto il risultato dell’incremento.
Dopodiché, t2 incrementa x che quindi sarà pari a 3, la scrive in memoria e da il commit.

Successivamente, viene ripresa l’esecuzione di t1, che scrive X che per essa valeva 3, e quindi in memoria
viene scritto 3.
Pertanto, al termine delle due transazioni risulterà X=3.

Il risultato è la perdita di un aggiornamento, in quanto il valore atteso era x=4.

La transazione t1 legge il valore di x, lo incrementa, e poi scrive il valore di x nel database.

T2 invece va solo a leggere il valore di x.

Supponiamo che inizialmente x=2.

Se t1 e t2 fossero eseguite in maniera seriale, t2 leggerebbe il valore 3.

Consideriamo l’esecuzione concorrente di t1 e t2.

Parte t1, legge x=2, incrementa x, e scrive 3.

Poi parte t2, legge il valore x=3 modificato da t1, e termina in commit.

Riprende poi t1, però essa termina in abort.

Questo significa che t2 legge un valore errato o, meglio, un valore intermedio, perché t1 non termina
correttamente (abort).
T1 legge x due volte in due istanti successivi.

T2 legge x, lo incrementa, e poi lo scrive nel database.

Supponendo un valore iniziale di x=2, vediamo l’esecuzione concorrente di t1 e t2.

Parte t1 e legge il valore x=2.

Dopodiché parte t2, legge X=2, lo incrementa, e lo scrive. Infine, termina con commit.

Successivamente t1 riprende l’esecuzione e legge nuovamente x che vale però 3.

Si ha in questo caso una lettura inconsistente: questo perché una medesima transazione che legge due volte
una stessa variabile in due istanti successivi, dovrà ottenere per tale variabile il medesimo valore a meno
che non sia stata lei stessa a modificarlo. In altri termini, una transazione non deve risentire dell’effetto di
un’altra transazione.

Consideriamo ora 3 oggetti x, y, z.

Su questi 3 oggetti c’è un vincolo di integrità: la loro somma x+y+z=1000.


T1 legge x, legge y e legge z, dopodiché calcola la loro somma e la memorizza in s.

T2 invece legge y, gli sottrae 100, legge z, gli somma 100, e poi scrive y e z.

Consideriamo ad esempio x=500, y=400, z=100.

Parte t1, legge x ottenendo quindi 500.

Parte t2, legge y, ottenendo 400.

Riprende t1 e legge y, ottenendo 400.

Riprende t2 che va a sottrare 100 ad y, legge z, incrementa z di 100, scrive y e poi scrive z. Infine, termina
con commit.

Dopodiché riprende t1, che legge z. Ma z vale ora 200 in quanto è stata incrementata di 100 da t1. Quindi,
eseguendo la somma x+y+z otterrà 500+400+200 = 1100.

C’è stata quindi una violazione del vincolo di integrità.

Si noti che t2 non viola tale vincolo, ma è l’esecuzione concorrente che dà luogo a tale violazione da parte di
t1.

Consideriamo due transazioni.

La prima t1 legge gli stipendi degli impiegati di un dipartimento A e ne calcola la media.

La seconda t2 inserisce un record nella tabella impiegati appartenente al dipartimento A.

Parte t1, che calcola la media degli stipendi degli impiegati del dipartimento A.

Parte t2, che inserisce un impiegato con dipartimento A.

T1 riprende l’esecuzione e legge nuovamente gli stipendi degli impiegati del dipartimento A: essa però legge
una nuova tupla che compare come un fantasma. La media che andrà ora a calcolare sarà diversa da quella
che ha calcolato precedentemente: questo non può verificarsi all’interno di una medesima transazione.
In tale ambito, una transazione viene definita come una serie di azioni di lettura e scrittura su generici
oggetti.
Due transazioni concorrenti sono mischiate tra loro nell’esecuzione, ma le operazioni nelle singole
transazioni vengono eseguite secondo l’ordine in cui sono scritte.

Il pedice ci indica la transazione alla quale stiamo facendo riferimento.

Uno schedule è una sequenza di operazioni eseguite da transazioni concorrenti.

Il controllore della concorrenza è detto scheduler ed ha il compito di autorizzare o meno determinati


schedule.

Commit-proiezione: consideriamo nello schedule solo transazioni che, come esito finale, hanno il commit.
Questo però è un qualcosa di impossibile nella pratica, cioè non possiamo conoscere a priori l’esito di una
transazione.
26-05-2021

Abbiamo detto che il gestore della concorrenza ragiona non sulla singola transazione ma su Schedule, cioè
da una serie di operazioni appartenenti a transazioni concorrenti.

Un esempio di schedule:

Le operazioni di diverse transazioni sono “mischiate”, ma le operazioni della singola vengono eseguite in
ordine.

Il controllore della concorrenza è detto scheduler e il suo funzionamento si basa su 3 regole che però sono
solo teoriche, in quanto inapplicabili nella pratica.

La proprietà alla quale facciamo riferimento è quella di isolamento: esecuzione parallela di transazioni come
se fossero eseguite in maniera seriale.

Vedremo quindi alcune tecniche per cercare di capire se uno schedule è serializzabile.

Uno schedule è detto seriale, se tutte le operazioni di ciascuna transazione sono eseguite l’una dopo l’altra,
ovvero se viene eseguita prima tutta una transazione, poi un’altra ecc senza operazioni intermedie di altre
transazioni.

Uno schedule concorrente, cioè con operazioni di diverse transazioni “mischiate fra loro, è detto
serializzabile se produce lo stesso risultato di uno schedule seriale. In altri termini è detto serializzabile se lo
posso trasformare in uno schedule seriale.

Nota bene: tali ragionamenti li facciamo sulle 3 ipotesi semplificative. Inoltre, stiamo supponendo la
commit-proiezione, cioè, assumiamo che le transazioni terminino necessariamente in commit.
Si parla di schedule equivalenti se questi producono lo stesso risultato.

T1 e T2 producono lo stesso risultato, ma solo se il valore iniziale di X è pari a 10. Pertanto, non sono
equivalenti perché essa deve essere indipendente dal valore iniziale assunto dall’oggetto X.
Supponiamo di avere operazioni di lettura e scrittura in uno schedule.

Una lettura legge da una scrittura in uno schedule, se la scrittura precede la lettura (sullo stesso oggetto X
ovviamente) e tra scrittura e lettura non c’è un’ulteriore scrittura diversa dalla prima.

Una scrittura su un oggetto X si dice finale se è l’ultima scrittura eseguita su quell’oggetto.

Due schedule view-equivalenti producono lo stesso risultato.


S4 è uno schedule seriale perché viene eseguita prima t0, poi t1 e poi t2.

S3 è view equivalente a s4 perché hanno le stesse operazioni sugli stessi oggetti, le stesse relazioni “legge-
da” e le stesse scritture finali. Essendo S4 seriale, allora S3 è anche view-serializzabile.

Anche S6 è seriale.

Risulta che S5 è view equivalente a S6, e quindi è anche view-serializzabile.

La view-equivalenza presenta un problema di complessità per la realizzazione pratica.

Se considero tutte le permutazioni delle operazioni di ciascuna transazione, ovvero se considero tutti i
possibili schedule, la complessità cresce in maniera polinomiale.

Nella pratica la view-equivalenza non può essere utilizzata.


In uno schedule due operazioni sono in conflitto se sono appartenenti a transazioni differenti che operano
sulla stessa risorsa ed almeno una delle due operazioni è una scrittura.

Abbiamo un conflitto tra w0(x) e r1(x), tra w0(x) e r2(x), w0(x) e w1(x).

Anche tra w0(z) e r1(z), w0(z) e r3(z), w0(z) e w3(z)

Nello schedule seriale equivalente eseguo prima t0, poi t2, poi t2 ed infine t3: non mischia le operazioni
delle varie transazioni.
Se uno schedule è conflict serializzabile allora è anche view serializzabile. (No dim.)

Gli schedule conflict serializzabili sono un sottoinsieme degli schedule view serializzabili.
Le transazioni le rappresentiamo con dei nodi e le azioni che esistono tra le varie transazione mediante
archi orientati.

Nell’esempio t0 ha un conflitto con t1, quindi avremo un arco orientato che da to va a t1 e così via per gli
altri.

Uno schedule è conflict serializzabile se il grafico non possiede cicli (no.dim).

Non avere cicli significa che, a partire da un nodo, non esiste un percorso che mi faccia tornare allo stesso
nodo (transazione nel nostro caso).
Il meccanismo che viene utilizzato nella pratica è il “Lock” (che è come un semaforo).

È una variabile associata ad una tabella, una riga, un attributo o l’intero DB che ne descrive lo stato facendo
riferimento alle operazioni che possono essere applicate.

Nella realtà non si utilizzano i lock binari.


Prima di eseguire una lettura si vanno a bloccare le letture e scritture su quell’oggetto con la primitiva
r_lock.

Tutte le scritture sono precedute dalla primitiva w_lock che blocca le scritture. Entrambe devono essere poi
seguite dalla primitiva di sblocco “unlock”: in tal caso si dice che la transazione è ben formata.

In tale tabella viene riportato lo stato di ciascuna risorsa che può essere: libero, blocco in lettura, blocco in
scrittura.

Con OK e NO indichiamo l’esito della richiesta, mentre quello che c’è dopo lo slash è lo stato assunto dalla
risorsa dopo aver invocato la primitiva.

La tabella si legge leggendo prima lo stato, poi la richiesta (la colonna a sx) ed infine la relativa cella.

Con bloccata in scrittura (w_locked) si intende che “qualcuno” sta scrivendo su quella risorsa, mentre con
bloccata in lettura (r_locked) si intende che “qualcuno” la sta leggendo.

Se la risorsa non viene concessa, la transazione richiedente viene messa in coda fino a quando quella risorsa
non diviene disponibile.

Tale tabella è gestita dal lock manager.


Lo scheduler che gestisce i meccanismi di lock viene denominato lock manager.

Protegge le letture e scritture tramite la primitiva lock.

Vincola le richieste e i rilasci dei lock.

Una transazione dopo aver rilasciato un lock non può acquisirne altri.

Il protocollo 2PL (2 phase locking) prevede una fase crescente ed una calante.

Nella fase crescente la risorsa acquisisce tutti i lock necessari, mentre nella fase calante va a rilasciare tutti i
lock acquisiti.
Si parla di classe 2PL perché anche in questo caso essa garantisce la conflict-serializzabilità.
Vediamo ora come, utilizzando le primitive r_lock e w_lock, riusciamo ad esempio a prevenire l’anomalia
dell’aggiornamento fantasma.

L’aggiornamento fantasma viola il vincolo di integrità. Abbiamo 3 oggetti con x+y+z e la somma era soggetta
ad un vincolo che però veniva spezzato da due transazioni concorrenti.

Abbiamo 3 risorse x, y e z inizialmente libere.

T1 deve fare una lettura e richiama la primitiva lock su x, e poi esegue la lettura.
T2 esegue una operazione di lock su Y (nell’immagine è sbagliato) in scrittura in tal caso. T2 legge quindi y.

La transazione t1 esegue nuovamente una rlock su y perché deve leggerla: ma siccome y è occupata in
scrittura, la transazione t1 viene messa in attesa.

Pertanto, t2 riprende, decrementa y, farà una lock su z, leggerà z, ne modificherà il valore, scriverà in
memoria y e z e poi farà il commit. Non appena fatto il commit, t2 rilascia y.

A questo punto, t1 può leggere il contenuto di y, e fa la lock su z: siccome z non è stato ancora rilasciato, t1
viene messo in wait su z.

Lo scheduler allora fa riprendere l’esecuzione di t2 che sblocca z, e da tale punto in poi t1 può riprendere
l’esecuzione e leggere il valore di z facendone la lock.

Si noti che t1 leggera il valore giusto di z.

T1 deve poi eseguire la fase calante, cioè l’unlock di tutte le risorse acquisite.

Nota: tutte le operazioni di t1 e t2 andranno in uno schedule


È un meccanismo alternativo al 2PL per il controllo della concorrenza.

Il timestamp è una informazione temporale assegnata dal DBMS a ciascuna transazione, che indica l’inizio
della sua esecuzione.

RTM(X) e WTM(X) sono relative alle transazioni che hanno il time stamp più grande, cioè le più recenti in
termini di inizio di esecuzione, ed esse contengono il timestamp delle ultime transazioni che hanno eseguito
una lettura e scrittura.
28-05-2021

Contatteremo il web server che occupa il DBMS e faremo delle query.

Ottenute le risposte, preleviamo i dati e li presentiamo all’utente. La presentazione dei dati avviene con il
linguaggio html.

Il paradigma utilizzato è client server: il client invia richieste al server, che è sempre acceso, ed esso
risponde alle richieste.
L’organizzazione dei file su un server ha la medesima struttura dei file memorizzati su di un sistema
operativo.

La parte di “Dettagli” indica in quale cartella andiamo a cercare i dati di interesse.

L’HTML così com’è consente di costruire pagine web statiche, cioè che non cambiano nel tempo.

Si parla di ipertesto perché non abbiamo solo stringhe, ma anche immagini, file multimediali ecc.
Si basa sulla scrittura di tag e solitamente essi vanno aperti e poi chiusi.

In un tag è poi possibile andare a specificare una serie di attributi.

Html non è case sensitive: non c’è differenza tra maiuscolo e minuscolo.
Essendo una interpretazione fatta dal browser (Chrome, Firefox ecc..) dobbiamo accertarci che tutti i tag
siano interpretabili dal browser in questione.

La struttura di un file html è una struttura gerarchica ad albero.

Tutta la pagina è racchiusa tra il tag “html”. All’interno di tale radice abbiamo due sezioni:

- Intestazione (head)
- Corpo (body)

Ciò che viene posto nell’intestazione è solitamente qualcosa che non è visibile: ci sono informazioni per
descrivere ciò che la pagina andrà a contenere (metadati).
Il tag doctype è facoltativo e se presente è solitamente la prima riga dell’intestazione.

Serve per indicizzare il contenuto della pagina web ed effettuare una classificazione del documento per
quanto riguarda il motore di ricerca. Sulla base di tali meta informazioni, il sito apparirà fra i risultati del
motore di ricerca.
Il titolo è il nome che compare nella scheda del browser.

Il body contiene il vero e proprio sito web statico.

Anche nel tag di body possiamo andare ad inserire attributi del tipo background, bgcolor, text, link ecc..
H1, H2 ecc. sono dei possibili livelli di enfatizzazione del testo in termini di dimensioni.

“div” è una sorta di contenitore virtuale che non è visibile nella pagina web ma serve ad esempio per poter
allineare il documento.
Il tag font consente di cambiare il font a porzioni di testo nel documento html.
Tali tag consentono di far sì che una porzione di testo html non venga interpretata dal browser e venga
visualizzata così com’è.

Applicano degli stili predefiniti per determinate tipologie di testo.


Molto utilizzate sono le liste non ordinate, ovvero gli elenchi puntati.

Ogni elemento della lista puntata è un list item.


È altresì possibile creare liste ordinate: un elemento di tale lista è sempre un list item.

Con l’attributo start possiamo specificare da quale numero partire.


Consente di introdurre dei link a siti esterni, o a sezioni della medesima pagina con l’attributo name.

HREF sta per Hypertext reference.


Il tag table aperto e chiuso specifica la tabella.

Possiamo poi definire delle righe con TR e con TD le colonne. TH serve a scrivere le intestazioni della tabella.
Una TR contiene vari TD, cioè una riga contiene varie celle.
Anche le intestazioni TH vengono viste come elementi di una riga, ma vengono marcati in grassetto.

Il caption è una etichetta che viene posta in alto alla tabella.


ROWSPAN viene utilizzato per unire righe, mentre COLSPAN per unire celle (colonne).

Nota: se creo una riga che ha ad esempio 4 celle, non posso creare nella stessa tabella un’altra riga che ha
un numero di celle inferiori.

Anche i colspan vengono contanti come celle: ad esempio se specifico COLSPAN=”2” è come se quella cella
fosse costituita da 2 celle.

Vengono utilizzati per permettere all’utente di inviare informazioni al webserver, che le elabora producendo
una risposta.
I form vengono creati con il tag <FORM>

Il method rappresenta il modo in cui vengono inviati i dati al server, ovvero se questi vengono inviati in
chiaro o meno attraverso l’URL.

L’attributo ACTION specifica l’URL della pagina server che deve elaborare la richiesta.

Nell’esempio vuol dire che nel server c’è una tabella “cgi-bin” la quale contiene un file di nome
“nome_script.cgi”.
I dati vengono inviati in chiaro nell’url (query string).

Nell’esempio abbiamo 3 parametri nell’url “q”, “ie” e “oe” con i relativi valori dopo “=”.

Con il metohd “post” il server riceve direttamente i dati, senza che questi vengano scritti in chiaro nell’URL.
Un form può contenere elementi di input che può avere gli attributi sopra elencati.

Value è il valore di default per quel campo.

Il type “hidden” è una casella di testo invisibile all’utente nella pagina web. Viene utilizzato se voglio far fare
all’utente una selezione e voglio salvare temporaneamente il valore da qualche parte. Per creare delle
piccole aree testuali dove memorizzo il testo.
Il tipo checkbox permette di creare quelle piccole caselle di flag.

Il tipo radio serve per creare una selezione alternativa (o una o l’altra): devono avere tutti gli stessi name per
avere la selezione esclusiva.

Il tipo submit crea un bottone per inviare i dati al server: value specifica il testo da visualizzare sul pulsante.
Il tag select permette la creazione di un meno a discesa. Esso contiene vari tag di tipo option, che
specificano appunto le varie opzioni del menu.

Il tag fieldset permette di creare un gruppo di campi nel form, cioè per raggruppare diverse aree a cui
possiamo dare un titolo mediante il tag legend. Mette una sorta di “cornice” per raggruppare diversi input
in un form, dandogli un titolo.
Risultato:
CGI: CENNI.

È una tecnologia oramai obsoleta perché porta diversi inconvenienti.

Tratta l’aspetto dinamico di un sito web, cioè delle richieste di un client verso un server.

CGI è il primo standard per la creazione di pagine web dinamiche. Consentiva di richiamare un programma
nel server web scritto in un qualsiasi linguaggio di programmazione.

Il client fa delle richieste al server, il server invoca un programma CGI e lo esegue. Tale programma genera
un risultato che fornisce al server, e il server lo inoltra al client.

Per ogni richiesta che arriva al server, crea un processo CGI.


I parametri nella url vengono passati in modalità nome=valore. Tali parametri sono quelli che si trovano
dopo il simbolo “?”.

In tale esempio è stato utilizzato C++ per la creazione della pagina web. In particolare, sono stati utilizzati
dei printf che contengono dei tag html.
È possibile utilizzare le CGI anche per invocare un DB. Ad esempio, nel codice C andrò ad includere del
codice SQL per eseguire delle query.

Lato client si utilizza sostanzialmente JavaScript.


Il linguaggio javascript viene interpretato dal browser e solitamente viene inserito all’interno dello stesso
file html.
JavaScript viene interpretato da tutti i browser.

01-06-2021

Il web dinamico attuale utilizza diverse tecnologie.

Lato client si utilizza ad esempio javascript.

Lato server si utilizzano servlet java, JSP, PHP ecc...

Con le JSP si inietta codice java nell’html, mentre con le servlet si utilizza linguaggio html all’interno del
codice java.
Le CGI trovano utilizzo con Python ad esempio nell’ambito dell’intelligenza artificiale.
Sono un’alternativa alla CGI: utilizzano linguaggio JAVA e trovano affinità con le applet JAVA.
Il container prende il codice java e produce un contenuto web dinamico. Il container è il web server stesso.

Il client invia una richiesta al web server, e il web server la inoltra alla servlet corrispondente. Dopodiché,
viene attivato un thread che elabora tale richiesta, ed infine fornirà una risposta al web server che la
inoltrerà al client.
Nel caso di richieste contemporanee saranno attivati più thread.

Un altro esempio di servlet container è XAMPP.

Una servlet è una classe che estende la classe HttpServlet.


Se devo utilizzare la servlet per il protocollo http vado ad includere javax.servlet.http.
Classes: contiene il codice java compilato (i .class).
La cartella principale nel container Tomcat è webapps. Essa contiene a sua volta una cartella per ogni
applicazione che vogliamo creare.

In essa, si crea la cartella WEB-INF che conterrà le sottocartelle classes e lib e il file web.xml.
Abbiamo una server http che utilizza il metodo doGet che accetta come parametri istanze delle classi
HttpServletRequest e HttpServletResponse.

setContentType definisce il formato della risposta: nel nostro esempio è codice html.

Il response.getWriter serve ad inviare dati attraverso lo stream I/O. Ci consente quindi di eseguire le varie
stampe a video del codice html.
Quando devo passare tanti parametri è preferibile utilizzare il metodo POST.
Nel tag FORM viene specificato l’ACTION che specifica la pagina alla quale devo inviare i dati, che nel nostro
caso è la servlet sul server. Il metodo utilizzato è GET.

La servlet riceve i dati e li processa: in particolare li va a visualizzare secondo il formato in alto a destra.
Come fare delle richieste al Database attraverso le servlet? Attraverso il JDBC.

Sono una serie di classi che ci consentono di connetterci al DB: in base al DB avremo differenti tipi di JDBC.

C’è anche un bridge JDBC-ODBC, che crea un ponte per poterci collegare ad un qualunque DB.
Se la connessione al DBMS va a buon fine viene restituito un oggetto statement.

La differenza principale tra Servlet e JSP è che nelle servlet iniettiamo codice HTML in codice java, mentre
con le JSP si inglobano direttamente pezzi di codice java nel codice html.
Abbiamo una richiesta http al web server.

Le JSP sono caricate sul webserver. Vengono tradotte in java servlet e vengono compilate. L’engine le carica,
le istanzia e le inizializza. Tale parte viene fatta prima, in maniera che, quando il client la richiede deve
essere solo mandata in esecuzione.

È la notazione più diffusa nel web


Nota: la JSP viene comunque tradotta in una servlet in maniera automatica.

Con le servlet ho codice JAVA nel quale vado ad includere pezzi di codice HTML, mentre con le JSP ho codice
HTML nel quale vado ad inserire vari pezzi di codice JAVA
Negli Scriptlet si inseriscono generici pezzi di codice Java col tag <% %>

Nota ogni volta che inserisco il codice java il tag va anche chiuso, soprattutto quando devo alternare una
riga di codice java con una riga di html.
04-06-2021

Una JSP viene tradotta comunque in una servlet.

Viene creata la JSP (Java Server Page): tramite l’engine viene tradotto in una servlet, viene compilata, viene
generato il file.class che viene messo nella struttura ad albero vista precedentemente. Può essere poi
richiamata dal client tramite il browser.

Con le JSP si ha codice html nel quale viene iniettato codice java.

Gli scriptlet sono tutto ciò che ci consente di inserire nella pagina html generico codice java (if, for, while
ecc..)

Sono oggetti richiamabili dalla pagina html, senza necessità di doverli dichiarare.
Vengono utilizzare per includere file esterni come codice html o anche altre JSP.
Taglib è simile all’include: consente di importare librerie esterne.

Ci consentono di interagire con un DB esterno.

A differenza delle Servlet le JSP ci consentono di utilizzare le Java Bean: sono classi particolari che hanno
una interfaccia molto semplice.
L’id rappresenta l’oggetto al quale stiamo facendo riferimento.

La classe indica il Bean che si vuole richiamare.

Lo scope serve ad indicare l’ambito di accessibilità e il tempo di vita dell’oggetto.


Nell’esempio le informazioni acquisite dal DB vengono visualizzate in una tabella HTML.

PHP è un linguaggio lato server (SERVER SIDE): sul server ovviamente deve esserci installato un compilatore
php.

PHP è simile alle JSP: posso richiamare nel codice PHP parti di codice HTML oppure in un file HTML posso
richiamare codice PHP.

Ovviamente, se eseguo il codice HTML da un browser lato client, il codice PHP in esso contenuto non
produrrà nessun effetto.
Se il codice riportato in esempio lo eseguo lato client, non vedrò mai la stampa a video, ma se invece lo
richiamo da un server allora vedrò l’Hello World.
Quando il codice dell’esempio precedente viene eseguito dal server, il client riceverà il codice riportato
sopra.
Non c’è bisogno di indicare il tipo di dato (int, string, ecc..)
Il singolo apice è utilizzato per puro testo.

In PHP gli array possono essere di due tipologie:

- I classici array con indice


- Array associativi: consentono di contestualizzare il contenuto dell’array perché vengono
memorizzati con la modalità coppi chiave-valore.
Il costrutto foreach scansiona l’array e ad ogni iterazione lo memorizza nella variabile “v”.

Vengono utilizzate per includere librerie esterne.

Le funzioni sono dichiarate con la clausola “function”. Il tipo di dato che viene ritornato non viene
specificato. Possono essere restituiti anche array.
L’operatore freccia viene utilizzato per accedere ai metodi di una classe. È l’equivalente del “.” in Java.
Se volessi utilizzare ad esempio la funzione Auto_Carrello senza istanzaire la classe potrei fare

Auto_Carrello::Auto_Carrello.
Tali variabili speciali sono degli array.

Nell’action viene specificata la pagina php alla quale vogliamo inviare i dati.

Quando l’utente preme il pulsante submit, i dati vengono inviati alla pagina action.php.
L’istruzione “die” si forza a far interrompere lo script php e visualizza il messaggio specificato.

v
Posso utilizzare tag html all’interno delle stampe a video (echo), così come fatto per le servlet. L’altro modo
è uscire dalla modalità php e inserire il tag html per inserire codice html.
Abbiamo un form che invia dati con il metodo “post” ed invoca il file “query_cognome.php” sul server.

CI consente di cercare un impiegato nel DB.

Lato server abbiamo:


Lato server:
Viene utilizzato per effettuare richieste http ad un server, oppure per trasferire dei dati.

Viene utilizzato in dispositivi che hanno una bassa capacità computazionale (televisori, automobili ecc).
Il formato più utilizzato in richieste e risposte http con CURL è JSON e XML.

È un linguaggio utilizzato lato client. Può essere ad esempio utilizzato per la validazione dei form, effetti
grafici, posizione dinamica degli oggetti ecc...

Una delle librerie più utilizzate è Jquery.

Non ha nulla a che fare col linguaggio Java.


Per la dichiarazione di variabili non è necessario definire il tipo, ma dichiararle solo attraverso la keyword
“var”.
Per accedere a proprietà di un oggetto si utilizza l’operatore “.” ma anche con la sintassi per accedere agli
array
Può essere integrato direttamente all’interno di una pagina html rendendola dinamica. È eseguito
completamente dal browser essendo un linguaggio lato client.

Quando parliamo di programmazione lato client con Javascript parliamo di programmazione associata ad
eventi che vengono richiamati quando si verificano determinate condizioni sulla pagina web.
Ci fornisce informazioni associate al browser

Inner… e outer… viene utilizzato soprattutto per adattare le dimensioni della pagina web al tipo di
dispositivo che la visualizza (PC, TABLET ecc.)
Ad esempio, la validazione di un form la posso fare sull’evento onSubmit.

Lo script javascript è solitamente incluso nell’head della pagina html


Se ho un client e un server devo utilizzare un linguaggio comune.

Si vuole trovare quindi uno stadio intermedio che si interpone fra i due per poter attuare una traduzione nel
rispettivo linguaggio.

NOTA: VEDI SLIDE PER QUESTA PARTE (SOAP)

(ALTRE SLIDE RIGUARDO CSS)

Potrebbero piacerti anche