Trovare le chiavi in una relazione
Troviamo le chiavi nella relazione
R(A, B, C)
F={A→B, B→C}
A è prima, perché si trova solo a sinistra di una relazione, B e C sono non prime.
Facciamo uno schemino con tre colonne: nella prima ci sono gli attributi che compaiono solo a sinistra, nella terza gli attributi che sono presenti sia a sinistra sia a destra di una DF, in mezzo gli attributi presenti sia a destra sia a sinistra di qualche DF.
S | E | D ---+---+--- A | B | C ---+---+---
Altro esempio:
R(ABCD)
F={AB→C, C→B, C→D}
S | E | D ---+---+--- A | B | D | C | ---+---+---
A+ è dato da A, ma ora dobbiamo trovare la chiusura di AB+, che è ABCD e la chiusura di AC+, che è ACBD.
La chiave primaria è data da ABC.
Altro esempio:
R(ABC)
F={A→B, B→C, C→A}
S | E | D ---+---+--- | A | | B | | C | ---+---+---
"Really sticky situation".
La chiave è (ABC) e la superchiave è - ovviamente - ABC
Boyce Codd FN
https://www.youtube.com/watch?v=hTFyG5o8-EA
Una relazione R(A1, A2, ... An)può essere scomposta in due relazioni:
R1(B1, B2, ... Bk)
R2(C1, C2, ... Ck)
e le relazioni risultanti sono di Boyce Codd se:
- Gli attributi di R1 ⋃ gli attributi di R2 = gli attributi di R
- La join naturale R1 ⨝ R2 è la proiezione di R
- Ogni attributo in comune tra R1 e R2 è una chiave
Per esempio:
Studenti(CodiceFiscale, Nome, Indirizzo, CodiceScuola, NomeScuola, NomeCittà, Punteggio, Priorità)
ha le seguenti DF:
CodiceFiscale → Nome, Indirizzo, Punteggio
Punteggio → Priorità
CodiceScuola → NomeScuola, NomeCittà
è in forma Boyce-Codd?
Le chiavi sono: CodiceFiscale, CodiceScuola.
Ogni DF ha una chiave contenente CodiceFiscale e Scuola? NO! Quindi la relazione non è in BCFN.
Altro esempio:
Iscritto(CodiceFiscale, Nome, Stato, Data, Maggiorenne
La DF è: CodiceFiscale, Nome, Stato → Data, Maggiorenne
è quindi in BCNF.
Algoritmo per decomporre una relazione in BCNF
Input: relazione + dipendenze funzionali
Output: decomposizione di R in relazioni che rispettano la BCNF senza perdita di join
- Calcolare le chiavi di R
- Ripetere finché tutte le relazioni non sono in BCNF:
- Prendere ogni R' con A→B che viola la BCNF
- Decomporre R' in R1(A, B), R2(A, resto). La join tra le relazioni deve restituire R
- Calcolare le dipendenze funzionali di R1 e R2
- Calcolare le chiavi di R1 e R2
Esempio:
Studenti(CodiceFiscale, Nome, Indirizzo, CodiceScuola, NomeScuola, NomeCittà, Punteggio, Priorità)
ha le seguenti DF:
CodiceFiscale → Nome, Indirizzo, Punteggio
Punteggio → Priorità
CodiceScuola → NomeScuola, NomeCittà
Come sopra, la chiave è CodiceFiscale, CodiceScuola
Creiamo due relazioni
S1(CodiceScuola, NomeScuola, NomeCittà)
S2(CodiceFiscale, Nome, Indirizzo, CodiceScuola, Punteggio, Priorità)
Ora S1 è in BCNF
Invece S2 non è in BCNF, quindi la decomponiamo in:
S3(Punteggio, Priorità)
S4(CodiceFiscale, Nome, Indirizzo, CodiceScuola, Punteggio)
Ora S3 è in BCNF.
Invece S4 non è in BCNF, quindi la decomponiamo in:
S5(CodiceFiscale, Nome, Indirizzo, Punteggio)
S6(CodiceFiscale, CodiceScuola)
Finalmente abbiamo S1, S3, S5, S6 che rappresentano la BCNF della relazione senza perdita di join.
Terza forma normale e Boyce-Codd
Alcuni esempi, redatti con l'aiuto di http://dblab.dsi.unive.it:8080/
Attributi | Dipendenze funzionali | Chiavi | Attributi primi | Boyce-Codd | 3NF | Commento |
---|---|---|---|---|---|---|
a | - | {(a)} | (a) | R<(a){}> | R<(a){}> | Nessuna d.f. |
a | a→a | {(a)} | (a) | R<(a){}> | R<(a){}> | D.f. banale, non si riporta |
a, b | a→b | {(a)} | (a) | R<(a,b){a→b}> | R<(a,b){a→b}> | |
a, b | a→b, b→a | {(a), (b)} | (a b) | R<(a,b){a→b,b→a}> | R<(a,b){b→a}> | Differenza tra BC e 3NF: nel primo caso sono esplicite entrambe le d.f., nella 3NF solo una |
a, b, c | a→b, b→c | {(a)} | (a) | R1<(a,b){a→b}>, R2<(b,c){b→c}> | R1<(a,b){a→b}>, R2<(b,c){b→c}> | In entrambi i casi, si "estrae" una relazione. Dipendenze preservate. |
Nota bene: b non è una chiave, ma diventa la chiave di una nuova relazione sia secondo BC sia secondo 3NF
a, b, c | a→b, ab→c | {(a)} | (a) | R<(a,b,c){a→b,a→c}> | R<(a,b,c){a→bc}> | b è determinato da a, quindi c è determinato dalla sola a (secondo Boyce-Codd) oppure in ogni caso da ab (secondo 3NF) |
a, b, c | a→b, ab→c, bc→a | {(a),(bc)} | (abc) | R<(a,b,c) {a→b, a→c, bc→a}> | R<(a,b,c) {bc→a}> | |
a, b, c, d, e, g, h, i | adg→gi ach→adg bc→ad ce→ach |
{(bce)} | (bce) | R<(a,b,c) {a→b, a→c, bc→a}> | R<(a,b,c) {bc→a}> |
Studio di una funzione: (2x-1)/(x-3)
Funzione tratta da http://matepratica.tutorando.com
Transazioni e concorrenza
Transazione
Atomicità: un'operazione può essere o completata o annullata, non può essere completata parzialmente
Persistenza: se una transazione termina con successo, le modifiche devono essere mantenute sui dati
Serializzabilità: non possono essere eseguite due transazioni che interferiscono tra loro (ved. esempio due prelievi bancomat contemporanei)
Problemi / giornale
- La transazione fallisce
- C'è un problema esterno temporaneo (es. caduta della corrente elettrica)
- C'è un disastro (rottura di un disco)
La garanzia per tutti e tre i tipi di problema è il giornale, cioè un log delle transazioni.
Il giornale è compilato con il contenuto delle transazioni e ogni tanto avviene un allineamento con la base dati. L'allineamento può avvenire tramite una sospensione di tutte le transazioni, ma esistono anche tecniche che riducono i tempi di attesa.
Disfare le transazioni
Se un'operazione fallisce la transazione si deve DISFARE.
Se un'operazione riesce ma non siamo sicuri che il dato sia scritto nel disco, occorre RIFARE.
Il giornale NON è soggetto a buffering, quindi le informazioni sono sempre scritte direttamente sul disco.
Organizzazione fisica dei database
Organizzazione fisica dei file
Heap: inserisco i dati a caso. È il metodo utilizzato di default nel filesystem. Può andare bene per pochi dati o accessi sempre su tutti i dati.
Sequenziale: inserisco i dati in ordine, con un ordine scelto a priori. Con un ordinamento sequenziale il costo dell'accesso a un dato è di N*log2(N).
L'ordinamento tipico di ordinamento nei database che memorizzano i dati in modo sequenziale è merge sort. MS si presta bene al calcolo in parallelo su più processori.
L'ordinamento può essere effettuato tramite hash o tramite albero, e può essere statico (le informazioni, una volta inserite, non cambiano) o dinamico.
Nell'hash si trasforma una hash in un indirizzo di pagina. Bisogna gestire le eccezioni (collisioni) e la possibilità che i dati aumentino o diminuiscano molto. L'hash è estremamente efficiente. L'hash si presta molto poco alla ricerca su intervallo.
La struttura ad albero permette sia un accesso diretto a un dato sia a un intervallo con un tempo di esecuzione molto soddisfacente. L'organizzazione ad albero B+ può essere applicato sia al dato sia a un indice.
Inoltre la struttura ad albero restituisce insiemi di dati già ordinati.
Gli indici devono essere usati con parsimonia e buon senso, perché la loro aggiunta selvaggia compromette i tempi delle operazioni di inserimento e la concorrenza nell'accesso ai dati.
La chiave primaria è corredata automaticamente di un indice, perché permette di verificare velocemente se esiste già una chiave uguale o meno.
Operazioni
Proiezione
π e π+ sono le due operazioni algebriche che rappresentano rispettivamente SELECT DISTINCT
e SELECT
.
π+ ha un'esecuzione banale: si eliminano i campi non interessanti e si scrive il risultato.
π invece esclude i duplicati, e funziona:
- riscrive una copia della tabella con i soli campi che interessano
- ordina per tutti gli attributi
- elimina duplicati
- scrive il risultato
L'operazione di π si indica con due operazioni (SORT + DISTINCT), a indicare che l'operazione di eliminazione dei duplicati agisce in due fasi diverse.
Restrizione
σ è l'operazione algebrica corrispondente alla clausola WHERE
(HAVING
).
L'operazione funziona in modi diversi se esiste un indice sugli attributi ricercati o se non esiste.
Se non esiste, occorre leggere tutta la tabella e scartare i record non corrispondenti.
Se esiste, il costo è dato dalla somma del costo per l'accesso all'indice e per l'accesso al dato. Il motore decide quale alternativa è migliore; in generale l'indice conviene quando la restrizione è molto selettiva (estrae pochi record).
Aggregazione
L'aggregazione senza GROUP BY
legge i record e applica la funzione di aggregazione.
Con GROUP BY bisogna prima raggruppare i dati e successivamente calcolare l'aggregazione.
In particolare, si ordinano i record sull'attributo di ordinamento e poi si calcolano le funzioni di aggregazione.
Anche in questo caso le operazioni sono SORT + GROUP BY.
Giunzione
Gli algoritmi possibili sono:
- Nested loop: leggo un record alla volta della prima relazione (i), leggo un record della seconda relazione (j) e restituisco il record se gli attributi sono uguali. Il tempo di esecuzione è O(n2). è comodo se la seconda relazione è molto piccola e la prima grande.
- Nested loop a pagine
- Indexed nested loop: usa un indice per fare il confronto (necessita di un indice sul campo di join)
- Merge join: quando entrambe le tabelle sono ordinate sull'attributo di join, l'algoritmo confronta solo i record finché l'attributo non cambia in entrambe le tabelle
Operatori
Lettura dei dati
- TableScan(R): scansione su una tabella
- IndexScan(R, Idx): scansione sull'indice di una tabella
- SortScan(R, {A1, A2, ... Ai}): scansione e ordinamento per attributo/i
Proiezione
- Project(R, {A1, A2, ... Ai}): proiezione senza eliminazione dei duplicati
- Distinct(R, {A1, A2, ... Ai}): proiezione con eliminazione dei duplicati
Restrizione
- Filter(O, ψ)
- IndexFilter(R, Idx, ψ)
Ordinamento
- Sort(R, {A1, A2, ... Ai})
Raggruppamento
- GroupBy(O, {A1, A2, ... Ai}, {f1, f2, ... fi}): f indica una funzione di aggregazione contenuta in SELECT e HAVING. L'operatore GroupBy è sempre preceduto da un Sort
Giunzione
- NestedLoop(OE, OI, ψJ)
- PageNestedLoop(OE, OI, ψJ)
- IndexNestedLoop(OE, OI, ψJ): OE rappresenta il risultato di un'operazione di IndexFilter
- MergeJoin(OE, OI, ψJ): OE e OI rappresentano due relazioni già ordinate per gli attributi di join
Equivalenze e coperture
Due insiemi di DF, F e G, sullo schema R sono equivalenti F ≡ G sse F+ = G+
F è una copertura di G e G è una copertura di F.
Copertura canonica
o canonica minima, ci permette di definire un insieme di equivalenze in maniera semplice e compatta.
Sia un insieme di dipendenze funzionali F, un attributo è estraneo sse (X-{Ai})→Y∈F+
Per esempio, data una relazione R({Nome, Telefono, Indirizzo, Città, Voto, Materia}, {(Nome, Telefono)→Indirizzo, (Nome, Indirizzo)→Telefono, (Nome, Materia)→Voto, Indirizzo→Città)})
si può scrivere A = Nome, B = Telefono, C = Indirizzo, D = Città, E = Voto, G = Materia
R({A, B, C, D, E, G}, {AB→C, AC→B, AG→E, C→D})
AB→C contiene l'attributo estraneo B, poiché AB-{B}→C è vero, cioè A→C
AC→B contiene l'attributo estraneo C, per lo stesso motivo.
ABGE è una superchiave di R, perché non esistono due elementi con lo stesso valore per gli attributi ABGE.
Però la chiave ABGE contiene un sottoinsieme AG che è superchiave ma è più "ristretta" della superchiave ABGE. Quando una superchiave (come AG) non contiene altre superchiavi più strette, si dice che è chiave.
Gli attributi che non compaiono mai a destra delle DF (come A e G nell'esempio) sono sicuramente contenute in ogni chiave di R.
Gli attributi che compaiono solo a destra delle DF (come D e E nell'esempio) non appartengono a nessuna chiave.
Appartiene a tutte le chiavi | Da determinare | Non appartiene sicuramente ad alcuna chiave |
---|---|---|
A, G | B, C | D, E |
Agli attributi B, C occorre applicare l'algoritmo di chiusura lenta per capire se fanno parte o meno di una chiave.
- AGB+ = AGB
- AGC+ = AGC
Facile da usare / cap. 5 – Roberto Polillo
http://www.rpolillo.it/libri/facile-da-usare/
Mini disclaimer: questi appunti non sono una recensione, non sono un riassunto, non rappresentano una valutazione del libro. Sono gli elementi che soggettivamente (cioè per me) sono da appuntarsi. Alcuni elementi, magari molto importanti, ma per me noti, potrebbero essere tralasciati. Quindi, cliccate sul link qui sopra, che fate prima: la licenza permette anche una lettura interessante senza spendere soldi.
Progettare VS realizzare
Progettare significa immaginare qualcosa e predisporre quanto serve per realizzarla.
Realizzare significa fare in concreto quanto si progetta.
Quando si progetta si descrive l'as-is e si immagina il to-be.
Progettazione orientata all'utente
La progettazione orientata all'utente è un approccio radicalmente diverso da quello tecnico tradizionale, poiché richiede competenze e sensibilità diverse da quelle del tecnico programmatore "standard".
Per esempio, un sistema completo di funzionalità è il massimo per un ingegnere progettista, ma può essere complicato da usare ed eccessivamente articolato per un utente.
Diversamente, chi progetta orientato agli utenti inizia dai casi d'uso, cioè dalle attività più frequenti svolte dagli utenti con i loro limiti e le loro caratteristiche.
Progettazione universale
La progettazione universale è la direttiva della programmazione orientata all'utente per cui un sistema dovrebbe essere il più possibile utilizzabile da qualunque utente, indipendentemente dalle sue peculiarità.
I livelli di maturità del software
- funziona
- ha tutte le funzionalità
- è facile da usare per l'utente
- è trasparente e l'utente non si accorge nemmeno di usarlo