Salta al contenuto
Note per Studenti Esercizio - Interrogazioni sullo stabilimento balneare (tema d'esame 18 giugno 2012)

Esercizio - Interrogazioni sullo stabilimento balneare (tema d'esame 18 giugno 2012)

Esame
In questa pagina 6

Testo (compito d'esame del 18 giugno 2012, "Stabilimento balneare", dal corso di Basi di Dati, Ing. Informatica UniPD). Lo stabilimento "Bella Napoli" vuole informatizzare la prenotazione dei posti ombrellone, a partire dalle stagioni estive dell'anno corrente:

  • ogni ombrellone è identificato da un numero (anche zero) e ha zero, uno o due lettini (che hanno un numero identificativo); lo stabilimento dispone, ogni stagione, di un certo numero di posti ombrellone che può variare da una stagione all'altra;
  • chi vuole un ombrellone deve lasciare gli estremi di un documento (basta uno in caso di due persone per lo stesso ombrellone); il prezzo di un ombrellone è di 5 euro all'ora; si possono prendere in prestito teli da mare a 3 euro a telo (i prezzi possono cambiare da una stagione all'altra); i teli da mare non sono molti, sicuramente meno dei lettini; ogni stagione ha un numero massimo di teli;
  • per ogni persona che prende un ombrellone si registra l'orario di entrata e quello di uscita; si può prenotare lo stesso ombrellone più volte nell'arco di una giornata.

Interrogazioni da scrivere in algebra relazionale e in SQL:

  1. calcolare quanti lettini sono stati utilizzati il 18 giugno 2012;
  2. controllare che, ad una certa ora del giorno, il numero di teli da mare prestati non sia superiore al massimo a disposizione;
  3. calcolare l'età media delle persone che hanno prenotato un ombrellone.

Qui si riporta lo schema relazionale della soluzione del corso e si risolvono le tre query; la progettazione ER (ombrellone identificato esternamente dalla stagione, prenotazione, lettini) è nell'esercizio di progettazione della stessa traccia. 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 →, 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 →.

Progettazione ER e schema relazionale della stessa traccia: Esercizio - Progettazione ER e schema relazionale dello stabilimento balneare (tema d'esame 18 giugno 2012).

Schema relazionale (dalla soluzione del corso)

Chiavi in grassetto.

Relazione Attributi Note
STAGIONE anno, n_teli, prezzo_ombrellone, prezzo_telo i prezzi e il massimo di teli sono per stagione
OMBRELLONE numero, stagione →\to STAGIONE, prenotato identificazione esterna: il numero vale dentro la stagione; prenotato diventa vero all'ingresso e falso all'uscita
CLIENTE documento, data_nascita
PRENOTAZIONE numero, n_teli, ingresso, uscita, (ombrellone,stagione)→(\text{ombrellone},\text{stagione})\to OMBRELLONE, cliente →\to CLIENTE uscita è NULL finché la persona è presente; ingresso e uscita sono data e ora
LETTINO numero
COLLOCATO prenotazione →\to PRENOTAZIONE, lettino →\to LETTINO un lettino è associato a una prenotazione solo se non c'è conflitto temporale

Vincoli esterni: ogni prenotazione ha al più 2 lettini; un lettino non può essere associato a due prenotazioni sovrapposte nel tempo; il numero di teli in uso non supera STAGIONE.n_teli.

Dati inventati. Stagione 2012: 5 teli. Ombrelloni 1-4. Clienti: A (nato 1990-05-10), B (1985-12-01), C (2000-02-29), E (1970-01-01, mai prenotato). Prenotazioni (numero; cliente; ombrellone; ingresso; uscita; teli): 1; A; 1; 2012-06-18 09:00; 13:00; 2 — 2; B; 1; 2012-06-18 14:00; (presente); 1 — 3; C; 2; 2012-06-18 10:00; (presente); 2 — 4; A; 3; 2012-06-17 10:00; 18:00; 1. COLLOCATO: (1,1), (1,2), (2,1), (3,3), (3,4), (4,5). L'ombrellone 1 è prenotato due volte nella stessa giornata (prenotazioni 1 e 2) e il lettino 1 è riusato.


Query 1: quanti lettini sono stati utilizzati il 18 giugno 2012

Idea. Le prenotazioni di quel giorno, collegate ai lettini tramite COLLOCATO, e un conteggio.

Soluzione del corso (algebra).

FCOUNT(∗)(COLLOCATO⋈prenotazione=numeroσingresso=2012/06/18(PRENOTAZIONE))\mathcal{F}_{\text{COUNT}(*)}\Big(\text{COLLOCATO}\bowtie_{\text{prenotazione}=\text{numero}}\sigma_{\text{ingresso}=2012/06/18}(\text{PRENOTAZIONE})\Big)

Due imprecisioni.

  1. ingresso è un timestamp (data e ora): ingresso = '2012-06-18' confronta un'ora precisa con una data e non trova nulla (provato: 0 righe). Si usa la parte data: DATE(ingresso) = '2012-06-18' (oppure un intervallo ≥\ge giorno e << giorno dopo, che permette di usare un indice).
  2. COUNT(*) conta le coppie (prenotazione, lettino), non i lettini: il lettino 1, usato nella prenotazione 1 al mattino e nella 2 nel pomeriggio, è contato due volte. "Quanti lettini sono stati utilizzati" chiede i lettini distinti.

SQL.

sql
SELECT COUNT(*)                      AS assegnazioni,
       COUNT(DISTINCT co.lettino)    AS lettini_usati
FROM collocato AS co INNER JOIN prenotazione AS p ON co.prenotazione = p.numero
WHERE DATE(p.ingresso) = '2012-06-18';

(DATE() funziona in SQLite e in PostgreSQL con una lieve variante, p.ingresso::date.) Risultato: 5 assegnazioni, 4 lettini distinti (1, 2, 3, 4). Il lettino 5 è della prenotazione del 17 giugno.

Algebra con i distinti. L'algebra non ha COUNT(DISTINCT ...): si proietta prima, e la proiezione elimina i duplicati.

FCOUNT(∗)(πlettino(COLLOCATO⋈prenotazione=numeroσDATE(ingresso)=2012/06/18(PRENOTAZIONE)))=4.\mathcal{F}_{\text{COUNT}(*)}\Big(\pi_{\text{lettino}}\big(\text{COLLOCATO}\bowtie_{\text{prenotazione}=\text{numero}}\sigma_{\text{DATE}(\text{ingresso})=2012/06/18}(\text{PRENOTAZIONE})\big)\Big)=4.

(Provato con un interprete di algebra: 5 senza la proiezione, 4 con.)

Query 2: controllare i teli da mare in uso a una certa ora

Idea. I teli in uso all'istante tt sono quelli delle prenotazioni già entrate (ingresso≤t\text{ingresso}\le t) e non ancora uscite (uscita\text{uscita} NULL oppure >t>t). Si somma n_teli e si confronta con STAGIONE.n_teli.

Soluzione del corso (algebra).

TELI←πnteli(σingresso=2012/06/18(PRENOTAZIONE)⋈stagione=annoSTAGIONE)TELIUSATI←FSUM(nteli)(σuscita IS NULL(PRENOTAZIONE))\text{TELI}\leftarrow\pi_{\text{nteli}}\big(\sigma_{\text{ingresso}=2012/06/18}(\text{PRENOTAZIONE})\bowtie_{\text{stagione}=\text{anno}}\text{STAGIONE}\big)\qquad\text{TELIUSATI}\leftarrow\mathcal{F}_{\text{SUM}(\text{nteli})}\big(\sigma_{\text{uscita IS NULL}}(\text{PRENOTAZIONE})\big) TELI−TELIUSATI\text{TELI}-\text{TELIUSATI}

con la nota "ricordarsi di applicare ρnteli←SUM(nteli)\rho_{\text{nteli}\leftarrow\text{SUM}(\text{nteli})}".

Errori della soluzione del corso.

  1. L'ultima operazione è una differenza insiemistica tra relazioni (TELI −- TELIUSATI), non una sottrazione tra numeri: toglie dalle tuple di TELI quelle uguali a TELIUSATI, e non calcola i teli disponibili. Per sottrarre si usa una proiezione generalizzata che calcola massimo−usati\text{massimo}-\text{usati} su una sola tupla ottenuta con un join o prodotto.
  2. PRENOTAZIONE e STAGIONE hanno entrambe un attributo n_teli: nel join vanno rinominati (il massimo ≠\ne il numero di teli di una prenotazione).
  3. uscita IS NULL rappresenta solo "adesso", cioè le persone ancora presenti: non risponde a "ad una certa ora del giorno" per un istante passato (una prenotazione già uscita alle 13 conta se tt è le 12).

Algebra corretta. Sia φt≡ingresso≤t ∧ (uscita IS NULL ∨ uscita>t)\varphi_t\equiv\text{ingresso}\le t\ \wedge\ (\text{uscita IS NULL}\ \vee\ \text{uscita}>t).

USATI←ρusati←SUM(n_teli)(stagioneFSUM(n_teli)(σφt(PRENOTAZIONE)))\text{USATI}\leftarrow\rho_{\text{usati}\leftarrow\text{SUM}(\text{n\_teli})}\Big({}_{\text{stagione}}\mathcal{F}_{\text{SUM}(\text{n\_teli})}\big(\sigma_{\varphi_t}(\text{PRENOTAZIONE})\big)\Big) DISP←πanno, massimo, usati, massimo−usati(ρmassimo←n_teli(σanno=2012(STAGIONE))⋈anno=stagioneUSATI)\text{DISP}\leftarrow\pi_{\text{anno},\ \text{massimo},\ \text{usati},\ \text{massimo}-\text{usati}}\Big(\rho_{\text{massimo}\leftarrow\text{n\_teli}}(\sigma_{\text{anno}=2012}(\text{STAGIONE}))\bowtie_{\text{anno}=\text{stagione}}\text{USATI}\Big)

(Se nessuna prenotazione è attiva, USATI è vuota e il join perde la stagione: con un left outer join si ha usati=NULL\text{usati}=\text{NULL} da leggere come 0.) Il controllo chiede che l'ultima colonna sia ≥0\ge0.

SQL.

sql
SELECT s.anno, s.n_teli AS massimo, COALESCE(SUM(p.n_teli), 0) AS usati,
       s.n_teli - COALESCE(SUM(p.n_teli), 0) AS disponibili
FROM stagione AS s
     LEFT JOIN prenotazione AS p
       ON p.stagione = s.anno AND p.ingresso <= '2012-06-18 12:00'
          AND (p.uscita IS NULL OR p.uscita > '2012-06-18 12:00')
WHERE s.anno = 2012
GROUP BY s.anno, s.n_teli;

Il LEFT JOIN con le condizioni nella ON (e non nella WHERE) tiene la stagione anche se nessuna prenotazione è attiva. Risultati sui dati: alle 12:00 sono attive le prenotazioni 1 (2 teli) e 3 (2 teli): 4 usati, 1 disponibile; alle 14:30 la 1 è uscita ma è entrata la 2: usano la 2 (1 telo) e la 3 (2 teli): 3 usati, 2 disponibili; alle 08:00 nessuna: 0 usati, 5 disponibili. Il valore della soluzione del corso (uscita IS NULL) darebbe 2 disponibili alle 12:00, sbagliato.

Come vincolo: un trigger. Più utile di un controllo a richiesta è impedire la violazione. In SQLite (e analogamente con BEFORE INSERT e RAISE EXCEPTION in PostgreSQL, 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 →):

sql
CREATE TRIGGER controlla_teli BEFORE INSERT ON prenotazione
FOR EACH ROW
BEGIN
  SELECT RAISE(ABORT, 'teli da mare insufficienti')
  WHERE COALESCE((SELECT SUM(p.n_teli) FROM prenotazione p
                  WHERE p.stagione = NEW.stagione AND p.ingresso <= NEW.ingresso
                    AND (p.uscita IS NULL OR p.uscita > NEW.ingresso)), 0) + NEW.n_teli
        > (SELECT n_teli FROM stagione WHERE anno = NEW.stagione);
END;

Provato: alle 15:00, con 3 teli già in uso (prenotazioni 2 e 3) e massimo 5, la richiesta di una nuova prenotazione con 3 teli è rifiutata, con 2 teli è accettata, e una successiva richiesta di 1 telo alle 15:10 è rifiutata (ormai 5 su 5). Il trigger controlla all'istante di ingresso; il conteggio andrebbe rifatto anche quando si modificano ingressi, uscite o teli (trigger anche su UPDATE).

Query 3: l'età media delle persone che hanno prenotato un ombrellone

Soluzione del corso. Si assume una funzione AGE(data_nascita)∈N\text{AGE}(\text{data\_nascita})\in\mathbb{N} che calcola l'età in anni:

FAVG(AGE(data_nascita))(CLIENTE)\mathcal{F}_{\text{AVG}(\text{AGE}(\text{data\_nascita}))}(\text{CLIENTE})

senza aggiungere un attributo età (sarebbe un attributo derivato che invecchia ogni giorno: la data di nascita basta).

Due precisazioni. (1) Ogni cliente ha almeno una prenotazione (cardinalità (1,N)(1,N) nell'ER): la soluzione del corso mediava su tutta CLIENTE. Se esistono clienti senza prenotazioni (come E nei dati) vanno esclusi: CLIENTE⋈πcliente(PRENOTAZIONE)\text{CLIENTE}\bowtie\pi_{\text{cliente}}(\text{PRENOTAZIONE}). (2) "Le persone" si può leggere come persone distinte (ciascuna conta una volta, anche se prenota più volte) oppure come prenotazioni.

SQL. Età alla data di riferimento 2012-06-18 (in PostgreSQL: EXTRACT(YEAR FROM AGE(DATE '2012-06-18', data_nascita)); in SQLite non c'è AGE, si ricostruisce):

sql
-- persone distinte che hanno prenotato
SELECT AVG(eta) FROM (
  SELECT cl.documento,
         CAST(strftime('%Y','2012-06-18') AS INT) - CAST(strftime('%Y', cl.data_nascita) AS INT)
         - (strftime('%m-%d','2012-06-18') < strftime('%m-%d', cl.data_nascita)) AS eta
  FROM cliente AS cl
  WHERE cl.documento IN (SELECT cliente FROM prenotazione)) AS t;

Età: A 22, B 26, C 12 (E ne ha 42 ma non ha prenotato). Media sulle persone distinte che hanno prenotato: (22+26+12)/3=20(22+26+12)/3=\mathbf{20}. Se invece si media per prenotazione (A ha due prenotazioni): (22+26+12+22)/4=20,5(22+26+12+22)/4=\mathbf{20{,}5}. Includendo anche E, che non ha prenotato: 25,525{,}5 (errore: media su un insieme diverso). Il compito di norma intende la prima lettura; basta dichiararla.

Errori nel materiale del corso (riassunto)

  • Query 1: uguaglianza tra timestamp e data (non trova nulla) e COUNT(*) al posto dei lettini distinti.
  • Query 2: differenza insiemistica al posto di una sottrazione numerica; attributo n_teli omonimo non rinominato; uscita IS NULL risponde solo a "adesso", non a "ad una certa ora".
  • Query 3: media su tutti i clienti (va bene solo se tutti hanno prenotato).

Versione ripasso

Schema (soluzione del corso): STAGIONE(anno, n_teli, prezzo_ombrellone, prezzo_telo), OMBRELLONE(numero, stagione, prenotato), CLIENTE(documento, data_nascita), PRENOTAZIONE(numero, n_teli, ingresso, uscita, ombrellone, stagione, cliente), LETTINO(numero), COLLOCATO(prenotazione, lettino). 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 →, 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 →.

  1. Lettini usati il 18/6/2012. ingresso è un timestamp: il confronto con la data va fatto sulla parte data, DATE(ingresso) = '2012-06-18', non con ingresso = '2012-06-18', che dà zero righe. Il COUNT(*) conta le coppie (prenotazione, lettino) e il lettino 1 è riusato, quindi servono i distinti: 4 lettini, contro 5 assegnazioni. In algebra si proietta su lettino e poi si conta.
sql
SELECT COUNT(DISTINCT co.lettino) AS lettini_usati
FROM collocato AS co INNER JOIN prenotazione AS p ON co.prenotazione = p.numero
WHERE DATE(p.ingresso) = '2012-06-18';
  1. Teli in uso a un'ora tt. Sono in uso le prenotazioni con ingresso≤t\text{ingresso}\le t e con uscita\text{uscita} NULL oppure >t>t. Il confronto è con STAGIONE.n_teli. Il corso usa uscita IS NULL, che vale solo per "adesso"; la differenza va fatta tra numeri, non tra relazioni. Con il LEFT JOIN e le condizioni in ON, la stagione compare anche se non ci sono prenotazioni attive. Alle 12:00 sono attivi 4 teli, quindi 1 disponibile; alle 14:30 sono 3, quindi 2 disponibili.
sql
SELECT s.anno, s.n_teli AS massimo, COALESCE(SUM(p.n_teli), 0) AS usati,
       s.n_teli - COALESCE(SUM(p.n_teli), 0) AS disponibili
FROM stagione AS s
     LEFT JOIN prenotazione AS p
       ON p.stagione = s.anno AND p.ingresso <= '2012-06-18 12:00'
          AND (p.uscita IS NULL OR p.uscita > '2012-06-18 12:00')
WHERE s.anno = 2012
GROUP BY s.anno, s.n_teli;
  • Come vincolo è meglio un trigger BEFORE INSERT con RAISE(ABORT, ...): provato, con 3 teli già in uso su 5, una richiesta di 3 teli è rifiutata e una di 2 è accettata.
  1. Età media dei clienti che hanno prenotato. Si assume una funzione AGE(data_nascita)\text{AGE}(\text{data\_nascita}) e si media su CLIENTE, senza memorizzare l'età, che invecchia. Va però considerato solo chi ha prenotato, quindi CLIENTE⋈πcliente(PRENOTAZIONE)\text{CLIENTE}\bowtie\pi_{\text{cliente}}(\text{PRENOTAZIONE}). Con persone distinte: (22+26+12)/3=20(22+26+12)/3=\mathbf{20}; per prenotazione: 20,520{,}5.

Errori del corso

  • Query 1: confronto tra timestamp e data, e COUNT(*) al posto dei lettini distinti.
  • Query 2: differenza insiemistica TELI−TELIUSATI\text{TELI}-\text{TELIUSATI} invece di una sottrazione; n_teli omonimo non rinominato; uscita IS NULL risponde solo a "adesso".
  • Query 3: media su tutti i clienti, anche quelli senza prenotazioni.

Errori tipici

  • Confrontare un timestamp con una data.
  • Contare assegnazioni al posto di oggetti distinti.
  • Sottrarre relazioni invece di numeri.
  • Fare la media su un insieme diverso da quello richiesto.

Esercizi su questo argomento

Teoria collegata