Lezione 17 – INNER JOIN spiegato: Unire i dati da più tabelle in MySQL

Scopri come usare la clausola INNER JOIN in MySQL per combinare righe da due o più tabelle basandoti su una condizione comune. Questa lezione, parte del nostro corso di MySQL, ti guiderà passo dopo passo con esempi pratici e spiegazioni chiare.

Introduzione ai JOIN e l'importanza di INNER JOIN

Benvenuti alla diciassettesima lezione del nostro corso "Impara MySQL in 45 lezioni"! Finora abbiamo imparato a creare tabelle, inserire dati, selezionare, aggiornare ed eliminare record all'interno di singole tabelle. Ma cosa succede quando i dati di cui abbiamo bisogno sono distribuiti su più tabelle correlate tra loro? È qui che entrano in gioco le operazioni di JOIN.

Nel mondo reale, i database relazionali sono progettati per archiviare i dati in modo efficiente e senza ridondanze. Questo approccio, noto come normalizzazione, implica spesso la divisione delle informazioni in diverse tabelle più piccole, ognuna focalizzata su un'entità specifica (ad esempio, clienti, ordini, prodotti). Per ottenere una visione completa di un'informazione, come "quali prodotti ha acquistato un determinato cliente?", dobbiamo essere in grado di collegare queste tabelle tra loro.

La clausola JOIN in SQL è lo strumento che ci permette di fare proprio questo: combinare righe da due o più tabelle in base a colonne correlate tra loro. Esistono diversi tipi di JOIN, ma il più comune e fondamentale è l'INNER JOIN. Questa lezione sarà interamente dedicata a comprenderlo in profondità.

Perché INNER JOIN è così importante? Perché è la base per query complesse che estraggono dati significativi da database ben strutturati. Senza INNER JOIN, la maggior parte delle applicazioni web moderne che interagiscono con un database relazionale non sarebbe in grado di funzionare in modo efficace. Capire l'INNER JOIN è un passo cruciale per diventare uno sviluppatore web competente e per manipolare dati in modo professionale.

Comprendere il concetto di INNER JOIN

Immaginate di avere due puzzle diversi. Ogni puzzle rappresenta una tabella. L'INNER JOIN è come trovare i pezzi che si incastrano perfettamente tra i due puzzle e unirli per formare un'immagine più grande e completa. In termini di database, questo significa combinare righe da due o più tabelle solo quando esiste una corrispondenza nei valori di una colonna specificata in entrambe le tabelle.

Consideriamo due tabelle di esempio:

  1. Tabella clienti: Contiene informazioni sui clienti (es. id_cliente, nome, email).
  2. Tabella ordini: Contiene informazioni sugli ordini (es. id_ordine, id_cliente, data_ordine, totale).

Notate che la tabella ordini ha una colonna id_cliente. Questa colonna è una chiave esterna (Foreign Key) che si riferisce alla colonna id_cliente nella tabella clienti, che è una chiave primaria (Primary Key). Questa relazione è il ponte che ci permette di collegare i dati. Un INNER JOIN unirà una riga della tabella clienti con una riga della tabella ordini solo se il valore di id_cliente è uguale in entrambe le tabelle.

Se un cliente non ha ordini, o se un ordine ha un id_cliente che non esiste nella tabella clienti (cosa che non dovrebbe accadere se le chiavi esterne sono configurate correttamente con vincoli di integrità referenziale), quelle righe non appariranno nel risultato di un INNER JOIN. L'INNER JOIN restituisce solo le righe che hanno una corrispondenza in entrambe le tabelle coinvolte. È come l'intersezione di due insiemi in matematica.

Questa caratteristica è fondamentale: INNER JOIN filtra implicitamente i dati, mostrando solo le relazioni esistenti e valide. Questo lo rende ideale per scenari in cui si desidera visualizzare solo i dati correlati che hanno una controparte in tutte le tabelle coinvolte nell'unione.

Chiavi primarie e chiavi esterne: Il cuore delle relazioni

