Salta al contenuto
Note per Studenti Algebra relazionale estesa - divisione, aggregazione e outer join

Algebra relazionale estesa - divisione, aggregazione e outer join

In questa pagina 10

Prerequisito: Algebra relazionale - operatori di base e joinLe operazioni sul modello relazionale sono di aggiornamento (inserimento, modifica, cancellazione, che possono violare i vincoli di integrità: si rifiuta, si corregge, si propaga con cascade o si imposta NULL/default) e di interrogazione (funzioni che da un'istanza del database producono una relazione). L'algebra relazionale è il linguaggio procedurale delle interrogazioni: gli operatori primitivi sono unione, differenza, prodotto, rinomina ρ, selezione σ e proiezione π; derivati sono intersezione, theta-join, equi-join, join naturale. Unione, intersezione e differenza richiedono relazioni compatibili all'unione (stessi attributi e domini). La selezione è una decomposizione orizzontale, la proiezione verticale (commutano solo se gli attributi della selezione sono nella proiezione). Il theta-join è σ_φ(R1×R2), il join naturale equivale a un equi-join sugli attributi omonimi seguito da una proiezione; la sua cardinalità è tra 0 e |R1||R2|, ≤|R1| se gli attributi comuni sono chiave di R2, =|R1| se sono anche chiave esterna di R1 con vincolo di integrità referenziale. Le tuple senza corrispondente sono "dangling". I valori NULL richiedono condizioni esplicite IS NULL / IS NOT NULL, perché σ_{A>40} ∪ σ_{A≤40} non restituisce le tuple con A nullo.Algebra relazionale - operatori di base e join →. Qui si estende l'algebra con quello che serve per le interrogazioni dei temi d'esame (somme, medie, massimi, "per ciascuno", "tutti"). La controparte in SQL è in SQL - interrogazioni, join e aggregazioniUn'interrogazione SQL ha almeno SELECT e FROM; le altre clausole (WHERE, GROUP BY, HAVING, ORDER BY) sono opzionali e HAVING richiede GROUP BY. Ordine logico di valutazione: FROM, WHERE, GROUP BY, HAVING, SELECT, DISTINCT, ORDER BY. SELECT corrisponde a π (ma senza DISTINCT non elimina i duplicati), WHERE a σ, gli alias ad AS/ρ, i join a ⋈ (INNER, LEFT, RIGHT, FULL). WHERE seleziona le righe prima del raggruppamento, HAVING seleziona i gruppi dopo. Le funzioni COUNT, SUM, AVG, MIN, MAX ignorano i NULL (COUNT(*) conta le righe, DISTINCT toglie i duplicati dentro la funzione); con un aggregato o con GROUP BY nella SELECT possono comparire solo colonne di raggruppamento o aggregati. LIKE usa % (qualsiasi sequenza) e _ (un carattere). I confronti con NULL non sono veri: serve IS NULL. Un filtro sulla tabella di destra di un LEFT JOIN va nella clausola ON, non in WHERE, altrimenti il join diventa interno. Con interi COUNT(a)/COUNT(b) è una divisione intera: serve CAST.SQL - interrogazioni, join e aggregazioni → e SQL - query nidificate, operatori insiemistici e visteUna sottoquery è una SELECT tra parentesi dentro un'altra: nella FROM (tabella derivata, con alias obbligatorio in PostgreSQL) oppure nella WHERE, come valore scalare con =, <, ..., come insieme con IN / NOT IN, ANY, ALL, oppure con EXISTS / NOT EXISTS. Una sottoquery correlata usa colonne della query esterna e va rivalutata per ogni riga. Le funzioni di aggregazione non si possono usare in WHERE: il massimo si ottiene con una sottoquery scalare (WHERE n = (SELECT MAX(n) ...)), con una vista o una CTE. NOT IN con una sottoquery che contiene NULL non restituisce mai righe: meglio NOT EXISTS, EXCEPT o LEFT JOIN ... IS NULL. "Per tutti" (divisione) = doppio NOT EXISTS o HAVING COUNT(DISTINCT ...) = numero totale. Gli operatori insiemistici UNION, INTERSECT, EXCEPT richiedono query compatibili e di default eliminano i duplicati (ALL li mantiene). Una vista è una tabella virtuale definita da una query (CREATE VIEW, DROP VIEW): semplifica le query, riusa sottoquery, nasconde i dati; la vista materializzata invece memorizza il risultato.SQL - query nidificate, operatori insiemistici e viste →; gli esercizi sono in Esercizio - Algebra relazionale estesa su pizzeria, ristoranti e conteggi con NULL (esercizi di algebra estesa del corso).

Perché serve un'estensione

Nell'algebra di base le operazioni valutano una tupla alla volta, indipendentemente dalle altre. Ma una query può chiedere informazioni che non sono memorizzate come valore di nessun attributo:

  • "l'incasso di ogni tipo di pizza": serve calcolare prezzo per quantità, cioè operare tra attributi;
  • "l'incasso di ogni sede": serve sommare su un gruppo di tuple.

Selezioni, proiezioni e join non bastano.

Proiezione generalizzata

Definizione. Data R(X)R(X) e funzioni F1,…,FnF_1,\dots,F_n sugli attributi di RR (attributi, costanti, espressioni aritmetiche), πF1,…,Fn(R)={(F1(t),…,Fn(t))∣t∈R}.\pi_{F_1,\dots,F_n}(R)=\{(F_1(t),\dots,F_n(t))\mid t\in R\}.

Estende la proiezione: oltre a scegliere colonne, ne calcola di nuove. Con Pizzeria(Pizza, Prezzo, Quantita) = (Margherita,8,10)(\text{Margherita},8,10), (Capricciosa,10,20)(\text{Capricciosa},10,20), (Funghi,9,2)(\text{Funghi},9,2), (Marinara,6,13)(\text{Marinara},6,13):

πPizza, Prezzo⋅Quantita(Pizzeria)={(Margherita,80),(Capricciosa,200),(Funghi,18),(Marinara,78)}.\pi_{\text{Pizza},\ \text{Prezzo}\cdot\text{Quantita}}(\text{Pizzeria})=\{(\text{Margherita},80),(\text{Capricciosa},200),(\text{Funghi},18),(\text{Marinara},78)\}.

La colonna calcolata non ha un nome: lo si dà con la rinomina, ρTotale←Prezzo⋅Quantita(πPizza, Prezzo⋅Quantita(Pizzeria))\rho_{\text{Totale}\leftarrow\text{Prezzo}\cdot\text{Quantita}}\big(\pi_{\text{Pizza},\ \text{Prezzo}\cdot\text{Quantita}}(\text{Pizzeria})\big) (è la notazione delle slide; in un compito si può scrivere direttamente πPizza, Totale←Prezzo⋅Quantita\pi_{\text{Pizza},\ \text{Totale}\leftarrow\text{Prezzo}\cdot\text{Quantita}}, purché sia chiaro). Una proiezione generalizzata può produrre duplicati che poi spariscono: due tuple con gli stessi risultati diventano una sola.

Aggregazione

Le funzioni di aggregazione lavorano su insiemi di valori e non su singole tuple.

Funzione Calcola
SUM(A)\text{SUM}(A) somma dei valori di AA
MAX(A)\text{MAX}(A), MIN(A)\text{MIN}(A) massimo, minimo dei valori di AA
AVG(A)\text{AVG}(A) media dei valori di AA
COUNT(⋅)\text{COUNT}(\cdot) numero di tuple (non di valori distinti)

Definizione (aggregazione). Data R(X)R(X) e funzioni di aggregazione f1(Ai),…,fn(Aj)f_1(A_i),\dots,f_n(A_j) con Ai,Aj∈XA_i,A_j\in X, Ff1(Ai),…,fn(Aj)(R).\mathcal{F}_{f_1(A_i),\dots,f_n(A_j)}(R). Il risultato è una relazione con nn attributi (uno per funzione) e esattamente una tupla: grado nn, cardinalità 1.

Esempio. "Incasso totale e incasso massimo" sui totali sopra, che chiamiamo INCOME(Pizza, Totale): FSUM(Totale), MAX(Totale)(INCOME)={(376, 200)},80+200+18+78=376.\mathcal{F}_{\text{SUM}(\text{Totale}),\ \text{MAX}(\text{Totale})}(\text{INCOME})=\{(376,\ 200)\},\qquad 80+200+18+78=376. Gli attributi hanno nomi SUM(Totale)\text{SUM}(\text{Totale}) e MAX(Totale)\text{MAX}(\text{Totale}); per cambiarli: ρTotIncome,MaxIncome←SUM(Totale),MAX(Totale)(… )\rho_{\text{TotIncome},\text{MaxIncome}\leftarrow\text{SUM}(\text{Totale}),\text{MAX}(\text{Totale})}(\dots).

Raggruppamento

L'aggregazione globale riassume l'intera relazione. Spesso serve invece calcolare per ciascun gruppo: il gruppo è l'insieme di tuple con gli stessi valori su uno o più attributi.

Definizione (raggruppamento). Data R(X)R(X), funzioni f1(Ai),…,fn(Aj)f_1(A_i),\dots,f_n(A_j) e mm attributi di raggruppamento A1,…,AmA_1,\dots,A_m, A1,…,AmFf1(Ai),…,fn(Aj)(R)._{A_1,\dots,A_m}\mathcal{F}_{f_1(A_i),\dots,f_n(A_j)}(R). Gli attributi prima di F\mathcal{F} sono quelli di raggruppamento. Il risultato ha grado n+mn+m e una tupla per ogni gruppo (cardinalità = numero di gruppi).

Esempio (ristoranti). Restaurant(Name, Date, PriceMenu, Seats): (R1, 10/12, 80, 100)(R1,\ 10/12,\ 80,\ 100), (R2, 10/12, 90, 200)(R2,\ 10/12,\ 90,\ 200), (R2, 11/12, 70, 300)(R2,\ 11/12,\ 70,\ 300), (R1, 11/12, 100, 150)(R1,\ 11/12,\ 100,\ 150), (R3, 11/12, 60, 70)(R3,\ 11/12,\ 60,\ 70). "Per ogni ristorante l'incasso totale e medio":

  1. INCOME←πName,Date, Income←PriceMenu⋅Seats(Restaurant)\text{INCOME}\leftarrow\pi_{\text{Name},\text{Date},\ \text{Income}\leftarrow\text{PriceMenu}\cdot\text{Seats}}(\text{Restaurant}) dà (R1,10/12,8000)(R1,10/12,8000), (R2,10/12,18000)(R2,10/12,18000), (R2,11/12,21000)(R2,11/12,21000), (R1,11/12,15000)(R1,11/12,15000), (R3,11/12,4200)(R3,11/12,4200);
  2. NameFSUM(Income), AVG(Income)(INCOME)_{\text{Name}}\mathcal{F}_{\text{SUM}(\text{Income}),\ \text{AVG}(\text{Income})}(\text{INCOME}) dà (R1,23000,11500)(R1,23000,11500), (R2,39000,19500)(R2,39000,19500), (R3,4200,4200)(R3,4200,4200).

Tre gruppi, tre tuple. Con m=0m=0 attributi di raggruppamento si ricade nell'aggregazione globale (un solo gruppo).

Il raggruppamento si può scrivere in due passi, per leggibilità: prima la proiezione generalizzata che calcola il valore (INCOME), poi l'aggregazione.

NULL e funzioni di aggregazione

SUM, AVG, MAX, MIN ignorano i NULL: il risultato è calcolato solo sulle tuple con valore non nullo. Se tutti i valori sono NULL il risultato è NULL.

Con Exam(Student, Date, Mark, Course): (123456, 10/12, 20, Analysis)(123456,\ 10/12,\ 20,\ \text{Analysis}), (123456, 10/12, 25, Databases)(123456,\ 10/12,\ 25,\ \text{Databases}), (384752, 11/12, NULL, Analysis)(384752,\ 11/12,\ \text{NULL},\ \text{Analysis}), (384752, 11/12, 18, Physics)(384752,\ 11/12,\ 18,\ \text{Physics}), (3841345, 11/12, NULL, Physics)(3841345,\ 11/12,\ \text{NULL},\ \text{Physics}):

  • COUNT(∗)=5\text{COUNT}(*)=5 (conta le tuple, anche con NULL), COUNT(Mark)=3\text{COUNT}(\text{Mark})=3 (solo i voti non nulli);
  • SUM(Mark)=63\text{SUM}(\text{Mark})=63, AVG(Mark)=63/3=21\text{AVG}(\text{Mark})=63/3=21 (non 63/563/5), MAX=25\text{MAX}=25, MIN=18\text{MIN}=18;
  • raggruppando per studente, la media dello studente 38413453841345, che ha solo NULL, è NULL.

Con Worker(Badge, Name, Surname, Car) e due tuple con la stessa auto NULL, i NULL formano un gruppo a sé: CarFCOUNT(∗)(Worker)_{\text{Car}}\mathcal{F}_{\text{COUNT}(*)}(\text{Worker}) dà (Volvo,1)(\text{Volvo},1), (Fiat,2)(\text{Fiat},2), (NULL,3)(\text{NULL},3). In generale si perdono tuple solo se l'aggregato ignora il valore, mai per il raggruppamento.

Outer join

Nel join le tuple dangling spariscono. L'outer join le mantiene, completando con NULL gli attributi dell'altro operando.

Le slide usano i simboli ⟗, ⟕ e ⟖; qui si scrive ⋈F\bowtie^{\text{F}}, ⋈L\bowtie^{\text{L}}, ⋈R\bowtie^{\text{R}} per full, left e right.

  • Full outer join R1⋈φFR2R_1\bowtie^{\text{F}}_\varphi R_2: al theta-join aggiunge le tuple dangling di entrambi gli operandi;
  • Left outer join R1⋈φLR2R_1\bowtie^{\text{L}}_\varphi R_2: solo le tuple dangling di R1R_1 (sinistra);
  • Right outer join R1⋈φRR2R_1\bowtie^{\text{R}}_\varphi R_2: solo quelle di R2R_2 (destra).

Con Employee(Surname, Dep) = (Rossi,A),(Neri,B),(Bianchi,B),(Neri,C)(\text{Rossi},A),(\text{Neri},B),(\text{Bianchi},B),(\text{Neri},C) e Department(Code, Manager) = (A,Mori),(B,Bruni),(D,Verdi)(A,\text{Mori}),(B,\text{Bruni}),(D,\text{Verdi}), Employee⋈Dep=CodeFDepartment\text{Employee}\bowtie^{\text{F}}_{\text{Dep}=\text{Code}}\text{Department} è

Surname Dep Code Manager
Rossi A A Mori
Neri B B Bruni
Bianchi B B Bruni
Neri C NULL NULL
NULL NULL D Verdi

La riga (Neri,C)(\text{Neri},C) è dangling a sinistra; (D,Verdi)(D,\text{Verdi}) a destra. Con il left join si ha solo la riga di Neri-C in più, con il right solo quella di Verdi.

L'outer join è un operatore derivato: si ottiene dal join con unione e differenza,

R1⋈φFR2=(R1⋈φR2) ∪ ((R1−πX(R1⋈φR2))×{(NULL,…,NULL)}) ∪ ({(NULL,…,NULL)}×(R2−πY(R1⋈φR2))),R_1\bowtie^{\text{F}}_\varphi R_2=(R_1\bowtie_\varphi R_2)\ \cup\ \Big(\big(R_1-\pi_{X}(R_1\bowtie_\varphi R_2)\big)\times\{(\text{NULL},\dots,\text{NULL})\}\Big)\ \cup\ \Big(\{(\text{NULL},\dots,\text{NULL})\}\times\big(R_2-\pi_{Y}(R_1\bowtie_\varphi R_2)\big)\Big),

dove XX e YY sono gli attributi di R1R_1 e R2R_2 e il singoletto di NULL ha tanti NULL quanti gli attributi dell'altro operando (le slide scrivono i due pezzi come ∪\cup con l'insieme dei NULL, ma l'idea è questa). Si usa il left join quando serve "tutte le tuple di R1R_1, anche quelle senza corrispondenza": per esempio "ogni post con il numero dei suoi commenti, anche zero".

NULL nella chiave di join. Se Employee contiene (Neri,NULL)(\text{Neri},\text{NULL}) e Department contiene (NULL,Verdi)(\text{NULL},\text{Verdi}), le due tuple non si accoppiano (NULL == NULL non è vero): nel full outer join compaiono entrambe, ciascuna completata con NULL.

Divisione

Definizione. Date R(X,Y)R(X,Y) e S(Y)S(Y), la divisione R÷SR\div S ha schema XX e contiene le tuple xx tali che per ogni y∈Sy\in S la tupla (x,y)(x,y) appartiene a RR: R÷S={x∣∀y∈S: (x,y)∈R}=πX(R)−πX((πX(R)×S)−R).R\div S=\{x\mid\forall y\in S:\ (x,y)\in R\}=\pi_X(R)-\pi_X\big((\pi_X(R)\times S)-R\big).

La derivazione si legge così: πX(R)×S\pi_X(R)\times S contiene tutte le coppie (x,y)(x,y) possibili; togliendo RR restano le coppie che mancano; proiettando su XX si hanno gli xx che hanno almeno una coppia mancante; sottraendoli da πX(R)\pi_X(R) restano quelli che non ne hanno nessuna.

Esempio. Passed(Student, Course) = (Anna,Analisi)(\text{Anna},\text{Analisi}), (Anna,Fisica)(\text{Anna},\text{Fisica}), (Anna,BD)(\text{Anna},\text{BD}), (Bruno,Analisi)(\text{Bruno},\text{Analisi}), (Bruno,Fisica)(\text{Bruno},\text{Fisica}), (Carla,Analisi)(\text{Carla},\text{Analisi}), (Carla,Fisica)(\text{Carla},\text{Fisica}), (Carla,BD)(\text{Carla},\text{BD}), (Carla,Algebra)(\text{Carla},\text{Algebra}), (Dario,Algebra)(\text{Dario},\text{Algebra}) e Course(Course) = {Analisi,Fisica,BD}\{\text{Analisi},\text{Fisica},\text{BD}\}. "Gli studenti che hanno superato tutti gli esami del primo anno":

  • πStudent(Passed)×Course\pi_{\text{Student}}(\text{Passed})\times\text{Course} ha 4⋅3=124\cdot3=12 coppie;
  • meno Passed\text{Passed} restano le coppie mancanti: (Bruno,BD)(\text{Bruno},\text{BD}), (Dario,Analisi)(\text{Dario},\text{Analisi}), (Dario,Fisica)(\text{Dario},\text{Fisica}), (Dario,BD)(\text{Dario},\text{BD});
  • proiettando: Bruno e Dario hanno qualcosa di mancante;
  • risultato: Passed÷Course={Anna,Carla}\text{Passed}\div\text{Course}=\{\text{Anna},\text{Carla}\}.

Carla ha superato anche Algebra, che non è in Course: non conta, la divisione chiede tutti quelli di SS, non solo quelli.

La divisione non è tra gli operatori delle slide del corso, che risolvono i "per tutti" con la differenza (il complemento della nota precedente); la formula è equivalente. In SQL non esiste un operatore: si usa il doppio NOT EXISTS o un conteggio con HAVING (SQL - query nidificate, operatori insiemistici e visteUna sottoquery è una SELECT tra parentesi dentro un'altra: nella FROM (tabella derivata, con alias obbligatorio in PostgreSQL) oppure nella WHERE, come valore scalare con =, <, ..., come insieme con IN / NOT IN, ANY, ALL, oppure con EXISTS / NOT EXISTS. Una sottoquery correlata usa colonne della query esterna e va rivalutata per ogni riga. Le funzioni di aggregazione non si possono usare in WHERE: il massimo si ottiene con una sottoquery scalare (WHERE n = (SELECT MAX(n) ...)), con una vista o una CTE. NOT IN con una sottoquery che contiene NULL non restituisce mai righe: meglio NOT EXISTS, EXCEPT o LEFT JOIN ... IS NULL. "Per tutti" (divisione) = doppio NOT EXISTS o HAVING COUNT(DISTINCT ...) = numero totale. Gli operatori insiemistici UNION, INTERSECT, EXCEPT richiedono query compatibili e di default eliminano i duplicati (ALL li mantiene). Una vista è una tabella virtuale definita da una query (CREATE VIEW, DROP VIEW): semplifica le query, riusa sottoquery, nasconde i dati; la vista materializzata invece memorizza il risultato.SQL - query nidificate, operatori insiemistici e viste →).

Ricette per i casi tipici

La richiesta dice Schema dell'espressione
"per ciascun xx il numero / la somma / la media di..." raggruppamento xFf(⋅)_{x}\mathcal{F}_{f(\cdot)} (dopo i join che portano insieme gli attributi)
"il xx con il massimo di..." (1) R←xFf(⋅)R\leftarrow{}_x\mathcal{F}_{f(\cdot)} per ogni xx; (2) M←ρm←MAX(v) FMAX(v)(R)M\leftarrow\rho_{m\leftarrow\text{MAX}(v)}\ \mathcal{F}_{\text{MAX}(v)}(R); (3) πx(R⋈v=mM)\pi_x(R\bowtie_{v=m}M)
"tutti i... che..." divisione, oppure tutti −- quelli per cui esiste un controesempio
"almeno due..." self-join con rinomina e condizione ≠\neq (o raggruppamento con COUNT≥2\text{COUNT}\ge2)
"nessuno..." / "che non..." tutti −- quelli che hanno almeno uno (differenza)
"anche quelli senza..." outer join, poi eventuale raggruppamento con COUNT\text{COUNT} su un attributo dell'operando destro

Sul massimo: nella terza fase si confrontano due attributi dopo un join (o un prodotto) tra RR e MM; scrivere σv=M(R)\sigma_{v=M}(R) con MM una relazione è un'abbreviazione imprecisa. Se il massimo non è unico, la tecnica restituisce tutti i massimi, come è giusto; ordinare e prendere il primo (LIMIT 1 in SQL) ne restituirebbe uno solo.

Errori tipici

  • Confondere COUNT(∗)\text{COUNT}(*) (tuple) con COUNT(A)\text{COUNT}(A) (valori non nulli); aspettarsi che AVG divida per tutte le tuple anche con NULL.
  • Mettere nel risultato di un raggruppamento attributi che non sono né di raggruppamento né aggregati.
  • Dimenticare di rinominare il risultato di un'aggregazione prima di usarlo in un join.
  • Fare il join prima del raggruppamento e moltiplicare i duplicati: contare dopo il join conta le tuple del join, non quelle di partenza.
  • Usare un join interno dove servivano anche i "senza corrispondenza" (conteggi che dovevano dare zero).
  • Dividere per SS vuoto: R÷∅R\div\emptyset è tutto πX(R)\pi_X(R) (ogni xx soddisfa il "per ogni" vuoto).

Domande d'esame

1. Come si esprime in algebra relazionale "per ogni brano il numero totale di ascolti", e quanto vale grado e cardinalità del risultato? Traccia. branoFSUM(numero)(Ascolta)_{\text{brano}}\mathcal{F}_{\text{SUM}(\text{numero})}(\text{Ascolta}): grado n+m=1+1=2n+m=1+1=2 (un attributo di raggruppamento e un aggregato), cardinalità pari al numero di brani con almeno un ascolto.

2. Cosa cambia tra left join e inner join per contare i commenti di ogni post? Traccia. L'inner join perde i post senza commenti; il left join li mantiene con NULL, e COUNT(commento)\text{COUNT}(\text{commento}) (non COUNT(∗)\text{COUNT}(*)!) dà 0 per quei post.

3. Scrivere con π,×,−\pi,\times,- la divisione R(X,Y)÷S(Y)R(X,Y)\div S(Y). Traccia. πX(R)−πX((πX(R)×S)−R)\pi_X(R)-\pi_X((\pi_X(R)\times S)-R): tutti gli xx meno quelli per cui manca almeno una coppia (x,y)(x,y).

Versione ripasso

Perché: l'algebra di base valuta una tupla alla volta. Servono le estensioni per calcolare valori tra attributi (incasso = prezzo per quantità), sommare su gruppi e per i "per tutti" (Algebra relazionale - operatori di base e joinLe operazioni sul modello relazionale sono di aggiornamento (inserimento, modifica, cancellazione, che possono violare i vincoli di integrità: si rifiuta, si corregge, si propaga con cascade o si imposta NULL/default) e di interrogazione (funzioni che da un'istanza del database producono una relazione). L'algebra relazionale è il linguaggio procedurale delle interrogazioni: gli operatori primitivi sono unione, differenza, prodotto, rinomina ρ, selezione σ e proiezione π; derivati sono intersezione, theta-join, equi-join, join naturale. Unione, intersezione e differenza richiedono relazioni compatibili all'unione (stessi attributi e domini). La selezione è una decomposizione orizzontale, la proiezione verticale (commutano solo se gli attributi della selezione sono nella proiezione). Il theta-join è σ_φ(R1×R2), il join naturale equivale a un equi-join sugli attributi omonimi seguito da una proiezione; la sua cardinalità è tra 0 e |R1||R2|, ≤|R1| se gli attributi comuni sono chiave di R2, =|R1| se sono anche chiave esterna di R1 con vincolo di integrità referenziale. Le tuple senza corrispondente sono "dangling". I valori NULL richiedono condizioni esplicite IS NULL / IS NOT NULL, perché σ_{A>40} ∪ σ_{A≤40} non restituisce le tuple con A nullo.Algebra relazionale - operatori di base e join →).

