Basi Dati - Sommario PDF
Document Details
Uploaded by IntegratedPyrite5211
Università degli Studi di Napoli Federico II
Tags
Summary
This document provides a summary of information systems and database systems. It explains the importance of information management, the role of data in organizations, and introduces the concept of database management systems. The text clarifies the differences between data and information, and it explores the significance of databases in managing large amounts of data effectively.
Full Transcript
BASI DATI: **Sommario** **Nessuna voce di sommario trovata.** **1.0 Sistemi Informativi e Sistemi Informatici** Nello svolgimento di ogni attività, la **disponibilità di informazioni** e la **capacità di gestirle efficacemente** sono essenziali, sia a livello individuale sia nelle organizzazioni...
BASI DATI: **Sommario** **Nessuna voce di sommario trovata.** **1.0 Sistemi Informativi e Sistemi Informatici** Nello svolgimento di ogni attività, la **disponibilità di informazioni** e la **capacità di gestirle efficacemente** sono essenziali, sia a livello individuale sia nelle organizzazioni di ogni dimensione. Ogni organizzazione, infatti, dispone di un **sistema informativo** che organizza e gestisce le informazioni necessarie per perseguire i propri scopi. È importante notare che l'esistenza di un sistema informativo è solo **in parte legata alla sua automatizzazione**: i sistemi informativi esistono da molto prima dell'era dei calcolatori elettronici (ad esempio, archivi bancari o anagrafici). Quando parliamo della parte automatizzata di un sistema informativo, usiamo il termine **sistema informatico**. Negli ultimi decenni, la diffusione capillare dell'informatica ha fatto sì che molti sistemi informativi siano anche sistemi informatici, ma non bisogna confondere i due concetti. Nelle attività umane più semplici, le informazioni possono essere gestite anche senza rappresentazioni complesse, utilizzando **lingua scritta, numeri o disegni**. In altri casi, le informazioni non vengono nemmeno formalizzate ma restano memorizzate a livello mentale. Con la crescita della complessità delle attività, è diventato necessario **organizzare e codificare** meglio le informazioni. Nei **sistemi informatici**, la **rappresentazione e codifica** delle informazioni diventa cruciale. Le informazioni vengono tradotte in **dati**, che devono essere interpretati per fornire significato. **I dati da soli non hanno alcun significato**; è solo attraverso la loro interpretazione e correlazione che essi diventano informazioni. Le informazioni, a loro volta, arricchiscono la nostra conoscenza del mondo. A livello pratico, possiamo considerare i **dati** come **simboli** (numeri, stringhe, ecc.) che richiedono **elaborazione** per acquisire significato, diventando così **informazioni** utili. Un esempio semplice chiarisce questa distinzione: la stringa \"Ferrari\" e il numero \"8\", scritti su un foglio di carta, sono **dati**. Da soli, non significano nulla. Tuttavia, nel contesto di un ristorante durante una notte di Capodanno, questi dati possono rappresentare un'ordinazione di una bottiglia di spumante della marca Ferrari, da addebitare alla camera numero 8. Questo esempio dimostra come, una volta aggiunte informazioni contestuali, i dati si trasformino in **informazioni**. Introducendo il concetto di **base di dati**, possiamo definire questa come una **collezione di dati** utilizzata per rappresentare le informazioni necessarie per un sistema informativo. Nei sistemi informativi complessi, una **base di dati** diventa uno strumento fondamentale per gestire grandi quantità di dati in modo efficiente. Inoltre, i dati tendono a essere molto più stabili nel tempo rispetto alle procedure che operano su di essi. Per esempio, i **dati bancari** sono rimasti invariati per decenni, mentre le procedure che li gestiscono cambiano più frequentemente. Quando una procedura viene sostituita, la nuova \"eredita\" i **dati** della vecchia, spesso con trasformazioni. Questa stabilità dei **dati** porta alla conclusione che essi rappresentano una **risorsa fondamentale** per l\'organizzazione: un vero e proprio patrimonio da sfruttare e proteggere. **2.0 Basi di Dati: Un Pilastro dell\'Informazione** Fin dagli albori dell\'informatica, l\'attenzione verso la **gestione dei dati** ha sempre rappresentato un aspetto centrale. Tuttavia, solo a partire dalla fine degli anni Sessanta sono stati sviluppati **sistemi software specifici per la gestione dei dati**, e, ancora oggi, alcune applicazioni non ne fanno uso. In assenza di un **software dedicato**, la gestione dei dati è stata tradizionalmente affidata a **linguaggi di programmazione** come C e Fortran o, in tempi più recenti, a **linguaggi a oggetti** come C++ e Java. Esistono ancora applicazioni scritte in COBOL, un linguaggio degli anni Sessanta ormai superato. L\'approccio **convenzionale** alla gestione dei dati si basa su **archivi o file** per memorizzare i dati in modo persistente su memoria di massa. Sebbene un file permetta di memorizzare e cercare dati, esso offre solo **semplici meccanismi di accesso** e condivisione. In questo modello, ogni programma gestisce i propri **file \"privati\"**, il che comporta **duplicazioni** dei dati e **possibili incoerenze** quando dati replicati non vengono aggiornati in modo sincrono. Un esempio concreto di questa problematica si può osservare nel contesto universitario, dove diverse entità gestiscono informazioni sui docenti. L\'**ufficio del personale** mantiene i dati relativi alla carriera accademica, le **presidenze delle facoltà** gestiscono gli incarichi di insegnamento, e l\'**ufficio stipendi** utilizza tali informazioni per calcolare le retribuzioni. Se ognuno di questi uffici gestisse le proprie informazioni separatamente, si creerebbero **incoerenze** tra le varie copie dei dati. Le **basi di dati** sono state sviluppate per **risolvere questi problemi**, permettendo la gestione integrata delle informazioni e riducendo **ridondanza** e **incoerenze**. Un **sistema di gestione di basi di dati** (DBMS) è un **software** progettato per gestire **collezioni di dati** di grandi dimensioni, condivise tra più utenti, e garantire che siano **persistenti**, **affidabili** e **private**. Le principali caratteristiche di un **DBMS** sono: **Grandi dimensioni**: Le basi di dati possono raggiungere dimensioni enormi, misurate in centinaia o migliaia di terabyte. I DBMS devono essere in grado di gestire queste collezioni di dati, che spesso superano la capacità della memoria centrale disponibile. **Condivisione dei dati**: I dati devono essere **accessibili** a più utenti o applicazioni in modo sicuro, riducendo la ridondanza e prevenendo **inconsistenze**. I DBMS includono un **controllo di concorrenza** per garantire che le operazioni simultanee non causino conflitti. **Persistenza**: I dati rimangono **disponibili** anche al termine dell\'esecuzione di un programma. A differenza dei dati temporanei gestiti in memoria centrale, i dati in un DBMS sono **permanenti**. **Affidabilità**: I DBMS offrono meccanismi di **salvataggio e ripristino** per proteggere i dati da errori hardware o software, garantendo che i dati cruciali, come quelli finanziari, siano **preservati nel tempo**. **Privatezza**: I DBMS regolano l\'accesso ai dati attraverso **meccanismi di autorizzazione**, permettendo solo ad utenti autorizzati di eseguire determinate operazioni sui dati. **Efficienza ed efficacia**: Un DBMS deve essere **efficiente** nell\'utilizzo delle risorse di sistema e **efficace** nel rendere produttive le attività degli utenti. Sebbene sia possibile gestire dati **persistenti** e **di grandi dimensioni** utilizzando file semplici, i **DBMS offrono funzionalità superiori**, come l\'accesso condiviso e la gestione centralizzata. Ad esempio, in un\'università, un sistema ideale prevedrebbe una **singola base di dati** condivisa tra tutti gli uffici per gestire le informazioni sui docenti, i corsi, le facoltà e gli aspetti amministrativi. Tuttavia, a volte è necessario mantenere basi di dati separate, come nel caso delle informazioni riservate sui docenti rispetto a quelle pubbliche sui corsi, che possono essere accessibili tramite il sito web dell\'ateneo. **2.1 Modelli dei dati** Un **modello dei dati** è un insieme di concetti utilizzati per **organizzare** i dati di interesse e descriverne la **struttura** in modo che possa essere compresa e gestita da un elaboratore. Ogni modello dei dati fornisce dei **meccanismi di strutturazione**, che sono analoghi ai costruttori di tipo dei linguaggi di programmazione. Questi meccanismi permettono di definire **nuovi tipi** basati su tipi **predefiniti** (elementari) e costruttori di tipo. Ad esempio, nel linguaggio di programmazione **C**, è possibile definire nuovi tipi attraverso costruttori come **struct**, **union**, **enum**, e **pointer** (\*). Il **modello relazionale**, introdotto formalmente agli inizi degli anni Settanta e diffusosi durante il decennio successivo, rappresenta il modello dei dati più utilizzato e viene considerato il modello di **riferimento** nel campo della gestione dei dati. Il **modello relazionale dei dati** consente di definire tipi attraverso il **costruttore relazione**, che organizza i dati in **insiemi di record a struttura fissa**. Una relazione è comunemente rappresentata come una **tabella**: le **righe** rappresentano specifici record, mentre le **colonne** corrispondono ai campi di ciascun record. È importante notare che l\'**ordine** delle righe e delle colonne in una tabella non ha rilevanza nel contesto del modello relazionale. **2.2 Schemi e istanze** Nelle **basi di dati**, si distingue tra una parte **sostanzialmente invariante nel tempo**, detta **schema della base di dati**, e una parte **variabile nel tempo**, chiamata **istanza o stato della base di dati**. Lo **schema** rappresenta le **caratteristiche strutturali dei dati**, mentre l\'**istanza** si riferisce ai **valori effettivi** che tali dati assumono in un dato momento. Ad esempio, nella relazione **DOCENZA**, lo schema è costituito dalla struttura fissa che include due **attributi**: **Corso** e **NomeDocente**. Lo schema della relazione si può rappresentare così:\ **DOCENZA(Corso, NomeDocente)**.\ Le **righe** della tabella, invece, sono soggette a cambiamenti nel tempo, riflettendo l\'**offerta corrente di corsi** e i **docenti associati**. Ad esempio, in un dato momento, l\'istanza potrebbe essere: Basi di dati -- Rossi Reti -- Neri Linguaggi -- Verdi In termini tecnici, lo **schema** rappresenta la componente **intensionale** della base di dati, mentre l\'**istanza** rappresenta la componente **estensionale**. **3.0 Architettura a tre livelli delle basi di dati** La nozione di modello e di schema può essere ulteriormente articolata attraverso una proposta di **architettura a tre livelli** per i DBMS. I tre livelli sono: **Schema esterno**: descrive una porzione della base di dati, specifica per l\'utente o un gruppo di utenti. Lo schema esterno può avere un'organizzazione diversa rispetto a quella dello schema logico. In effetti, è possibile avere diversi schemi esterni per uno stesso schema logico. **Schema logico**: rappresenta una descrizione **complessiva** della base di dati secondo il **modello logico** scelto (ad esempio, relazionale o a oggetti). **Schema interno**: descrive la rappresentazione **fisica** dei dati, come vengono **memorizzati** su disco, per esempio tramite file sequenziali o hash. In pratica, nei sistemi moderni, il **livello esterno** può essere gestito tramite l\'uso di **viste** (**views**), ovvero relazioni derivate da altre tabelle. Queste permettono di adattare l'**accesso ai dati** in base alle esigenze degli utenti. Ad esempio, uno studente di **Ingegneria Elettronica** potrebbe vedere solo i corsi del suo manifesto, tramite una **vista** sulla base di dati più ampia, come mostrato nella relazione **ELETTRONICA**. Inoltre, i meccanismi di **autorizzazione** consentono di regolare l'**accesso degli utenti** alle informazioni della base di dati, garantendo così sia la sicurezza che la **privatezza** dei dati sensibili. **4.0 Modelli logici nei sistemi di basi dati** Il **modello relazionale** si fonda su due concetti principali: **relazione** e **tabella**. Questi concetti, pur avendo origini diverse, sono strettamente connessi. La **relazione** proviene dalla **teoria degli insiemi** nella matematica, mentre la **tabella** è un concetto intuitivo e di uso comune. Questa combinazione ha contribuito al successo del modello relazionale, in quanto offre sia una base formale rigorosa sia una rappresentazione comprensibile e utilizzabile facilmente dagli utenti finali. **4.1 Indipendenza dei dati** Il modello relazionale è stato progettato per garantire l\'**indipendenza dei dati**, separando il **livello fisico** da quello **logico**. Gli utenti e i programmatori operano esclusivamente al livello logico, senza la necessità di conoscere come i dati vengono effettivamente memorizzati fisicamente. Questa separazione rende il modello versatile e adatto per molte applicazioni, poiché eventuali modifiche alla struttura fisica dei dati non richiedono cambiamenti nelle applicazioni che li utilizzano. In confronto, i modelli di basi di dati **precedenti** (come quello **reticolare** e quello **gerarchico**) incorporavano riferimenti espliciti alle strutture fisiche attraverso puntatori e l'ordine fisico dei dati. Questo vincolo rendeva più difficile la gestione e l\'evoluzione delle basi di dati. **4.1.1 Tre accezioni del termine \"relazione\"** Il termine **relazione**, nell'ambito delle basi di dati, ha tre diverse interpretazioni: **Relazione matematica**: deriva dalla teoria degli insiemi e si riferisce a una collezione di n-tuples o coppie ordinate di elementi. **Relazione nel modello relazionale**: utilizza la nozione matematica come base, ma presenta alcune differenze, come vedremo più avanti. Una tabella rappresenta una relazione in questo contesto. **Relazione nel modello Entità-Relazione (Entity-Relationship)**: questo concetto, tradotto dall\'inglese \"relationship\", è usato per rappresentare i **legami tra entità** del mondo reale nel modello concettuale. **5.0 Relazioni e Tabelle** Il **prodotto cartesiano** è un concetto fondamentale nella matematica, in particolare nella teoria degli insiemi, ed è alla base del **modello relazionale** nelle basi di dati. Dati due insiemi [*D*~1~]{.math.inline} e [*D*~2~]{.math.inline}, il loro prodotto cartesiano, indicato come [*D*~1~ × *D*~2~]{.math.inline}, è l\'insieme di tutte le **coppie ordinate** [(*u*~1~, *u*~2~)]{.math.inline}, dove [*u*~1~ ]{.math.inline}appartiene a [*D*~1~]{.math.inline} e [*u*~2~]{.math.inline} appartiene a [*D*~2~]{.math.inline}. **Esempio di prodotto cartesiano:** Se consideriamo gli insiemi: \ [*A* = {1, 2, 4}]{.math.display}\ \ [*B* = {*a*, *b*}]{.math.display}\ Il prodotto cartesiano [*A* × *B*]{.math.inline} è l\'insieme di tutte le possibili coppie [(*a*, *b*)]{.math.inline} in cui il primo elemento proviene da A e il secondo da B, cioè: \ [*A* × *B* = {(1, *a*), (1, *b*), (2, *a*), (2, *b*), (4, *a*), (4, *b*)}]{.math.display}\ Questo esempio genera sei coppie ordinate, poiché A contiene 3 elementi e B ne contiene 2, quindi [3 × 2 = 6]{.math.inline}. **5.0.1 Relazione matematica:** Una **relazione matematica** sugli insiemi [*D*~1~]{.math.inline} e [*D*~2~]{.math.inline} è semplicemente un **sottoinsieme del prodotto cartesiano** [*D*~1~ × *D*~2~]{.math.inline}. Ad esempio, una relazione su A e B potrebbe essere: \ [{(1, *a*), (1, *b*), (4, *b*)}]{.math.display}\ Questa relazione contiene solo alcune coppie del prodotto cartesiano, quindi è un sottoinsieme. **5.0.2 Rappresentazione tabellare:** Le relazioni matematiche possono essere rappresentate in forma di **tabelle**, dove: Le colonne della tabella corrispondono ai domini [*D*~1~]{.math.inline}, [*D*~2~]{.math.inline}, ecc. Le righe corrispondono alle n-uple o coppie ordinate della relazione. Nell\'esempio precedente, il prodotto cartesiano [*A* × *B*]{.math.inline} e la relazione specifica possono essere rappresentati come tabelle con righe e colonne. **5.1 Generalizzazione a n insiemi:** Il concetto di prodotto cartesiano può essere generalizzato a più di due insiemi. Dati n insiemi [*D*~1~, *D*~2~, ..., *D*~*n*~]{.math.inline}, il loro prodotto cartesiano è indicato come [*D*~1~ × *D*~2~ × ... × *D*~*n*~]{.math.inline} ed è costituito dalle **n-uple** [(*v*~1~, *v*~2~, ..., *v*~*n*~),]{.math.inline} dove ciascun [*v*~*i*~]{.math.inline} appartiene a [*D*~*i*~]{.math.inline}. Una **relazione matematica** su questi insiemi è un sottoinsieme del prodotto cartesiano. Il numero n delle componenti di una n-upla (e quindi del prodotto cartesiano) è chiamato **grado** della relazione. Il numero di n-uple in una relazione è la sua **cardinalità**. **5.2 Applicazione pratica nelle basi di dati:** Le relazioni matematiche trovano una rappresentazione pratica nel modello relazionale delle basi di dati. Ad esempio, una tabella che registra i risultati di partite di calcio può essere rappresentata come una relazione. Se i domini sono: Squadra1:Stringa Squadra2:Stringa GolSquadra1:Intero GolSquadra2:Intero La relazione risulterebbe essere un sottoinsieme del prodotto cartesiano di questi domini: \ [Stringa × Stringa × Intero × Intero]{.math.display}\ Ogni riga della tabella rappresenta una partita e i rispettivi risultati. In sintesi, il concetto di **prodotto cartesiano** e di **relazione** formano la base del **modello relazionale** utilizzato nelle basi di dati, dove ogni tabella rappresenta una relazione matematica che può essere rappresentata e manipolata in modo chiaro e intuitivo. **5.3 Relazioni con attributi** Le osservazioni sulle relazioni e le loro rappresentazioni tabellari nel contesto delle basi di dati evidenziano diversi aspetti importanti. Ogni **n-upla** in una relazione stabilisce un legame tra i suoi elementi. Ad esempio, una n-upla [(*u*~1~, *u*~2~, ..., *u*~*n*~)]{.math.inline} collega i valori dei domini [*D*~1~, *D*~2~, ..., *D*~*n*~]{.math.inline} in un certo ordine. Nella relazione che rappresenta i risultati di partite di calcio, una riga come [\$(\"\\text{Juventus}\",\"\\text{Lazio}\",3,1)\$]{.math.inline} stabilisce un collegamento tra i nomi delle squadre e i rispettivi punteggi. È importante notare che una relazione è un insieme di n-uple, e come tale presenta alcune proprietà. Innanzitutto, non esiste alcun ordinamento tra le n-uple; l\'ordine degli elementi in un insieme non è rilevante. Anche se in una rappresentazione tabellare le righe sono visualizzate in un certo ordine, questo è \"accidentale\". Cambiando l\'ordine delle righe, la relazione rappresentata rimane invariata. Inoltre, le n-uple di una relazione devono essere distinte; non possono esserci n-uple duplicate, poiché gli elementi di un insieme devono essere unici. Pertanto, ogni riga di una tabella deve essere unica. Allo stesso tempo, l\'ordinamento interno delle n-uple è cruciale. Sebbene l\'ordine delle righe in una relazione sia irrilevante, all\'interno di ciascuna n-upla l\'ordine dei valori è importante. Ogni valore è associato a un dominio specifico e la posizione del valore è fondamentale per interpretarlo correttamente. Se, ad esempio, si scambiasse la posizione dei domini che rappresentano i gol segnati dalle squadre, il significato della relazione cambierebbe drasticamente. Il fatto che i valori siano interpretati in base alla loro posizione rende il concetto di relazione matematica meno flessibile quando si tratta di gestire dati complessi. In molti contesti informatici, si preferisce l\'uso di notazioni non posizionali, come nei record, dove i campi sono identificati da nomi (attributi), piuttosto che da posizioni. Questa preferenza deriva dal fatto che i nomi sono più esplicativi e permettono una gestione più semplice e comprensibile dei dati. Quando usiamo le relazioni per organizzare i dati nelle basi di dati, possiamo pensare a ciascuna relazione come a un insieme di record omogenei, definiti sugli stessi campi (o attributi). Ciò rende la struttura della relazione più vicina a quella di un record piuttosto che a una semplice n-upla posizionale. Una relazione che registra i risultati delle partite potrebbe avere attributi come **SquadraDiCasa**, **SquadraOspitata**, **RetiCasa**, e **RetiOspitata**. Associando a ciascun dominio un nome simbolico (attributo), si può migliorare la chiarezza della relazione. Invece di riferirsi ai domini in base alla loro posizione, possiamo riferirci agli attributi. Per esempio, nella tabella delle partite, gli attributi possono essere usati come intestazioni delle colonne, rendendo la relazione più leggibile e interpretabile. Una volta introdotti gli attributi, l\'ordine delle colonne (e degli attributi stessi) diventa irrilevante. Non è più necessario parlare di primo dominio, secondo dominio e così via; è sufficiente fare riferimento agli attributi. Questo consente una maggiore flessibilità nella rappresentazione dei dati. Inoltre, è possibile utilizzare una notazione utile per riferirsi agli attributi, come [*t*\[*A*\]]{.math.inline}, dove t è una tupla e A è un attributo. Questa notazione ci permette di accedere al valore della tupla per un dato attributo. Infine, possiamo formalizzare il concetto di tupla: una tupla su un insieme di attributi X è una funzione t che associa a ciascun attributo [*A* ∈ *X*]{.math.inline} un valore appartenente al dominio associato a A. Con questa nuova definizione, una relazione non è più vista come un insieme di n-uple posizionali, ma come un insieme di tuple che associano i valori ai nomi degli attributi. Questa definizione elimina la necessità di interpretare i dati in base alla loro posizione, rendendo la gestione dei dati più flessibile e intuitiva. **6.0 Relazione e basi di dati** Come già osservato, una relazione può essere utilizzata per organizzare dati rilevanti nell\'ambito di un\'applicazione di interesse. Tuttavia, di solito non è sufficiente una singola relazione; una base di dati è generalmente costituita da più relazioni, le cui tuple contengono valori comuni, necessari per stabilire corrispondenze. Per esemplificare, consideriamo una base di dati in cui: **La prima relazione** contiene informazioni relative a un insieme di studenti, con numero di matricola, cognome, nome e data di nascita. **La terza relazione** include informazioni su alcuni corsi, comprendenti codice, titolo e docente. **La seconda relazione** raccoglie informazioni relative agli esami, specificando il numero di matricola dello studente, il codice del corso e il voto. Questa relazione fa riferimento ai dati contenuti nelle altre due: agli studenti, attraverso i numeri di matricola, e ai corsi, tramite i relativi codici. La base di dati presenta una delle caratteristiche fondamentali del modello relazionale, spesso descritta come \"basata su valori\": i riferimenti tra dati in relazioni diverse sono rappresentati mediante valori dei domini che compaiono nelle tuple. Gli altri modelli logici, come quello reticolare e gerarchico (sviluppati prima del modello relazionale ma ancora in uso), stabiliscono le corrispondenze in modo esplicito attraverso puntatori e sono perciò detti modelli \"basati su record e puntatori\". Sebbene non approfondiremo questi modelli, è utile evidenziare la loro caratteristica principale: i dati sono collegati attraverso puntatori. Rispetto a un modello basato su record e puntatori, il modello relazionale presenta diversi vantaggi: Richiede di rappresentare solo ciò che è rilevante dal punto di vista dell\'applicazione (e quindi dell\'utente); i puntatori sono un elemento aggiuntivo, legato a aspetti realizzativi. Nei modelli con puntatori, il programmatore si riferisce a dati che non sono significativi per l\'applicazione. La rappresentazione logica dei dati, costituita esclusivamente dai valori, non fa alcun riferimento alla rappresentazione fisica, che può cambiare nel tempo. Il modello relazionale consente quindi di ottenere l\'indipendenza fisica dei dati; poiché tutte le informazioni sono contenute nei valori, è relativamente semplice trasferire i dati da un contesto a un altro (ad esempio, trasferire una base di dati da un computer a un altro). In presenza di puntatori, l\'operazione risulta più complessa, poiché i puntatori hanno un significato locale al singolo sistema, il che non sempre è immediato da esportare. È interessante notare che anche in una base di dati relazionale, a livello fisico, i dati possono essere rappresentati attraverso l\'uso di puntatori. Tuttavia, nei modelli relazionali, i puntatori non sono visibili a livello logico. Inoltre, nei sistemi di basi di dati a oggetti, che rappresentano una delle direzioni evolutive delle basi di dati, vengono introdotti identificatori di oggetto, i quali, sebbene a un livello di astrazione più alto, presentano alcune caratteristiche dei puntatori. Possiamo ora riassumere le definizioni relative al modello relazionale, distinguendo il livello degli schemi da quello delle istanze: **Schema di relazione**: costituito da un simbolo R (nome della relazione) e da un insieme di (nomi di) attributi [*X* = {*A*~1~, *A*~2~, ..., *A*~*n*~}]{.math.inline}, di solito indicato come [*R*(*X*)]{.math.inline}. A ciascun attributo è associato un dominio, come visto in precedenza. **Schema di base di dati**: un insieme di schemi di relazione con nomi diversi: \ [*R* = {*R*~1~(*X*~1~), *R*~2~(*X*~2~), ..., *R*~*n*~(*X*~*n*~)}]{.math.display}\ I nomi di relazione servono principalmente a distinguere le varie relazioni all\'interno della base di dati. **Istanza di relazione**: un insieme r di tuple su X. \ [*R* = {*R*~1~(*X*~1~), *R*~2~(*X*~2~), ..., *R*~*n*~(*X*~*n*~)}]{.math.display}\ dove ogni [*r*~*i*~]{.math.inline} è una relazione sullo schema [*R*~*i*~(*X*~*i*~)]{.math.inline}. Per chiarire, lo schema della base di dati può essere definito come segue (con opportune definizioni per i domini): \ [*R* = {STUDENTI(Matricola, Cognome, Nome), ESAMI(Voto, Corso), CORSI(Codice, Titolo, Docente)}]{.math.display}\ Accenniamo brevemente alle convenzioni che adotteremo in seguito, al fine di favorire la sinteticità della notazione senza compromettere la comprensione: Gli attributi verranno indicati con lettere iniziali dell\'alfabeto (maiuscole), eventualmente con indici e/o pedici: [*A*, *B*, *C*, *A*^′^, *A*~1~, ...]{.math.inline} Insiemi di attributi saranno indicati con lettere finali dell\'alfabeto (maiuscole): [*X*, *Y*, *Z*, *X*′, *X*~*i*~, ...]{.math.inline}. Un insieme in cui si vogliano evidenziare gli attributi componenti verrà denotato dalla giustapposizione dei nomi degli attributi stessi: ad esempio, scriveremo [*X* = ABC]{.math.inline} anziché [*X* = {*A*, *B*, *C*}]{.math.inline}. Analogamente, l\'unione di insiemi verrà denotata dalla giustapposizione dei relativi nomi: scriveremo [XY]{.math.inline} anziché [*X* ∪ *Y*]{.math.inline}; unendo le due convenzioni, scriveremo [XA ]{.math.inline}anziché [*X* ∪ {*A*}]{.math.inline}. Per i nomi di relazione, quando non si possano o debbano usare nomi significativi, utilizzeremo R e lettere contigue (maiuscole): [*R*~1~, *S*, *S*′, ...]{.math.inline}. Per le relazioni, utilizzeremo gli stessi simboli, ma in lettere minuscole. Per approfondire ulteriormente i concetti fondamentali del modello relazionale, esaminiamo un paio di esempi. In primo luogo, notiamo che secondo la definizione sono ammissibili relazioni su un solo attributo. Ciò può avere senso, in particolare, in basi di dati composte da più relazioni, in cui la relazione su singolo attributo contiene valori che appaiono come valori di un attributo di un\'altra relazione. Ad esempio, in una base di dati che include la relazione STUDENTI, si può utilizzare un\'altra relazione sul solo attributo Matricola per indicare gli studenti lavoratori (attraverso i rispettivi numeri di matricola, che devono apparire nella relazione STUDENTI). Discutiamo ora un esempio un po\' più complesso, che mostra come, sebbene indirettamente, il modello relazionale consenta di rappresentare informazioni strutturate in modo articolato. Le ricevute fiscali emesse da un ristorante hanno una struttura che prevede, oltre alle frasi prestampate, alcune informazioni fisse (numero, data e totale) e un numero di righe variabile, ognuna relativa a un insieme di portate omogenee (con quantità, descrizione e importo complessivo). Poiché le relazioni hanno una struttura fissa, non è possibile rappresentare l\'insieme delle ricevute con un\'unica relazione, in quanto non sarebbe possibile gestire le righe in un numero non predeterminato. **7.0 Informazione incompleta e valori nulli** La struttura del modello relazionale, come discusso, è molto semplice e potente, ma presenta anche una certa rigidità. Le informazioni devono essere rappresentate tramite tuple di dati omogenee, e ogni relazione può contenere solo tuple conformi al suo schema. Questo può risultare problematico quando i dati disponibili non corrispondono esattamente al formato previsto. Ad esempio, in una relazione con lo schema: **PERSONE(Cognome, Nome, Indirizzo, Telefono)** il valore dell\'attributo Telefono potrebbe non essere disponibile per tutte le tuple. Utilizzare un valore del dominio per rappresentare l\'assenza di informazioni, come ad esempio lo zero per i numeri di telefono, può generare confusione e non è una pratica corretta. Ciò richiede che esista un valore mai utilizzato per indicare l\'assenza, e non sempre è possibile trovare un valore del dominio adeguato. Ad esempio, in un attributo per le date di nascita, non ci sono valori non utilizzati in un tipo Data correttamente definito. Inoltre, l\'uso di valori del dominio può mascherare la distinzione tra valori \"veri\" e valori fittizi, rendendo necessario che i programmi accedano alla base di dati con attenzione, distinguendo correttamente i valori. Per rappresentare la non disponibilità di valori in modo più chiaro, si introduce il concetto di valore nullo. Un valore nullo denota l\'assenza di informazione ed è un valore speciale distinto dai valori del dominio. Nelle rappresentazioni tabellari, il valore nullo viene indicato con il simbolo NULL. Consideriamo i seguenti esempi di valori nulli: **Città A**: ha un valore nullo per l\'indirizzo della prefettura, indicando che il valore è sconosciuto. **Città B**: non ha una prefettura, quindi l\'attributo relativo è inesistente; il valore nullo rappresenta l\'inesistenza del valore. **Città C**: è una nuova provincia e non si conosce né se esista né quale sia l\'indirizzo della prefettura; il valore nullo indica l\'incertezza riguardo all\'esistenza dell\'informazione. Nei sistemi di basi di dati relazionali, i valori nulli sono gestiti in modo semplice, senza ipotesi particolari, collocandoli nella categoria di valori \"senza informazione\". Riflettendo ulteriormente sui valori nulli, consideriamo una base di dati con lo stesso schema delle PERSONE. Un valore nullo per la data di nascita potrebbe essere accettabile, mentre un valore nullo per il numero di matricola o per il codice di un corso comporterebbe problematiche maggiori, poiché questi valori sono essenziali per stabilire correlazioni tra tuple di relazioni diverse. La presenza di valori nulli in una relazione, come nella relazione degli esami, può rendere inutilizzabili le informazioni. Inoltre, un numero eccessivo di valori nulli in una relazione può sollevare dubbi sull\'identità delle tuple stesse. È quindi cruciale controllare la presenza di valori nulli nelle relazioni, specificando che sono ammessi solo su alcuni attributi e non su altri. Alla fine, vedremo che è possibile definire criteri per individuare quali attributi non dovrebbero contenere valori nulli. **8.0 Vincoli di Integrità** In una base di dati, è fondamentale evitare situazioni problematiche come quelle descritte in precedenza. A tal fine, è stato introdotto il concetto di **vincolo di integrità**, che rappresenta una proprietà che deve essere soddisfatta dalle istanze per **garantire informazioni corrette per l\'applicazione**. Ogni vincolo può essere considerato come un predicato che associa a ogni istanza un valore di vero o falso. Se il predicato assume il valore vero, si afferma che l\'istanza soddisfa il vincolo. In generale, a uno schema di base di dati è associato un insieme di vincoli e si considerano corrette (o lecite, o ammissibili) le istanze che soddisfano tutti i vincoli. Per ciascuno dei casi precedentemente discussi, potrebbe essere introdotto un vincolo per vietare la situazione indesiderata. I vincoli possono essere classificati in base agli elementi della base di dati che ne sono coinvolti. Distinguiamo due categorie, di cui la prima presenta alcuni casi particolari: **Vincoli intrarelazionali**: questi vincoli sono definiti rispetto a singole relazioni della base di dati. I primi tre casi precedentemente discussi corrispondono a vincoli intrarelazionali. Talvolta, il coinvolgimento riguarda le tuple (o addirittura i valori) separatamente l\'una dall\'altra: **Vincolo di tupla**: è un vincolo che può essere valutato su ciascuna tupla indipendentemente dalle altre. I vincoli relativi ai primi due casi rientrano in questa categoria. **Vincolo su valori o vincolo di dominio**: è un caso ancora più specifico, in cui si impone una restrizione sul dominio di un attributo. Ad esempio, nel caso in cui sono ammessi solo valori dell\'attributo Voto compresi fra 18 e 30. **Vincoli interrelazionali**: questi vincoli coinvolgono più relazioni. Ad esempio, nella situazione indesiderata del quarto caso, è possibile vietare la situazione richiedendo che un numero di matricola compaia nella relazione ESAMI solo se è presente nella relazione STUDENTI. Nei prossimi paragrafi, esamineremo con maggiore dettaglio tre classi di vincoli molto importanti: Una classe interessante di vincoli di tupla. I vincoli di chiave, che sono i più rilevanti vincoli intrarelazionali. I vincoli di integrità referenziale, che sono i vincoli interrelazionali di maggiore interesse. **8.1 Vincoli di tupla** Come abbiamo discusso, i vincoli di tupla esprimono condizioni sui valori di ciascuna tupla, indipendentemente dalle altre. Una sintassi utile per esprimere vincoli di questo tipo consente di definire espressioni booleane, che utilizzano connettivi logici come AND, OR e NOT, con atomi che confrontano (con operatori di uguaglianza, disuguaglianza e ordinamento) valori di attributo o espressioni aritmetiche su valori di attributo. I vincoli violati identificati possono essere descritti come segue: \ [(Voto \> 18) AND (Voto \ 1; **16.0 Operatori Insiemistici in SQL** In SQL, gli operatori insiemistici consentono di combinare i risultati di due o più query trattandoli come se fossero insiemi matematici. Le principali operazioni insiemistiche sono **UNION**, **INTERSECT** ed **EXCEPT**. Ognuno di questi operatori ha caratteristiche uniche che li rendono utili in diverse situazioni. **UNION** L\'operatore **UNION** è utilizzato per unire i risultati di due o più query, producendo un insieme di righe distinte. Questo significa che, se ci sono righe duplicate tra le query, queste verranno eliminate nel risultato finale. Un aspetto importante da considerare quando si utilizza **UNION** è che le colonne delle query devono avere lo stesso numero e tipo di dati compatibili. Ad esempio, se una query seleziona nomi e cognomi, anche l\'altra deve avere colonne corrispondenti. Immagina di avere due tabelle, **STUDENTI\_BASIDATI** e **STUDENTI\_ANALISI1**, entrambe contenenti dati sugli studenti. Utilizzando **UNION**, puoi ottenere un elenco unico di nomi e cognomi degli studenti presenti in entrambe le tabelle, senza duplicati. Ad esempio: SELECT Nome, Cognome FROM STUDENTI\_BASIDATI UNION SELECT Nome, Cognome FROM STUDENTI\_ANALISI1; Questo comando restituirebbe un insieme di studenti, assicurandosi che ogni nome e cognome appaiano una sola volta. **INTERSECT** L\'operatore **INTERSECT** è utile quando desideri identificare le righe comuni tra due query. Questa operazione restituisce solo le righe che si trovano in entrambe le tabelle, consentendo di vedere le sovrapposizioni tra i due set di dati. Proseguendo con l\'esempio delle tabelle degli studenti, se desideri trovare solo gli studenti presenti in entrambe le tabelle, puoi scrivere: SELECT Nome, Cognome FROM STUDENTI\_BASIDATI INTERSECT SELECT Nome, Cognome FROM STUDENTI\_ANALISI1; In questo caso, il risultato includerebbe solo gli studenti che sono elencati in entrambe le tabelle, il che può essere particolarmente utile per analisi comparative o per verificare l\'appartenenza a gruppi specifici. **EXCEPT** L\'operatore **EXCEPT** serve a trovare le righe presenti nella prima query ma non nella seconda. Questo è utile per isolare le differenze tra i due set di risultati. Tornando all\'esempio delle tabelle, se vuoi ottenere un elenco di studenti che si trovano in **STUDENTI\_BASIDATI** ma non in **STUDENTI\_ANALISI1**, puoi utilizzare: SELECT Nome, Cognome FROM STUDENTI\_BASIDATI EXCEPT SELECT Nome, Cognome FROM STUDENTI\_ANALISI1; In questo modo, otterrai solo gli studenti che non sono inclusi nella seconda tabella, fornendo un insight utile per analisi e reportistica. Quando si utilizzano gli operatori insiemistici, è importante prestare attenzione alle performance, soprattutto con dataset di grandi dimensioni. Le operazioni insiemistiche possono richiedere tempo, poiché il database deve confrontare i dati per trovare duplicati o sovrapposizioni. È quindi consigliabile testare le singole query per assicurarsi che funzionino come previsto prima di combinarle. Inoltre, è possibile ordinare i risultati finali utilizzando la clausola **ORDER BY**, ma questa deve essere applicata solo all\'ultima query della catena di operazioni. Infine, se si desidera mantenere anche i duplicati, è possibile utilizzare **UNION ALL**, che non rimuove le righe duplicate e può offrire migliori performance. **16.1 Query Nidificate** Le query nidificate, note anche come sottointerrogazioni o subquery, sono una caratteristica potente di SQL che consente di utilizzare il risultato di una query all\'interno di un\'altra. Questa funzionalità permette di eseguire interrogazioni più complesse e dettagliate, facilitando l\'analisi e la manipolazione dei dati. **Struttura delle Query Nidificate** In una query nidificata, la sottointerrogazione viene generalmente inserita nella clausola **WHERE** della query principale. La condizione di confronto può essere effettuata utilizzando vari operatori, inclusi quelli di confronto standard e alcuni operatori speciali che estendono le capacità di confronto. **16.2 Operatori di Confronto** **ALL**: Quando si utilizza **ALL**, la condizione specificata deve essere verificata per tutti gli elementi restituiti dalla sottointerrogazione. Ad esempio, se vuoi trovare tutti i dipendenti il cui stipendio è maggiore di quello di tutti i manager, potresti scrivere: SELECT Nome FROM DIPENDENTI WHERE Stipendio \> ALL (SELECT Stipendio FROM MANAGER); In questo caso, il nome del dipendente verrà restituito solo se il suo stipendio è superiore a quello di tutti i manager. **ANY**: Utilizzando **ANY**, la condizione deve essere verificata per almeno uno degli elementi restituiti dalla sottointerrogazione. Ad esempio: SELECT Nome FROM DIPENDENTI WHERE Stipendio \< ANY (SELECT Stipendio FROM MANAGER); Qui, il nome del dipendente sarà restituito se il suo stipendio è inferiore a quello di almeno un manager. **IN e NOT IN**: Questi operatori funzionano in modo simile a **=ANY** e **≠ALL**. Usando **IN**, puoi verificare se un valore è presente nel risultato della sottointerrogazione, mentre **NOT IN** verifica se un valore non è presente. SELECT Nome FROM DIPENDENTI WHERE Stipendio IN (SELECT Stipendio FROM MANAGER); Questo esempio restituirà i nomi dei dipendenti il cui stipendio è esattamente uguale a quello di almeno un manager. **EXISTS e NOT EXISTS**: Questi operatori sono utilizzati per verificare se una sottointerrogazione restituisce un insieme di risultati non vuoto. SELECT Nome FROM DIPENDENTI D WHERE EXISTS (SELECT 1 FROM MANAGER M WHERE D.Stipendio = M.Stipendio); In questo caso, il nome del dipendente verrà restituito se esiste almeno un manager con lo stesso stipendio. **Struttura delle Query Nidificate** Una query nidificata può contenere altre sottointerrogazioni, creando una gerarchia di interrogazioni. Questo consente di costruire interrogazioni molto complesse. Ad esempio, potresti voler trovare tutti i dipendenti il cui stipendio è superiore alla media degli stipendi dei manager: SELECT Nome FROM DIPENDENTI WHERE Stipendio \> (SELECT AVG(Stipendio) FROM MANAGER); In questo caso, la sottointerrogazione calcola prima la media degli stipendi dei manager e poi confronta il risultato con il salario di ciascun dipendente. **Considerazioni sull\'Uso delle Query Nidificate** Le query nidificate sono estremamente utili per l\'analisi dei dati, ma possono anche influenzare le performance, soprattutto quando si lavora con dataset molto grandi. È consigliabile testare le sottointerrogazioni separatamente per assicurarsi che funzionino come previsto prima di integrarle in query più complesse. Inoltre, l\'uso eccessivo di sottointerrogazioni può rendere le query più difficili da leggere e mantenere. Pertanto, è importante bilanciare la complessità delle query con la loro chiarezza e comprensibilità. **Esempio 1: Trovare i nomi e i cognomi delle persone che partecipano al progetto con codice \'EUI05\'** Questa query cerca i dipendenti che partecipano al progetto con un codice specifico. Si utilizza una sottointerrogazione per cercare le matricole dei dipendenti che partecipano al progetto \'EUI05\' e poi, con la query esterna, si recuperano i loro nomi e cognomi. SELECT A.Nome, A.Cognome FROM ANAGRAFICA A WHERE A.Matricola IN ( SELECT T.Mat FROM TEAM T WHERE T.Prog = \'EUI05\' ); **Esempio 2: Trovare i nomi e i cognomi delle persone che partecipano al progetto "Sistemi Informatici e Calcolo Parallelo"** In questo caso, si utilizza una doppia sottointerrogazione: la prima per ottenere il codice del progetto tramite il nome, e la seconda per trovare le matricole dei partecipanti al progetto. SELECT A.Nome, A.Cognome FROM ANAGRAFICA A WHERE A.Matricola IN ( SELECT T.Mat FROM TEAM T WHERE T.Prog IN ( SELECT P.Codice FROM PROGETTI P WHERE P.Nome = \'Sistemi Informatici e Calcolo Parallelo\' ) ); **Esempio 3: Trovare i nomi e i cognomi delle persone che partecipano al progetto con codice \'EUI05\' (usando EXISTS)** In questo esempio, si utilizza l\'operatore EXISTS per verificare l\'esistenza di almeno una tupla nella sottointerrogazione. SELECT A.Nome, A.Cognome FROM ANAGRAFICA A WHERE EXISTS ( SELECT \* FROM TEAM T WHERE T.Prog = \'EUI05\' AND T.Mat = A.Matricola ); **Esempio 4: Trovare il nome dei progetti i cui fondi sono maggiori del progetto \"IDEA\"** In questa query si cerca il nome di tutti i progetti che hanno fondi maggiori rispetto al progetto \"IDEA\", utilizzando l\'operatore ANY. SELECT P1.Codice FROM PROGETTI P1 WHERE P1.Fondi \> ANY ( SELECT P2.Fondi FROM PROGETTI P2 WHERE P2.Nome = \'IDEA\' ); **Esempio 5: Trovare il codice dei progetti con maggiori fondi (usando ALL)** Questa query utilizza l\'operatore ALL per trovare il progetto con i fondi più alti confrontandolo con tutti gli altri progetti. SELECT P1.Codice FROM PROGETTI P1 WHERE P1.Fondi \>= ALL ( SELECT P2.Fondi FROM PROGETTI P2 ); **16.3 Le Viste** Le **viste** in SQL rappresentano un concetto potente e flessibile per creare tabelle virtuali che consentono di presentare i dati in modi personalizzati senza duplicare le informazioni fisicamente. Ecco una spiegazione più articolata del concetto: **Che cos\'è una vista?** Una vista è una **tabella virtuale** derivata da una o più tabelle esistenti tramite una query SQL. Tuttavia, a differenza delle tabelle tradizionali, le viste non memorizzano i dati in sé; piuttosto, memorizzano una definizione della query che può essere utilizzata per accedere ai dati originali. Questo fa sì che le viste non occupino spazio aggiuntivo nel database, ma agiscano come uno strato di astrazione sopra le tabelle esistenti. **Caratteristiche principali delle viste** **Virtualità**: Una vista non memorizza fisicamente i dati; le righe e le colonne che mostra sono il risultato di una query eseguita al momento della richiesta. Questo rende le viste efficienti in termini di spazio. **Interfaccia di accesso**: Le viste sono un\'utile interfaccia per gli utenti o le applicazioni che necessitano di accedere frequentemente a determinati set di dati, ma che potrebbero non aver bisogno di vedere l\'intera tabella. Ad esempio, una vista può essere creata per mostrare solo determinati attributi di una tabella o solo le righe che soddisfano determinate condizioni. **Limitazioni sugli aggiornamenti**: Una conseguenza del fatto che le viste sono tabelle virtuali è che non sempre possono essere aggiornate direttamente. In altre parole, esistono delle restrizioni nel modificare i dati attraverso una vista, soprattutto se la query che la definisce è complessa (ad esempio, se coinvolge più tabelle o funzioni aggregate). **Esempio di sintassi per la creazione di una vista** La creazione di una vista in SQL avviene tramite la seguente sintassi: CREATE VIEW nome\_vista AS SELECT \... FROM \... WHERE \... **Esempio pratico** Supponiamo di avere una tabella ANAGRAFICA che contiene informazioni sui dipendenti, e di voler creare una vista per mostrare solo i dipendenti di un particolare dipartimento, in questo caso il dipartimento \"DIS\". La sintassi sarà la seguente: CREATE VIEW IMPIEGATI\_DIS AS SELECT \* FROM ANAGRAFICA WHERE NomeDip = \'DIS\'; In questo esempio: **IMPIEGATI\_DIS** è il nome della vista. La query seleziona tutte le righe dalla tabella ANAGRAFICA dove il campo NomeDip (che indica il dipartimento) è uguale a \'DIS\'. **Operazioni set-oriented** Le **operazioni set-oriented** in SQL permettono di applicare comandi di inserimento, aggiornamento o cancellazione a **insiemi di tuple** piuttosto che a singole righe, rendendo l\'interazione con la base di dati più efficiente. Queste operazioni si basano su comandi che operano su interi set di dati, grazie alla potenza delle query SELECT che identificano le tuple su cui intervenire. Vediamo nel dettaglio come funzionano. **Inserimento set-oriented (INSERT)** L\'operazione INSERT può essere estesa per aggiungere **insiemi di dati** in una tabella, utilizzando il risultato di una query SELECT. Invece di inserire i dati riga per riga, è possibile inserire in blocco le righe che soddisfano determinate condizioni. Esempio: INSERT INTO IMPIEGATI\_DIS ( SELECT \* FROM ANAGRAFICA WHERE NomeDip = \'DIS\'); Questo comando copia tutte le righe della tabella ANAGRAFICA in cui il campo NomeDip è uguale a \'DIS\', inserendole nella tabella IMPIEGATI\_DIS. Si sfrutta la potenza del SELECT per specificare il set di dati che devono essere inseriti. **Cancellazione set-oriented (DELETE)** Similmente, l\'operazione di cancellazione può essere applicata a **più righe contemporaneamente**, utilizzando condizioni che determinano quali tuple devono essere eliminate. Esempio: DELETE FROM ANAGRAFICA WHERE NomeDip = \'DIS\'; Questo comando cancella tutte le righe della tabella ANAGRAFICA in cui il campo NomeDip ha il valore \'DIS\'. In questo modo, si eliminano tutte le informazioni relative ai dipendenti del dipartimento \'DIS\' in un\'unica operazione. **Aggiornamento set-oriented (UPDATE)** L\'operazione UPDATE può essere utilizzata per modificare **più tuple** contemporaneamente. Anche in questo caso, si utilizza una condizione che determina quali righe devono essere aggiornate. Esempio: UPDATE ANAGRAFICA SET Cognome = \'\' WHERE NomeDip = \'DIS\'; Questo comando aggiorna il campo Cognome a un valore vuoto per tutte le righe della tabella ANAGRAFICA dove il campo NomeDip è \'DIS\'. L\'operazione UPDATE viene eseguita su tutte le righe che soddisfano la condizione specificata. **17.0 Normalizzazione e Forme Normali** La **normalizzazione** è il processo di riorganizzazione delle relazioni in una base di dati per ridurre la **ridondanza** e migliorare l\'integrità dei dati. L'obiettivo principale della normalizzazione è quello di eliminare anomalie di aggiornamento, cancellazione e inserimento, ottenendo schemi di relazione che soddisfino determinati requisiti, chiamati **forme normali**. Una **forma normale** rappresenta un insieme di condizioni che un determinato schema deve rispettare per evitare problemi strutturali. Il raggiungimento di una forma normale migliora la qualità dello schema stesso, riducendo duplicazioni e migliorando la coerenza dei dati. **17.1 Problemi negli Schemi Non Normalizzati** Uno schema non normalizzato può presentare **ridondanza** di informazioni, cioè la ripetizione di dati senza aggiungere nuove informazioni utili. Questo genera **anomalie** quando si cerca di aggiornare, eliminare o inserire dati, perché ogni modifica può comportare l'intervento su più tuple, riducendo l\'efficienza e aumentando la possibilità di errore. **Esempio di Schema Non Normalizzato** Consideriamo la relazione **Progettazioni (Impiegato, Stipendio, Progetto, Bilancio, Funzione)**, dove: Ogni **Impiegato** ha un solo **Stipendio**, indipendentemente dai progetti a cui partecipa. Ogni **Progetto** ha un **Bilancio** unico. Se un impiegato partecipa a tre progetti, lo stipendio sarà ripetuto tre volte, causando ridondanza. Inoltre, se lo stipendio dell'impiegato cambia, dovrà essere aggiornato in tutte le tuple in cui compare, generando **anomalia di aggiornamento**. **17.2 Le Anomalie** Le anomalie sono problemi derivanti dall\'utilizzo di un\'unica relazione per rappresentare informazioni eterogenee. Quando concetti diversi vengono fusi in una singola relazione, si creano problemi di ridondanza e anomalie che rendono difficoltose le operazioni di manipolazione dei dati. Le principali **anomalie** sono: **Anomalia di aggiornamento**: ogni volta che si modifica un valore ripetuto (ad esempio, lo stipendio), bisogna aggiornare tutte le tuple che lo contengono. **Anomalia di cancellazione**: eliminando un progetto da cui dipendono altre informazioni (ad esempio, l\'impiegato), si rischia di perdere anche dati ancora validi (ad esempio, lo stipendio dell\'impiegato). **Anomalia di inserimento**: non è possibile inserire un impiegato che non partecipa a nessun progetto, perché mancherebbe una parte delle informazioni necessarie per completare la tupla. **Esempio di Anomalia di Cancellazione** Supponiamo che un impiegato partecipi a un solo progetto. Se quel progetto viene eliminato, anche tutte le informazioni sull'impiegato verranno cancellate, nonostante potrebbe essere ancora un dipendente dell'azienda. **18.0 Dipendenze Funzionali (DF)** Per eliminare le anomalie, è necessario identificare le relazioni logiche tra gli attributi di una relazione, note come **dipendenze funzionali (DF)**. Una dipendenza funzionale è una relazione tra due insiemi di attributi di una relazione, in cui un attributo (o un insieme di attributi) determina univocamente un altro attributo. Se Y e Z sono due sottoinsiemi di attributi di una relazione, una dipendenza funzionale Y→Z indica che il valore di Z è determinato univocamente dal valore di Y. Formalmente: Y→Z si legge: \"Y determina Z\" o \"Z dipende funzionalmente da Y\". **Definizione Formale di Dipendenza Funzionale** Consideriamo: Una relazione r su uno schema di relazione R(X), Due sottoinsiemi Y e Z di X. Esiste una dipendenza funzionale da Y a Z, indicata con Y→Z, se, per ogni coppia di tuple [*t*~1~]{.math.inline} e [*t*~2~]{.math.inline} in r, che hanno lo stesso valore in Y, risulta che [*t*~1~]{.math.inline} e [*t*~2~]{.math.inline} hanno anche lo stesso valore in Z: [∀*t*~1~, *t*~2~ ∈ *r* : *t*~1~\[*Y*\] = *t*~2~\[*Y*\] ⇒ *t*~1~\[*Z*\] = *t*~2~\[*Z*\]]{.math.inline} Questa condizione stabilisce che se due tuple concordano nei valori di Y, devono concordare anche nei valori di Z. **Esempi di Dipendenze Funzionali** Ogni impiegato ha un solo stipendio: \ [Impiegato → Stipendio]{.math.display}\ Anche se l\'impiegato partecipa a più progetti, il suo stipendio rimane unico. Ogni progetto ha un bilancio: \ [Progetto → Bilancio]{.math.display}\ Il bilancio di un progetto dipende solo dal progetto e non dagli impiegati. Ogni impiegato ha una funzione specifica in ogni progetto: \ [Impiegato, Progetto → Funzione]{.math.display}\ Anche se un impiegato può svolgere più ruoli in progetti diversi, in un progetto specifico avrà una sola funzione. **18.1 Dipendenze Funzionali Banali e Non Banali** Le **dipendenze funzionali banali** sono quelle che si verificano per definizione e non forniscono nuove informazioni utili sulla struttura della relazione. Per esempio, Impiegato,Progetto→Progetto è una dipendenza banale, poiché \"Progetto\" è già parte dell'insieme a sinistra dell'implicazione. Una dipendenza funzionale Y→Z è **non banale** se nessuno degli attributi in Z è già presente in Y. Ad esempio: \ [Impiegato → Stipendio]{.math.display}\ è una dipendenza non banale perché lo stipendio dipende interamente dall\'impiegato e non è parte dell\'insieme Y. **18.2 Chiavi e Attributi** Una **chiave** in uno schema di relazione è un insieme di attributi che identifica univocamente una tupla. Gli attributi che partecipano a una chiave si chiamano **attributi primi**, mentre quelli che non partecipano a nessuna chiave sono detti **attributi non primi**. Se K è una chiave per una relazione R(X), allora ogni attributo non primo dipende funzionalmente da K. Questo significa che possiamo esprimere una dipendenza funzionale completa come: \ [*K* → *Z*]{.math.display}\ dove Z è un insieme di attributi non primi e [*X* ⊇ *K* ∪ *Z*]{.math.inline}. **Esempio di Chiave e Attributi** In una relazione **Impiegati (Impiegato, Stipendio)**, la chiave è Impiegato, e quindi ogni altro attributo della relazione, come Stipendio, dipende da esso: \ [Impiegato → Stipendio]{.math.display}\ **18.3 Dipendenza Funzionale Completa** Una dipendenza funzionale Y→Z è detta **completa** se nessun sottoinsieme proprio di Y determina Z. In altre parole, l\'intero insieme di attributi Y è necessario per determinare univocamente Z. **Esempio di Dipendenza Funzionale Completa** Nella relazione **Partecipazioni (Impiegato, Progetto, Funzione)**, l'attributo Funzione dipende sia da Impiegato che da Progetto. Quindi, la dipendenza Impiegato,Progetto→Funzione è completa. Se considerassimo solo uno di questi attributi, non saremmo in grado di determinare la funzione di un impiegato senza conoscere anche il progetto specifico. **19.0 Forme Normali** Le forme normali rappresentano un insieme di regole o criteri progettuali, mirati a ridurre la ridondanza e prevenire le anomalie nei database relazionali. Tali regole definiscono come organizzare i dati in modo efficiente e consistente all\'interno delle relazioni, o tabelle, di un database, e guidano il processo di normalizzazione, che è il processo attraverso cui si trasforma uno schema di dati in modo da soddisfare determinate condizioni per evitare problemi. **19.1 Prima forma normale (1NF)** La **Prima Forma Normale (1NF)** rappresenta la base del modello relazionale, che richiede che ogni attributo di una relazione contenga un valore atomico, ossia indivisibile. In altre parole, non sono ammessi attributi composti o multivalore all\'interno delle tabelle. Ciò significa che una tabella deve essere strutturata in modo tale che ogni cella contenga un solo dato, e ogni riga abbia un valore unico per un determinato attributo. L\'applicazione della 1NF ha l\'obiettivo di rendere le tabelle più facili da gestire ed elaborare. Se una tabella contiene colonne con insiemi di valori, può risultare difficile da gestire nelle query, e potrebbero emergere problemi come la ridondanza o le difficoltà nella manipolazione dei dati. **19.2 Seconda forma normale (2NF)** Una relazione è in **Seconda Forma Normale (2NF)** se soddisfa due condizioni: è in 1NF e ogni attributo non primo dipende completamente da tutta la chiave primaria. In altre parole, gli attributi non chiave non devono dipendere da una parte sola della chiave primaria (dipendenze parziali), ma devono essere totalmente determinati dalla chiave. Per comprendere meglio, supponiamo di avere una relazione che descrive gli impiegati e i progetti a cui lavorano, dove la chiave primaria è composta da \"Impiegato\" e \"Progetto\". Se esiste un attributo come \"Stipendio\", che dipende solo dall\'impiegato e non dal progetto, si creerebbe una dipendenza parziale. Questa condizione violerebbe la 2NF perché lo stipendio dipende solo da una parte della chiave e non dalla combinazione \"Impiegato-Progetto\". La decomposizione di tale relazione è una delle soluzioni per riportare la tabella in 2NF, separando le informazioni dipendenti esclusivamente dall\'impiegato in una nuova tabella. **19.2.1 Decomposizione e decomposizione senza perdita** Quando una relazione non soddisfa la 2NF o una forma normale più elevata, è possibile decomporre la relazione in più tabelle che soddisfano i requisiti della normalizzazione. Tuttavia, è fondamentale che la decomposizione sia eseguita senza perdita di informazioni, garantendo che il join naturale tra le nuove tabelle ricostruisca la relazione originale. La decomposizione senza perdita è ottenuta quando il join naturale delle proiezioni della relazione originale sulle nuove tabelle restituisce esattamente l\'istanza originale. Per garantire questo, è necessario che gli attributi comuni tra le tabelle risultanti contengano una superchiave di almeno una delle tabelle. In altre parole, gli attributi condivisi tra le tabelle decomposte devono permettere di ricostruire le tuple originarie senza creare dati spuri. **19.2.3 Conservazione delle dipendenze** Un altro aspetto cruciale nella decomposizione è la **conservazione delle dipendenze funzionali**, ossia la capacità di preservare i vincoli esistenti tra gli attributi nel processo di decomposizione. Se una decomposizione separa gli attributi di una dipendenza funzionale, può diventare difficile verificare il soddisfacimento di tale dipendenza senza dover ricostruire più tabelle. Ciò può compromettere l\'integrità dei dati e rendere più complessa la gestione del database. **19.3 Terza forma normale (3NF)** Una relazione è in **Terza Forma Normale (3NF)** se è in 2NF e non contiene dipendenze transitive tra attributi non primi e la chiave primaria. Una dipendenza transitiva si verifica quando un attributo non primo è determinato da un altro attributo non primo, che a sua volta dipende dalla chiave. Per esempio, se abbiamo una relazione con gli attributi \"Impiegato\", \"Progetto\" e \"Sede\", e se \"Sede\" dipende da \"Progetto\", e \"Progetto\" dipende da \"Impiegato\", allora \"Sede\" dipende transitivamente da \"Impiegato\". Questa condizione viola la 3NF, poiché l\'attributo \"Sede\" non dovrebbe dipendere da un attributo che non è parte della chiave primaria. La 3NF si concentra dunque sull\'eliminazione delle dipendenze transitive per evitare anomalie come ridondanze o difficoltà negli aggiornamenti. Per raggiungere questa forma normale, è spesso necessario eseguire ulteriori decomposizioni delle relazioni. **19.4 Forma normale di Boyce-Codd (BCNF)** La **Forma Normale di Boyce-Codd (BCNF)** è una versione più rigorosa della 3NF. Una relazione è in BCNF se per ogni dipendenza funzionale non banale, l\'insieme di attributi che determina gli altri attributi (determinante) deve essere una superchiave della relazione. In altre parole, una relazione non può avere dipendenze funzionali in cui l\'insieme di attributi determinante non sia una superchiave. Sebbene una relazione in 3NF soddisfi la maggior parte dei requisiti per evitare anomalie, esistono casi in cui alcune dipendenze funzionali violano comunque il principio della superchiave, che la BCNF risolve. Tuttavia, passare alla BCNF non garantisce sempre la conservazione delle dipendenze funzionali, quindi il processo deve essere bilanciato attentamente. **Comportamento attivo delle basi di dati e trigger** Tradizionalmente, i DBMS erano considerati passivi: eseguivano solo le istruzioni transazionali inviate dagli utenti, come inserimenti, cancellazioni o aggiornamenti. Tuttavia, il comportamento attivo, introdotto con i trigger, consente ai DBMS di reagire autonomamente a determinati eventi. Questa caratteristica si basa sul paradigma E-C-A (evento-condizione-azione), che permette alla base di dati di rispondere automaticamente a una situazione specifica quando si verifica un evento particolare. I trigger, dunque, non sono altro che regole definite che si attivano a fronte di eventi transazionali predefiniti. Ogni trigger segue il paradigma E-C-A, suddiviso in tre componenti fondamentali: - **Evento**: rappresenta il momento di attivazione del trigger, ossia una modifica dello stato dei dati, come un\'operazione di INSERT, DELETE o UPDATE. - **Condizione**: una condizione logica che specifica se il trigger debba effettivamente essere eseguito quando si verifica l\'evento. - **Azione**: rappresenta una sequenza di comandi SQL o una procedura memorizzata che viene eseguita se la condizione è soddisfatta. Grazie ai trigger, una parte delle logiche applicative diventa \"condivisa\" all\'interno della base di dati, garantendo l\'indipendenza della conoscenza: le regole e la logica reattiva vengono estratte dal codice dell'applicazione e centralizzate nella gestione della base di dati stessa. Ciò rende la manutenzione delle regole aziendali più agevole e, allo stesso tempo, aumenta la consistenza dei dati, poiché tutti i sistemi che accedono alla base di dati condividono lo stesso insieme di regole. **Creazione e sintassi dei trigger** I trigger vengono definiti utilizzando il linguaggio di definizione dei dati (DDL) tramite l\'istruzione CREATE TRIGGER. La sintassi standard, stabilita dalla normativa SQL:2003, include vari elementi: - Il **nome del trigger** e il **nome della tabella target** su cui esso viene applicato. - La specifica dell\'**evento** che lo attiva (ad esempio, INSERT, DELETE, UPDATE). - Il **modo di esecuzione**, ossia quando il trigger viene eseguito rispetto all\'evento: BEFORE o AFTER. - La **granularità** dell\'evento, che può essere **statement-level** (per l\'intera operazione) o **row-level** (per ciascuna riga coinvolta nell\'operazione). - Eventuali **condizioni** e **azioni**, cioè le istruzioni che il trigger deve eseguire se la condizione è verificata. **Modo di esecuzione dei trigger: BEFORE e AFTER** I trigger possono essere configurati per essere eseguiti prima (BEFORE) o dopo (AFTER) l\'operazione specificata: - **BEFORE**: esegue il trigger prima che l\'evento venga effettivamente applicato sulla base di dati. Questa modalità è utile quando è necessario controllare o validare le modifiche prima che vengano registrate, garantendo integrità e consistenza dei dati in fase di modifica. - **AFTER**: esegue il trigger subito dopo che l\'operazione è stata completata. Questo è il comportamento più comune e viene utilizzato in moltissime applicazioni, come la gestione dei log o l\'aggiornamento di record collegati in altre tabelle. **Granularità dei trigger: statement-level e row-level** La granularità di un trigger definisce se l\'attivazione debba avvenire a livello di singola istruzione (statement-level) o di singola riga (row-level): - **Statement-level**: il trigger viene eseguito una sola volta per ciascuna istruzione SQL, indipendentemente dal numero di righe coinvolte. Questo approccio è coerente con l\'approccio set-oriented dei comandi SQL, che si applicano a insiemi di dati. - **Row-level**: il trigger viene eseguito una volta per ogni riga modificata dall\'istruzione SQL. È utile per operazioni che richiedono la verifica o la modifica di singole righe, ma può risultare meno efficiente se l\'operazione coinvolge un elevato numero di tuple. **Clausola REFERENCING e variabili di transizione** Per i trigger row-level, si possono utilizzare le variabili di transizione OLD e NEW, che rappresentano i valori della riga prima e dopo la modifica. Per i trigger statement-level, sono invece disponibili OLD TABLE e NEW TABLE, ossia due tabelle di transizione che contengono le tuple modificate prima e dopo l\'operazione: - **OLD** e **NEW** sono utilizzabili solo nei trigger row-level e permettono di riferirsi ai valori di una singola tupla. - **OLD TABLE** e **NEW TABLE** sono disponibili per i trigger statement-level e consentono di accedere a tutte le tuple coinvolte nell'operazione. **Esempi di trigger: row-level e statement-level** Per chiarire meglio il funzionamento, vediamo due esempi di trigger, uno con granularità row-level e uno con granularità statement-level: - **Esempio row-level**: Questo trigger monitora gli aggiornamenti nella tabella Conti e inserisce una registrazione in SingoliVersamenti ogni volta che il saldo di un conto aumenta. - **Esempio statement-level**: In questo esempio, il trigger ArchiviaFattureCanc copia le fatture cancellate dalla tabella Fatture nella tabella FattureCancellate. Poiché è statement-level, il trigger si attiva una sola volta per ogni istruzione DELETE, indipendentemente dal numero di righe eliminate. **Gestione dei conflitti tra trigger** Quando più trigger sono associati allo stesso evento, è fondamentale stabilire una sequenza di esecuzione: 1. Esecuzione dei trigger BEFORE statement-level. 2. Esecuzione dei trigger BEFORE row-level. 3. Applicazione dell'operazione sulla base di dati e verifica dei vincoli di integrità. 4. Esecuzione dei trigger AFTER row-level. 5. Esecuzione dei trigger AFTER statement-level. **Trigger per la gestione dell\'integrità referenziale** Oltre a regole aziendali personalizzate, i trigger vengono spesso impiegati per gestire vincoli di integrità referenziale. Ad esempio, in uno schema di base dati come: è possibile creare trigger per gestire modifiche nelle tabelle correlate. Se viene eliminato un record nella tabella DIPARTIMENTI, un trigger potrebbe, ad esempio, aggiornare automaticamente i riferimenti degli impiegati oppure generare avvisi.