Salta al contenuto
Note per Studenti Esercizio - Algebra relazionale estesa su pizzeria, ristoranti e conteggi con NULL (esercizi di algebra estesa del corso)

Esercizio - Algebra relazionale estesa su pizzeria, ristoranti e conteggi con NULL (esercizi di algebra estesa del corso)

In questa pagina 4

Testo (slide di algebra relazionale estesa del corso di Basi di Dati, Ing. Informatica UniPD). Quattro gruppi di domande:

  1. Pizzeria, Pizzeria(Pizza, Prezzo, Quantita) =(Margherita,8,10),(Capricciosa,10,20),(Funghi,9,2),(Marinara,6,13)=(\text{Margherita},8,10),(\text{Capricciosa},10,20),(\text{Funghi},9,2),(\text{Marinara},6,13): l'incasso di ogni tipo di pizza; l'incasso totale e l'incasso massimo;
  2. Pizzeria con più sedi, Pizzeria(ID, Sede, Pizza, Prezzo, Quantita): l'incasso di ogni sede;
  3. Ristoranti, Restaurant(Name, Date, PriceMenu, Seats): per ogni ristorante l'incasso totale e medio (tre ristoranti, due giorni);
  4. NULL: come si comportano COUNT\text{COUNT}, SUM\text{SUM}, AVG\text{AVG} con valori nulli e con il raggruppamento.

Teoria: Algebra relazionale estesa - divisione, aggregazione e outer joinL'algebra relazionale di base lavora una tupla alla volta e non sa fare calcoli sui valori né riassumere più tuple. L'estensione aggiunge: la proiezione generalizzata π_{F1,...,Fn}(R), che calcola espressioni sugli attributi (prezzo per quantità); l'aggregazione ℱ_{SUM, MAX, MIN, AVG, COUNT}(R), che produce una sola tupla; il raggruppamento (A1,...,Am)ℱ_{f(B)}(R), che produce una tupla per gruppo (grado n+m, cardinalità = numero di gruppi). Le funzioni di aggregazione ignorano i NULL, COUNT(*) conta le tuple, i NULL formano un gruppo a sé. Gli outer join (left, right, full) mantengono con NULL le tuple dangling del join; i NULL nella chiave di join non si accoppiano mai. La divisione R÷S dà gli x tali che (x,y) sta in R per ogni y di S, e si ottiene con π, × e −. Ricette: massimo = aggregazione + join con il massimo, "tutti" = divisione o complemento, "almeno due" = self-join.Algebra relazionale estesa - divisione, aggregazione e outer join →. Ogni risultato è stato verificato su dati inventati con un interprete di algebra e con SQLite.


1. Incasso per tipo di pizza, totale e massimo

L'incasso di una pizza è Prezzo⋅Quantita\text{Prezzo}\cdot\text{Quantita}: un valore calcolato tra attributi della stessa tupla, quindi una proiezione generalizzata.

INCOME←ρTotale(πPizza, Prezzo⋅Quantita(Pizzeria))\text{INCOME}\leftarrow\rho_{\text{Totale}}\Big(\pi_{\text{Pizza},\ \text{Prezzo}\cdot\text{Quantita}}(\text{Pizzeria})\Big)

Pizza Totale
Margherita 80
Capricciosa 200
Funghi 18
Marinara 78

SQL: SELECT Pizza, Prezzo * Quantita AS Totale FROM Pizzeria;.

Incasso totale e massimo. Su INCOME si applica un'aggregazione con due funzioni: una sola tupla con due attributi.

FSUM(Totale), MAX(Totale)(INCOME)⇒(376, 200)\mathcal{F}_{\text{SUM}(\text{Totale}),\ \text{MAX}(\text{Totale})}(\text{INCOME})\quad\Rightarrow\quad(376,\ 200)