Proiezione generalizzata: πF1,…,Fn(R)={(F1(t),…,Fn(t))∣t∈R}\pi_{F_1,\dots,F_n}(R)=\{(F_1(t),\dots,F_n(t))\mid t\in R\}. Con Pizzeria(Pizza, Prezzo, Quantita): πPizza, Prezzo⋅Quantita\pi_{\text{Pizza},\ \text{Prezzo}\cdot\text{Quantita}} dà (Margherita,80), (Capricciosa,200), (Funghi,18), (Marinara,78). La colonna calcolata si nomina con ρ\rho.

Aggregazione Ff1(A),…,fn(B)(R)\mathcal{F}_{f_1(A),\dots,f_n(B)}(R): esattamente una tupla, grado nn. Funzioni: SUM, MAX, MIN, AVG, COUNT. Sui totali sopra, FSUM(Totale), MAX(Totale)\mathcal{F}_{\text{SUM}(\text{Totale}),\ \text{MAX}(\text{Totale})} dà (376, 200)(376,\ 200).

Raggruppamento A1,…,AmFf(B)(R){}_{A_1,\dots,A_m}\mathcal{F}_{f(B)}(R): grado n+mn+m, una tupla per gruppo. Esempio ristoranti, con Income=PriceMenu⋅Seats\text{Income}=\text{PriceMenu}\cdot\text{Seats} calcolato prima con π\pi:

  • INCOME\text{INCOME}: (R1,10/12,8000), (R2,10/12,18000), (R2,11/12,21000), (R1,11/12,15000), (R3,11/12,4200);
  • NameFSUM(Income), AVG(Income){}_{\text{Name}}\mathcal{F}_{\text{SUM}(\text{Income}),\ \text{AVG}(\text{Income})}: (R1,23000,11500), (R2,39000,19500), (R3,4200,4200).