Perché un INNER JOIN funzioni correttamente, è essenziale che le tabelle siano correlate tramite chiavi. Una chiave primaria (PRIMARY KEY) identifica in modo univoco ogni riga in una tabella. Una chiave esterna (FOREIGN KEY) in una tabella è una colonna (o un insieme di colonne) che fa riferimento alla chiave primaria di un'altra tabella. È attraverso queste chiavi che MySQL (e qualsiasi altro sistema di gestione di database relazionali) capisce come collegare i dati tra diverse tabelle. La condizione ON nell'INNER JOIN solitamente specifica l'uguaglianza tra una chiave esterna e la sua corrispondente chiave primaria.

Sintassi di base di INNER JOIN

La sintassi generale per un INNER JOIN tra due tabelle è la seguente:

SELECT colonna1, colonna2, ...
FROM nome_tabella1
INNER JOIN nome_tabella2
ON nome_tabella1.colonna_comune = nome_tabella2.colonna_comune;

Analizziamo i componenti:

  • SELECT colonna1, colonna2, ...: Qui elenchiamo le colonne che vogliamo vedere nel nostro risultato finale. Queste colonne possono provenire da qualsiasi delle tabelle coinvolte nel JOIN.
  • FROM nome_tabella1: Specifica la prima tabella da cui iniziare la query.
  • INNER JOIN nome_tabella2: Indica che vogliamo unire nome_tabella1 con nome_tabella2 usando un INNER JOIN.
  • ON nome_tabella1.colonna_comune = nome_tabella2.colonna_comune: Questa è la condizione di join. È la regola che MySQL usa per capire quali righe delle due tabelle devono essere combinate. Solitamente, si tratta dell'uguaglianza tra una chiave primaria di una tabella e la sua corrispondente chiave esterna nell'altra tabella. È fondamentale specificare il nome della tabella prima del nome della colonna (nome_tabella.colonna) quando le colonne hanno lo stesso nome in entrambe le tabelle, per evitare ambiguità.

Esempio pratico 1: Unire Clienti e Ordini

Per illustrare l'uso di INNER JOIN, creeremo un piccolo database di esempio con due tabelle: clienti e ordini. Poi useremo INNER JOIN per visualizzare le informazioni combinate.

Preparazione del database di esempio

Innanzitutto, connettiti al tuo server MySQL e crea un nuovo database (se non ne hai già uno per il corso) e le tabelle necessarie:

-- Creazione del database
CREATE DATABASE IF NOT EXISTS corso_mysql;
USE corso_mysql;

-- Creazione della tabella 'clienti'
CREATE TABLE IF NOT EXISTS clienti (
    id_cliente INT AUTO_INCREMENT PRIMARY KEY,
    nome VARCHAR(100) NOT NULL,
    cognome VARCHAR(100) NOT NULL,
    email VARCHAR(255) UNIQUE NOT NULL
);

-- Creazione della tabella 'ordini'
CREATE TABLE IF NOT EXISTS ordini (
    id_ordine INT AUTO_INCREMENT PRIMARY KEY,
    id_cliente INT NOT NULL,
    data_ordine DATE NOT NULL,
    totale DECIMAL(10, 2) NOT NULL,
    FOREIGN KEY (id_cliente) REFERENCES clienti(id_cliente)
);

-- Inserimento di dati di esempio nella tabella 'clienti'
INSERT INTO clienti (nome, cognome, email) VALUES
('Mario', 'Rossi', 'mario.rossi@example.com'),
('Anna', 'Verdi', 'anna.verdi@example.com'),
('Luca', 'Bianchi', 'luca.bianchi@example.com'),
('Sara', 'Neri', 'sara.neri@example.com');

-- Inserimento di dati di esempio nella tabella 'ordini'
INSERT INTO ordini (id_cliente, data_ordine, totale) VALUES
(1, '2023-01-15', 150.75),
(1, '2023-02-20', 300.00),
(2, '2023-03-10', 50.20),
(4, '2023-04-05', 220.50),
(2, '2023-05-12', 120.00),
(1, '2023-06-01', 75.90);

-- Inserimento di un cliente senza ordini per dimostrazione
INSERT INTO clienti (nome, cognome, email) VALUES
('Giulia', 'Gialli', 'giulia.gialli@example.com');