perché 80+200+18+78=37680+200+18+78=376 e il massimo è quello della Capricciosa. Per dare nomi leggibili: ρTotIncome,MaxIncome←SUM(Totale),MAX(Totale)\rho_{\text{TotIncome},\text{MaxIncome}\leftarrow\text{SUM}(\text{Totale}),\text{MAX}(\text{Totale})}. In SQL basta una query: SELECT SUM(Prezzo*Quantita) AS TotIncome, MAX(Prezzo*Quantita) AS MaxIncome FROM Pizzeria; (le due funzioni lavorano su un'espressione, quindi la proiezione intermedia non serve).

Errore nel materiale del corso. Nella slide introduttiva la Capricciosa ha prezzo 11 (e totale 220), mentre le slide con la soluzione usano 10 (totale 200, somma 376). I risultati 376 e 200 valgono con il prezzo 10; con 11 sarebbero 396 e 220 (verificato). È una discrepanza tra una slide e le successive.

2. Incasso di ogni sede

Sedi: (1,Vicenza,Margherita,8,10)(1,\text{Vicenza},\text{Margherita},8,10), (2,Padova,Capricciosa,11,20)(2,\text{Padova},\text{Capricciosa},11,20), (3,Padova,Margherita,9,2)(3,\text{Padova},\text{Margherita},9,2), (4,Vicenza,Marinara,6,13)(4,\text{Vicenza},\text{Marinara},6,13).

Il dato che serve non è un attributo, e va sommato su un gruppo: raggruppamento per Sede con la somma dell'incasso di ogni riga.

SedeFSUM(Incasso)(πSede, Incasso←Prezzo⋅Quantita(Pizzeria))_{\text{Sede}}\mathcal{F}_{\text{SUM}(\text{Incasso})}\Big(\pi_{\text{Sede},\ \text{Incasso}\leftarrow\text{Prezzo}\cdot\text{Quantita}}(\text{Pizzeria})\Big)

Passi: la proiezione generalizzata calcola 80, 220, 18, 78; i gruppi sono Vicenza (80+78=15880+78=158) e Padova (220+18=238220+18=238). Grado n+m=1+1=2n+m=1+1=2, cardinalità 2 (due gruppi).

Sede SUM(Incasso)
Vicenza 158
Padova 238

SQL: SELECT Sede, SUM(Prezzo * Quantita) AS Incasso FROM Pizzeria GROUP BY Sede;.

3. Ristoranti: totale e media per ristorante

Restaurant: (R1,10/12,80,100)(R_1,10/12,80,100), (R2,10/12,90,200)(R_2,10/12,90,200), (R2,11/12,70,300)(R_2,11/12,70,300), (R1,11/12,100,150)(R_1,11/12,100,150), (R3,11/12,60,70)(R_3,11/12,60,70) (con Name abbreviato).

Passo 1: l'incasso di ogni riga (menù per posti):

INCOME←ρIncome←PriceMenu⋅Seats(πName,Date, PriceMenu⋅Seats(Restaurant))\text{INCOME}\leftarrow\rho_{\text{Income}\leftarrow\text{PriceMenu}\cdot\text{Seats}}\Big(\pi_{\text{Name},\text{Date},\ \text{PriceMenu}\cdot\text{Seats}}(\text{Restaurant})\Big)

Name Date Income
R1R_1 10/12 8000
R2R_2 10/12 18000
R2R_2 11/12 21000
R1R_1 11/12 15000
R3R_3 11/12 4200

Passo 2: raggruppamento per ristorante con somma e media:

NameFSUM(Income), AVG(Income)(INCOME)_{\text{Name}}\mathcal{F}_{\text{SUM}(\text{Income}),\ \text{AVG}(\text{Income})}(\text{INCOME})

Name SUM(Income) AVG(Income)
R1R_1 23000 11500
R2R_2 39000 19500
R3R_3 4200 4200

R3R_3 ha un solo giorno: somma e media coincidono. Si può rinominare con ρTotal,Average←SUM(Income),AVG(Income)\rho_{\text{Total},\text{Average}\leftarrow\text{SUM}(\text{Income}),\text{AVG}(\text{Income})}. In SQL: SELECT Name, SUM(PriceMenu*Seats) AS Total, AVG(PriceMenu*Seats) AS Average FROM Restaurant GROUP BY Name;.

Errore nel materiale del corso. Le slide riportano per le prime quattro righe incassi di 800, 1800, 2100 e 1500, e quindi totali 2300, 3900 (e medie 1150, 1950). Con i dati della tabella 80⋅100=800080\cdot100=8000, 90⋅200=1800090\cdot200=18000, 70⋅300=2100070\cdot300=21000, 100⋅150=15000100\cdot150=15000: i valori corretti sono 8000, 18000, 21000, 15000, con totali 23000 e 39000 e medie 11500 e 19500. Solo la riga di R3R_3 (60⋅70=420060\cdot70=4200) è giusta nella slide (verificato su SQLite). La procedura delle slide è comunque quella giusta.

Estensione: il ristorante con l'incasso totale massimo (pattern del massimo della nota di teoria). Con T←ρTotal←SUM(Income)(NameFSUM(Income)(INCOME))T\leftarrow\rho_{\text{Total}\leftarrow\text{SUM}(\text{Income})}\big({}_{\text{Name}}\mathcal{F}_{\text{SUM}(\text{Income})}(\text{INCOME})\big) (totale per ristorante) e M←ρMaxT←MAX(Total)(FMAX(Total)(T))M\leftarrow\rho_{\text{MaxT}\leftarrow\text{MAX}(\text{Total})}\big(\mathcal{F}_{\text{MAX}(\text{Total})}(T)\big) (il massimo dei totali):

πName,Total(T⋈Total=MaxTM)={(R2,39000)}\pi_{\text{Name},\text{Total}}\big(T\bowtie_{\text{Total}=\text{MaxT}}M\big)=\{(R_2,39000)\}

In SQL: ... GROUP BY Name HAVING SUM(PriceMenu*Seats) = (SELECT MAX(t) FROM (SELECT SUM(PriceMenu*Seats) AS t FROM Restaurant GROUP BY Name) AS q): restituisce solo R2R_2 (provato; in PostgreSQL l'alias q è obbligatorio).

4. NULL e aggregazione

Esami. 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}).