NULL e aggregati

  • SUM, AVG, MAX, MIN ignorano i NULL; se sono tutti NULL il risultato è NULL.
  • Con Exam(Student, Date, Mark, Course) e cinque tuple, due con Mark NULL: COUNT(∗)=5\text{COUNT}(*)=5, COUNT(Mark)=3\text{COUNT}(\text{Mark})=3, SUM=63\text{SUM}=63, AVG=63/3=21\text{AVG}=63/3=21 (non 63/563/5), MAX=25\text{MAX}=25, MIN=18\text{MIN}=18.
  • Nel raggruppamento i NULL formano un gruppo a sé.

Outer join

  • Full ⋈F\bowtie^{\text{F}}: join più dangling di entrambi i lati; left ⋈L\bowtie^{\text{L}}: solo di R1R_1; right ⋈R\bowtie^{\text{R}}: solo di R2R_2. I dangling si completano con NULL.
  • Esempio: Employee(Surname, Dep) = (Rossi,A), (Neri,B), (Bianchi,B), (Neri,C) e Department(Code, Manager) = (A,Mori), (B,Bruni), (D,Verdi). Il full outer join ha cinque righe: le tre del join, (Neri,C,NULL,NULL) e (NULL,NULL,D,Verdi).
  • NULL nella chiave di join non si accoppiano.

