SQL
Indice
1 Il Linguaggio SQL: Panoramica 3
2 Comandi DML: Inserire, Modificare e Cancellare Dati 3
3 Il Comando SELECT e le sue Clausole 6
4 La JOIN: Combinare Dati da Più Tabelle 8
5 Concetti Fondamentali e Sintassi Avanzata 12
6 Errori Comuni 20
7 Ottimizzazione e Performance delle Query 33
2
1. Il Linguaggio SQL: Panoramica
SQL (Structured Query Language) è il linguaggio standard per comunicare con i database
relazionali. È importante sapere che SQL non è un unico insieme di comandi, ma si divide in
sotto-linguaggi con scopi distinti:
• DDL — Data Definition Language: Comandi per creare e modificare la struttura del
database (tabelle, indici, viste). I comandi principali sono CREATE, ALTER e DROP.
• DML — Data Manipulation Language: Comandi per inserire, aggiornare e cancellare i
dati nelle tabelle. I comandi principali sono INSERT, UPDATE e DELETE.
• DQL — Data Query Language: Il comando SELECT, usato per interrogare il database
e ottenere informazioni.
• DCL — Data Control Language: Comandi per gestire i permessi degli utenti (GRANT,
REVOKE).
Nota pratica: In molti testi si parla genericamente di “DML” includendo anche il
SELECT. Nella pratica professionale, il confine è quello sopra descritto: DML modifica i
dati, DQL li legge.
2. Comandi DML: Inserire, Modificare e Cancellare Dati
I comandi DML sono le operazioni che “scrivono” sul database. A differenza del SELECT,
che lascia i dati intatti, questi comandi modificano permanentemente il contenuto delle tabelle
(salvo che non si stia lavorando all’interno di una transazione non ancora confermata).
1. INSERT — Inserire nuovi dati
Il comando INSERT INTO aggiunge una o più righe a una tabella esistente.
Sintassi base: inserire una riga
INSERT INTO NomeTabella (Colonna1, Colonna2, Colonna3)
VALUES (Valore1, Valore2, Valore3);
Esempio concreto: inserire un nuovo studente nella tabella STUDENTE.
INSERT INTO Studente (Matricola, Nome, Cognome, Indirizzo, C i t t )
VALUES (’S099’, ’Mario’, ’Rossi’, ’Via Roma 1’, ’Milano’);
Regole da rispettare:
• Ordine delle colonne: I valori in VALUES devono corrispondere nell’ordine esatto alle
colonne elencate dopo il nome della tabella.
• Tipi di dato: I valori stringa vanno racchiusi tra apici singoli (’Milano’), i numeri no (30).
• Colonne omesse: Le colonne non elencate riceveranno il valore NULL (o il valore di
DEFAULT se definito nello schema), purché non abbiano il vincolo NOT NULL.
• Chiavi primarie: Non è possibile inserire una riga con una chiave primaria già esistente
(violazione del vincolo di unicità).
3
Inserire più righe in un solo comando
La maggior parte dei DBMS moderni permette di inserire più righe in un unico INSERT:
INSERT INTO Studente (Matricola, Nome, Cognome, C i t t )
VALUES
(’S100’, ’Luigi’, ’Verdi’, ’Roma’),
(’S101’, ’Anna’, ’Bianchi’,’Napoli’),
(’S102’, ’Carla’, ’Neri’, ’Milano’);
INSERT ... SELECT: inserire dati da un’altra tabella
È possibile usare il risultato di una SELECT come sorgente per l’inserimento. Questo è
particolarmente utile per copiare o archiviare dati.
-- Copia gli studenti di Roma in una tabella di archivio
INSERT INTO StudentiRoma (Matricola, Nome, Cognome)
SELECT Matricola, Nome, Cognome
FROM Studente
WHERE C i t t = ’Roma’;
2. UPDATE — Modificare dati esistenti
Il comando UPDATE modifica il valore di una o più colonne nelle righe che soddisfano
una condizione.
UPDATE NomeTabella
SET Colonna1 = NuovoValore1,
Colonna2 = NuovoValore2
WHERE Condizione;
Esempio concreto: aggiornare l’indirizzo di uno studente specifico.
UPDATE Studente
SET Indirizzo = ’Corso Venezia 10’,
Citt = ’Milano’
WHERE Matricola = ’S099’;
Il pericolo dell’UPDATE senza WHERE: Omettere la clausola WHERE è uno degli errori più
gravi che si possano commettere. In quel caso, il DBMS aggiorna tutte le righe della tabella.
-- ATTENZIONE: questa query imposta C i t t = ’Roma’ per TUTTI gli
studenti!
UPDATE Studente
SET C i t t = ’Roma’;
È buona pratica, prima di eseguire un UPDATE, testare la clausola WHERE con una SELECT
per verificare quante e quali righe verranno coinvolte:
4
-- Passo 1: verifico le righe che verranno toccate
SELECT * FROM Studente WHERE Matricola = ’S099’;
-- Passo 2: solo dopo eseguo l’UPDATE
UPDATE Studente
SET Indirizzo = ’Corso Venezia 10’
WHERE Matricola = ’S099’;
UPDATE con espressioni
Il nuovo valore non deve essere necessariamente una costante: può essere un’espressione
che dipende dal valore attuale della colonna.
-- Aumenta lo stipendio di tutti i professori di Milano del 10%
UPDATE Professore
SET Stipendio = Stipendio * 1.10
WHERE C i t t = ’Milano’;
3. DELETE — Cancellare dati
Il comando DELETE FROM rimuove le righe che soddisfano una condizione.
DELETE FROM NomeTabella
WHERE Condizione;
Esempio concreto: eliminare uno studente specifico.
DELETE FROM Studente
WHERE Matricola = ’S099’;
DELETE senza WHERE — pericolo massimo: Esattamente come per l’UPDATE, omettere la
clausola WHERE cancella tutte le righe della tabella (la struttura della tabella rimane, ma diventa
vuota). Per svuotare una tabella in modo efficiente esiste il comando TRUNCATE TABLE, che
però non è reversibile tramite transazione su tutti i DBMS.
Vincoli di integrità referenziale
Il DELETE può fallire se la riga che si vuole eliminare è referenziata da un’altra tabella
tramite una chiave esterna (Foreign Key). Il DBMS protegge l’integrità dei dati.
-- Tentativo di eliminare uno studente che ha esami registrati:
DELETE FROM Studente WHERE Matricola = ’S001’;
-- Possibile errore:
-- ERROR: violazione del vincolo di chiave esterna su "Esame"
-- (non si p u eliminare uno studente con esami pendenti)
5
Strategie per gestire le dipendenze:
• DELETE a cascata: Prima si eliminano le righe dipendenti, poi quella principale.
• ON DELETE CASCADE: Se definito nello schema, il DBMS elimina
automaticamente le righe dipendenti.
• ON DELETE SET NULL: La chiave esterna nelle tabelle dipendenti viene impostata
a NULL.
3. Il Comando SELECT e le sue Clausole
Il SELECT è il comando fondamentale per interrogare un database. Permette di estrarre
dati da una o più tabelle, filtrarli, raggrupparli e ordinarli.
Struttura completa di una query SELECT
Una query SELECT completa ha questa forma:
SELECT [DISTINCT] lista_colonne -- Quali colonne restituire
FROM nome_tabella -- Da quale/i tabella/e
[JOIN altra_tabella ON condizione] -- Combinare p i tabelle
[WHERE condizione] -- Filtrare le righe
[GROUP BY colonne] -- Raggruppare
[HAVING condizione_aggregata] -- Filtrare i gruppi
[ORDER BY colonne [ASC|DESC]] -- Ordinare il risultato
[FETCH FIRST n ROWS ONLY | LIMIT n]; -- Limitare il numero di righe
Non tutte le clausole sono obbligatorie: l’unica indispensabile è SELECT ... FROM
....
1. La clausola SELECT: scegliere le colonne
La SELECT determina quali colonne (o espressioni) appariranno nel risultato.
-- Selezione di colonne specifiche
SELECT Nome, Cognome, C i t t FROM Studente;
-- Selezione di tutte le colonne (sconsigliato in produzione)
SELECT * FROM Studente;
-- Uso di espressioni e alias
SELECT Nome, Cognome,
Voto + 2 AS VotoMaggiorato,
’Studente’ AS Tipo
FROM Studente JOIN Esame ON Matricola = MatrStud;
DISTINCT: eliminare i duplicati
Il modificatore DISTINCT forza il DBMS a restituire solo le righe uniche, eliminando i
duplicati dal risultato.
6
-- Restituisce le c i t t distinte degli studenti (senza duplicati)
SELECT DISTINCT C i t t FROM Studente;
Attenzione all’uso di DISTINCT: L’operazione di deduplicazione richiede un ordina-
mento interno ed è computazionalmente costosa. Va usata solo quando strettamente
necessaria, non come soluzione a join mal costruite che generano duplicati.
2. La clausola FROM: la sorgente dei dati
La clausola FROM indica la tabella (o le tabelle) da cui vengono estratti i dati. Quando
si specificano più tabelle separate da virgola, si esegue un prodotto cartesiano tra di esse
(tutte le combinazioni di righe), che va poi filtrato con WHERE o gestito con JOIN.
-- Prodotto cartesiano (raramente quello che si vuole!)
SELECT * FROM Studente, Esame;
-- Forma corretta con filtro esplicito (sintassi old-style)
SELECT * FROM Studente, Esame
WHERE [Link] = [Link];
3. La clausola WHERE: filtrare le righe
La WHERE seleziona solo le righe che soddisfano una condizione logica. Le condizioni
possono essere combinate con AND, OR, NOT.
-- Studenti di Milano o Roma
SELECT Nome, Cognome FROM Studente
WHERE C i t t = ’Milano’ OR C i t t = ’Roma’;
-- Esami con voto tra 25 e 30 (inclusi)
SELECT * FROM Esame
WHERE Voto BETWEEN 25 AND 30;
-- Studenti il cui nome inizia per ’A’
SELECT * FROM Studente
WHERE Nome LIKE ’A%’;
-- Studenti senza indirizzo registrato
SELECT * FROM Studente
WHERE Indirizzo IS NULL;
7
Operatori di confronto più usati:
• =, <> (diverso da), <, >, <=, >=
• BETWEEN a AND b: equivale a >= a AND <= b
• IN (v1, v2, ...): verifica l’appartenenza a un insieme di valori
• LIKE ’pattern’: confronto con pattern testuale (% = qualsiasi sequenza, _ = un
carattere)
• IS NULL / IS NOT NULL: unico modo corretto per verificare il valore nullo
4. La clausola ORDER BY: ordinare il risultato
ORDER BY specifica l’ordinamento delle righe nel risultato finale. Si può ordinare per
una o più colonne, in modo crescente (ASC, default) o decrescente (DESC).
-- Studenti ordinati per cognome (crescente, default)
SELECT Nome, Cognome FROM Studente
ORDER BY Cognome ASC;
-- Esami ordinati dal voto p i alto, a p a r i t di voto per
matricola
SELECT MatrStud, Voto FROM Esame
ORDER BY Voto DESC, MatrStud ASC;
Ordine logico vs ordine di scrittura: ORDER BY è l’ultima operazione eseguita dal
DBMS. Per questo motivo può fare riferimento agli alias definiti nella SELECT (a
differenza di WHERE e HAVING, che vengono eseguiti prima).
4. La JOIN: Combinare Dati da Più Tabelle
Nella maggior parte dei database reali, le informazioni utili sono distribuite su più tabelle
collegate tra loro tramite chiavi. La JOIN è il meccanismo con cui si “uniscono” queste tabelle
in un’unica interrogazione.
Il concetto di base: ogni tipo di JOIN risponde a una domanda diversa su come trattare le righe
che non trovano corrispondenza nell’altra tabella.
1. INNER JOIN — solo le righe con corrispondenza
L’INNER JOIN (spesso scritto semplicemente JOIN) restituisce solo le righe che hanno
una corrispondenza in entrambe le tabelle. Le righe senza corrispondenza vengono scartate
da entrambe le parti.
-- Studenti con i loro esami (solo chi ha sostenuto almeno un esame)
SELECT [Link], [Link], [Link], [Link]
FROM Studente S
INNER JOIN Esame E ON [Link] = [Link];
8
Studente Esame Risultato INNER JOIN
S001 - Mario S001, BD101, 28 ✓S001 incluso
S002 - Anna S002, MAT01, 30 ✓S002 incluso
S003 - Luca (nessun esame) × S003 escluso
Quando usarla: quando si vuole lavorare solo con le entità che hanno una relazione
completa. Esempio: ottenere la lista degli studenti che hanno effettivamente sostenuto
almeno un esame.
2. LEFT [OUTER] JOIN — tutti i record della tabella sinistra
La LEFT JOIN restituisce tutte le righe della tabella di sinistra (quella scritta prima di
JOIN), indipendentemente dal fatto che abbiano corrispondenza nella tabella destra. Per le
righe senza corrispondenza, le colonne della tabella destra assumono il valore NULL.
-- Tutti gli studenti, con i loro esami (se presenti)
-- Gli studenti senza esami appaiono comunque, con NULL nelle
colonne Esame
SELECT [Link], [Link], [Link], [Link]
FROM Studente S
LEFT JOIN Esame E ON [Link] = [Link];
Matricola Nome CodCorso Voto
S001 Mario BD101 28
S002 Anna MAT01 30
S003 Luca NULL NULL
Uso pratico — trovare chi non ha corrispondenza: filtrando le righe dove la colonna della
tabella destra è NULL, si ottengono le entità senza corrispondenza. Questo è il modo standard
per trovare, ad esempio, studenti senza esami.
-- Studenti che non hanno mai sostenuto nessun esame
-- (alternativa a NOT IN, p i robusta con i NULL)
SELECT [Link], [Link], [Link]
FROM Studente S
LEFT JOIN Esame E ON [Link] = [Link]
WHERE [Link] IS NULL;
3. RIGHT [OUTER] JOIN — tutti i record della tabella destra
La RIGHT JOIN è il duale della LEFT JOIN: restituisce tutte le righe della tabella di
destra, con NULL nelle colonne della tabella sinistra laddove manca la corrispondenza.
-- Tutti i corsi, anche quelli per cui non esiste nessun esame
registrato
SELECT [Link], [Link], [Link], [Link]
FROM Esame E
RIGHT JOIN Corso C ON [Link] = [Link];
9
Nota pratica: una RIGHT JOIN può sempre essere riscritta come una LEFT JOIN
invertendo l’ordine delle tabelle. Per uniformità e leggibilità, molti sviluppatori
preferiscono usare sempre LEFT JOIN e cambiare l’ordine delle tabelle.
-- Le due query seguenti producono lo stesso risultato:
SELECT * FROM A RIGHT JOIN B ON [Link] = [Link];
SELECT * FROM B LEFT JOIN A ON [Link] = [Link];
4. FULL [OUTER] JOIN — tutti i record di entrambe le tabelle
La FULL OUTER JOIN è la più “generosa”: restituisce tutte le righe di entrambe le
tabelle. Dove manca la corrispondenza su un lato, le colonne di quel lato sono NULL.
-- Tutti gli studenti e tutti gli esami, mostrando i "vuoti" su
entrambi i lati
SELECT [Link], [Link], [Link], [Link]
FROM Studente S
FULL OUTER JOIN Esame E ON [Link] = [Link];
Compatibilità: FULL OUTER JOIN è supportata da PostgreSQL, SQL Server e Oracle, ma non
da MySQL e SQLite. Su questi ultimi si può simulare unendo una LEFT JOIN e una RIGHT
JOIN con UNION.
5. CROSS JOIN — prodotto cartesiano
La CROSS JOIN restituisce il prodotto cartesiano tra due tabelle: ogni riga della prima
viene combinata con ogni riga della seconda. Se la tabella A ha 3 righe e B ne ha 4, il risultato
avrà 3 × 4 = 12 righe.
-- Tutti gli abbinamenti possibili tra corsi e anni accademici
SELECT [Link], [Link]
FROM Corso C
CROSS JOIN (SELECT DISTINCT AnnoAccademico FROM Insegnamento) A;
Quando usarla: il CROSS JOIN ha usi specifici (generare combinazioni, costruire
calendari, tabelle di test). Su tabelle grandi può essere devastante per le performance:
due tabelle da 1000 righe producono 1.000.000 di righe nel risultato.
6. Self-JOIN — una tabella unita a se stessa
Il Self-Join non è un tipo di join separato, ma l’applicazione di qualsiasi tipo di JOIN
(solitamente INNER o LEFT) tra una tabella e una copia di se stessa. Questo è necessario
quando si vogliono confrontare righe diverse della stessa tabella.
• Necessità degli Alias: Nel Self-Join l’uso degli alias è obbligatorio. Senza di essi, il
DBMS non saprebbe distinguere a quale “istanza” della tabella ci si riferisce.
10
• Casi d’uso comuni: gerarchie (trovare il nome del manager di un impiegato dove
entrambi sono nella stessa tabella), confronti tra righe dello stesso insieme (trovare
coppie di studenti con lo stesso voto).
-- Trovare coppie di studenti che abitano nella stessa c i t t
SELECT [Link] AS Studente1, [Link] AS Studente2, S1. C i t t
FROM Studente S1
INNER JOIN Studente S2 ON S1. C i t t = S2. C i t t
WHERE [Link] < [Link]; -- evita duplicati speculari (A-B
e B-A)
-- Trovare coppie di studenti che hanno preso lo stesso voto
-- nello stesso esame (evitando il confronto di uno studente con se
stesso)
SELECT [Link], [Link], [Link]
FROM Esame E1
INNER JOIN Esame E2 ON [Link] = [Link]
AND [Link] = [Link]
WHERE [Link] < [Link];
Logica del confronto: Usando [Link] <> [Link] otterresti ogni cop-
pia due volte (A-B e B-A). Usando <, imponi un ordine rigido che restituisce ogni coppia
una sola volta.
7. Riepilogo visivo dei tipi di JOIN
Tipo Cosa restituisce NULL nelle colonne
INNER JOIN Solo righe con corrispondenza in en- Nessuno
trambe le tabelle
LEFT JOIN Tutte le righe di sinistra + corrispon- Colonne destra
denze destra
RIGHT JOIN Tutte le righe di destra + corrispon- Colonne sinistra
denze sinistra
FULL OUTER JOIN Tutte le righe di entrambe le tabelle Entrambi i lati
CROSS JOIN Prodotto cartesiano (ogni riga con Nessuno
ogni riga)
La sintassi USING: un’alternativa a ON
Quando le due colonne di join hanno lo stesso nome in entrambe le tabelle, si può usare
la sintassi USING come scorciatoia elegante:
-- Con ON (generale, funziona sempre)
SELECT * FROM Esame JOIN Corso ON [Link] = [Link];
-- Con USING (solo se il nome della colonna identico in entrambe
le tabelle)
SELECT * FROM Esame JOIN Studente USING (MatrStud);
11
5. Concetti Fondamentali e Sintassi Avanzata
1. Funzioni di Aggregazione e Raggruppamento
Le funzioni di aggregazione operano su un insieme di righe per restituire un unico valore
scalare sintetico.
• Funzioni principali: COUNT(*) (conteggio righe), SUM() (somma), AVG() (media),
MIN() / MAX() (valori estremi).
La clausola GROUP BY
Il GROUP BY trasforma la tabella in “sottogruppi” logici basati sui valori di una o più
colonne. Quando si usa questa clausola, la SELECT subisce un vincolo rigido:
Requisito: Ogni colonna presente nella SELECT che non è inclusa in una funzione
di aggregazione deve obbligatoriamente apparire nel GROUP BY.
Perché? Se chiedessimo il nome di uno studente e la sua media voti raggruppando solo per
facoltà, il DBMS non saprebbe quale singolo nome scegliere tra i molti presenti nel gruppo
“facoltà”.
Il filtro sui gruppi: HAVING
Mentre la clausola WHERE filtra le singole righe prima che queste vengano raggruppate, la
clausola HAVING agisce esclusivamente sui risultati aggregati.
Come distinguerli:
• WHERE: Si usa per condizioni sui dati “atomici” (es. WHERE Voto > 18).
• HAVING: Si usa per condizioni sui calcoli di gruppo (es. HAVING AVG(Voto) >
27).
2. Subquery e Operatori Insiemistici
Le subquery (query nidificate) permettono di utilizzare il risultato di un’interrogazione
all’interno di un’altra.
• Predicati di confronto:
– IN: Verifica l’appartenenza a un insieme.
– ANY / ALL: Richiedono sempre un operatore di confronto precedente (es. > ANY,
<= ALL).
– Nota di equivalenza: L’operatore IN è logicamente equivalente a = ANY.
– EXISTS: Verifica l’esistenza di almeno un record restituito dalla subquery.
• Operatori Insiemistici: UNION, INTERSECT e EXCEPT. Richiedono che le tabelle siano
“unione-compatibili” (stesso schema per numero e tipo di colonne).
12
3. Viste (Views)
Una vista è una “tabella virtuale” definita da una query. È utile per semplificare query
complesse o per riutilizzare logiche di filtraggio frequenti.
CREATE VIEW MediaStudenti AS
SELECT MatrStud, AVG(Voto) as Media
FROM Esame
GROUP BY MatrStud;
4. Ordine Logico di Esecuzione
È fondamentale ricordare che l’ordine con cui scriviamo la query non coincide con l’ordine
in cui il DBMS la esegue:
1) FROM / JOIN (Reperimento dati)
2) WHERE (Filtraggio righe)
3) GROUP BY (Raggruppamento)
4) HAVING (Filtraggio gruppi)
5) SELECT (Proiezione colonne)
6) ORDER BY (Ordinamento finale)
5. La Logica a tre valori e il valore NULL
In SQL, il valore NULL non è un dato, ma uno stato: rappresenta un’informazione
mancante, non applicabile o sconosciuta. Questa natura introduce una logica a tre valori
(Ternary Logic): True, False e Unknown.
• Il concetto di UNKNOWN: Qualsiasi confronto logico o operazione aritmetica che
coinvolga un NULL restituisce UNKNOWN.
Esempio: Se il Voto è NULL, la condizione Voto = 30 non è falsa, è semplicemente
“non determinabile”.
• Comportamento nelle clausole WHERE: Le clausole di filtraggio (WHERE e HAVING)
permettono il passaggio dei soli record per cui la condizione è TRUE. I record che
restituiscono FALSE o UNKNOWN vengono scartati.
Operazione con NULL Risultato
Voto = 30 (se Voto è NULL) UNKNOWN (riga scartata)
Voto <> 30 (se Voto è NULL) UNKNOWN (riga scartata)
Voto IS NULL TRUE (riga selezionata)
Voto + 10 NULL
Attenzione alle Aggregazioni: Le funzioni statistiche si comportano in modo differenziato
rispetto ai valori nulli:
• COUNT(Voto): Conta solo i record dove il voto è presente (ignora i NULL).
• AVG(Voto): Calcola la media ignorando completamente le righe nulle (non le conta
nemmeno al denominatore).
• COUNT(*): Conta la riga fisica, indipendentemente dal contenuto, includendo quindi anche
i record con campi NULL.
13
6. Gestione dei risultati e classifiche: LIMIT e Funzioni di Ranking
Nelle query di analisi, è spesso necessario limitare il numero di record restituiti o stilare
graduatorie. Esistono due approcci: uno pragmatico (legato al post-processing) e uno
analitico (legato alla logica dei predicati).
A. Limitazione dei risultati: LIMIT, FETCH FIRST e le insidie del non-determinismo
La limitazione dei risultati è un’operazione di post-processing che interviene sull’insieme già
estratto. Sebbene molto comune, l’uso di LIMIT (o dello standard FETCH FIRST) presenta
criticità semantiche e logiche fondamentali che lo studente deve saper governare.
• Sintassi e Standard: Mentre LIMIT n è un’estensione ampiamente diffusa (MySQL,
PostgreSQL, SQLite), lo standard ISO prevede la clausola FETCH FIRST n ROWS
ONLY. Entrambe impongono un taglio fisico al set di risultati.
• Paginazione con OFFSET: Lo standard accoppia spesso il fetch alla clausola OFFSET
m ROWS, che permette di saltare le prime m righe.
-- Salta i primi 20 studenti e prendi i successivi 10
SELECT Matricola, Nome FROM Studente
ORDER BY Matricola
OFFSET 20 ROWS FETCH FIRST 10 ROWS ONLY;
• Il problema del Determinismo: SQL opera su multiset non ordinati. L’estrazione
di una riga tramite LIMIT 1 o FETCH FIRST 1 ROW senza una clausola ORDER BY
esplicita è un’operazione non-deterministica.
• L’occultamento dei Pari Merito (Ties): LIMIT e FETCH FIRST ... ONLY sono
costrutti procedurali e non predicati logici. Se tre record condividono il valore massimo,
un taglio netto ne estrarrà uno solo in modo arbitrario.
• La soluzione ISO: WITH TIES:
-- Estrae tutti gli studenti che hanno il voto massimo
SELECT MatrStud, Voto FROM Esame
ORDER BY Voto DESC
FETCH FIRST 1 ROWS WITH TIES;
• Compatibilità: FETCH FIRST vs LIMIT
DBMS FETCH FIRST LIMIT
PostgreSQL ✓ ✓
Oracle 12c+ ✓ ×
SQL Server ✓(con OFFSET) ×
DB2 ✓ ×
MySQL/MariaDB × ✓
SQLite × ✓
Regola pratica: Usa FETCH FIRST per SQL standard su DBMS enterprise. Usa
LIMIT con MySQL o SQLite. Per gestire i pari merito in modo robusto usa
14
DENSE_RANK() in una subquery.
B. Le Window Functions (Funzioni Finestra)
A differenza del GROUP BY, le funzioni finestra (window functions) eseguono un calcolo
su un insieme di righe correlate alla riga corrente, senza collassare il risultato in un unico
valore aggregato. Ogni riga del risultato mantiene la propria identità e riceve in aggiunta il
valore calcolato sulla “finestra” di righe di riferimento.
La sintassi generale è:
funzione() OVER (
[PARTITION BY colonna] -- divide il dataset in partizioni
indipendenti
[ORDER BY colonna] -- ordina le righe all’interno di ogni
partizione
[frame_clause] -- definisce il sottoinsieme di righe (
opzionale)
)
Window Function vs GROUP BY – differenza chiave: Con GROUP BY, ogni gruppo
collassa in una sola riga. Con una Window Function, le righe originali restano tutte
presenti nel risultato: ogni riga riceve il valore aggregato calcolato sulla sua finestra di
appartenenza. Questo è il motivo per cui le Window Functions non possono apparire in
WHERE o HAVING: vengono calcolate dopo il raggruppamento.
Le funzioni di ranking: RANK, DENSE_RANK, ROW_NUMBER
Queste tre funzioni assegnano una posizione a ogni riga basandosi sull’ordine specificato
in OVER (ORDER BY ...). Si comportano in modo diverso in presenza di pari merito
(ties).
Voto ROW_NUMBER() RANK() DENSE_RANK()
30 1 1 1
30 2 1 1
28 3 3 2
27 4 4 3
27 5 4 3
25 6 6 4
• ROW_NUMBER(): Numera le righe in modo sempre univoco e sequenziale. Utile per la
paginazione.
• RANK(): Assegna lo stesso numero a pari merito, ma poi salta le posizioni successive
(“buchi”).
• DENSE_RANK(): Come RANK() ma senza buchi. Da preferire per filtrare i “top N”
includendo i pari merito.
Esempi pratici
Esempio 1 – Graduatoria semplice degli studenti per media voti:
15
SELECT
MatrStud,
AVG(Voto) AS Media,
RANK() OVER (ORDER BY AVG(Voto) DESC) AS Pos_RANK,
DENSE_RANK() OVER (ORDER BY AVG(Voto) DESC) AS
Pos_DENSERANK,
ROW_NUMBER() OVER (ORDER BY AVG(Voto) DESC) AS Pos_ROWNUM
FROM Esame
GROUP BY MatrStud;
Esempio 2 – Top 10 studenti per media (con pari merito al decimo posto):
-- Con DENSE_RANK: include tutti i pari merito alla posizione 10
SELECT MatrStud, Media
FROM (
SELECT MatrStud,
AVG(Voto) AS Media,
DENSE_RANK() OVER (ORDER BY AVG(Voto) DESC) AS Posizione
FROM Esame
GROUP BY MatrStud
) AS Classifica
WHERE Posizione <= 10;
Perché serve la derived table? Le Window Functions vengono calcolate dopo WHERE e HAVING,
quindi non è possibile scrivere WHERE DENSE_RANK() OVER (...) <= 10 direttamente.
La soluzione è calcolare il rank in una subquery e poi filtrare sul risultato.
Esempio 3 – Classifiche indipendenti per facoltà (PARTITION BY):
SELECT
MatrStud,
Facolt ,
AVG(Voto) AS Media,
DENSE_RANK() OVER (
PARTITION BY F a c o l t
ORDER BY AVG(Voto) DESC
) AS PosizioneInFacolta
FROM Esame
JOIN Studente USING (MatrStud)
GROUP BY MatrStud, F a c o l t ;
Esempio 4 – Top 3 studenti per facoltà (classifica multipla filtrata):
SELECT MatrStud, F a c o l t , Media, Posizione
FROM (
SELECT
MatrStud,
Facolt ,
AVG(Voto) AS Media,
16
DENSE_RANK() OVER (
PARTITION BY F a c o l t
ORDER BY AVG(Voto) DESC
) AS Posizione
FROM Esame
JOIN Studente USING (MatrStud)
GROUP BY MatrStud, F a c o l t
) AS ClassificaPerFacolta
WHERE Posizione <= 3;
Esempio 5 – ROW_NUMBER per paginazione (pagina 2, 10 risultati per pagina):
SELECT MatrStud, Media
FROM (
SELECT MatrStud,
AVG(Voto) AS Media,
ROW_NUMBER() OVER (ORDER BY AVG(Voto) DESC, MatrStud) AS
riga
FROM Esame
GROUP BY MatrStud
) AS Paginato
WHERE riga BETWEEN 11 AND 20; -- seconda pagina da 10 elementi
Esempio 6 – Confronto diretto tra RANK, DENSE_RANK e ROW_NUMBER:
SELECT
MatrStud,
Voto,
ROW_NUMBER() OVER (ORDER BY Voto DESC) AS RowNum,
RANK() OVER (ORDER BY Voto DESC) AS Rank,
DENSE_RANK() OVER (ORDER BY Voto DESC) AS DenseRank
FROM Esame
WHERE CodCorso = ’BD101’;
-- Output esempio con voti 30,30,28,27,27,25:
-- MatrStud | Voto | RowNum | Rank | DenseRank
-- S001 | 30 | 1 | 1 | 1
-- S002 | 30 | 2 | 1 | 1
-- S003 | 28 | 3 | 3 | 2
-- S004 | 27 | 4 | 4 | 3
-- S005 | 27 | 5 | 4 | 3
-- S006 | 25 | 6 | 6 | 4
17
7. L’operatore EXISTS e la convenzione SELECT 1
L’operatore EXISTS viene utilizzato per testare l’esistenza di righe che soddisfano un
determinato predicato in una subquery. È un operatore di tipo booleano: restituisce TRUE se
la subquery estrae almeno una riga, FALSE altrimenti.
Perché si usa SELECT 1?
All’interno di un predicato EXISTS, il DBMS non è interessato al contenuto delle colonne,
ma solo alla presenza di record che soddisfano la clausola WHERE.
-- Verifica se uno studente ha sostenuto l’esame BD101
SELECT Nome, Cognome FROM Studente S
WHERE EXISTS (
SELECT 1
FROM Esame E
WHERE [Link] = [Link] AND [Link] = ’BD101’
);
Significato Didattico:
• Semantica: L’uso di SELECT 1 chiarisce al lettore che la subquery agisce come un puro
indicatore di esistenza.
• Corto Circuito: Non appena il motore trova la prima riga corrispondente, l’esecuzione
della subquery si interrompe, rendendo EXISTS molto più performante di un COUNT(*)
> 0.
18
8. Alias e Riferimenti alle Tabelle
In SQL è possibile rinominare temporaneamente tabelle e colonne durante l’esecuzione di
una query attraverso gli Alias.
• Sintassi: Si utilizza la parola chiave AS (es. SELECT Nome AS N), ma nella maggior
parte dei dialetti SQL è opzionale: scrivere Studente S è equivalente a Studente
AS S.
• Qualificazione obbligatoria: Quando una query coinvolge più tabelle (JOIN), è buo-
na norma anteporre l’alias al nome della colonna (es. [Link]). Questo è
obbligatorio se il nome della colonna è presente in più tabelle.
• Self-Join: L’alias è indispensabile quando si confronta una tabella con se stessa.
9. Ricerca di Pattern: L’operatore LIKE
L’operatore LIKE viene utilizzato nella clausola WHERE per ricercare stringhe che rispetta-
no un determinato modello (pattern).
• Caratteri Jolly (Wildcards):
– %: Rappresenta zero, uno o più caratteri qualsiasi.
– _ (underscore): Rappresenta esattamente un singolo carattere.
-- Trova studenti il cui nome inizia per ’A’ e ha almeno 3 caratteri
SELECT Nome FROM Studente
WHERE Nome LIKE ’A__%’;
Performance e Indici: Ricerche con il jolly all’inizio (es. LIKE ’%Rossi’) impedisco-
no al DBMS di usare gli indici standard, costringendo a una scansione completa della
tabella (Full Table Scan).
19
6. Errori Comuni
In questa sezione vengono analizzati gli errori logici e strutturali più frequenti.
1. Errori nella Clausola WHERE
• Clausole auto-contraddittorie: Inserire condizioni che si escludono a vicenda, come
WHERE Id = 1 AND Id = 2. Il risultato sarà sempre un insieme vuoto.
• Aggregati nel WHERE: Tentare di filtrare record usando funzioni di aggregazione (es.
WHERE AVG(Voto) > 25). Errore: Gli aggregati vanno nella clausola HAVING.
2. Errori nell’uso di IN e NOT IN
• Mancanza dell’attributo a sinistra: Scrivere WHERE [NOT] IN (SELECT ...)
senza specificare quale colonna confrontare.
• Discordanza di schemi: Cercare una corrispondenza tra tipi di dati diversi, ad esempio
WHERE DataInizio IN (SELECT Nome, Cognome ...).
• NOT IN con NULL: Se la subquery usata con NOT IN restituisce anche un solo valore
NULL, l’intera condizione diventa UNKNOWN e la query non restituisce nulla. In questi
casi, NOT EXISTS è più robusto.
3. Errori di Aggregazione e Raggruppamento
Target list miste: Se non è presente la clausola GROUP BY, non è possibile selezionare
contemporaneamente colonne atomiche e funzioni di aggregazione. Esempio errato:
SELECT Nome, AVG(Voto) FROM Studente.
• Aggregati di aggregati: Scrivere MAX(AVG(Voto)). SQL non permette la nidificazio-
ne diretta. Usare una subquery o una CTE.
• Common Table Expression (CTE): Definita tramite la clausola WITH, è un set di
risultati temporaneo ad uso esclusivo della singola query.
WITH MediePerStudente AS (
SELECT Matricola, AVG(Voto) AS Media
FROM Esami
GROUP BY Matricola
)
SELECT MAX(Media) FROM MediePerStudente;
4. Errori Logici nei Predicati
• Il paradosso di COUNT(*) = 0: All’interno di un HAVING, la condizione COUNT(*)
= 0 è logicamente impossibile. Se il gruppo esiste, deve contenere almeno una riga.
Usare OUTER JOIN o NOT EXISTS.
• Conteggio di predicati: COUNT(Voto > 25). COUNT conta valori non nulli, non
valuta espressioni logiche. Usare SUM(CASE WHEN Voto > 25 THEN 1 ELSE 0
END).
• UPDATE/DELETE senza WHERE: Come descritto nella sezione DML, omettere la
clausola WHERE aggiorna o cancella tutte le righe della tabella.
20
5. Note sulla Consapevolezza
• Query nidificate senza ANY/ALL: Usare un confronto scalare (WHERE Voto > (SELECT
...)) funziona solo se la subquery restituisce esattamente un valore.
• EXISTS con SELECT specifico: Sebbene corretto, la convenzione suggerisce SELECT
* o SELECT 1 per chiarire l’intento.
21
Schema Relazionale
Si consideri il seguente schema di base di dati per la gestione di una carriera universitaria:
• STUDENTE (Matricola, Nome, Cognome, Indirizzo, Città)
• ESAME (CodCorso, MatrStud, Data, Voto, Lode)
• CORSO (Codice, Nome, AnnoDiCorso, Facoltà, NumeroCrediti)
• INSEGNAMENTO (CodCorso, AnnoAccademico, MatrProf, NumeroStudenti)
• PROFESSORE (Matricola, Nome, Cognome, Città, Telefono, Stipendio)
Esercizi
Scrivere le interrogazioni SQL per estrarre le seguenti informazioni:
1. Gli studenti che hanno sostenuto esattamente 10 esami.
2. Gli studenti che non hanno mai sostenuto nessun esame.
3. Le matricole dei professori che hanno insegnato in corsi di tutte le facoltà.
4. I corsi in cui qualche voto non è mai stato assegnato.
5. Matricola, nome e cognome degli studenti di Milano con almeno 20 CFU e voti ≥ 28.
6. L’edizione di corso con il maggior numero di studenti.
7. Di ogni facoltà, corsi con il minor numero di studenti.
select matricola from studenti join
8. Studenti che hanno preso più 27 che 24. esame where (select count() from
esame where matricola=[Link]
9. Studenti con almeno 3 esami del 2° anno ma meno di 3 del 1° anno.
and voto=27)>(select count() from
... voto=24)
10. Studenti che hanno superato più esami del 3° anno che del 2°.
11. Studenti con la media più alta (pesata rispetto ai CFU).
12. Studenti con media superiore alla media globale di ateneo.
13. Corsi con almeno il 50% degli studenti con voto > 25.
14. I “top ten” studenti per media pesata (minimo 10 esami).
15. Corsi di professori di Torino mai superati da studenti di Torino.
16. Studenti che non hanno mai sostenuto l’esame del corso ’BD101’.
22
Soluzioni
1. Gli studenti che hanno sostenuto esattamente 10 esami.
SELECT MatrStud
FROM Esame
GROUP BY MatrStud
HAVING COUNT(*) = 10;
2. Gli studenti che non hanno mai sostenuto nessun esame.
SELECT Matricola
FROM Studente
WHERE Matricola NOT IN (SELECT MatrStud FROM Esame);
Esempio di errore comune:
SELECT MatrStud
FROM Esame
GROUP BY MatrStud HAVING COUNT(*) = 0
Perché questa query non può funzionare?
• Il limite del GROUP BY: Raggruppa solo tuple esistenti nella tabella.
• L’assenza di dati: Se uno studente non ha sostenuto esami, non c’è nessuna riga in
ESAME con la sua matricola.
• Il paradosso: Un gruppo, per esistere, deve avere almeno un elemento. Di
conseguenza, COUNT(*) = 0 non sarà mai soddisfatta.
3. Le matricole dei professori che hanno insegnato in corsi di tutte le facoltà.
SELECT MatrProf
FROM Insegnamento JOIN Corso ON Codice = CodCorso
GROUP BY MatrProf
HAVING COUNT(DISTINCT F a c o l t ) = (
SELECT COUNT(DISTINCT F a c o l t )
FROM Corso
);
23
4. I corsi in cui qualche voto non è mai stato assegnato.
SELECT CodCorso
FROM Esame
GROUP BY CodCorso
HAVING COUNT(DISTINCT Voto) < 13;
Nota bene: La query sopra estrae solo i corsi che hanno almeno un esame registrato. Per
includere anche i corsi senza esami:
• a) RIGHT JOIN:
SELECT Codice
FROM Esame RIGHT JOIN Corso ON CodCorso = Codice
GROUP BY Codice
HAVING COUNT(DISTINCT Voto) < 13
• b) Complemento:
SELECT Codice
FROM Corso
WHERE Codice NOT IN (
SELECT CodCorso FROM Esame
GROUP BY CodCorso HAVING COUNT(DISTINCT Voto) = 13
)
• c) UNION:
SELECT CodCorso FROM Esame GROUP BY CodCorso
HAVING COUNT(DISTINCT Voto) < 13
UNION
SELECT Codice FROM Corso WHERE Codice
NOT IN (SELECT CodCorso FROM Esame)
5. Matricola, nome e cognome degli studenti di Milano che hanno superato esami
per un totale di almeno 20 crediti e non hanno mai preso un voto minore di 28.
-- Soluzione 1: Utilizzando MIN() nella clausola HAVING
SELECT Matricola, Nome, Cognome
FROM (Studente JOIN Esame ON Matricola = MatrStud)
JOIN Corso ON CodCorso = Codice
WHERE C i t t = ’Milano’
GROUP BY Matricola, Nome, Cognome
HAVING SUM(NumeroCrediti) >= 20 AND MIN(Voto) >= 28;
24
-- Soluzione 2: Utilizzando NOT IN per escludere voti bassi
SELECT Matricola, Nome, Cognome
FROM (Studente JOIN Esame ON Matricola = MatrStud)
JOIN Corso ON CodCorso = Codice
WHERE C i t t = ’Milano’
AND Matricola NOT IN (
SELECT MatrStud
FROM Esame
WHERE Voto < 28
)
GROUP BY Matricola, Nome, Cognome
HAVING SUM(NumeroCrediti) >= 20;
-- Soluzione 3: Utilizzando subquery correlate
SELECT Matricola, Nome, Cognome
FROM Studente S
WHERE C i t t = ’Milano’
AND 28 <= (SELECT MIN(Voto) FROM Esame WHERE MatrStud = S.
Matricola)
AND 20 <= (SELECT SUM(NumeroCrediti)
FROM Esame JOIN Corso ON CodCorso = Codice
WHERE MatrStud = [Link]);
Esempio di errore logico grave:
SELECT Matricola, Nome, Cognome
FROM (Studente JOIN Esame ON Matricola = MatrStud)
JOIN Corso ON CodCorso = Codice
WHERE C i t t = ’Milano’ AND Voto >= 28
GROUP BY Matricola, Nome, Cognome
HAVING SUM(NumeroCrediti) >= 20;
Perché questa query è sbagliata?
• Il filtro WHERE: Inserendo Voto >= 28 nella clausola WHERE, il database scarta
prima del raggruppamento tutti gli esami con voto inferiore a 28.
• Il risultato falsato: Se uno studente ha preso un 18, la query lo ignora invece di
escludere lo studente dal risultato.
• Conseguenza: Verrebbero estratti studenti con voti bassi, purché la somma dei crediti
dei soli esami ≥ 28 superi i 20 CFU.
25
6. L’edizione di corso (l’insegnamento) con il maggior numero di studenti in assoluto.
SELECT CodCorso, AnnoAccademico
FROM Insegnamento
WHERE NumeroStudenti = (
SELECT MAX(NumeroStudenti)
FROM Insegnamento
);
7. Di ogni facoltà, corsi con il minor numero di studenti.
SELECT F a c o l t , Codice, Nome, NumeroStudenti, AnnoAccademico
FROM Corso C JOIN Insegnamento ON Codice = CodCorso
WHERE NumeroStudenti = (
SELECT MIN(NumeroStudenti)
FROM Corso JOIN Insegnamento ON Codice = CodCorso
WHERE F a c o l t = C. F a c o l t
);
8. Studenti che hanno preso più 27 che 24.
SELECT MatrStud
FROM Esame E
WHERE Voto = 27
GROUP BY MatrStud
HAVING COUNT(*) > (
SELECT COUNT(*)
FROM Esame E2
WHERE [Link] = [Link]
AND Voto = 24
);
26
9. Studenti che hanno superato almeno 3 esami del secondo anno ma meno di 3
esami del primo.
-- Soluzione 1: Utilizzo di Viste e INTERSECT
CREATE VIEW AlmenoTreDelSecondoAnno AS
SELECT Matricola, Nome, Cognome
FROM (Studente JOIN Esame ON Matricola = MatrStud) JOIN
Corso ON CodCorso = Codice
WHERE AnnoDiCorso = 2
GROUP BY Matricola, Nome, Cognome
HAVING COUNT(*) >= 3;
CREATE VIEW MenoDiTreDelPrimoAnno AS
SELECT Matricola, Nome, Cognome
FROM Studente
WHERE Matricola NOT IN (
SELECT MatrStud
FROM Esame JOIN Corso ON CodCorso = Codice
WHERE AnnoDiCorso = 1
GROUP BY MatrStud
HAVING COUNT(*) >= 3
);
SELECT * FROM AlmenoTreDelSecondoAnno
INTERSECT
SELECT * FROM MenoDiTreDelPrimoAnno;
-- Soluzione 2: Query singola con NOT IN (Consigliata)
SELECT Matricola, Nome, Cognome
FROM (Studente JOIN Esame ON Matricola = MatrStud) JOIN Corso
ON CodCorso = Codice
WHERE AnnoDiCorso = 2
AND Matricola NOT IN (
SELECT MatrStud
FROM Esame JOIN Corso ON CodCorso = Codice
WHERE AnnoDiCorso = 1
GROUP BY MatrStud
HAVING COUNT(*) >= 3
)
GROUP BY Matricola, Nome, Cognome
HAVING COUNT(*) >= 3;
Il problema degli “Studenti Estremi”:
• Definizione: Uno studente che ha sostenuto 3 esami del secondo anno ma zero esami
del primo anno deve essere incluso nel risultato.
• L’errore del COUNT < 3: Se lo studente ha zero esami, non esistono righe, non viene
creato il gruppo e lo studente viene ignorato.
• La logica corretta: Procedere per complemento: identificare chi ne ha almeno 3 e
escluderlo con NOT IN.
10. Matricola, nome e cognome degli studenti che hanno superato più esami del terzo
27
anno che del secondo.
SELECT Matricola, Nome, Cognome
FROM (Studente S JOIN Esame ON Matricola = MatrStud)
JOIN Corso ON CodCorso = Codice
WHERE AnnoDiCorso = 3
GROUP BY Matricola, Nome, Cognome
HAVING COUNT(*) > (
SELECT COUNT(*)
FROM Esame JOIN Corso ON CodCorso = Codice
WHERE AnnoDiCorso = 2 AND MatrStud = [Link]
);
11. Studenti con la media più alta (pesata rispetto ai CFU).
SELECT MatrStud
FROM Esame JOIN Corso ON Codice = CodCorso
GROUP BY MatrStud
HAVING SUM(Voto * NumeroCrediti) / CAST(SUM(NumeroCrediti) AS
FLOAT) >= ALL (
SELECT SUM(Voto * NumeroCrediti) / CAST(SUM(NumeroCrediti)
AS FLOAT)
FROM Esame JOIN Corso ON Codice = CodCorso
GROUP BY MatrStud
);
Logica della Media Pesata:
P
(Voto×CFU)
• La formula è P
CFU .
• L’operatore >= ALL confronta la media di ogni studente con l’intero insieme delle
medie, identificando il valore massimo (o i valori massimi in caso di parità).
12. Studenti con media superiore alla media globale di ateneo.
SELECT MatrStud, AVG(Voto) as Media
FROM Esame
GROUP BY MatrStud
HAVING AVG(Voto) > (
SELECT AVG(MediaSingola)
FROM (
SELECT AVG(Voto) as MediaSingola
FROM Esame
GROUP BY MatrStud
) as TabellaMedie
);
La “Media delle Medie”: Questa query richiede due livelli di aggregazione. Non è
possibile fare AVG(AVG(Voto)) direttamente. Bisogna prima calcolare le medie dei
singoli studenti (nella subquery) e poi calcolare la media di quei risultati.
28
13. Corsi in cui almeno il 50% degli studenti ha preso un voto maggiore di 25.
SELECT CodCorso
FROM Esame E
WHERE Voto > 25
GROUP BY CodCorso
HAVING COUNT(*) >= 0.5 * (
SELECT COUNT(*)
FROM Esame E2
WHERE [Link] = [Link]
);
29
14. Trovare i “top ten” studenti in base alla media pesata, tra quelli che abbiano
sostenuto almeno 10 esami.
Soluzione A: Approccio Insiemistico (Subquery Correlata)
SELECT MatrStud, SUM(Voto * NumeroCrediti)/SUM(NumeroCrediti)
as MediaP
FROM Esame JOIN Corso ON Codice = CodCorso
GROUP BY MatrStud
HAVING COUNT(*) >= 10
AND 9 >= (
SELECT COUNT(*)
FROM (
SELECT MatrStud, SUM(Voto * NumeroCrediti)/SUM(
NumeroCrediti) as MP
FROM Esame JOIN Corso ON Codice = CodCorso
GROUP BY MatrStud
HAVING COUNT(*) >= 10
) AS Altri
WHERE [Link] > SUM(Voto * NumeroCrediti)/SUM(
NumeroCrediti)
);
Soluzione B: Approccio Procedurale (LIMIT)
SELECT MatrStud, SUM(Voto * NumeroCrediti)/SUM(NumeroCrediti)
as MediaP
FROM Esame JOIN Corso ON Codice = CodCorso
GROUP BY MatrStud
HAVING COUNT(*) >= 10
ORDER BY MediaP DESC
LIMIT 10;
Soluzione C: Approccio Moderno (Window Functions)
SELECT MatrStud, MediaP
FROM (
SELECT MatrStud,
SUM(Voto * NumeroCrediti)/SUM(NumeroCrediti) as
MediaP,
RANK() OVER (ORDER BY SUM(Voto * NumeroCrediti)/SUM(
NumeroCrediti) DESC) as Posizione
FROM Esame JOIN Corso ON Codice = CodCorso
GROUP BY MatrStud
HAVING COUNT(*) >= 10
) AS Graduatoria
WHERE Posizione <= 10;
30
Soluzione D: Approccio Standard ISO (FETCH FIRST)
SELECT MatrStud, SUM(Voto * NumeroCrediti)/SUM(NumeroCrediti)
as MediaP
FROM Esame JOIN Corso ON Codice = CodCorso
GROUP BY MatrStud
HAVING COUNT(*) >= 10
ORDER BY MediaP DESC
FETCH FIRST 10 ROWS WITH TIES;
15. Corsi tenuti da professori di Torino che non sono stati superati da nessuno studente
di Torino.
SELECT [Link]
FROM Insegnamento I JOIN Professore P ON [Link] = P.
Matricola
WHERE P. C i t t = ’Torino’
AND NOT EXISTS (
SELECT *
FROM Esame E JOIN Studente S ON [Link] = [Link]
WHERE S. C i t t = ’Torino’
AND [Link] = [Link]
);
31
16. Studenti che non hanno mai sostenuto l’esame del corso ’BD101’
-- Soluzione 1: Utilizzo dell’operatore di differenza EXCEPT
SELECT Matricola FROM Studente
EXCEPT
SELECT MatrStud FROM Esame WHERE CodCorso = ’BD101’;
-- Soluzione 2: Utilizzo di NOT EXISTS con SELECT 1
SELECT [Link], [Link], [Link]
FROM Studente S
WHERE NOT EXISTS (
SELECT 1
FROM Esame E
WHERE [Link] = [Link]
AND [Link] = ’BD101’
);
Differenze tecniche tra i due approcci:
• EXCEPT (Insiemistica): Funziona come una sottrazione tra insiemi. Richiede che le
due SELECT abbiano lo stesso numero e tipo di colonne e rimuove automaticamente i
duplicati.
• NOT EXISTS / SELECT 1: È una query correlata. È spesso più efficiente di NOT IN in
presenza di valori nulli.
32
7. Ottimizzazione e Performance delle Query
La stesura di un’interrogazione SQL richiede non solo correttezza semantica, ma anche
attenzione all’efficienza computazionale. Il DBMS utilizza un Optimizer per determinare il
piano d’esecuzione (Query Plan), ma la struttura del codice SQL influenza drasticamente la
capacità del sistema di utilizzare indici e ridurre il carico di I/O.
1. Il principio del filtraggio preventivo (Early Filtering)
L’obiettivo primario dell’ottimizzazione è ridurre il volume dei dati elaborati nelle prime
fasi della query.
Utilizzo efficiente di WHERE vs HAVING:
La clausola WHERE opera prima dell’aggregazione (Pre-filtering). La clausola HAVING
agisce invece sui gruppi già formati (Post-filtering).
Configurazione inefficiente:
SELECT MatrStud, COUNT(*)
FROM Studente JOIN Esame ON Matricola=MatrStud
GROUP BY MatrStud, C i t t
HAVING C i t t = ’Milano’;
Configurazione ottimale:
SELECT MatrStud, COUNT(*)
FROM Studente JOIN Esame ON Matricola=MatrStud
WHERE C i t t = ’Milano’
GROUP BY MatrStud;
2. Proiezione selettiva ed efficienza degli indici
L’utilizzo di SELECT * è considerato una pratica non ottimale in ambienti di produzione:
• Carico di rete e memoria: Vengono estratti dati non necessari aumentando l’overhead.
• Covering Indexes: Se la SELECT richiede solo colonne già presenti in un indice, il
DBMS può evitare di accedere alla tabella fisica con un guadagno enorme.
3. SARGability e utilizzo degli indici
Il termine SARGable (Search ARGumentable) indica la capacità di una query di sfruttare
gli indici esistenti.
• LIKE ’Ros%’: SARGable. L’indice può essere utilizzato.
• LIKE ’%ssi’: Non-SARGable. Il database è costretto a un Full Table Scan.
4. Verifica di esistenza: EXISTS vs COUNT
Efficienza del corto circuito:
L’utilizzo di SELECT COUNT(*) obbliga il DBMS a scansionare tutte le occorrenze.
L’utilizzo di EXISTS permette di interrompere la scansione alla prima occorrenza
valida.
-- Approccio ottimale:
IF EXISTS (SELECT 1 FROM Studente WHERE Nome = ’Mario’) ...
33
5. Manipolazione delle colonne nei filtri
L’applicazione di funzioni a una colonna nella clausola WHERE impedisce l’uso degli indici
su quella colonna.
Esempio sulla gestione temporale:
Inefficiente (l’indice su ’Data’ viene ignorato):
SELECT * FROM Esame WHERE YEAR(Data) = 2023;
Ottimizzato (Range Scan basato su indice):
SELECT * FROM Esame
WHERE Data >= ’2023-01-01’ AND Data <= ’2023-12-31’;
6. Overhead dell’operatore DISTINCT
L’operatore DISTINCT non deve essere utilizzato per correggere errori nelle join che
generano duplicati. La deduplicazione è costosa perché richiede l’ordinamento e il confronto
di tutti i record estratti. È opportuno verificare sempre se la struttura della query può evitare
righe ridondanti tramite join più precise o l’utilizzo di EXISTS.
34