Guida Completa con Esempi in MariaDB/MySQL
Useremo un semplice database con Studenti e Corsi per tutti gli esempi.
| id | nome | città | età |
|---|---|---|---|
| 1 | Mario | Roma | 22 |
| 2 | Laura | Milano | 20 |
| 3 | Luca | Roma | 23 |
| 4 | Anna | Napoli | 21 |
| id_corso | nome_corso | docente |
|---|---|---|
| 101 | Database | Rossi |
| 102 | Programmazione | Bianchi |
| 103 | Reti | Verdi |
| 104 | Yoga | Aldi |
| id_studente | id_corso | voto |
|---|---|---|
| 1 | 101 | 28 |
| 1 | 102 | 30 |
| 2 | 101 | 25 |
| 3 | 103 | 27 |
-- Creazione del database CREATE DATABASE IF NOT EXISTS universita; USE universita; -- Tabella STUDENTI CREATE TABLE studenti ( id INT PRIMARY KEY, nome VARCHAR(50), citta VARCHAR(50), eta INT ); INSERT INTO studenti VALUES (1, 'Mario', 'Roma', 22), (2, 'Laura', 'Milano', 20), (3, 'Luca', 'Roma', 23), (4, 'Anna', 'Napoli', 21); -- Tabella CORSI CREATE TABLE corsi ( id_corso INT PRIMARY KEY, nome_corso VARCHAR(50), docente VARCHAR(50) ); INSERT INTO corsi VALUES (101, 'Database', 'Rossi'), (102, 'Programmazione', 'Bianchi'), (103, 'Reti', 'Verdi'); -- Tabella ESAME (tabella di collegamento) CREATE TABLE ESAME ( id_studente INT, id_corso INT, voto INT, PRIMARY KEY (id_studente, id_corso), FOREIGN KEY (id_studente) REFERENCES studenti(id), FOREIGN KEY (id_corso) REFERENCES corsi(id_corso) ); INSERT INTO ESAME VALUES (1, 101, 28), (1, 102, 30), (2, 101, 25), (3, 103, 27);
La selezione filtra le righe di una tabella in base a una condizione. Restituisce solo le tuple (righe) che soddisfano una condizione.
🎯 Obiettivo: Trovare tutti gli studenti che vivono a Roma
| id | nome | città | età |
|---|---|---|---|
| 1 | Mario | Roma | 22 |
| 2 | Laura | Milano | 20 |
| 3 | Luca | Roma | 23 |
| 4 | Anna | Napoli | 21 |
| id | nome | città | età |
|---|---|---|---|
| 1 | Mario | Roma | 22 |
| 3 | Luca | Roma | 23 |
-- Selezione: tutti gli studenti di Roma SELECT * FROM studenti WHERE citta = 'Roma'; -- Selezione con più condizioni (AND): tutti gli studenti che vivono a Roma e hanno più di 21 anni SELECT * FROM studenti WHERE citta = 'Roma' AND eta > 21; -- Selezione con OR: tutti gli studenti che vivono a Roma o a Milano SELECT * FROM studenti WHERE citta = 'Roma' OR citta = 'Milano'; -- Selezione con IN (più elegante): tutti gli studenti che vivono a Roma o a Milano SELECT * FROM studenti WHERE citta IN ('Roma', 'Milano');
La selezione opera sulle RIGHE (orizzontalmente). Il numero di colonne resta invariato!
La proiezione seleziona solo alcune colonne dalla tabella, eliminando le altre. .
🎯 Obiettivo: Ottenere solo nome e città degli studenti
| nome | città | id | età |
|---|---|---|---|
| Mario | Roma | 1 | 22 |
| Laura | Milano | 2 | 20 |
| Luca | Roma | 3 | 23 |
| Anna | Napoli | 4 | 21 |
| nome | città |
|---|---|
| Mario | Roma |
| Laura | Milano |
| Luca | Roma |
| Anna | Napoli |
-- L'obiettivo è mostrare solo nome e città di tutti gli studenti -- Nascondiamo id ed età perché non servono per questa visualizzazione SELECT nome, citta FROM studenti; -- Per eliminare duplicati (come in algebra relazionale) SELECT DISTINCT citta FROM studenti; -- Risultato: Roma, Milano, Napoli (3 righe, non 4) -- Combinazione Selezione + Proiezione -- σ città='Roma' e poi π nome SELECT nome FROM studenti WHERE citta = 'Roma';
La proiezione opera sulle COLONNE (verticalmente). Il numero di righe può diminuire se si usa DISTINCT!
Il prodotto cartesiano combina ogni riga della prima tabella con ogni riga della seconda tabella. Genera tutte le combinazioni possibili.
| id | nome |
|---|---|
| 1 | Mario |
| 2 | Laura |
| id_studente | voto |
|---|---|
| 1 | 28 |
| 2 | 30 |
| id | nome | id_studente | voto |
|---|---|---|---|
| 1 | Mario | 1 | 28 |
| 1 | Mario | 2 | 30 |
| 2 | Laura | 1 | 28 |
| 2 | Laura | 2 | 30 |
Il computer non sa quali righe appartengono a chi. Combina ogni studente con ogni esame esistente nel database.
Sarà poi compito del JOIN filtrare solo quelle dove gli ID corrispondono.
| Riga | Studente (id) | Esame (id_studente) | Condizione: id = id_studente? |
|---|---|---|---|
| 1 | Mario (1) | 1 → voto 28 | ✅ 1 = 1 → MANTIENI |
| 2 | Mario (1) | 2 → voto 30 | ⛔ 1 ≠ 2 → SCARTA : IL VOTO NON E' DI MARIO |
| 3 | Laura (2) | 1 → voto 28 | ⛔ 2 ≠ 1 → SCARTA:IL VOTO NON E' DI LAURA |
| 4 | Laura (2) | 2 → voto 30 | ✅ 2 = 2 → MANTIENI |
Il JOIN non è altro che questo Prodotto Cartesiano a cui applichiamo una condizione.
Questo elimina le righe rosse (SCARTA) e tiene solo quelle verdi (MANTIENI).
-- Prodotto Cartesiano Puro (tutte le combinazioni) SELECT * FROM studenti CROSS JOIN esami;
L'INNER JOIN è un prodotto cartesiano filtrato da una condizione. Restituisce solo le righe dove la condizione specificata nel join è soddisfatta
💡 Nota: In SQL, JOIN e INNER JOIN sono equivalenti.
La parola INNER è opzionale: scrivere" FROM A JOIN B ON ... "è identico a "FROM A INNER JOIN B ON ...".
Entrambi restituiscono solo le righe che hanno corrispondenza in entrambe le tabelle.
🎯 Obiettivo: Collegare studenti ai loro esami
| id | nome |
|---|---|
| 1 | Mario |
| 2 | Laura |
| 3 | Luca |
| 4 | Anna |
| id_studente | id_corso | voto |
|---|---|---|
| 1 | 101 | 28 |
| 1 | 102 | 30 |
| 2 | 101 | 25 |
| 3 | 103 | 27 |
| id | nome | id_studente | id_corso | voto |
|---|---|---|---|---|
| 1 | Mario | 1 | 101 | 28 |
| 1 | Mario | 1 | 102 | 30 |
| 2 | Laura | 2 | 101 | 25 |
| 3 | Luca | 3 | 103 | 27 |
Anna (id=4) non appare nel risultato perché non ha esami (nessun match nella tabella ESAMI)!
Mario appare 2 volte perché ha sostenuto 2 esami.
-- INNER JOIN: mostra solo studenti che hanno almeno un esame -- L'obiettivo è abbinare ogni studente ai suoi esami (se esistono) SELECT * FROM studenti s INNER JOIN esami e ON s.id = e.id_studente; -- Equivalente: JOIN senza INNER produce lo stesso risultato -- Mostra gli stessi studenti con i loro esami SELECT * FROM studenti s JOIN esami e ON s.id = e.id_studente;
🎯 Obiettivo: visualizzare nome studente, nome corso e voto
| id | nome |
|---|---|
| 1 | Mario |
| 2 | Laura |
| id_studente | id_corso | voto |
|---|---|---|
| 1 | 101 | 28 |
| 1 | 102 | 30 |
| 2 | 101 | 25 |
| id | nome_corso | crediti |
|---|---|---|
| 101 | Matematica | 6 |
| 102 | Fisica | 9 |
| 103 | Chimica | 6 |
| nome | nome_corso | crediti | voto |
|---|---|---|---|
| Mario | Matematica | 6 | 28 |
| Mario | Fisica | 9 | 30 |
| Laura | Matematica | 6 | 25 |
-- L'obiettivo è ottenere un report completo: nome studente, corso frequentato e voto ricevuto -- Servono 3 tabelle perché ogni tabella contiene informazioni diverse che vanno combinate SELECT s.nome, c.nome_corso, c.crediti, e.voto FROM studenti s JOIN esami e ON s.id = e.id_studente JOIN corsi c ON e.id_corso = c.id;
🎯 Obiettivo: visualizzare cosa ha comprato ogni cliente
| id | nome | città |
|---|---|---|
| 1 | Marco | Roma |
| 2 | Giulia | Milano |
| id | id_cliente | id_prodotto | quantità |
|---|---|---|---|
| 1 | 1 | 10 | 2 |
| 2 | 1 | 20 | 1 |
| 3 | 2 | 10 | 3 |
| id | nome | prezzo |
|---|---|---|
| 10 | Laptop | 800 |
| 20 | Mouse | 25 |
| cliente | città | prodotto | quantità | prezzo_unit | totale |
|---|---|---|---|---|---|
| Marco | Roma | Laptop | 2 | 800 | 1600 |
| Marco | Roma | Mouse | 1 | 25 | 25 |
| Giulia | Milano | Laptop | 3 | 800 | 2400 |
-- L'obiettivo è mostrare: chi ha comprato cosa, quanto, e il totale speso (quantità × prezzo) SELECT c.nome AS cliente, c.città, p.nome AS prodotto, o.quantità, p.prezzo AS prezzo_unit, (o.quantità * p.prezzo) AS totale FROM clienti c JOIN ordini o ON c.id = o.id_cliente JOIN prodotti p ON o.id_prodotto = p.id;
🎯 Obiettivo: Trovare i turni di lavoro per ogni dipendente, abbinando sia il reparto che la sede (per evitare errori)
| id | nome | reparto | sede |
|---|---|---|---|
| 1 | Anna | Vendite | Milano |
| 2 | Marco | Vendite | Roma |
| 3 | Sara | IT | Milano |
| reparto | sede | giorno | orario |
|---|---|---|---|
| Vendite | Milano | Lunedì | 9-17 |
| Vendite | Roma | Martedì | 10-18 |
| IT | Milano | Lunedì | 10-18 |
| nome | reparto | sede | giorno | orario |
|---|---|---|---|---|
| Anna | Vendite | Milano | Lunedì | 9-17 |
| Marco | Vendite | Roma | Martedì | 10-18 |
| Sara | IT | Milano | Lunedì | 10-18 |
-- L'obiettivo è trovare il turno esatto per ogni dipendente -- Serve il match su DUE colonne per evitare errori
--(es. non assegnare il turno di Milano a chi lavora a Roma) SELECT d.nome, d.reparto, d.sede, t.giorno, t.orario FROM dipendenti d JOIN turni t ON d.reparto = t.reparto AND d.sede = t.sede; -- Cosa succede se usi solo UNA colonna? (ERRATO!) SELECT d.nome, d.reparto, t.giorno, t.orario FROM dipendenti d JOIN turni t ON d.reparto = t.reparto; -- Risultato SBAGLIATO: Anna (Vendite-Milano) avrebbe anche il turno di Roma! -- Join con condizioni multiple (AND) - Esempio generico SELECT * FROM tabella1 t1 JOIN tabella2 t2 ON t1.colonna_a = t2.colonna_a AND t1.colonna_b = t2.colonna_b;
🎯 Obiettivo: Trovare prodotti nel budget del cliente
| id | nome | budget |
|---|---|---|
| 1 | Marco | 500 |
| 2 | Giulia | 1000 |
| id | nome | prezzo |
|---|---|---|
| 1 | Mouse | 25 |
| 2 | Tastiera | 80 |
| 3 | Monitor | 300 |
| 4 | Laptop | 800 |
| cliente | budget | prodotto | prezzo |
|---|---|---|---|
| Marco | 500 | Mouse | 25 |
| Marco | 500 | Tastiera | 80 |
| Marco | 500 | Monitor | 300 |
| Giulia | 1000 | Mouse | 25 |
| Giulia | 1000 | Tastiera | 80 |
| Giulia | 1000 | Monitor | 300 |
| Giulia | 1000 | Laptop | 800 |
Marco può acquistare 3 prodotti (budget 500€), Giulia può acquistarne 4 (budget 1000€).
-- Theta Join con condizione >=: l'obiettivo è trovare prodotti che il cliente può permettersi -- Uniamo clienti e prodotti dove il budget del cliente è maggiore o uguale al prezzo del prodotto SELECT c.nome AS cliente, c.budget, p.nome AS prodotto, p.prezzo FROM clienti c JOIN prodotti p ON c.budget >= p.prezzo ORDER BY c.nome, p.prezzo; -- Altro esempio di Theta join: trovare prodotti in una fascia di prezzo SELECT * FROM fasce_prezzo f JOIN prodotti p ON p.prezzo >= f.min_prezzo AND p.prezzo <= f.max_prezzo;
🎯 Obiettivo: Trovare esami con voto alto di studenti specifici
ON: condizione per collegare le tabelle (relazione)
WHERE: filtro sui risultati (selezione)
-- Esami con voto >= 28 di studenti il cui nome inizia per 'M' SELECT s.nome, e.corso, e.voto FROM studenti s JOIN esami e ON s.id = e.id_studente -- fin quì esami con voto di tutti gli studenti WHERE s.nome LIKE 'M%' -- filtro studente AND e.voto >= 28; -- filtro voto -- Ordini del 2024 con importo > 100€ SELECT c.nome, o.data_ordine, o.importo FROM clienti c JOIN ordini o ON c.id = o.id_cliente WHERE YEAR(o.data_ordine) = 2024 AND o.importo > 100 ORDER BY o.data_ordine DESC;
Il LEFT JOIN restituisce tutte le righe della tabella di SINISTRA, più le corrispondenze dalla tabella di destra. Se non c'è corrispondenza, le colonne della destra sono NULL.
🎯 Obiettivo: Tutti gli studenti CON le loro iscrizioni (anche chi non è iscritto)
| id | nome |
|---|---|
| 1 | Mario |
| 2 | Laura |
| 3 | Luca |
| 4 | Anna |
| id_studente | id_corso | voto |
|---|---|---|
| 1 | 101 | 28 |
| 1 | 102 | 30 |
| 2 | 101 | 25 |
| 3 | 103 | 27 |
| id | nome | id_corso | voto |
|---|---|---|---|
| 1 | Mario | 101 | 28 |
| 1 | Mario | 102 | 30 |
| 2 | Laura | 101 | 25 |
| 3 | Luca | 103 | 27 |
| 4 | Anna | NULL | NULL |
Anna appare! Anche se non ha iscrizioni, viene inclusa con NULL nelle colonne di iscrizioni perché è nella tabella di SINISTRA.
Tutte le iscrizioni esistenti sono abbinate agli studenti corrispondenti.
-- LEFT JOIN: mostra TUTTI gli studenti, anche quelli senza esami -- L'obiettivo è visualizzare chi ha esami e chi no (NULL = nessun esame) SELECT s.id, s.nome, i.id_corso, i.voto FROM studenti s LEFT JOIN iscrizioni i ON s.id = i.id_studente; -- Trovare studenti SENZA esami (filtro su NULL) -- L'obiettivo è trovare chi NON ha sostenuto alcun esame SELECT s.* FROM studenti s LEFT JOIN iscrizioni i ON s.id = i.id_studente WHERE i.id_studente IS NULL; -- Risultato: solo Anna (non ha esami)
Il RIGHT JOIN è l'opposto del LEFT: restituisce tutte le righe della tabella di DESTRA, più le corrispondenze dalla sinistra. Se non c'è corrispondenza, le colonne della sinistra sono NULL.
🎯 Obiettivo: Voglio l'elenco di TUTTI i corsi offerti dall'università e visualizzare chi è iscritto. Se un corso non ha iscritti (es. Yoga), voglio vederlo lo stesso!
| id_studente | id_corso | voto |
|---|---|---|
| 1 | 101 | 28 |
| 1 | 102 | 30 |
| 2 | 101 | 25 |
| 3 | 103 | 27 |
| id_corso | nome_corso |
|---|---|
| 101 | Database |
| 102 | Programmazione |
| 103 | Reti |
| 104 | Yoga |
| nome_corso | id_studente | voto |
|---|---|---|
| Database | 1 | 28 |
| Database | 2 | 25 |
| Programmazione | 1 | 30 |
| Reti | 3 | 27 |
| Yoga | NULL | NULL |
Il corso di Yoga appare nel risultato (con NULL a sinistra) perché abbiamo chiesto di preservare la tabella di DESTRA (Corsi), anche se non c'erano match nella tabella Iscrizioni.
-- RIGHT JOIN: mostra TUTTI i corsi, anche quelli senza studenti iscritti -- L'obiettivo è visualizzare quali corsi hanno iscritti e quali no (NULL = nessuno iscritto) SELECT c.nome_corso, i.id_studente, i.voto FROM iscrizioni i RIGHT JOIN corsi c ON i.id_corso = c.id_corso; -- Risultato: Yoga appare con NULL perché nessuno è iscritto -- Database | 1 | 28 -- Database | 2 | 25 -- Programmazione| 1 | 30 -- Reti | 3 | 27 -- Yoga | NULL | NULL -- Equivalente: LEFT JOIN invertendo l'ordine delle tabelle -- Produce lo stesso risultato della query precedente ma usando LEFT JOIN SELECT c.nome_corso, i.id_studente, i.voto FROM corsi c LEFT JOIN iscrizioni i ON c.id_corso = i.id_corso;
L'esempio precedente ci mostra che "A RIGHT JOIN B" equivale a "B LEFT JOIN A". Per questo motivo, molti programmatori usano sempre LEFT JOIN e semplicemente invertono l'ordine delle tabelle invece di usare RIGHT JOIN. È questione di leggibilità!
Il FULL JOIN restituisce tutte le righe di ENTRAMBE le tabelle. Se non c'è corrispondenza in una delle due, le colonne mancanti sono NULL.
Bisogna simularlo con UNION di LEFT JOIN e RIGHT JOIN.
🎯 Obiettivo: visualizzare TUTTI gli studenti e TUTTE le iscrizioni, anche quelle senza corrispondenza
| id | nome |
|---|---|
| 1 | Mario |
| 2 | Laura |
| 3 | Luca |
| 4 | Anna |
| 5 | Marco |
| id_studente | id_corso | voto |
|---|---|---|
| 1 | 101 | 28 |
| 1 | 102 | 30 |
| 2 | 101 | 25 |
| 3 | 103 | 27 |
| NULL | 104 | -- |
| id | nome | id_studente | id_corso | voto | Note |
|---|---|---|---|---|---|
| 1 | Mario | 1 | 101 | 28 | ✅ Match perfetto |
| 1 | Mario | 1 | 102 | 30 | ✅ Match perfetto |
| 2 | Laura | 2 | 101 | 25 | ✅ Match perfetto |
| 3 | Luca | 3 | 103 | 27 | ✅ Match perfetto |
| 4 | Anna | NULL | NULL | NULL | ⚠️ Studentessa senza iscrizioni |
| 5 | Marco | NULL | NULL | NULL | ⚠️ Studente senza iscrizioni |
| NULL | NULL | NULL | 104 | -- | ⚠️ Iscrizione senza studente |
Anna e Marco appaiono con NULL nelle colonne delle iscrizioni (studenti senza iscrizioni)
Iscrizione 104 appare con NULL nelle colonne degli studenti (iscrizione senza studente associato)
Tutte le altre iscrizioni sono abbinate agli studenti corrispondenti
Nessun dato viene perso da nessuna delle due tabelle!
-- Inseriamo dati per creare uno scenario con "orfani" in entrambe le tabelle -- Studente senza iscrizioni (Marco) e iscrizione senza studente (corso 104) INSERT INTO studenti (id, nome, citta, eta) VALUES (5, 'Marco', 'Torino', 24); -- Studente senza iscrizioni INSERT INTO iscrizioni (id_studente, id_corso, voto) VALUES (NULL, 104, NULL); -- Iscrizione senza studente associato -- FULL JOIN simulato in MariaDB: UNION di LEFT e RIGHT JOIN -- L'obiettivo è ottenere TUTTI i record da entrambe le tabelle, -- incluse le righe che non hanno corrispondenza nell'altra tabella SELECT s.id, s.nome, i.id_studente, i.id_corso, i.voto FROM studenti s LEFT JOIN iscrizioni i ON s.id = i.id_studente UNION SELECT s.id, s.nome, i.id_studente, i.id_corso, i.voto FROM studenti s RIGHT JOIN iscrizioni i ON s.id = i.id_studente; -- NOTA: In altri DBMS (PostgreSQL, SQL Server) si usa direttamente FULL OUTER JOIN -- SELECT * FROM studenti s FULL OUTER JOIN iscrizioni i ON s.id = i.id_studente;
Solo righe con match in entrambe
Tutto A + match da B (o NULL)
Match da A (o NULL) + tutto B
Tutto A + match + tutto B
Il NATURAL JOIN è un'alternativa a "JOIN...ON..." ed èun join automatico (ovvero che non ha bisogno della specificazione ON t1.chiaveprimaria=t2.chiaveesterna): unisce le tabelle basandosi sulle colonne con lo stesso nome. Elimina automaticamente le colonne duplicate.
🎯 Esempio Corretto: Quando le colonne corrispondono semanticamente...
| id | nome |
|---|---|
| 1 | Mario |
| 2 | Laura |
| id | voto |
|---|---|
| 1 | 28 |
| 1 | 30 |
-- NATURAL JOIN: unisce automaticamente le tabelle sulle colonne con lo stesso nome -- L'obiettivo è collegare studenti e voti senza specificare ON (trova 'id' in comune) SELECT * FROM studenti NATURAL JOIN voti; -- Equivale a: JOIN voti ON studenti.id = voti.id
| id | nome | voto |
|---|---|---|
| 1 | Mario | 28 |
| 1 | Mario | 30 |
Il NATURAL JOIN può sembrare comodo, ma nasconde insidie gravi che possono corrompere silenziosamente i tuoi risultati.
Immagina di avere due tabelle dove "nome" appare in entrambe ma con significati diversi:
| id_dip | nome | stipendio |
|---|---|---|
| 1 | Mario | 30000 |
| 2 | Laura | 35000 |
| 3 | Giuseppe | 28000 |
| id_dip | nome | budget |
|---|---|---|
| 1 | Alpha | 50000 |
| 2 | Beta | 75000 |
| 1 | Gamma | 30000 |
🎯 Obiettivo: Trovare i progetti assegnati a ogni dipendente
SELECT * FROM dipendenti NATURAL JOIN progetti; La query equivale a: SELECT * FROM dipendenti JOIN progetti ON dipendenti.id_dip = progetti.id_dip AND dipendenti.nome = progetti.nome ← PROBLEMA
| id_dip | nome | stipendio | budget |
|---|---|---|---|
| (Nessun risultato - VUOTO!) | |||
⚠️ Nessun dipendente si chiama "Alpha", "Beta" o "Gamma"!
SELECT d.id_dip, d.nome AS dipendente, p.nome AS progetto, d.stipendio, p.budget FROM dipendenti d JOIN progetti p ON d.id_dip = p.id_dip;
| id_dip | dipendente | progetto | stipendio | budget |
|---|---|---|---|---|
| 1 | Mario | Alpha | 30000 | 50000 |
| 1 | Mario | Gamma | 30000 | 30000 |
| 2 | Laura | Beta | 35000 | 75000 |
Una query che funziona oggi può rompersi domani senza che tu modifichi nulla!
| id_cliente | data_ordine | totale |
|---|---|---|
| 1 | 2024-01-15 | 150.00 |
| 2 | 2024-01-16 | 200.00 |
| id_cliente | nome | |
|---|---|---|
| 1 | Mario | [email protected] |
| 2 | Laura | [email protected] |
SELECT * FROM ordini NATURAL JOIN clienti; -- Oggi funziona: unisce solo su id_cliente ✓
| id_cliente | data_ordine | totale | nome | |
|---|---|---|---|---|
| 1 | 2024-01-15 | 150.00 | Mario | mario@email.it |
| 2 | 2024-01-16 | 200.00 | Laura | laura@email.it |
⏰ 6 mesi dopo... Un altro sviluppatore aggiunge un campo "note" a entrambe le tabelle per scopi diversi.
| id_cliente | data_ordine | totale | note |
|---|---|---|---|
| 1 | 2024-01-15 | 150.00 | Urgente |
| 2 | 2024-01-16 | 200.00 | Standard |
| id_cliente | nome | note | |
|---|---|---|---|
| 1 | Mario | [email protected] | VIP |
| 2 | Laura | [email protected] | Nuovo |
SELECT * FROM ordini NATURAL JOIN clienti; -- ORA equivale a: SELECT * FROM ordini JOIN clienti ON ordini.id_cliente = clienti.id_cliente AND ordini.note = clienti.note ← DISASTRO! -- "Urgente" ≠ "VIP", "Standard" ≠ "Nuovo" -- Risultato: ZERO righe! La query si rompe silenziosamente!
| id_cliente | data_ordine | totale | note | nome | |
|---|---|---|---|---|---|
| (Nessun risultato - Query silenziosamente corrotta!) | |||||
A volte il NATURAL JOIN non fallisce completamente, ma perde alcune righe senza avvisarti:
| id_cat | nome_prodotto | stato |
|---|---|---|
| 1 | Laptop | attivo |
| 1 | Mouse | sospeso |
| 2 | Sedia | attivo |
| id_cat | nome_categoria | stato |
|---|---|---|
| 1 | Elettronica | attivo |
| 2 | Arredamento | attivo |
SELECT * FROM prodotti NATURAL JOIN categorie; -- Unisce su: id_cat AND stato -- Il Mouse ha stato='sospeso', la categoria ha stato='attivo' -- → Mouse ESCLUSO dal risultato!
| id_cat | stato | nome_prodotto | nome_categoria |
|---|---|---|---|
| 1 | attivo | Laptop | Elettronica |
| 2 | attivo | Sedia | Arredamento |
| id_cat | nome_prodotto | stato_prod | nome_categoria | stato_cat |
|---|---|---|---|---|
| 1 | Laptop | attivo | Elettronica | attivo |
| 1 | Mouse | sospeso | Elettronica | attivo |
| 2 | Sedia | attivo | Arredamento | attivo |
| Problema | Conseguenza |
|---|---|
| Colonne omonime casuali | Join su campi non correlati → risultati vuoti o errati |
| Schema che evolve | Query che funzionano oggi si rompono domani senza preavviso |
| Perdita dati silenziosa | Righe escluse senza errori, impossibile debuggare |
| Mancanza di chiarezza | Impossibile capire su quali colonne avviene il join leggendo la query |
-- ❌ MAI fare questo in produzione: SELECT * FROM tabella1 NATURAL JOIN tabella2; -- ✅ SEMPRE specificare la condizione: SELECT t1.*, t2.colonna FROM tabella1 t1 JOIN tabella2 t2 ON t1.id = t2.id_riferimento;
📌 Il JOIN esplicito è: più verboso, ma chiaro, prevedibile, manutenibile e sicuro.
La clausola USING è una sintassi alternativa a ON per specificare le condizioni di join quando le colonne di collegamento hanno lo stesso nome in entrambe le tabelle. È particolarmente utile quando si lavora con chiavi esterne che mantengono lo stesso nome della chiave primaria di riferimento. Quindi "JOIN ... USING... è un'alternativa a "JOIN... ON..."; un'ulteriore alternativa oltre a "NATURAL JOIN"
La clausola USING richiede che le colonne utilizzate abbiano lo stesso identico nome in entrambe le tabelle coinvolte nel join.
Unire le tabelle STUDENTI e ESAMI dove studenti.id = esami.id
La colonna deve chiamarsi id in entrambe le tabelle per poter usare USING.
🎯 Obiettivo: Collegare studenti ai loro esami utilizzando entrambi i metodi
| id | nome |
|---|---|
| 1 | Mario |
| 2 | Laura |
| 3 | Luca |
| id | id_corso | voto |
|---|---|---|
| 1 | 101 | 28 |
| 1 | 102 | 30 |
| 2 | 101 | 25 |
-- JOIN con ON: bisogna specificare tabella.colonna per entrambi i lati -- L'obiettivo è collegare studenti ed esami tramite l'id comune SELECT * FROM studenti JOIN esami ON studenti.id = esami.id;
| studenti.id | nome | esami.id | id_corso | voto |
|---|---|---|---|---|
| 1 | Mario | 1 | 101 | 28 |
| 1 | Mario | 1 | 102 | 30 |
| 2 | Laura | 2 | 101 | 25 |
Con ON, vengono mostrate entrambe le colonne id (quella in STUDENTI e quella in ESAMI), anche se contengono gli stessi valori e si riferiscono allo stesso soggetto (id ello studente). Questo può creare confusione ma basta nel SELECT specificare le colonne per ovviare a ciò
-- USING è più pulito: non serve specificare le colonne SELECT * FROM studenti JOIN esami USING (id);
| id | nome | id_corso | voto |
|---|---|---|---|
| 1 | Mario | 101 | 28 |
| 1 | Mario | 102 | 30 |
| 2 | Laura | 101 | 25 |
Con USING, la colonna id appare una sola volta nel risultato! Il database sa che è la stessa colonna in entrambe le tabelle e la mostra una sola volta. Molto più pulito!
| studenti.id | nome | esami.id | voto |
|---|---|---|---|
| 1 | Mario | 1 | 28 |
| 1 | Mario | 1 | 30 |
❌ Due colonne ID identiche → confusione
| id | nome | voto |
|---|---|---|
| 1 | Mario | 28 |
| 1 | Mario | 30 |
✅ Una sola colonna ID → più chiaro
🎯 Obiettivo: USING funziona con tutti i tipi di JOIN!
-- LEFT JOIN con USING: tutti gli studenti, anche senza esami SELECT * FROM studenti LEFT JOIN esami USING (id); -- RIGHT JOIN con USING: tutti gli esami, anche senza studenti SELECT * FROM studenti RIGHT JOIN esami USING (id);
Quando usi USING con LEFT JOIN o RIGHT JOIN, puoi facilmente identificare le righe senza corrispondenza perché le colonne della tabella "perduta" saranno tutte NULL.
Se le colonne hanno nomi diversi, USING non funziona! Devi usare ON.
| id | nome |
|---|---|
| 1 | Mario |
| id_studente | id_corso |
|---|---|
| 1 | 101 |
-- ❌ Questo dà ERRORE! La colonna 'id' non esiste in ISCRIZIONI -- SELECT * FROM studenti JOIN iscrizioni USING (id);-> errore
-- ✅ USA ON con nomi diversi SELECT * FROM studenti JOIN iscrizioni ON studenti.id = iscrizioni.id_studente;
🎯 Obiettivo: USING può unire su più colonne contemporaneamente!
-- Join su due colonne che devono avere lo stesso nome SELECT * FROM tabella1 JOIN tabella2 USING (colonna1, colonna2); -- Equivalente a: -- SELECT * FROM tabella1 -- JOIN tabella2 ON tabella1.colonna1 = tabella2.colonna1 -- AND tabella1.colonna2 = tabella2.colonna2;
| Scenario | Clausola Consigliata |
|---|---|
| Colonne con lo stesso nome | ✅ USING (più pulito) |
| Colonne con nomi diversi | ❌ ON (obbligatorio) |
| Condizioni di join complesse (AND/OR) | ❌ ON (necessario) |
| Join su più colonne con stesso nome | ✅ USING (col1, col2) |
| Vuoi evitare colonne duplicate nel risultato | ✅ USING (più elegante) |
Se le colonne hanno lo stesso nome e vuoi un risultato più pulito, usa USING. Se i nomi sono diversi o hai bisogno di condizioni complesse, usa ON. Entrambi sono validi e standard SQL, la scelta dipende dalla situazione!
| Aspetto | ON | USING |
|---|---|---|
| Flessibilità nomi colonna | Colonne possono avere nomi diversi | Deve avere lo stesso nome |
| Clausola | ON tabella1.colonna1 = tabella2.colonna2 | USING (colonna_comune) |
| Leggibilità | Più verboso, ma esplicito | Più conciso e pulito |
| Output | Mantiene entrambe le colonne | Mostra una sola colonna |
| Uso tipico | Join con nomi diversi o condizioni complesse | Join su chiavi con nomi identici |
Per UNION, INTERSECT e EXCEPT le tabelle devono essere union-compatibili ovvero avere stesso numero di colonne e tipi compatibili!
Per gli esempi qui sotto, assumiamo di avere due tabelle studenti_roma e studenti_db.
| id | nome |
|---|---|
| 1 | Mario |
| 3 | Luca |
| id | nome |
|---|---|
| 1 | Mario |
| 2 | Laura |
L'unione combina i risultati di due query, restituendo tutte le righe di entrambe senza duplicati (per default).
| id | nome |
|---|---|
| 1 | Mario |
| 3 | Luca |
| id | nome |
|---|---|
| 1 | Mario |
| 2 | Laura |
| id | nome |
|---|---|
| 1 | Mario |
| 2 | Laura |
| 3 | Luca |
-- UNION: combina le righe di due tabelle, eliminando i duplicati -- L'obiettivo è avere un elenco unico di tutti gli studenti (Roma + DB) SELECT * FROM studenti_roma UNION SELECT * FROM studenti_db; -- UNION ALL: combina le righe di due tabelle, MANTENENDO i duplicati -- L'obiettivo è avere TUTTE le righe, anche se si ripetono (Mario appare 2 volte) SELECT * FROM studenti_roma UNION ALL SELECT * FROM studenti_db;
L'intersezione restituisce solo le righe che appaiono in entrambe le query.
| id | nome |
|---|---|
| 1 | Mario |
| 3 | Luca |
| id | nome |
|---|---|
| 1 | Mario |
| 2 | Laura |
| id | nome |
|---|---|
| 1 | Mario |
Bisogna simularlo con JOIN o subquery.
-- INTERSECT simulato con IN: trova studenti presenti in ENTRAMBE le tabelle -- L'obiettivo è trovare chi è sia a Roma che iscritto al DB (solo Mario) SELECT * FROM studenti_roma WHERE id IN ( SELECT id FROM studenti_db ); -- INTERSECT simulato con JOIN: alternativa più efficiente -- L'obiettivo è lo stesso: trovare studenti comuni alle due tabelle SELECT a.* FROM studenti_roma a JOIN studenti_db b ON a.id = b.id; -- NOTA: In altri DBMS (PostgreSQL, SQL Server) si usa direttamente INTERSECT -- SELECT * FROM studenti_roma INTERSECT SELECT * FROM studenti_db;
La differenza restituisce le righe che sono nella prima tabella ma NON nella seconda.
| id | nome |
|---|---|
| 1 | Mario |
| 3 | Luca |
| id | nome |
|---|---|
| 1 | Mario |
| 2 | Laura |
| id | nome |
|---|---|
| 3 | Luca |
Bisogna simularlo con LEFT JOIN + WHERE NULL o NOT IN.
-- EXCEPT simulato con NOT IN: trova studenti che sono presenti SOLO nella prima tabella -- L'obiettivo è trovare chi è a Roma ma NON è iscritto al DB (solo Luca) SELECT * FROM studenti_roma WHERE id NOT IN ( SELECT id FROM studenti_db ); -- EXCEPT simulato con LEFT JOIN + IS NULL: alternativa più efficiente -- L'obiettivo è lo stesso: trovare studenti presenti solo nella prima tabella SELECT a.* FROM studenti_roma a LEFT JOIN studenti_db b ON a.id = b.id WHERE b.id IS NULL; -- NOTA: In altri DBMS (PostgreSQL, SQL Server) si usa direttamente EXCEPT -- SELECT * FROM studenti_roma EXCEPT SELECT * FROM studenti_db;
Filtra righe
WHERE condizione
Seleziona colonne
SELECT col1, col2
Tutte le possibili combinazioni di una riga della prima tabella con una riga della seconda tabella
CROSS JOIN
Righe della prima tabella combinate con le righe della seconda tabella collegate
JOIN ... ON
Join automatico su colonne uguali
NATURAL JOIN
Tutto A + righe B collegate con A
LEFT JOIN ... ON
righe A collegate con B + tutto B
RIGHT JOIN ... ON
Tutto A + tutto B
LEFT UNION RIGHT
Unisce righe A a righe B
UNION / UNION ALL
Righe in comune
JOIN su tutto
righe di A non presenti in B
LEFT JOIN + IS NULL
-- ═══════════════════════════════════════════════════════════════ -- OPERATORI RELAZIONALI CON MARIADB -- ═══════════════════════════════════════════════════════════════ -- SELEZIONE (σ) - filtra righe SELECT * FROM tabella WHERE condizione; -- PROIEZIONE (π) - seleziona colonne SELECT col1, col2 FROM tabella; SELECT DISTINCT col1 FROM tabella; -- senza duplicati -- PRODOTTO CARTESIANO (×) SELECT * FROM A CROSS JOIN B; SELECT * FROM A, B; -- equivalente -- JOIN... ON... (⋈) - solo match SELECT * FROM A JOIN B ON A.id = B.id; -- JOIN... USING.. (⋈) - solo match SELECT * FROM A JOIN B USING (id) -- NATURAL JOIN - join automatico SELECT * FROM A NATURAL JOIN B; -- LEFT JOIN (⟕) - tutto A + match B SELECT * FROM A LEFT JOIN B ON A.id = B.id; -- RIGHT JOIN (⟖) - match A + tutto B SELECT * FROM A RIGHT JOIN B ON A.id = B.id; -- RIGHT JOIN equivale a LEFT JOIN con tabelle invertite "A RIGHT JOIN B" equivale a "B LEFT JOIN A" -- FULL JOIN (⟗) - simulato in MariaDB SELECT * FROM A LEFT JOIN B ON A.id = B.id UNION SELECT * FROM A RIGHT JOIN B ON A.id = B.id; -- UNIONE (∪) SELECT * FROM A UNION SELECT * FROM B; -- senza duplicati SELECT * FROM A UNION ALL SELECT * FROM B; -- con duplicati -- INTERSEZIONE (∩) - simulato in MariaDB SELECT * FROM A WHERE id IN (SELECT id FROM B); -- DIFFERENZA (−) - simulato in MariaDB SELECT * FROM A WHERE id NOT IN (SELECT id FROM B); -- oppure SELECT A.* FROM A LEFT JOIN B ON A.id = B.id WHERE B.id IS NULL;