Salta al contenuto
Note per Studenti Esercizio - Interrogazioni sulla piattaforma di streaming musicale (tema d'esame 12 febbraio 2024)

Esercizio - Interrogazioni sulla piattaforma di streaming musicale (tema d'esame 12 febbraio 2024)

Esame
In questa pagina 7

Testo (esame di Basi di Dati del 12 febbraio 2024, II e III parte, dal corso di Basi di Dati, Ing. Informatica UniPD). Degli studenti vogliono progettare una piattaforma per lo streaming di brani musicali. Requisiti:

  • ogni utente è iscritto con un nome utente e una password, e può, se vuole, inserire l'anno di nascita e i generi musicali preferiti;
  • ogni brano è identificato da un codice, ha un titolo, una durata e un artista di riferimento;
  • ogni artista è identificato da un codice, ha un nome, e può essere un gruppo o un artista singolo. Per un gruppo si vuole l'anno di formazione e il cantante leader (sempre definito per un gruppo, ed è un artista singolo; le formazioni non cambiano nel tempo); per un artista singolo nome, cognome, anno di nascita e sesso;
  • un brano appartiene a uno o più generi (identificati dal nome, con una descrizione); un genere deve avere almeno un brano;
  • per le canzoni ascoltate almeno una volta si vuole sapere quante volte in totale sono state riprodotte dallo stesso utente.

Interrogazioni, in SQL e in algebra relazionale (senza aggiungere attributi ridondanti):

  1. per ciascun brano, il numero totale di ascolti;
  2. la band musicale del genere "rock" ascoltata dal maggior numero di utenti (non il maggior numero di ascolti);
  3. i cantanti leader dei gruppi musicali "pop" dei brani ascoltati dagli utenti nati dopo il 2000.

(Domanda 2, per la lode: quali modifiche allo schema ER e relazionale rendono più efficiente almeno una delle tre query.)

La progettazione ER è 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 →, 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 →, 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 dello streaming di musica (tema d'esame 12 febbraio 2024).

Schema relazionale (dalla soluzione del corso)

Chiavi in grassetto. Nel nome delle tabelle di relazione si usano quelli della soluzione (R1,R2,R3R_1,R_2,R_3) con il significato indicato.

Relazione Attributi Note
UTENTE username, password, anno_nascita anno_nascita opzionale (può essere NULL)
GENERE nome, descrizione
R1R_1 utente →\to UTENTE, genere →\to GENERE generi preferiti (N-N)
R2R_2 genere →\to GENERE, brano →\to BRANO appartenenza brano–genere (N-N); vincolo: ogni brano e ogni genere compaiono almeno una volta
BRANO codice, titolo, durata, artista →\to ARTISTA un solo artista per brano (1-N)
R3R_3 utente →\to UTENTE, brano →\to BRANO, numero numero = quante volte l'utente ha ascoltato il brano (attributo della relazione N-N)
ARTISTA codice, tipo, nome, anno_formazione, leader →\to ARTISTA, cognome_c, anno_nascita_c, sesso gerarchia gruppo/cantante assorbita nel genitore: per un gruppo valgono anno_formazione e leader; per un cantante valgono cognome_c, anno_nascita_c e sesso (gli altri NULL); leader punta a un cantante (autoriferimento)

Il suffisso _c distingue gli attributi del cantante da quelli omonimi (anno_nascita di UTENTE): nella soluzione del corso il nome del cantante riusa nome e il cognome diventa cognome_c. Vincoli esterni: se tipo = 'gruppo' allora anno_formazione e leader non sono NULL; leader deve essere un cantante.

Dati inventati. Utenti con anno: u1 1995, u2 2001, u3 2003, u4 2005, u5 NULL. Generi: rock, pop. Cantanti (codice, nome, cognome): 11 Luca Neri, 12 Anna Verdi, 13 Piero Bianchi, 14 Sara Gialli, 15 Elio Rossi. Gruppi (codice, nome, leader): 1 I Fulmini (11), 2 Le Onde (12), 3 Nebbia (13), 4 Alba (14). Brani (codice, titolo, artista): 1 Tuono (1), 2 Lampo (1), 3 Foschia (3), 4 Marea (2), 5 Aurora (4), 6 Solo io (15), 7 Ponte (2), 8 Inascoltato (1), 9 Bruma (3), 10 Foschia 2 (3). R2R_2: rock: 1, 2, 3, 7, 8, 9, 10; pop: 4, 5, 6, 7. R3R_3 (utente, brano, numero): u1: (1,3), (2,2); u2: (1,1), (3,4), (4,2), (9,2), (10,1); u3: (3,1), (5,5), (7,1), (9,1), (10,2); u4: (4,1), (6,3); u5: (2,1).


Query 1: il numero totale di ascolti di ciascun brano

Idea. Il numero di ascolti di un brano è la somma, sugli utenti, di numero in R3R_3: un raggruppamento per brano.

Algebra. branoFSUM(numero)(R3)_{\text{brano}}\mathcal{F}_{\text{SUM}(\text{numero})}(R_3) (grado 2, una tupla per brano ascoltato).

SQL.

sql
SELECT brano, SUM(numero) AS totale
FROM r3
GROUP BY brano;

Risultato: brano 1: 4; 2: 3; 3: 5; 4: 3; 5: 5; 6: 3; 7: 1; 9: 3; 10: 3. Il brano 8 (mai ascoltato) non compare: con la soluzione del corso si ottengono solo i brani con almeno un ascolto. Se si vuole ogni brano, anche con totale 0, si parte da BRANO con un left join:

sql
SELECT b.codice, b.titolo, COALESCE(SUM(r.numero), 0) AS totale
FROM brano AS b LEFT JOIN r3 AS r ON r.brano = b.codice
GROUP BY b.codice, b.titolo;

(8 compare con 0; COALESCE trasforma il NULL dell'aggregato su un gruppo vuoto in 0.) Nota: il brano è chiamato brano in R3R_3 e codice in BRANO; nella soluzione del corso la query scrive codice per l'attributo di R3R_3, un'incoerenza di nomi.

Query 2: la band rock ascoltata dal maggior numero di utenti

Idea. Si passa da rock →\to brani rock →\to band autrici →\to ascolti, poi si conta per band e si prende il massimo. Il testo precisa "il maggior numero di utenti (non il maggior numero di ascolti)": si contano gli utenti distinti per band.

Soluzione del corso (algebra).

GEN_BRANO←ρcod_brano←codice(σgenere=′Rock′(R2)⋈brano=codiceBRANO)\text{GEN\_BRANO}\leftarrow\rho_{\text{cod\_brano}\leftarrow\text{codice}}\big(\sigma_{\text{genere}='\text{Rock}'}(R_2)\bowtie_{\text{brano}=\text{codice}}\text{BRANO}\big) ART_GEN_BRANO←ρart_code←codice(GEN_BRANO⋈codice_artista=codiceσtipo=′band′(ARTISTA))\text{ART\_GEN\_BRANO}\leftarrow\rho_{\text{art\_code}\leftarrow\text{codice}}\big(\text{GEN\_BRANO}\bowtie_{\text{codice\_artista}=\text{codice}}\sigma_{\text{tipo}='\text{band}'}(\text{ARTISTA})\big) ASCOLTI←ρartista,totale(art_codeFCOUNT(∗)(ART_GEN_BRANO⋈cod_brano=codiceR3))\text{ASCOLTI}\leftarrow\rho_{\text{artista},\text{totale}}\big({}_{\text{art\_code}}\mathcal{F}_{\text{COUNT}(*)}(\text{ART\_GEN\_BRANO}\bowtie_{\text{cod\_brano}=\text{codice}}R_3)\big) MAXA←FMAX(totale)(ASCOLTI)σtotale=MAXA(ASCOLTI)\text{MAXA}\leftarrow\mathcal{F}_{\text{MAX}(\text{totale})}(\text{ASCOLTI})\qquad\sigma_{\text{totale}=\text{MAXA}}(\text{ASCOLTI})

sql
CREATE VIEW Ascolti AS
SELECT a.codice AS artista, COUNT(*) AS totale
FROM R2 AS r INNER JOIN BRANO AS b ON r.brano = b.codice
     INNER JOIN ARTISTA AS a ON b.codice_artista = a.codice
     INNER JOIN R3 AS r3 ON b.codice = r3.brano
WHERE genere = 'Rock' AND tipo = 'band'
GROUP BY a.codice;

SELECT artista, totale FROM Ascolti WHERE totale = (SELECT MAX(totale) FROM Ascolti);

Errore nella soluzione del corso. COUNT(*) conta le righe del join, cioè le coppie (utente, brano) di R3R_3: un utente che ha ascoltato due brani della stessa band viene contato due volte. Il testo chiede il numero di utenti, non di ascolti (e nemmeno di coppie). Sui dati: I Fulmini (1) ha 3 utenti (u1u_1, u2u_2, u5u_5) ma 4 coppie; Nebbia (3) ha solo 2 utenti (u2u_2, u3u_3) ma 6 coppie (ciascuno ascolta 3 brani della band). La query del corso restituisce Nebbia (6), quella corretta I Fulmini (3). Lo stesso errore è nell'algebra: FCOUNT(∗)\mathcal{F}_{\text{COUNT}(*)} dopo il join conta le tuple del join.

Soluzione corretta (SQL). Si contano gli utenti distinti per band:

sql
SELECT a.codice, a.nome, COUNT(DISTINCT r3.utente) AS utenti
FROM r2 AS r
     INNER JOIN brano AS b ON r.brano = b.codice
     INNER JOIN artista AS a ON b.artista = a.codice
     INNER JOIN r3 ON b.codice = r3.brano
WHERE r.genere = 'rock' AND a.tipo = 'gruppo'
GROUP BY a.codice, a.nome
HAVING COUNT(DISTINCT r3.utente) = (
  SELECT MAX(n) FROM (
    SELECT COUNT(DISTINCT r3.utente) AS n
    FROM r2 AS r INNER JOIN brano AS b ON r.brano = b.codice
         INNER JOIN artista AS a ON b.artista = a.codice
         INNER JOIN r3 ON b.codice = r3.brano
    WHERE r.genere = 'rock' AND a.tipo = 'gruppo'
    GROUP BY a.codice) AS t);

(Più leggibile con una vista come nella soluzione del corso, sostituendo COUNT(*) con COUNT(DISTINCT r3.utente).) Risultato: I Fulmini, 3 utenti. Le Onde ha 1 utente (via il brano 7, che è sia pop sia rock), Nebbia 2.

Algebra corretta. In algebra si ottengono i distinti con una proiezione (che elimina i duplicati) sulle coppie (band, utente) prima di contare:

AU←πart_code,nome,utente(ART_GEN_BRANO⋈cod_brano=branoR3)\text{AU}\leftarrow\pi_{\text{art\_code},\text{nome},\text{utente}}\big(\text{ART\_GEN\_BRANO}\bowtie_{\text{cod\_brano}=\text{brano}}R_3\big) U←art_code,nomeFCOUNT(∗)(AU)  (attributo rinominato n)M←FMAX(n)(U)  (rinominato m)πart_code,nome(U⋈n=mM)U\leftarrow{}_{\text{art\_code},\text{nome}}\mathcal{F}_{\text{COUNT}(*)}(\text{AU})\ \ (\text{attributo rinominato }n)\qquad M\leftarrow\mathcal{F}_{\text{MAX}(n)}(U)\ \ (\text{rinominato }m)\qquad\pi_{\text{art\_code},\text{nome}}\big(U\bowtie_{n=m}M\big)

Provato con un interprete di algebra: stesso risultato dell'SQL (I Fulmini).

Query 3: i cantanti leader dei gruppi "pop" dei brani ascoltati da utenti nati dopo il 2000

Idea. Utenti con anno_nascita > 2000 →\to i loro ascolti (R3R_3) →\to i brani pop (R2R_2) →\to l'artista del brano, che deve essere un gruppo →\to il suo leader (un altro artista).

Soluzione del corso (algebra).

BRANI_POP←σanno_nascita>2000(UTENTE)⋈R3⋈(σgenere=′Pop′(R2)⋈brano=codiceBRANO)\text{BRANI\_POP}\leftarrow\sigma_{\text{anno\_nascita}>2000}(\text{UTENTE})\bowtie R_3\bowtie\big(\sigma_{\text{genere}='\text{Pop}'}(R_2)\bowtie_{\text{brano}=\text{codice}}\text{BRANO}\big) πleader(σleader IS NOT NULL(ρart_code←codice(ARTISTA)⋈art_code=codice_artistaBRANI_POP))\pi_{\text{leader}}\Big(\sigma_{\text{leader IS NOT NULL}}\big(\rho_{\text{art\_code}\leftarrow\text{codice}}(\text{ARTISTA})\bowtie_{\text{art\_code}=\text{codice\_artista}}\text{BRANI\_POP}\big)\Big)

La condizione leader IS NOT NULL serve per tenere i gruppi (i cantanti singoli non hanno leader); l'espressione restituisce i codici dei leader.

Imprecisioni della soluzione del corso. (1) La versione SQL è priva di DISTINCT: un leader compare una volta per ogni ascolto di un brano pop del suo gruppo (la proiezione dell'algebra elimina i duplicati, SELECT no): sui dati la query restituisce 4 righe (12, 14, 12, 12) invece di 2 leader. (2) anno_nascita può essere ambiguo se anche ARTISTA ha un attributo con quel nome (qui anno_nascita_c): si qualifica con l'alias. (3) Il risultato è il solo codice del leader: per "i cantanti leader" è meglio un join con ARTISTA per ottenere nome e cognome. (4) anno_nascita > 2000 scarta l'utente con anno NULL (u5), come è giusto.

SQL corretto.

sql
SELECT DISTINCT l.codice, l.nome, l.cognome_c
FROM utente AS u
     INNER JOIN r3 ON u.username = r3.utente
     INNER JOIN r2 ON r3.brano = r2.brano
     INNER JOIN brano AS b ON r2.brano = b.codice
     INNER JOIN artista AS g ON b.artista = g.codice
     INNER JOIN artista AS l ON g.leader = l.codice
WHERE u.anno_nascita > 2000 AND r2.genere = 'pop' AND g.tipo = 'gruppo';

artista compare due volte: g (il gruppo del brano) e l (il suo leader), collegati dall'autoriferimento g.leader = l.codice (è un self-join). Risultato: Anna Verdi (leader di Le Onde, ascoltata da u2u_2, u3u_3, u4u_4 tramite i brani 4 e 7) e Sara Gialli (leader di Alba, ascoltata da u3u_3 tramite il brano 5). Il brano 6 è pop ma di un cantante singolo (Elio Rossi): non è di un gruppo e non produce un leader.

Algebra corretta. Gli stessi passi con la rinomina del secondo uso di ARTISTA e una proiezione finale su codice, nome e cognome del leader (provata: stesso risultato).

Domanda 2 (per la lode): modifiche per rispondere più in fretta

  • Query 1: serve la somma sulle righe di R3R_3 per brano. Un indice su R3(brano)R_3(\text{brano}) (o, poiché la chiave è (utente,brano)(\text{utente},\text{brano}), un indice con brano in prima posizione) rende il raggruppamento una scansione ordinata. In alternativa un attributo derivato totale_ascolti in BRANO, da aggiornare a ogni ascolto con un trigger (letture immediate, ma aggiornamenti più cari e ridondanza).
  • Query 2: serve contare gli utenti distinti per band: un attributo derivato n_ascoltatori in ARTISTA (aggiornato quando un utente ascolta per la prima volta un brano della band) evita tre join e un COUNT(DISTINCT); conviene se la query è frequente e gli ascolti, rispetto alle letture, sono meno frequenti. In alternativa la vista materializzata dei conteggi per band.
  • Query 3: il percorso utente →\to brano →\to genere →\to artista →\to leader è lungo: si può spostare l'attributo leader (o una colonna genere_principale) dove serve, o indicizzare UTENTE(anno_nascita) e R2(genere)R_2(\text{genere}).

In ogni caso va confrontato il guadagno in lettura con il costo in scrittura e con il rischio di incoerenza (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 2: COUNT(*) conta coppie (utente, brano) e non utenti: dà Nebbia (6) invece di I Fulmini (3).
  • Query 3: SQL senza DISTINCT (4 righe invece di 2 leader) e risultato con solo il codice del leader.
  • Query 1: il brano mai ascoltato manca dal risultato (accettabile, ma va dichiarato); incoerenza codice/brano tra le tabelle.

Versione ripasso

Schema: UTENTE(username, password, anno_nascita), GENERE(nome, descrizione), R1R_1(utente, genere), R2R_2(genere, brano), BRANO(codice, titolo, durata, artista), R3R_3(utente, brano, numero), ARTISTA(codice, tipo, nome, anno_formazione, leader, cognome_c, anno_nascita_c, sesso). Il gruppo ha leader che punta a un cantante. 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 - 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 →.

  1. Ascolti per brano. branoFSUM(numero)(R3)_{\text{brano}}\mathcal{F}_{\text{SUM}(\text{numero})}(R_3). Risultato: brano 1: 4; 2: 3; 3: 5; 4: 3; 5: 5; 6: 3; 7: 1; 9: 3; 10: 3. Il brano 8 non è ascoltato e non compare; con un LEFT JOIN da BRANO e COALESCE compare con 0.
sql
SELECT brano, SUM(numero) AS totale FROM r3 GROUP BY brano;
SELECT b.codice, b.titolo, COALESCE(SUM(r.numero), 0) AS totale
FROM brano AS b LEFT JOIN r3 AS r ON r.brano = b.codice GROUP BY b.codice, b.titolo;
  1. Band rock ascoltata da più utenti. Si conta per band il numero di utenti distinti, poi si prende il massimo. Il conteggio va fatto su COUNT(DISTINCT utente): il COUNT(*) del corso conta coppie (utente, brano) e dà Nebbia con 6 invece di 2 utenti. Risultato corretto: I Fulmini, 3 utenti (u1, u2, u5). Le Onde ha 1 utente e Nebbia 2.
sql
SELECT a.codice, a.nome, COUNT(DISTINCT r3.utente) AS utenti
FROM r2 AS r INNER JOIN brano AS b ON r.brano = b.codice
     INNER JOIN artista AS a ON b.artista = a.codice
     INNER JOIN r3 ON b.codice = r3.brano
WHERE r.genere = 'rock' AND a.tipo = 'gruppo'
GROUP BY a.codice, a.nome;
  • Il massimo si ottiene con HAVING e una tabella derivata, come nelle query del corso. In algebra si fa prima la proiezione su (band, utente), che elimina i duplicati, e poi il conteggio.
  1. Leader dei gruppi pop, per brani ascoltati da utenti nati dopo il 2000. Il percorso è utente, R3R_3, R2R_2 con genere pop, brano, gruppo, leader. Il leader è un altro record di ARTISTA, quindi serve un self-join con alias g (gruppo) e l (leader). Risultato: Anna Verdi (leader di Le Onde, ascoltata da u2, u3, u4) e Sara Gialli (leader di Alba, ascoltata da u3). Il brano 6, pop ma di un cantante singolo, non produce leader.
sql
SELECT DISTINCT l.codice, l.nome, l.cognome_c
FROM utente AS u
     INNER JOIN r3 ON u.username = r3.utente
     INNER JOIN r2 ON r3.brano = r2.brano
     INNER JOIN brano AS b ON r2.brano = b.codice
     INNER JOIN artista AS g ON b.artista = g.codice
     INNER JOIN artista AS l ON g.leader = l.codice
WHERE u.anno_nascita > 2000 AND r2.genere = 'pop' AND g.tipo = 'gruppo';
  • Senza DISTINCT il leader compare una volta per ogni ascolto: 4 righe invece di 2. Il corso non lo mette.
  • In algebra la rinomina del secondo ARTISTA (per esempio art_code←codice\text{art\_code}\leftarrow\text{codice}) è necessaria; poi si proietta su codice, nome e cognome del leader, che elimina i duplicati.

Domanda per la lode: indici su R3(brano)R_3(\text{brano}), R2(genere)R_2(\text{genere}) e UTENTE(anno_nascita); oppure attributi derivati come totale_ascolti o n_ascoltatori, aggiornati da un trigger, o una vista materializzata. Lettura più veloce, scrittura più cara, rischio di incoerenza.

Errori tipici

  • Contare coppie invece di utenti distinti.
  • Dimenticare DISTINCT dopo un join con più righe per leader.
  • Confondere il leader (codice) con il suo nome.

Esercizi su questo argomento

Teoria collegata