Divisione R(X,Y)÷S(Y)={x∣∀y∈S: (x,y)∈R}=πX(R)−πX((πX(R)×S)−R)R(X,Y)\div S(Y)=\{x\mid\forall y\in S:\ (x,y)\in R\}=\pi_X(R)-\pi_X\big((\pi_X(R)\times S)-R\big).

Ricette

La richiesta dice Schema
"per ciascun xx il numero / la somma / la media" raggruppamento xF{}_x\mathcal{F}, dopo i join
"il xx con il massimo di vv" (1) R←xFf(v)R\leftarrow{}_x\mathcal{F}_{f(v)}; (2) M←ρm←MAX(v)FMAX(v)(R)M\leftarrow\rho_{m\leftarrow\text{MAX}(v)}\mathcal{F}_{\text{MAX}(v)}(R); (3) πx(R⋈v=mM)\pi_x(R\bowtie_{v=m}M)
"tutti i... che..." divisione, oppure tutti meno quelli con un controesempio
"almeno due" self-join con rinomina e ≠\neq, oppure COUNT≥2\text{COUNT}\ge2
"nessuno" tutti meno quelli che hanno almeno uno
"anche quelli senza..." outer join, poi COUNT\text{COUNT} su un attributo del lato destro

Nel massimo il confronto v=mv=m avviene dopo un join. Se il massimo non è unico la tecnica restituisce tutti i massimi; LIMIT 1 ne darebbe uno solo.