-- Tentativo di inserire un ordine con un id_cliente non esistente (fallirà se FOREIGN KEY è attiva)
-- INSERT INTO ordini (id_cliente, data_ordine, totale) VALUES (99, '2023-07-01', 100.00);

Esegui queste query nel tuo client MySQL (es. MySQL Workbench, phpMyAdmin o terminale). Assicurati che non ci siano errori.

La query INNER JOIN

Ora, vogliamo ottenere una lista di tutti gli ordini, ma vogliamo anche vedere il nome e il cognome del cliente che ha effettuato ciascun ordine. Ecco come usiamo INNER JOIN:

SELECT
    c.nome,
    c.cognome,
    o.id_ordine,
    o.data_ordine,
    o.totale
FROM
    clienti AS c
INNER JOIN
    ordini AS o ON c.id_cliente = o.id_cliente;

Spiegazione dettagliata dell'esempio

Analizziamo la query e il suo risultato:

  • SELECT c.nome, c.cognome, o.id_ordine, o.data_ordine, o.totale: Stiamo selezionando il nome e cognome dalla tabella clienti (identificata con l'alias c) e l'ID dell'ordine, la data dell'ordine e il totale dalla tabella ordini (identificata con l'alias o). L'uso degli alias (AS c, AS o) è una best practice molto comune per rendere le query più concise e leggibili, specialmente quando si lavora con molte tabelle o nomi di tabelle lunghi.
  • FROM clienti AS c: Specifichiamo che la tabella principale è clienti, e le diamo l'alias c.
  • INNER JOIN ordini AS o ON c.id_cliente = o.id_cliente: Questa è la parte cruciale. Diciamo a MySQL di unire la tabella clienti (c) con la tabella ordini (o). La condizione ON c.id_cliente = o.id_cliente indica che le righe devono essere combinate solo se il valore nella colonna id_cliente di clienti è uguale al valore nella colonna id_cliente di ordini.

Il risultato di questa query sarà una tabella che mostra solo le righe dove esiste una corrispondenza di id_cliente in entrambe le tabelle. Ogni riga del risultato conterrà le informazioni del cliente e dell'ordine corrispondente. Noterete che il cliente 'Giulia Gialli' non comparirà nel risultato, perché non ha ordini associati nella tabella ordini. Questo è il comportamento tipico dell'INNER JOIN: mostra solo le intersezioni.

nome cognome id_ordine data_ordine totale
Mario Rossi 1 2023-01-15 150.75
Mario Rossi 2 2023-02-20 300.00
Anna Verdi 3 2023-03-10 50.20
Sara Neri 4 2023-04-05 220.50
Anna Verdi 5 2023-05-12 120.00
Mario Rossi 6 2023-06-01 75.90

Come potete vedere, per il cliente 'Mario Rossi' ci sono tre righe, una per ciascuno dei suoi ordini. Questo dimostra come INNER JOIN espande il set di risultati per includere tutte le combinazioni valide basate sulla condizione ON.

Unire più di due tabelle con INNER JOIN

Spesso avrai bisogno di unire più di due tabelle per ottenere tutti i dati necessari. L'INNER JOIN può essere concatenato per unire quante tabelle desideri. Ogni INNER JOIN successivo si basa sul risultato dell'unione precedente.

Continuiamo con il nostro esempio. Supponiamo di voler sapere non solo chi ha fatto un ordine, ma anche quali prodotti specifici erano inclusi in quell'ordine e il loro prezzo. Questo richiederà l'introduzione di altre due tabelle:

  1. Tabella prodotti: Contiene informazioni sui prodotti (es. id_prodotto, nome_prodotto, prezzo).
  2. Tabella dettagli_ordine: Collega ordini a prodotti, specificando quali prodotti sono in quale ordine e la quantità (es. id_dettaglio, id_ordine, id_prodotto, quantita, prezzo_unitario).

Estensione del database di esempio

Aggiungiamo queste tabelle e i relativi dati:

-- Creazione della tabella 'prodotti'
CREATE TABLE IF NOT EXISTS prodotti (
    id_prodotto INT AUTO_INCREMENT PRIMARY KEY,
    nome_prodotto VARCHAR(255) NOT NULL,
    descrizione TEXT,
    prezzo DECIMAL(10, 2) NOT NULL
);

-- Creazione della tabella 'dettagli_ordine'
CREATE TABLE IF NOT EXISTS dettagli_ordine (
    id_dettaglio INT AUTO_INCREMENT PRIMARY KEY,
    id_ordine INT NOT NULL,
    id_prodotto INT NOT NULL,
    quantita INT NOT NULL,
    prezzo_unitario DECIMAL(10, 2) NOT NULL,
    FOREIGN KEY (id_ordine) REFERENCES ordini(id_ordine),
    FOREIGN KEY (id_prodotto) REFERENCES prodotti(id_prodotto)
);

-- Inserimento di dati di esempio nella tabella 'prodotti'
INSERT INTO prodotti (nome_prodotto, descrizione, prezzo) VALUES
('Laptop X1', 'Potente laptop per professionisti', 1200.00),
('Mouse Wireless', 'Mouse ergonomico senza fili', 25.00),
('Tastiera Meccanica', 'Tastiera da gaming retroilluminata', 80.00),
('Monitor Ultrawide', 'Monitor 34 pollici curvo', 450.00),
('Webcam Full HD', 'Webcam per videoconferenze', 50.00);

-- Inserimento di dati di esempio nella tabella 'dettagli_ordine'
INSERT INTO dettagli_ordine (id_ordine, id_prodotto, quantita, prezzo_unitario) VALUES
(1, 1, 1, 1200.00), -- Ordine 1: 1x Laptop X1
(1, 2, 2, 25.00),  -- Ordine 1: 2x Mouse Wireless
(2, 3, 1, 80.00),  -- Ordine 2: 1x Tastiera Meccanica
(2, 4, 1, 450.00), -- Ordine 2: 1x Monitor Ultrawide
(3, 5, 1, 50.00),  -- Ordine 3: 1x Webcam Full HD
(4, 1, 1, 1200.00), -- Ordine 4: 1x Laptop X1
(4, 3, 1, 80.00),  -- Ordine 4: 1x Tastiera Meccanica
(5, 2, 3, 25.00),  -- Ordine 5: 3x Mouse Wireless
(6, 5, 1, 50.00);  -- Ordine 6: 1x Webcam Full HD

Esegui anche queste query per popolare le nuove tabelle.

Query con INNER JOIN multipli

Ora, vogliamo vedere il nome del cliente, l'ID dell'ordine, la data dell'ordine, il nome del prodotto, la quantità e il prezzo unitario per ogni articolo ordinato. Questo richiede unire clienti con ordini, ordini con dettagli_ordine e dettagli_ordine con prodotti.

SELECT
    c.nome AS nome_cliente,
    c.cognome AS cognome_cliente,
    o.id_ordine,
    o.data_ordine,
    p.nome_prodotto,
    do.quantita,
    do.prezzo_unitario
FROM
    clienti AS c
INNER JOIN
    ordini AS o ON c.id_cliente = o.id_cliente
INNER JOIN
    dettagli_ordine AS do ON o.id_ordine = do.id_ordine
INNER JOIN
    prodotti AS p ON do.id_prodotto = p.id_prodotto
ORDER BY
    o.id_ordine, c.cognome, c.nome;

Analisi della query con JOIN multipli

  • Abbiamo aggiunto alias descrittivi per le colonne (nome_cliente, cognome_cliente) per rendere il risultato più chiaro.
  • Il primo INNER JOIN (clienti con ordini) è lo stesso di prima.
  • Il secondo INNER JOIN collega il risultato della prima unione (clienti JOIN ordini) con la tabella dettagli_ordine tramite o.id_ordine = do.id_ordine.
  • Il terzo INNER JOIN collega il risultato delle unioni precedenti con la tabella prodotti tramite do.id_prodotto = p.id_prodotto.
  • ORDER BY o.id_ordine, c.cognome, c.nome è stato aggiunto per ordinare i risultati e renderli più facili da leggere.

Il risultato sarà una tabella molto più ampia, che combina dati da tutte e quattro le tabelle, fornendo una vista completa di ogni articolo ordinato, chi l'ha ordinato e quando. Ancora una volta, solo le righe che hanno una corrispondenza in tutte le tabelle coinvolte appariranno nel risultato.

Questo esempio dimostra la potenza e la flessibilità dell'INNER JOIN nel costruire query complesse che estraggono informazioni significative da database relazionali ben normalizzati.

Errori comuni e come risolverli

Quando si inizia a usare INNER JOIN, è facile commettere alcuni errori. Conoscerli ti aiuterà a diagnosticarli e risolverli rapidamente.

1. Dimenticare la clausola ON

Errore:

SELECT c.nome, o.id_ordine
FROM clienti AS c
INNER JOIN ordini AS o;

Risultato: MySQL restituirà un errore di sintassi o, in alcuni casi, un CROSS JOIN implicito (che unisce ogni riga della prima tabella con ogni riga della seconda tabella), producendo un numero enorme di righe non significative. Soluzione: Includere sempre la clausola ON per specificare la condizione di join.

SELECT c.nome, o.id_ordine
FROM clienti AS c
INNER JOIN ordini AS o ON c.id_cliente = o.id_cliente;

2. Condizione ON errata o colonne non corrispondenti

Errore:

SELECT c.nome, o.id_ordine
FROM clienti AS c
INNER JOIN ordini AS o ON c.nome = o.totale; -- Errore logico: confrontare nome con totale

Risultato: La query potrebbe essere eseguita senza errori di sintassi, ma il set di risultati sarà vuoto o non quello atteso, perché non ci saranno corrispondenze tra c.nome e o.totale. Soluzione: Assicurati che la condizione ON confronti colonne che sono logicamente correlate e che contengono valori comparabili (es. chiavi primarie con chiavi esterne).

3. Ambiguità delle colonne (stesso nome in tabelle diverse)

Errore:

SELECT id_cliente, nome, id_ordine
FROM clienti
INNER JOIN ordini ON clienti.id_cliente = ordini.id_cliente;

Risultato: MySQL restituirà un errore come Column 'id_cliente' in field list is ambiguous. Questo perché entrambe le tabelle clienti e ordini hanno una colonna id_cliente, e MySQL non sa quale delle due vuoi selezionare. Soluzione: Specifica sempre la tabella di provenienza della colonna quando i nomi delle colonne sono duplicati o potrebbero esserlo. L'uso degli alias è la soluzione più elegante.

SELECT c.id_cliente, c.nome, o.id_ordine
FROM clienti AS c
INNER JOIN ordini AS o ON c.id_cliente = o.id_cliente;

4. Nessun risultato o meno risultati del previsto

Problema: La query INNER JOIN viene eseguita, ma restituisce un set di risultati vuoto o con meno righe di quanto ti aspetti. Causa: Questo è spesso dovuto al fatto che non ci sono corrispondenze tra le tabelle per la condizione ON specificata. Ricorda, INNER JOIN restituisce solo le righe che hanno una corrispondenza in entrambe le tabelle. Soluzione:

  • Verifica che i dati nelle colonne di join siano effettivamente presenti e corrispondenti in entrambe le tabelle. Potresti aver inserito dati con chiavi esterne non valide o mancanti.
  • Controlla la condizione ON per assicurarti che sia corretta e che le colonne siano del tipo di dato appropriato per il confronto.
  • Se ti aspetti di vedere anche le righe senza corrispondenza, potresti aver bisogno di un tipo diverso di JOIN, come LEFT JOIN (che sarà trattato in una lezione futura).

Quando usare INNER JOIN: Scenari tipici

L'INNER JOIN è la scelta predefinita per unire tabelle nella maggior parte dei casi d'uso. Ecco alcuni scenari tipici in cui è indispensabile:

  • Recuperare dati correlati: Ogni volta che hai bisogno di informazioni che sono divise tra due o più tabelle che hanno una relazione uno-a-molti o uno-a-uno. Ad esempio, ottenere il nome del reparto per un impiegato, o l'autore di un libro.
  • Filtrare dati: Se vuoi vedere solo i record che hanno una corrispondenza in tutte le tabelle. Ad esempio, mostrare solo i clienti che hanno effettivamente effettuato un ordine, escludendo quelli che non hanno mai comprato nulla.
  • Reportistica: Per generare report complessi che aggregano dati da diverse fonti. Ad esempio, un report delle vendite che mostra i prodotti venduti, i clienti che li hanno acquistati e la data dell'acquisto.
  • Verifica dell'integrità dei dati: Sebbene non sia il suo scopo primario, puoi usarlo per identificare record orfani (se non ci sono vincoli di chiave esterna) o dati inconsistenti, cercando l'assenza di corrispondenze (anche se LEFT JOIN è più adatto per questo).

In generale, se la tua domanda inizia con "Mostra tutti i X che hanno un Y", dove X e Y sono entità in tabelle diverse, molto probabilmente avrai bisogno di un INNER JOIN.

INNER JOIN vs. altri tipi di JOIN (breve accenno)

È importante capire che INNER JOIN è solo uno dei tipi di JOIN disponibili in SQL. Brevemente, per contestualizzare:

  • LEFT JOIN (o LEFT OUTER JOIN): Restituisce tutte le righe dalla tabella di sinistra (la prima specificata nel FROM) e le righe corrispondenti dalla tabella di destra. Se non c'è corrispondenza nella tabella di destra, le colonne della tabella di destra conterranno NULL. Questo è utile quando si vogliono vedere tutti i record di una tabella, anche se non hanno una correlazione nell'altra.
  • RIGHT JOIN (o RIGHT OUTER JOIN): Funziona in modo speculare al LEFT JOIN, restituendo tutte le righe dalla tabella di destra e le righe corrispondenti dalla tabella di sinistra. Se non c'è corrispondenza, le colonne della tabella di sinistra conterranno NULL.
  • FULL JOIN (o FULL OUTER JOIN): Restituisce tutte le righe quando c'è una corrispondenza in una delle tabelle. È la combinazione di LEFT JOIN e RIGHT JOIN. MySQL non supporta FULL JOIN direttamente, ma può essere emulato con UNION di LEFT JOIN e RIGHT JOIN.

Per ora, concentrati su INNER JOIN poiché è il più utilizzato e la base per comprendere gli altri. Le prossime lezioni approfondiranno gli altri tipi di JOIN.

Prossimi passi

Complimenti! Hai completato la lezione sull'INNER JOIN, uno degli strumenti più potenti e fondamentali di SQL. Ora sei in grado di unire dati da più tabelle in MySQL, un'abilità essenziale per qualsiasi sviluppatore web che lavora con database relazionali.

Per consolidare la tua comprensione, ti suggerisco di:

  1. Sperimentare con il database di esempio: Prova a creare nuove tabelle e relazioni, quindi scrivi query INNER JOIN per estrarre informazioni diverse. Ad esempio, potresti aggiungere una tabella categorie_prodotto e unirla alla tabella prodotti.
  2. Combinare INNER JOIN con altre clausole: Prova a usare WHERE per filtrare i risultati di un INNER JOIN, GROUP BY per aggregare i dati uniti, o HAVING per filtrare i gruppi. Ad esempio, "Mostra il nome dei clienti che hanno speso più di 500 euro in totale".
  3. Rivedere i concetti di chiavi primarie e chiavi esterne: Assicurati di avere una solida comprensione di come queste chiavi definiscono le relazioni tra le tabelle, poiché sono cruciali per l'efficacia dei JOIN.
  4. Preparati per i prossimi tipi di JOIN: Nelle prossime lezioni, esploreremo LEFT JOIN e RIGHT JOIN, che ti daranno ancora più flessibilità nel recuperare dati, anche quando non esistono corrispondenze perfette tra le tabelle.

Continuando a praticare e sperimentare, diventerai sempre più abile nella manipolazione dei dati e nella costruzione di query SQL complesse e performanti. Il mondo dei database è vasto e affascinante, e l'INNER JOIN è solo l'inizio del tuo viaggio per diventarne un maestro!