Uno schema di relazione si può decomporre in più schemi, ognuno un sottoinsieme degli attributi di su cui valgono le dipendenze funzionali ereditate da , rilevanti per i suoi attributi. Ciò equivale a proiettare ogni tupla dell’istanza originaria sugli attributi dei singoli sottoschemi.
Definizione formale:
Una decomposizione di è una famiglia di sottoinsiemi di che partiziona (, i sottoinsiemi possono avere intersezione non vuota).
Gli schemi si decompongono
- per motivi di performance (le tuple di taglia più piccola aumentano la velocità e la capacità massima delle operazioni di lettura)
- o per raggruppare attributi semanticamente diversi, usati da operazioni diverse
- o per ottenere più schemi in 3NF (terza forma normale) quando lo schema originale non è in 3NF.
N.B.: È sempre possibile trovare una decomposizione in 3NF valida, ma non vale lo stesso per la forma normale di Boyce-Codd.
Anche se uno schema è stato decomposto in più schemi in 3NF, non è detto che la decomposizione è soddisfacente, infatti ad esempio , decomposto e poi ricomposto tramite il join naturale tra sue decomposizioni, non conserva .
R1 = A B R2 = A C R = A B C
a1 b1 a1 c1 a1 b1 c1
a2 b1 a2 c2 a2 b1 c2
Criteri per distinguere una buona decomposizione da una svantaggiosa
- Ogni sottoschema deve essere in 3NF.
- La decomposizione deve preservare tutte le dipendenze in , anche quelle parziali e transitive.
- la decomposizione deve permettere di ricostruire ogni istanza legale senza perdita di informazione (ricostruzione mediante join senza perdita), che contro-intuitivamente può anche significare un’aggiunta di tuple sbagliate (“tuple Frankenstein” ottenute da pezzi di tuple giuste).
Criterio 1: determinare se ogni sottoschema è in 3NF
Basta calcolare le chiavi di ogni sottoschema per controllare che ognuno sia in 3NF.
Criterio 2: applicare l’algoritmo per determinare se la decomposizione preserva tutte le dipendenze
Sia una decomposizione dello schema , sul quale valgono le dipendenze funzionali . Diciamo che preserva se
Dove , cioè l’insieme delle dipendenze funzionali, anche quelle calcolate con gli assiomi di Armstrong, che valgono nello schema (che è il risultato della proiezione di su ).
Chiamiamo il risultato di . Per verificare , dobbiamo dimostrare che , quindi .
Per come abbiamo definito , sicuramente varrà sempre , quindi per il lemma delle chiusure (vedi sotto) varrà .
Manca solo di verificare se nel caso specifico considerato vale , quindi se (lemma delle chiusure): l’algoritmo controlla solo questo.
Lemma delle chiusure:
Questo lemma ci permette di verificare l’equivalenza tra e in tempo polinomiale, senza dover calcolare chiusure.
Dimostrazione
Ipotesi:
Tesi:
Sia ().
- Ogni dipendenza funzionale in può essere derivata da con gli assiomi di Armstrong da per ipotesi e perché .
- è derivabile con gli assiomi di Armstrong da perché .
- se è derivabile da e ogni dipendenza in può essere derivata da , allora è derivabile da con gli assiomi di Armstrong, quindi .
Algoritmo
Input: Gli insiemi di dipendenze funzionali e su .
Output: La variabile booleana successo, vera se .
successo:= true- for in :
- calcola
- if :
successo:=false
- return
successo
Cioè basta verificare che una sola dipendenza non appartiene a per dire che .
Come calcolare nello step 2.1 dell’algoritmo sopra?
Se volessimo usare l’algoritmo per il calcolo della chiusura di un insieme di attributi, dovremmo prima calcolare , che per definizione richiede il calcolo di , ma ciò richiede tempo esponenziale; però esiste un altro algoritmo per il calcolo di da .
Input: uno schema , un insieme di dipendenze su , una decomposizione , un sottoinsieme .
Output: salvata nella variabile .
- for to :
- calcolare questa chiusura non richiede tempo esponenziale, perché si calcola direttamente sull’insieme delle dipendenze funzionali di , che è molto più piccolo di completo.
- while :
- for to :
- return
Dimostrazione della validità dell’algoritmo
todo pag. 22 pdf 13
Criterio 3: applicare l’algoritmo per determinare se ogni istanza legale è ricostruibile tramite join naturale
Operatore
Una decomposizione di ha un join senza perdita se .
Per aiutarci nella dimostrazione, definiamo l’operatore unario “m rho” che agisce su un’istanza di .
Osserviamo che gode delle seguenti proprietà:
Dimostrazione di 1: Sia una tupla di . , che è un sottoinsieme di , quindi .
Dimostrazione di 2:
Dimostrazione di 3: , ma per la proprietà 2 sappiamo che , quindi per definizione.
Algoritmo per verificare se esiste un join senza perdita (tempo polinomiale)
Input: uno schema , un insieme di dipendenze su , una decomposizione di .
Output: Una booleana che indica se esiste o meno un join senza perdita.
- Costruisci una tabella nel modo seguente: ha colonne e righe. all’incrocio dell’i-esima riga e della j-esima colonna metti il simbolo se l’attributo , altrimenti metti il simbolo .
- (fase iterativa di modifica dell’istanza) for every :
- if ci sono due tuple e in tali che e :
- for every in :
- if then else
- if ha una riga con tutte “a” o non è cambiato:
- return
true
- return
- for every in :
- if ci sono due tuple e in tali che e :
- return
false
N.B.: e sono solo etichette, valori particolari contenuti nel dominio dell’attributo e possiamo considerare tutti i valori uguali fra loro, ma ciò non vale per .
N.B.: Nell’algoritmo diamo precedenza alle , che non diventano mai .
Dimostrazione della validità dell’algoritmo
todo pag 25 pdf 15
Esempio
Si hanno:
Determinare se , così decomposto, ha un join senza perdita.
Costruzione iniziale della tabella:
| A | B | C | D | E | |
|---|---|---|---|---|---|
| AC | a1 | b12 | a3 | b14 | b15 |
| ADE | a1 | b22 | b23 | a4 | a5 |
| CDE | b31 | b32 | a3 | a4 | a5 |
| AD | a1 | b42 | b43 | a4 | b45 |
| B | b51 | a2 | b53 | b54 | b55 |
Prima iterazione
- : la prima e la terza riga coincidono sull’attributo a3, quindi cambiano b14 in a4 in modo che sia soddisfatta (si cambiano sempre le b in a, mai il contrario).
| A | B | C | D | E | |
|---|---|---|---|---|---|
| AC | a1 | b12 | a3 | b14 → a4 | b15 |
| ADE | a1 | b22 → b12 | b23 | a4 | a5 |
| CDE | b31 | b32 → b12 | a3 | a4 | a5 |
| AD | a1 | b42 → b12 | b43 | a4 | b45 |
| B | b51 | a2 | b53 | b54 | b55 |
- : la dipendenza funzionale è già soddisfatta, in quanto non ci sono (ancora) tuple uguali su AB e diverse su E.
- : nelle prime quattro righe D=a4, quindi cambiamo b22 in b12, b32 in b12, b42 in b12 (potevano scegliere una diversa sostituzione delle b, purché le rendesse tutte uguali)
Seconda iterazione
- è già soddisfatta.
- : sostituiamo b15 e b45 con a5
| A | B | C | D | E | |
|---|---|---|---|---|---|
| AC | a1 | b12 | a3 | b14 → a4 | b15 → a5 |
| ADE | a1 | b22 → b12 | b23 | a4 | a5 |
| CDE | b31 | b32 → b12 | a3 | a4 | a5 |
| AD | a1 | b42 → b12 | b43 | a4 | b45 → a5 |
| B | b51 | a2 | b53 | b54 | b55 |
Terza iterazione
- è già soddisfatta
- è già soddisfatta
- è già soddisfatta
Non c’è una riga con tutte a, il join non è senza perdita.