Salta al contenuto
Note per Studenti Esercizio - Interrogazioni sulla compagnia di treni (tema d'esame 17 giugno 2013)

Esercizio - Interrogazioni sulla compagnia di treni (tema d'esame 17 giugno 2013)

Esame
In questa pagina 6

Testo (compito d'esame del 17 giugno 2013, "Compagnia treni", dal corso di Basi di Dati, Ing. Informatica UniPD). La compagnia Romolo vuole gestire rifornimenti e composizione dei treni:

  • ogni treno (composizione) è formato da una o due motrici (in quest'ultimo caso una in testa e una in coda) e da un certo numero di carrozze;
  • le motrici hanno un motore, una strumentazione di guida e un numero di posizioni di guida; le carrozze hanno un numero di posti, un numero di bagni, una disposizione su uno o due piani; entrambe hanno una lunghezza, un peso e un tipo di attacco;
  • ci sono fornitori (con partita iva) che riforniscono la compagnia di pezzi; ogni pezzo ha un prezzo, diverso a seconda del fornitore; per ogni veicolo si vuole sapere quale fornitore fornisce quale pezzo;
  • per ogni tratta (origine, destinazione) coperta si vuole sapere la composizione utilizzata: non ci sono due treni per la stessa coppia di città.

Interrogazioni, in algebra relazionale e in SQL:

  1. il fornitore che fornisce più pezzi degli altri;
  2. il costo complessivo di ciascuna composizione;
  3. la tratta dei treni che hanno due motrici fornite dal fornitore f1 (almeno un pezzo per ciascuna motrice, non necessariamente tutti).

La progettazione ER è nell'esercizio di progettazione della stessa traccia. Qui si ricava lo schema relazionale e si risolvono le tre query. 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 →, 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 - 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 →.

Progettazione ER e schema relazionale della stessa traccia: Esercizio - Progettazione ER e schema relazionale della compagnia di treni (tema d'esame 17 giugno 2013).

Schema relazionale

Il PDF della soluzione contiene solo lo schema ER e le espressioni di algebra: lo schema relazionale qui sotto è ricavato dal diagramma ER (veicolo con tipo, scelta 2 del corso: genitore che assorbe i figli) con le regole di traduzione di Ristrutturazione dello schema ER e traduzione nel modello relazionaleLa progettazione logica ha due passi. 1) Ristrutturazione dello schema ER, per togliere i costrutti che il modello relazionale non esprime: analisi delle ridondanze (attributi derivati e cicli di relazioni, decisa con volume dei dati e carico, tabelle di frequenza e accessi), eliminazione degli attributi multivalore (nuova entità), composti (appiattiti o nuova entità), delle IS-A (relazione con partecipazione (1,1) dalla sottoclasse) e delle generalizzazioni (opzione 1 relazioni, 2 accorpamento nel padre con attributo discriminante, 3 accorpamento nei figli, solo se completa e disgiunta), scelta degli identificatori principali (niente cicli di identificazione esterna), riscrittura dei vincoli esterni, partizionamento e accorpamento di entità. 2) Traduzione quasi automatica nel modello relazionale: ogni entità diventa una relazione con la chiave primaria data dall'identificatore principale (anche esterno); una relazione molti-a-molti diventa una relazione con le chiavi delle entità; una uno-a-molti con partecipazione obbligatoria si fonde con l'entità dal lato max 1 con una chiave esterna; con partecipazione opzionale si fonde (NULL) o si crea una relazione a parte; le uno-a-uno si fondono sul lato con partecipazione obbligatoria; le ternarie seguono la stessa logica con le chiavi di tutte le entità.Ristrutturazione dello schema ER e traduzione nel modello relazionale →. Chiavi in grassetto.

Relazione Attributi Da dove viene
FORNITORE piva entità
PEZZO codice, descrizione, categoria entità
OFFRE fornitore →\to FORNITORE, pezzo →\to PEZZO, prezzo relazione N-N con attributo: il prezzo dipende dalla coppia
COMPOSIZIONE codice, descrizione, (costo) entità; costo è un attributo derivato, ammesso per la seconda query
VEICOLO id, peso, lunghezza, attacco, tipo, n_guida, n_posti, disposizione, n_bagni, composizione →\to COMPOSIZIONE, testa generalizzazione motrice/carrozza assorbita nel genitore (tipo); gli attributi delle sole motrici (n_guida, testa) o delle sole carrozze (n_posti, disposizione, n_bagni) sono NULL per l'altro tipo; le relazioni 1-N "fa parte di" (0,1)-(1,N) diventano la chiave esterna composizione; testa è l'attributo della relazione con le motrici
VENDE fornitore, pezzo, veicolo →\to VEICOLO; (fornitore,pezzo)→(\text{fornitore},\text{pezzo})\to OFFRE relazione ternaria fornitore–pezzo–veicolo
CITTA id, nome entità
TRATTA origine →\to CITTA, destinazione →\to CITTA, composizione →\to COMPOSIZIONE entità identificata esternamente dalle due città; composizione può essere NULL (tratta non ancora servita)

Vincoli esterni (non esprimibili con chiavi): una composizione ha una o due motrici; la motrice testa è unica; la coppia (fornitore,pezzo)(\text{fornitore},\text{pezzo}) di VENDE deve esistere in OFFRE (altrimenti non si conosce il prezzo).

Dati inventati. Fornitori f1, f2, f3; OFFRE (fornitore, pezzo, prezzo): f1: p1 1000, p2 500, p3 200, p5 100; f2: p1 900, p3 220, p4 300, p5 110, p6 50; f3: p6 60. Composizioni: K1 Padova-Venezia (motrici m1 in testa, m2; carrozze c1, c2), K2 Milano-Venezia (m3, m4; c3, c4), K3 Roma-Napoli (m5; c5), K4 Torino-Milano (m6, m7; c6). TRATTA: Padova-Venezia: K1; Milano-Venezia: K2; Roma-Napoli: K3; Torino-Milano: K4; Venezia-Trieste: nessuna. VENDE (fornitore, pezzo, veicolo): f1/p1/m1, f1/p2/m2, f1/p1/m3, f2/p1/m4, f1/p1/m5, f1/p1/m6, f1/p2/m6, f2/p1/m7, f1/p3/c1, f2/p4/c1, f2/p3/c2, f1/p5/c3, f2/p5/c4, f2/p6/c5, f3/p6/c6, f1/p5/m1, f2/p5/m2.


Query 1: il fornitore che fornisce più pezzi degli altri

Idea. "Fornisce" i pezzi nel senso di OFFRE (sono i pezzi che mette in catalogo): si conta per fornitore e si prende il massimo. È il pattern del massimo: (1) conteggio per gruppo, (2) massimo dei conteggi, (3) join per tenere i gruppi con quel valore.

Algebra.

R1←ρNPezzi←COUNT(∗)(fornitoreFCOUNT(∗)(OFFRE))R_1\leftarrow\rho_{\text{NPezzi}\leftarrow\text{COUNT}(*)}\big({}_{\text{fornitore}}\mathcal{F}_{\text{COUNT}(*)}(\text{OFFRE})\big) MAXF←ρMaxPezzi←MAX(NPezzi)(FMAX(NPezzi)(R1))\text{MAXF}\leftarrow\rho_{\text{MaxPezzi}\leftarrow\text{MAX}(\text{NPezzi})}\big(\mathcal{F}_{\text{MAX}(\text{NPezzi})}(R_1)\big) πfornitore(R1⋈NPezzi=MaxPezziMAXF)\pi_{\text{fornitore}}\big(R_1\bowtie_{\text{NPezzi}=\text{MaxPezzi}}\text{MAXF}\big)

Nella soluzione del corso l'ultimo passo è scritto σNPezzi=MAXF(R1)\sigma_{\text{NPezzi}=\text{MAXF}}(R_1), dove MAXF è una relazione e non un valore: è un'abbreviazione, la forma corretta è il join (o il prodotto seguito dalla selezione), che il corso indica come alternativa valida.

SQL.

sql
SELECT fornitore, COUNT(*) AS npezzi
FROM offre
GROUP BY fornitore
HAVING COUNT(*) = (SELECT MAX(n)
                   FROM (SELECT COUNT(*) AS n FROM offre GROUP BY fornitore) AS t);

Risultato sui dati: f2 con 5 pezzi (f1 ne ha 4, f3 uno). In caso di parità la query restituisce tutti i fornitori a pari merito.

Commenti.

  • Si può leggere "fornisce più pezzi" anche come pezzi realmente venduti per i veicoli (VENDE): con COUNT(DISTINCT pezzo) su VENDE si ottengono f1: 4, f2: 5, f3: 1: stesso risultato qui, ma i due significati possono differire. Occorre dichiarare quello usato.
  • Un fornitore senza offerte (cardinalità (0,N)(0,N) nell'ER) non compare in OFFRE: la query lo ignora, il che va bene per il massimo. Per elencare anche loro, con conteggio 0, si userebbe un LEFT JOIN da FORNITORE e COUNT(o.pezzo).

Query 2: il costo complessivo di ciascuna composizione

Con l'attributo derivato costo (la scelta della soluzione, comoda per questa query):

πcodice,costo(COMPOSIZIONE)SQL: SELECT codice, costo FROM composizione;\pi_{\text{codice},\text{costo}}(\text{COMPOSIZIONE})\qquad\text{SQL: SELECT codice, costo FROM composizione;}

È una semplice proiezione, ma il prezzo da pagare è la ridondanza: costo va ricalcolato (a mano, da un trigger o dall'applicazione) ogni volta che cambiano i veicoli della composizione, i pezzi venduti o i prezzi di OFFRE. Per questo il testo del corso lo marca come "attributo derivato (per Q2)".

Senza l'attributo derivato, il costo si calcola: la composizione contiene i veicoli, per ogni veicolo ci sono le righe di VENDE (quali pezzi, da quale fornitore), e il prezzo di ciascun pezzo è in OFFRE per la coppia (fornitore, pezzo). Il costo è la somma di tutti questi prezzi.

VO←VENDE⋈OFFRE  (su fornitore e pezzo)CV←veicoloFSUM(prezzo)(VO)\text{VO}\leftarrow\text{VENDE}\bowtie\text{OFFRE}\ \ (\text{su fornitore e pezzo})\qquad\text{CV}\leftarrow{}_{\text{veicolo}}\mathcal{F}_{\text{SUM}(\text{prezzo})}(\text{VO}) composizioneFSUM(CV)(ρvid←veicolo(CV)⋈vid=idπid,composizione(VEICOLO))_{\text{composizione}}\mathcal{F}_{\text{SUM}(\text{CV})}\big(\rho_{\text{vid}\leftarrow\text{veicolo}}(\text{CV})\bowtie_{\text{vid}=\text{id}}\pi_{\text{id},\text{composizione}}(\text{VEICOLO})\big)

sql
SELECT cm.codice, cm.descrizione, SUM(o.prezzo) AS costo
FROM composizione AS cm
     INNER JOIN veicolo AS v ON v.composizione = cm.codice
     INNER JOIN vende AS ve ON ve.veicolo = v.id
     INNER JOIN offre AS o ON o.fornitore = ve.fornitore AND o.pezzo = ve.pezzo
GROUP BY cm.codice, cm.descrizione;

Risultato sui dati: K1: 2430, K2: 2110, K3: 1050, K4: 2460. Passaggi di K1: m1 ha p1 di f1 (1000) e p5 di f1 (100), m2 ha p2 di f1 (500) e p5 di f2 (110): 1100 + 610; c1 ha p3 di f1 (200) e p4 di f2 (300): 500; c2 ha p3 di f2: 220; totale 1100+610+500+220=24301100+610+500+220=2430.

La join con OFFRE deve essere sulla coppia (fornitore, pezzo): il prezzo cambia con il fornitore, quindi unire solo per pezzo darebbe un prodotto sbagliato (si sommerebbero i prezzi di tutti i fornitori di quel pezzo). Il costo non comprende il "prezzo" dei veicoli in sé: il testo non lo prevede, conta solo la fornitura dei pezzi.

Query 3: la tratta dei treni con due motrici fornite da f1

Idea. Si cercano le composizioni in cui due motrici distinte hanno almeno un pezzo fornito da f1. "Almeno un pezzo per ciascuna motrice": basta una riga di VENDE per motrice. Il punto delicato è che una motrice può avere più pezzi di f1 (più righe in VENDE): si devono contare le motrici distinte, non le righe.

Algebra (soluzione del corso). Con la scelta del veicolo unico:

VEICOLI←COMPOSIZIONE⋈codice=composizioneVEICOLO\text{VEICOLI}\leftarrow\text{COMPOSIZIONE}\bowtie_{\text{codice}=\text{composizione}}\text{VEICOLO} MOTRICI←σfornitore=′f1′ ∧ tipo=′motrice′(VENDE⋈veicolo=idVEICOLI)\text{MOTRICI}\leftarrow\sigma_{\text{fornitore}='f1'\ \wedge\ \text{tipo}='\text{motrice}'}\big(\text{VENDE}\bowtie_{\text{veicolo}=\text{id}}\text{VEICOLI}\big) MOT←πcomposizione,veicolo,fornitore(MOTRICI)\text{MOT}\leftarrow\pi_{\text{composizione},\text{veicolo},\text{fornitore}}(\text{MOTRICI}) NUMFOR←ρNumF←COUNT(∗)(composizione,fornitoreFCOUNT(∗)(MOT))\text{NUMFOR}\leftarrow\rho_{\text{NumF}\leftarrow\text{COUNT}(*)}\big({}_{\text{composizione},\text{fornitore}}\mathcal{F}_{\text{COUNT}(*)}(\text{MOT})\big) πcomposizione,origine,destinazione(σNumF=2(NUMFOR)⋈TRATTA)\pi_{\text{composizione},\text{origine},\text{destinazione}}\big(\sigma_{\text{NumF}=2}(\text{NUMFOR})\bowtie\text{TRATTA}\big)

(join naturale: l'unico attributo in comune è composizione).

La proiezione che costruisce MOT elimina i duplicati: una motrice con due pezzi di f1 genera due righe in MOTRICI ma una sola in MOT. Perciò COUNT(*) su MOT conta le motrici distinte di f1 per composizione. La condizione NumF=2\text{NumF}=2 scarta sia le composizioni con una sola motrice (non potranno mai arrivare a 2), sia quelle in cui f1 fornisce pezzi a una sola delle due motrici: la soluzione del corso lo nota ("evita di aggiungere attributi derivati extra"). Il risultato è una tratta per composizione, e il join con TRATTA dà origine e destinazione.

SQL.

sql
SELECT t.origine, t.destinazione
FROM tratta AS t
WHERE t.composizione IN (
  SELECT v.composizione
  FROM veicolo AS v INNER JOIN vende AS ve ON ve.veicolo = v.id
  WHERE v.tipo = 'motrice' AND ve.fornitore = 'f1'
  GROUP BY v.composizione
  HAVING COUNT(DISTINCT v.id) = 2);

Risultato sui dati: la sola tratta Padova-Venezia (K1: m1 ha pezzi di f1, m2 anche). K2 ha una sola motrice di f1 (m3, mentre m4 è di f2), K3 una sola motrice in tutto, K4 ha m6 con due pezzi di f1 ma m7 nessuno.

La trappola. Con COUNT(*) al posto di COUNT(DISTINCT v.id) la query restituirebbe Torino-Milano (K4: le due righe di VENDE di m6 sembrano "due motrici") e scarterebbe Padova-Venezia (m1 ha due righe, m2 una: tre). Provato sui dati. La proiezione dell'algebra evita l'errore; in SQL serve DISTINCT dentro l'aggregato.

Un'alternativa senza raggruppamento è un self-join sulle motrici della stessa composizione con a.id < b.id ed EXISTS su VENDE per ciascuna (stesso risultato, provato).

Errori nel materiale del corso (riassunto)

  • Query 1: σNPezzi=MAXF(R1)\sigma_{\text{NPezzi}=\text{MAXF}}(R_1) confronta un attributo con una relazione; vale solo come abbreviazione del join.
  • Query 2: la soluzione presuppone l'attributo derivato costo; la versione senza derivato qui sopra mostra che il costo si ricava da VENDE e OFFRE.
  • Query 3: la traduzione diretta in SQL richiede COUNT(DISTINCT ...); con COUNT(*) si contano le righe di VENDE e non le motrici. Inoltre l'espressione usa tipo = 'motrice', valida solo con la scelta di ristrutturazione 2 (veicolo unico).

Versione ripasso

Schema (ricavato dall'ER, con veicolo unico): FORNITORE(piva), PEZZO(codice, descrizione, categoria), OFFRE(fornitore, pezzo, prezzo), COMPOSIZIONE(codice, descrizione, costo) con costo derivato, VEICOLO(id, peso, lunghezza, attacco, tipo, n_guida, n_posti, disposizione, n_bagni, composizione, testa), VENDE(fornitore, pezzo, veicolo), CITTA(id, nome), TRATTA(origine, destinazione, composizione). 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 →, 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 →.

  1. Fornitore con più pezzi. Si conta in OFFRE per fornitore, si prende il massimo e si fa il join sul massimo: πfornitore(R1⋈NPezzi=MaxPezziMAXF)\pi_{\text{fornitore}}(R_1\bowtie_{\text{NPezzi}=\text{MaxPezzi}}\text{MAXF}). Il σNPezzi=MAXF\sigma_{\text{NPezzi}=\text{MAXF}} del corso è un'abbreviazione del join. Risultato: f2 con 5 pezzi (f1 ne ha 4, f3 uno). In caso di parità restituisce tutti.
sql
SELECT fornitore, COUNT(*) AS npezzi FROM offre GROUP BY fornitore
HAVING COUNT(*) = (SELECT MAX(n) FROM (SELECT COUNT(*) AS n FROM offre GROUP BY fornitore) AS t);
  1. Costo di ogni composizione. Con l'attributo derivato costo è una proiezione, ma il dato va mantenuto a mano. Senza, si fa il join di COMPOSIZIONE, VEICOLO, VENDE e OFFRE sulla coppia (fornitore, pezzo) e si somma il prezzo per composizione. Risultato: K1 2430, K2 2110, K3 1050, K4 2460. Conto di K1: m1 1000+1001000+100, m2 500+110500+110, c1 200+300200+300, c2 220220; totale 24302430.
sql
SELECT cm.codice, cm.descrizione, SUM(o.prezzo) AS costo
FROM composizione AS cm
     INNER JOIN veicolo AS v ON v.composizione = cm.codice
     INNER JOIN vende AS ve ON ve.veicolo = v.id
     INNER JOIN offre AS o ON o.fornitore = ve.fornitore AND o.pezzo = ve.pezzo
GROUP BY cm.codice, cm.descrizione;
  1. Tratta con due motrici fornite da f1. Si proietta su (composizione, veicolo, fornitore), eliminando i duplicati, poi si conta: NUMFOR←composizione,fornitoreFCOUNT(∗)(MOT)\text{NUMFOR}\leftarrow{}_{\text{composizione},\text{fornitore}}\mathcal{F}_{\text{COUNT}(*)}(\text{MOT}), con selezione NumF=2\text{NumF}=2 e join naturale con TRATTA. Risultato: Padova-Venezia (K1).
sql
SELECT t.origine, t.destinazione FROM tratta AS t
WHERE t.composizione IN (SELECT v.composizione FROM veicolo AS v
     INNER JOIN vende AS ve ON ve.veicolo = v.id
     WHERE v.tipo = 'motrice' AND ve.fornitore = 'f1'
     GROUP BY v.composizione HAVING COUNT(DISTINCT v.id) = 2);
  • Trappola: con COUNT(*) si contano le righe di VENDE. Una motrice con due pezzi di f1 vale due, e la query dà Torino-Milano (K4, m6 con due pezzi, m7 nessuno) invece di Padova-Venezia.

Errori tipici

  • Join con OFFRE solo su pezzo: si sommano i prezzi di tutti i fornitori.
  • COUNT(*) al posto di COUNT(DISTINCT).
  • Dimenticare di mantenere l'attributo derivato costo.
  • Confrontare un attributo con una relazione, come nel σ\sigma del corso.

Esercizi su questo argomento

Teoria collegata