Errori tipici

  • Confondere COUNT(∗)\text{COUNT}(*) con COUNT(A)\text{COUNT}(A); aspettarsi che AVG divida per tutte le tuple.
  • Join prima del raggruppamento: moltiplica le tuple e il conteggio è sbagliato.
  • Join interno dove servono i "senza corrispondenza" (conteggi che devono dare zero: si usa COUNT(commento)\text{COUNT}(\text{commento}), non COUNT(∗)\text{COUNT}(*)).
  • R÷∅R\div\emptyset è tutto πX(R)\pi_X(R).

Esercizi: Esercizio - Algebra relazionale estesa su pizzeria, ristoranti e conteggi con NULL (esercizi di algebra estesa del corso). In SQL: SQL - interrogazioni, join e aggregazioniUn'interrogazione SQL ha almeno SELECT e FROM; le altre clausole (WHERE, GROUP BY, HAVING, ORDER BY) sono opzionali e HAVING richiede GROUP BY. Ordine logico di valutazione: FROM, WHERE, GROUP BY, HAVING, SELECT, DISTINCT, ORDER BY. SELECT corrisponde a π (ma senza DISTINCT non elimina i duplicati), WHERE a σ, gli alias ad AS/ρ, i join a ⋈ (INNER, LEFT, RIGHT, FULL). WHERE seleziona le righe prima del raggruppamento, HAVING seleziona i gruppi dopo. Le funzioni COUNT, SUM, AVG, MIN, MAX ignorano i NULL (COUNT(*) conta le righe, DISTINCT toglie i duplicati dentro la funzione); con un aggregato o con GROUP BY nella SELECT possono comparire solo colonne di raggruppamento o aggregati. LIKE usa % (qualsiasi sequenza) e _ (un carattere). I confronti con NULL non sono veri: serve IS NULL. Un filtro sulla tabella di destra di un LEFT JOIN va nella clausola ON, non in WHERE, altrimenti il join diventa interno. Con interi COUNT(a)/COUNT(b) è una divisione intera: serve CAST.SQL - interrogazioni, join e aggregazioni →.

Esercizi su questo argomento

Teoria collegata