Espressione Risultato Spiegazione
COUNT(∗)\text{COUNT}(*) 5 conta le tuple, anche con voto nullo
COUNT(Mark)\text{COUNT}(\text{Mark}) 3 solo i voti non nulli
SUM(Mark)\text{SUM}(\text{Mark}) 63 20+25+1820+25+18
AVG(Mark)\text{AVG}(\text{Mark}) 21 63/363/3, non 63/563/5
MAX(Mark)\text{MAX}(\text{Mark}), MIN(Mark)\text{MIN}(\text{Mark}) 25, 18 solo valori non nulli
StudentFAVG(Mark)_{\text{Student}}\mathcal{F}_{\text{AVG}(\text{Mark})} (123456;22,5)(123456;22{,}5), (384752;18)(384752;18), (3841345;NULL)(3841345;\text{NULL}) l'ultimo studente ha solo NULL: la media è NULL

Con COALESCE(Mark, 0) la media diventerebbe 63/5=12,663/5=12{,}6: contare il "nessun voto" come zero è una scelta di significato, non un dettaglio.

Lavoratori. Worker(Badge, Name, Surname, Car): (123456,Mario,Rossi,Volvo)(123456,\text{Mario},\text{Rossi},\text{Volvo}), (384762,Matteo,Verdi,NULL)(384762,\text{Matteo},\text{Verdi},\text{NULL}), (382320,Marco,Verdi,NULL)(382320,\text{Marco},\text{Verdi},\text{NULL}), (596830,John,Rossi,NULL)(596830,\text{John},\text{Rossi},\text{NULL}), (192092,Mary,Bianchi,Fiat)(192092,\text{Mary},\text{Bianchi},\text{Fiat}), (384135,Isabel,Bianchi,Fiat)(384135,\text{Isabel},\text{Bianchi},\text{Fiat}).

Espressione Risultato
FCOUNT(∗)(Worker)\mathcal{F}_{\text{COUNT}(*)}(\text{Worker}) 6
COUNT(Car)\text{COUNT}(\text{Car}) (SQL standard) 3 (Volvo, Fiat, Fiat)
COUNT(DISTINCT Car)\text{COUNT}(\text{DISTINCT Car}) 2 (Volvo, Fiat)
Badge,SurnameFCOUNT(∗)_{\text{Badge},\text{Surname}}\mathcal{F}_{\text{COUNT}(*)} 6 gruppi (ogni badge è unico)
Surname,CarFCOUNT(∗)_{\text{Surname},\text{Car}}\mathcal{F}_{\text{COUNT}(*)} 4 gruppi: (Rossi,Volvo,1)(\text{Rossi},\text{Volvo},1), (Verdi,NULL,2)(\text{Verdi},\text{NULL},2), (Rossi,NULL,1)(\text{Rossi},\text{NULL},1), (Bianchi,Fiat,2)(\text{Bianchi},\text{Fiat},2)
CarFCOUNT(∗)_{\text{Car}}\mathcal{F}_{\text{COUNT}(*)} 3 gruppi: (Volvo,1)(\text{Volvo},1), (Fiat,2)(\text{Fiat},2), (NULL,3)(\text{NULL},3)

Il raggruppamento tratta i NULL come un gruppo unico: i tre lavoratori senza auto stanno nel gruppo NULL\text{NULL} con conteggio 3 (verificato in SQLite con GROUP BY Car).

Errore nel materiale del corso. La slide scrive COUNT(car)=2\text{COUNT}(\text{car})=2 e COUNT(surname,car)=4\text{COUNT}(\text{surname},\text{car})=4. Con la semantica SQL standard COUNT(Car)\text{COUNT}(\text{Car}) conta i valori non nulli, duplicati compresi, e vale 3; il valore 2 è quello di COUNT(DISTINCT Car)\text{COUNT}(\text{DISTINCT Car}). Il 44 del conteggio su due attributi coincide con il numero di coppie distinte (Surname,Car)(\text{Surname},\text{Car}) (compresa quella con NULL), e in SQL non esiste un COUNT\text{COUNT} su più colonne senza DISTINCT e parentesi di riga. I numeri della slide vanno letti come conteggi di valori distinti.

