Salta al contenuto
Note per Studenti Esercizio - Interrogazioni sugli ordini di una pizzeria (tema d'esame 1 marzo 2013)

Esercizio - Interrogazioni sugli ordini di una pizzeria (tema d'esame 1 marzo 2013)

Esame
In questa pagina 7

Testo (compito d'esame del 1 marzo 2013, "Gestione ordini pizzeria", dal corso di Basi di Dati, Ing. Informatica UniPD). Una pizzeria a Padova vuole gestire le ordinazioni:

  • ci sono dei tavoli identificati da un numero, liberi, occupati o in attesa del conto; ad ogni tavolo c'è un solo cameriere che prende la comanda;
  • la comanda ha un numero, una data, il tavolo e il cameriere che l'ha compilata, e la lista delle pietanze ordinate;
  • le pietanze si dividono in piatti e bevande, hanno una categoria (antipasti, pizze, pizze speciali, dolci, analcolici, birre, vini...), un numero identificativo, un nome, una descrizione e un prezzo; ogni elemento della comanda ha una quantità;
  • ogni piatto è composto da alcuni ingredienti; solo per le pizze si possono fare aggiunte o eliminazioni di ingredienti (una capricciosa senza funghi con doppia mozzarella); ogni ingrediente ha un prezzo, che si conta nell'aggiunta;
  • alla fine si chiede il conto, uno scontrino (con lista delle pietanze, prezzi e data di emissione) o una fattura (con un numero identificativo, e un codice fiscale o una partita IVA); non c'è sovrapprezzo per il coperto.

Interrogazioni da scrivere in algebra relazionale e in SQL:

  1. la somma delle birre e delle pizze ordinate il giorno 2013-03-01;
  2. la fattura numero 10: tutti i piatti ordinati e il loro prezzo (comprese le aggiunte e le eliminazioni);
  3. la media dei conti del tavolo numero 5.

La progettazione ER (entità, relazioni, scelta della "pietanza ordinata" come entità debole, il conto come generalizzazione scontrino/fattura) è nell'esercizio di progettazione di questa stessa traccia. Qui si riporta soltanto lo schema relazionale della soluzione del corso e si risolvono le tre query. Teoria: 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 →, 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 →.

Progettazione ER e schema relazionale della stessa traccia: Esercizio - Progettazione ER e schema relazionale della pizzeria (tema d'esame 30 giugno 2025).

Schema relazionale (dalla soluzione del corso)

Le chiavi primarie sono in grassetto; le chiavi esterne con la freccia.

Relazione Attributi Note
TAVOLO numero, stato stato ∈\in {libero, occupato, attesa conto}
CAMERIERE id
COMANDA numero, data, tavolo →\to TAVOLO, cameriere →\to CAMERIERE un tavolo ha più comande (non una sola), ciascuna con un cameriere
CONTO id, comanda →\to COMANDA (chiave: un conto per comanda), totale, tipo, numero_fattura, piva, cf, data generalizzazione scontrino/fattura assorbita nel genitore con l'attributo tipo; numero_fattura e (piva o cf) obbligatori se tipo = fattura (vincolo esterno)
INGREDIENTE nome, prezzo
PIETANZA numero, nome, descrizione, prezzo, tipo, categoria piatto/bevanda assorbite nel genitore (tipo, categoria)
COMPONE ingrediente →\to INGREDIENTE, pietanza →\to PIETANZA solo per le pizze (vincolo esterno)
PIETANZA_ORDINATA id, pietanza →\to PIETANZA, comanda →\to COMANDA, qta entità identificata da id e dalla pietanza (identificazione esterna): due pizze margherita nella stessa comanda sono due istanze con id diversi
MODIFICA id, pietanza, ingrediente →\to INGREDIENTE, aggiunge (id,pietanza)→(\text{id},\text{pietanza})\to PIETANZA_ORDINATA; aggiunge vero = aggiunta, falso = eliminazione

Due scelte della soluzione da conoscere: (a) totale è un attributo derivato (somma, per ogni pietanza ordinata, di qta per il prezzo con le aggiunte): lo si tiene perché serve per la terza query; (b) la PIETANZA_ORDINATA è un'entità a sé, con modifiche per ogni istanza, perché una comanda può contenere due pizze uguali con modifiche diverse. Per le query sotto si è aggiunta a CONTO la data (il testo la richiede per lo scontrino; la soluzione la ometteva nella tabella a mano) e aggiunge come booleano.

Dati inventati per provare

TAVOLO: (1, libero), (5, occupato), (7, libero). CAMERIERE: 1, 2. INGREDIENTE: mozzarella 1,5; funghi 1,0; prosciutto 2,0; pomodoro 0,5; olive 0,8. PIETANZA (numero, nome, prezzo, tipo, categoria): (1, Margherita, 6, piatto, pizze), (2, Capricciosa, 8, piatto, pizze), (3, Birra media, 4, bevanda, birre), (4, Tiramisù, 5, piatto, dolci), (5, Bruschetta, 3,5, piatto, antipasti), (6, Cola, 3, bevanda, analcolici).

COMANDA (numero, data, tavolo, cameriere): (1, 2013-03-01, 5, 1), (2, 2013-03-01, 7, 2), (3, 2013-03-02, 5, 1), (4, 2013-03-01, 5, 2).

PIETANZA_ORDINATA (id, pietanza, comanda, qta): (1,1,1,1), (2,1,1,1), (1,3,1,3), (1,4,1,1), (1,2,2,1), (1,6,2,2), (1,5,3,1), (3,1,3,1), (2,2,4,2), (2,3,4,1). MODIFICA (id, pietanza, ingrediente, aggiunge): (1,1,mozzarella,vero), (2,1,pomodoro,falso), (1,2,funghi,falso), (1,2,olive,vero).

CONTO (id, comanda, totale, tipo, numero_fattura): (1, 1, 30,5, fattura, 10), (2, 2, 14,8, scontrino, -), (3, 3, 9,5, scontrino, -), (4, 4, 20, fattura, 11).

Nella comanda 1 c'è una margherita con doppia mozzarella (aggiunta a 1,5) e una senza pomodoro (l'eliminazione non cambia il prezzo), tre birre e un tiramisù: (6+1,5)+6+3⋅4+5=30,5(6+1{,}5)+6+3\cdot4+5=30{,}5, il totale del conto 1.


Query 1: la somma delle birre e delle pizze ordinate il 2013-03-01

Idea. Occorrono le pietanze ordinate nelle comande di quel giorno (COMANDA ⋈\bowtie PIETANZA_ORDINATA), poi la categoria della pietanza (PIETANZA), poi un filtro sulla categoria e una somma delle quantità.

Algebra. Con PO\text{PO} per PIETANZA_ORDINATA:

COM←σdata=′2013−03−01′(COMANDA)⋈numero=comandaPO\text{COM}\leftarrow\sigma_{\text{data}='2013-03-01'}(\text{COMANDA})\bowtie_{\text{numero}=\text{comanda}}\text{PO} PB←σcategoria=′pizze′ ∨ categoria=′birre′(PIETANZA)\text{PB}\leftarrow\sigma_{\text{categoria}='\text{pizze}'\ \vee\ \text{categoria}='\text{birre}'}(\text{PIETANZA}) categoriaFSUM(qta)(COM⋈pietanza=numeroPB)_{\text{categoria}}\mathcal{F}_{\text{SUM}(\text{qta})}\big(\text{COM}\bowtie_{\text{pietanza}=\text{numero}}\text{PB}\big)

(Nel join tra COM e PB l'attributo numero è quello della pietanza; nel primo join quello della comanda: va rinominato o proiettato via, per esempio πid,pietanza,comanda,qta(COM)\pi_{\text{id},\text{pietanza},\text{comanda},\text{qta}}(\text{COM}) prima del secondo join, come fa la soluzione.) Per avere un solo numero (pizze e birre insieme) si toglie la categoria dal raggruppamento: FSUM(qta)(… )\mathcal{F}_{\text{SUM}(\text{qta})}(\dots).

SQL.

sql
SELECT p.categoria, SUM(po.qta) AS totale
FROM comanda AS c
     INNER JOIN pietanza_ordinata AS po ON c.numero = po.comanda
     INNER JOIN pietanza AS p ON po.pietanza = p.numero
WHERE p.categoria IN ('pizze', 'birre') AND c.data = '2013-03-01'
GROUP BY p.categoria;

Risultato sui dati: birre 4 (3 nella comanda 1 e 1 nella 4), pizze 5 (2 margherite nella 1, 1 capricciosa nella 2, 2 capricciose nella 4). Con un unico totale (togliendo p.categoria dalla SELECT e il GROUP BY) il risultato è 9.

Commenti.

  • La richiesta "la somma delle birre e delle pizze" è ambigua: due numeri (uno per categoria) o uno solo? La soluzione del corso raggruppa per categoria; si può fare una query sola con GROUP BY e un'altra senza. Meglio dichiarare la scelta.
  • Si somma qta, non si conta con COUNT(*): ogni riga ordinata ha una quantità (3 birre in una riga).
  • "Pizze speciali" è una categoria diversa da "pizze": è corretto escluderla con IN ('pizze','birre'), ma se la richiesta le comprendesse servirebbe LIKE 'pizze%'.
  • Errore nel materiale del corso: nella soluzione a mano il valore 'Bme' al posto di 'Birre' nel WHERE è un refuso che non restituirebbe nessuna birra.
  • Algebra e SQL sono stati confrontati: stesso risultato.

Query 2: la fattura numero 10, con piatti e prezzo

Idea. Dalla fattura (CONTO con numero_fattura = 10) si risale alla comanda, alle pietanze ordinate, e per ogni pietanza si calcola il prezzo unitario = prezzo base + prezzo delle sole aggiunte; l'eliminazione di un ingrediente non cambia il prezzo (il testo conta il prezzo "nell'aggiunta"). L'importo di una riga è qta per il prezzo unitario.

Soluzione del corso.

PIETANZE10←(πcomanda(σfattura=10(CONTO)))⋈comandaPOPIETANZE10⋈MODIFICA⋈pietanza=numeroPIETANZA\text{PIETANZE10}\leftarrow\Big(\pi_{\text{comanda}}\big(\sigma_{\text{fattura}=10}(\text{CONTO})\big)\Big)\bowtie_{\text{comanda}}\text{PO}\qquad\text{PIETANZE10}\bowtie\text{MODIFICA}\bowtie_{\text{pietanza}=\text{numero}}\text{PIETANZA}

sql
SELECT * FROM conto AS c
  INNER JOIN pietanza_ordinata AS po ON c.pietanza = po.pietanza   -- la soluzione scrive così
  INNER JOIN modifica AS m ON po.id = m.id AND po.pietanza = m.pietanza
  INNER JOIN pietanza AS p ON po.pietanza = p.numero
WHERE fattura = 10;

Errori della soluzione del corso. (1) CONTO non ha l'attributo pietanza: il collegamento con PIETANZA_ORDINATA è c.comanda = po.comanda (SQLite: no such column: c.pietanza). (2) Il join interno con MODIFICA elimina le pietanze senza modifiche: sui dati, la fattura 10 ha 4 righe ordinate, ma solo 2 hanno una modifica, quindi la query ne restituisce 2 (le due margherite) e perde birre e tiramisù. Serve un left outer join. (3) Non si calcola nessun prezzo: SELECT * mostra solo le righe. Il testo chiede "il prezzo (comprese le aggiunte/eliminazioni)".

Soluzione corretta (SQL).

sql
SELECT po.id, p.nome, po.qta, p.prezzo,
       COALESCE(SUM(CASE WHEN m.aggiunge THEN i.prezzo END), 0) AS extra,
       po.qta * (p.prezzo + COALESCE(SUM(CASE WHEN m.aggiunge THEN i.prezzo END), 0)) AS importo
FROM conto AS c
     INNER JOIN pietanza_ordinata AS po ON po.comanda = c.comanda
     INNER JOIN pietanza AS p ON p.numero = po.pietanza
     LEFT JOIN modifica AS m ON m.id = po.id AND m.pietanza = po.pietanza
     LEFT JOIN ingrediente AS i ON i.nome = m.ingrediente
WHERE c.numero_fattura = 10
GROUP BY po.id, po.pietanza, p.nome, po.qta, p.prezzo;

Il LEFT JOIN conserva le pietanze senza modifiche (con m e i a NULL); CASE WHEN m.aggiunge THEN i.prezzo END prende il prezzo solo per le aggiunte (le eliminazioni danno NULL e SUM le ignora); COALESCE(..., 0) trasforma il NULL (nessuna aggiunta) in 0. Si raggruppa per l'identificatore della pietanza ordinata, perché il join con MODIFICA moltiplica le righe (una per modifica): senza GROUP BY si avrebbero righe duplicate.

Risultato sulla fattura 10 (comanda 1):

id nome qta prezzo extra importo
1 Margherita 1 6 1,5 7,5
2 Margherita 1 6 0 6
1 Birra media 3 4 0 12
1 Tiramisù 1 5 0 5

La somma degli importi è 30,5, uguale al totale memorizzato nel conto: la verifica che l'attributo derivato è coerente.

Algebra relazionale (corretta). Si evita il NULL con un'unione:

P10←πid,pietanza,qta(ρc10←comanda(πcomanda(σnumero_fattura=10(CONTO)))⋈c10=comandaPO)\text{P10}\leftarrow\pi_{\text{id},\text{pietanza},\text{qta}}\Big(\rho_{\text{c10}\leftarrow\text{comanda}}\big(\pi_{\text{comanda}}(\sigma_{\text{numero\_fattura}=10}(\text{CONTO}))\big)\bowtie_{\text{c10}=\text{comanda}}\text{PO}\Big) ADD←πid,pietanza,pi(σaggiunge=vero(MODIFICA)⋈ingrediente=ingρing,pi←nome,prezzo(INGREDIENTE))\text{ADD}\leftarrow\pi_{\text{id},\text{pietanza},\text{pi}}\Big(\sigma_{\text{aggiunge}=\text{vero}}(\text{MODIFICA})\bowtie_{\text{ingrediente}=\text{ing}}\rho_{\text{ing},\text{pi}\leftarrow\text{nome},\text{prezzo}}(\text{INGREDIENTE})\Big) EXTRA←ρextra←SUM(pi)(id,pietanzaFSUM(pi)(ADD)) ∪ ((πid,pietanza(P10)−πid,pietanza(ADD))×{0})\text{EXTRA}\leftarrow\rho_{\text{extra}\leftarrow\text{SUM}(\text{pi})}\big({}_{\text{id},\text{pietanza}}\mathcal{F}_{\text{SUM}(\text{pi})}(\text{ADD})\big)\ \cup\ \Big(\big(\pi_{\text{id},\text{pietanza}}(\text{P10})-\pi_{\text{id},\text{pietanza}}(\text{ADD})\big)\times\{0\}\Big)

(dove {0}\{0\} è la relazione con il solo attributo extra e la sola tupla (0)(0).) πid,nome,qta,prezzo,extra, qta⋅(prezzo+extra)(P10⋈EXTRA⋈ρpietanza←numero(PIETANZA))\pi_{\text{id},\text{nome},\text{qta},\text{prezzo},\text{extra},\ \text{qta}\cdot(\text{prezzo}+\text{extra})}\big(\text{P10}\bowtie\text{EXTRA}\bowtie\rho_{\text{pietanza}\leftarrow\text{numero}}(\text{PIETANZA})\big)

Il primo passo isola le pietanze ordinate della fattura; il secondo i prezzi delle sole aggiunte; il terzo somma per pietanza ordinata e aggiunge (id,pietanza,0)(\text{id},\text{pietanza},0) per quelle senza aggiunte (la differenza del terzo passo), così il join finale non perde nessuna riga; l'ultimo è una proiezione generalizzata che calcola l'importo. Il risultato coincide con quello SQL (verificato).

Query 3: la media dei conti del tavolo 5

Idea. Il conto non conosce il tavolo: si passa dalla comanda. Poi si media il totale.

Algebra (la soluzione del corso, con una rinomina necessaria: CONTO e COMANDA hanno entrambe data):

TAVOLO5←σtavolo=5(COMANDA⋈numero=comandaρid′,data′←id,data(CONTO))FAVG(totale)(TAVOLO5)\text{TAVOLO5}\leftarrow\sigma_{\text{tavolo}=5}\big(\text{COMANDA}\bowtie_{\text{numero}=\text{comanda}}\rho_{\text{id}',\text{data}'\leftarrow\text{id},\text{data}}(\text{CONTO})\big)\qquad\mathcal{F}_{\text{AVG}(\text{totale})}(\text{TAVOLO5})

SQL.

sql
SELECT AVG(c.totale) AS media_conti
FROM conto AS c INNER JOIN comanda AS co ON c.comanda = co.numero
WHERE co.tavolo = 5;

Sui dati: i conti del tavolo 5 sono quelli delle comande 1, 3 e 4, con totali 30,530{,}5, 9,59{,}5, 2020: media (30,5+9,5+20)/3=20(30{,}5+9{,}5+20)/3=\mathbf{20}. Se il tavolo non ha conti la media è NULL.

Senza l'attributo derivato totale, bisogna ricalcolarlo per ogni comanda (somma per pietanza ordinata di qta per prezzo con le aggiunte, come nella query 2) e mediare i totali: stesso 20 (verificato). L'attributo derivato fa risparmiare un join e un raggruppamento, a costo di dover mantenere coerente il totale quando la comanda cambia (un candidato a trigger, vedi SQL avanzato - indici, transazioni e triggerUn file di record può essere heap (inserimento in ordine, ricerca per uguaglianza in media n/2 accessi), ordinato (log2 n), hash (costante, solo uguaglianza) o B-tree (log_F n, anche intervalli). Un indice è una struttura ausiliaria di coppie (chiave K, puntatore P) ordinate, molto più piccola della tabella: l'indice primario è sul campo di ordinamento del file ordinato (una chiave per blocco, non denso, log2(m)+1 accessi), l'indice secondario è su un campo qualsiasi (denso). CREATE [UNIQUE] INDEX, anche su espressioni; PostgreSQL usa tabelle heap con indici secondari B-tree (predefinito) o hash, e PRIMARY KEY e UNIQUE creano un indice implicito. Gli indici velocizzano le letture ma costano nelle scritture e in spazio. Una transazione è una sequenza atomica di operazioni con proprietà ACID (atomicità, consistenza, isolamento, durabilità): START TRANSACTION, COMMIT, SAVEPOINT, ROLLBACK [TO SAVEPOINT]. Un trigger è una regola evento-condizione-azione (ECA): BEFORE/AFTER, ROW/STATEMENT, WHEN, con funzione PL/pgSQL che usa NEW e OLD; serve per vincoli complessi e attributi derivati.SQL avanzato - indici, transazioni e trigger →).

Errori nel materiale del corso (riassunto)

  • Query 1: refuso 'Bme'; richiesta ambigua (una somma o una per categoria).
  • Query 2: join interno con MODIFICA che elimina le pietanze senza modifiche; ON c.pietanza = ... su una colonna inesistente; nessun calcolo del prezzo.
  • Query 3: nel join COMANDA ⋈\bowtie CONTO gli attributi omonimi (data) vanno rinominati.

Versione ripasso

Schema (soluzione del corso): TAVOLO(numero, stato), CAMERIERE(id), COMANDA(numero, data, tavolo, cameriere), CONTO(id, comanda, totale, tipo, numero_fattura, piva, cf), INGREDIENTE(nome, prezzo), PIETANZA(numero, nome, descrizione, prezzo, tipo, categoria), COMPONE(ingrediente, pietanza), PIETANZA_ORDINATA(id, pietanza, comanda, qta), MODIFICA(id, pietanza, ingrediente, aggiunge). Nel conto si aggiungono data e aggiunge booleano. Teoria: 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 →, 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. Birre e pizze del 2013-03-01: COM←σdata=′2013−03−01′(COMANDA)⋈PO\text{COM}\leftarrow\sigma_{\text{data}='2013-03-01'}(\text{COMANDA})\bowtie\text{PO}, poi il join con PIETANZA filtrata per categoria e categoriaFSUM(qta)_{\text{categoria}}\mathcal{F}_{\text{SUM}(\text{qta})}. Si somma qta, non si conta. Risultato: birre 4 (3 nella comanda 1, 1 nella 4), pizze 5 (2 margherite nella 1, 1 capricciosa nella 2, 2 nella 4); senza distinzione per categoria, 9. Il testo non dice se la somma va fatta per categoria: conviene dichiarare la scelta.

    • SQL: WHERE p.categoria IN ('pizze', 'birre') AND c.data = '2013-03-01' GROUP BY p.categoria.
    • Refuso del corso: 'Bme' al posto di 'Birre'.
  2. Fattura 10: piatti e prezzo. Il prezzo unitario è il prezzo base più il prezzo delle sole aggiunte; l'eliminazione non cambia il prezzo. Righe: Margherita con doppia mozzarella (6+1,5)(6+1{,}5), Margherita senza pomodoro 66, tre birre 3⋅4=123\cdot4=12, tiramisù 55: totale 30,530{,}5, uguale al totale del conto.

sql
SELECT po.id, p.nome, po.qta, p.prezzo,
       COALESCE(SUM(CASE WHEN m.aggiunge THEN i.prezzo END), 0) AS extra
FROM conto AS c
     INNER JOIN pietanza_ordinata AS po ON po.comanda = c.comanda
     INNER JOIN pietanza AS p ON p.numero = po.pietanza
     LEFT JOIN modifica AS m ON m.id = po.id AND m.pietanza = po.pietanza
     LEFT JOIN ingrediente AS i ON i.nome = m.ingrediente
WHERE c.numero_fattura = 10
GROUP BY po.id, po.pietanza, p.nome, po.qta, p.prezzo;
  • Il LEFT JOIN con MODIFICA conserva le pietanze senza modifiche; il GROUP BY serve perché il join moltiplica le righe. L'importo è qta⋅(prezzo+extra)\text{qta}\cdot(\text{prezzo}+\text{extra}), cioè 7,57{,}5, 66, 1212 e 55.
  • Algebra: EXTRA\text{EXTRA} è la somma degli extra per (id,pietanza)(\text{id},\text{pietanza}), unita a (id,pietanza,0)(\text{id},\text{pietanza},0) per le pietanze senza aggiunte, così il join finale non perde righe. La formula finale è una proiezione generalizzata che calcola l'importo.
  • Errori del corso: join interno con MODIFICA, che lascia 2 righe su 4; c.pietanza non esiste in CONTO; prezzo mai calcolato.
  1. Media dei conti del tavolo 5. I conti delle comande 1, 3 e 4 hanno totali 30,530{,}5, 9,59{,}5 e 2020: media 2020.
sql
SELECT AVG(c.totale) AS media_conti
FROM conto AS c INNER JOIN comanda AS co ON c.comanda = co.numero
WHERE co.tavolo = 5;
  • Nel join data compare in entrambe le tabelle e va rinominata: in algebra TAVOLO5←σtavolo=5(COMANDA⋈numero=comandaρid′,data′←id,data(CONTO))\text{TAVOLO5}\leftarrow\sigma_{\text{tavolo}=5}(\text{COMANDA}\bowtie_{\text{numero}=\text{comanda}}\rho_{\text{id}',\text{data}'\leftarrow\text{id},\text{data}}(\text{CONTO})), poi FAVG(totale)(TAVOLO5)\mathcal{F}_{\text{AVG}(\text{totale})}(\text{TAVOLO5}). Un tavolo senza conti dà NULL.
  • Senza l'attributo derivato totale si ricalcola per ogni comanda, come nella query 2.

Errori tipici

  • Join interno dove servono le pietanze senza modifica.
  • COUNT(*) al posto di sommare qta.
  • Join con MODIFICA senza GROUP BY: righe duplicate.
  • Media su un tavolo senza conti: NULL.

Esercizi su questo argomento

Teoria collegata