Versione ripasso

Teoria: Algebra relazionale estesa - divisione, aggregazione e outer joinL'algebra relazionale di base lavora una tupla alla volta e non sa fare calcoli sui valori né riassumere più tuple. L'estensione aggiunge: la proiezione generalizzata π_{F1,...,Fn}(R), che calcola espressioni sugli attributi (prezzo per quantità); l'aggregazione ℱ_{SUM, MAX, MIN, AVG, COUNT}(R), che produce una sola tupla; il raggruppamento (A1,...,Am)ℱ_{f(B)}(R), che produce una tupla per gruppo (grado n+m, cardinalità = numero di gruppi). Le funzioni di aggregazione ignorano i NULL, COUNT(*) conta le tuple, i NULL formano un gruppo a sé. Gli outer join (left, right, full) mantengono con NULL le tuple dangling del join; i NULL nella chiave di join non si accoppiano mai. La divisione R÷S dà gli x tali che (x,y) sta in R per ogni y di S, e si ottiene con π, × e −. Ricette: massimo = aggregazione + join con il massimo, "tutti" = divisione o complemento, "almeno due" = self-join.Algebra relazionale estesa - divisione, aggregazione e outer join →.

1. Incasso per pizza, totale e massimo. Proiezione generalizzata: πPizza, Prezzo⋅Quantita(Pizzeria)\pi_{\text{Pizza},\ \text{Prezzo}\cdot\text{Quantita}}(\text{Pizzeria}) dà (Margherita,80), (Capricciosa,200), (Funghi,18), (Marinara,78). Poi FSUM(Totale), MAX(Totale)(INCOME)=(376, 200)\mathcal{F}_{\text{SUM}(\text{Totale}),\ \text{MAX}(\text{Totale})}(\text{INCOME})=(376,\ 200), perché 80+200+18+78=37680+200+18+78=376.

  • SQL: SELECT SUM(Prezzo*Quantita) AS TotIncome, MAX(Prezzo*Quantita) AS MaxIncome FROM Pizzeria;

2. Incasso per sede. Il valore è calcolato e poi sommato su gruppo: 80, 220, 18, 78 per riga, poi SedeFSUM(Incasso){}_{\text{Sede}}\mathcal{F}_{\text{SUM}(\text{Incasso})}. Vicenza 80+78=15880+78=158, Padova 220+18=238220+18=238. Grado 1+1=21+1=2, cardinalità 2 (due gruppi).

  • SQL: SELECT Sede, SUM(Prezzo*Quantita) AS Incasso FROM Pizzeria GROUP BY Sede;

3. Ristoranti: totale e media.

  • Passo 1: Income=PriceMenu⋅Seats\text{Income}=\text{PriceMenu}\cdot\text{Seats}, poi NameFSUM(Income), AVG(Income){}_{\text{Name}}\mathcal{F}_{\text{SUM}(\text{Income}),\ \text{AVG}(\text{Income})}: R1 2300023000 e 1150011500; R2 3900039000 e 1950019500; R3 42004200 e 42004200.
  • Errore del corso: la slide riporta 800, 1800, 2100, 1500 per le prime quattro righe.
  • Massimo del totale: si aggrega il totale per ristorante, si prende il massimo e si fa join sul valore. Risultato (R2,39000)(R_2,39000).

4. NULL e aggregazione. Esami, con Mark NULL per 384752 (Analysis) e per 3841345:

  • 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;
  • media per studente: 123456 dà 22,5; 384752 dà 18; 3841345, che ha solo NULL, dà NULL.

Lavoratori, con Car NULL per tre dei sei:

  • COUNT(∗)=6\text{COUNT}(*)=6; COUNT(Car)=3\text{COUNT}(\text{Car})=3 (valori non nulli, duplicati compresi); COUNT(DISTINCT Car)=2\text{COUNT}(\text{DISTINCT Car})=2;
  • CarFCOUNT(∗){}_{\text{Car}}\mathcal{F}_{\text{COUNT}(*)} dà (Volvo,1), (Fiat,2), (NULL,3): i NULL formano un gruppo unico.
  • Errore del corso: la slide scrive COUNT(car)=2\text{COUNT}(\text{car})=2, che è il valore di COUNT(DISTINCT).

Errori tipici

  • Dividere l'AVG per tutte le tuple anche con NULL.
  • COUNT(∗)\text{COUNT}(*) al posto di COUNT(A)\text{COUNT}(A).
  • Calcolare l'incasso dopo il raggruppamento invece che prima.

Teoria collegata