🧚 Ascolta! Beekeeper Studio è una GUI per database veloce, moderna e open source Scarica
2 luglio 2025 Di Matthew Rathbone

I JOIN SQL combinano le righe di due tabelle confrontando una colonna in comune, come l'author_id di un libro e l'id di un autore. Il tipo di join decide cosa succede alle righe che non corrispondono. Gli esempi funzionano in PostgreSQL, MySQL, SQLite e SQL Server.

Come usare i JOIN SQL

Metti la seconda tabella dopo JOIN e indica in ON come le sue righe corrispondono a quelle della prima:

SELECT columns
FROM table_a
JOIN table_b ON table_a.key = table_b.key;

Tutti gli esempi di questa sezione uniscono le stesse due tabelle: Butler ha scritto due dei libri, Asimov nessuno, e nessuno sa chi abbia scritto Beowulf.

INNER JOIN

Diagramma di Inner Join

Un INNER JOIN restituisce solo le righe che corrispondono in entrambe le tabelle. JOIN da solo significa INNER JOIN.

-- Input: authors
-- id | name
-- ---+--------
-- 1  | Le Guin
-- 2  | Butler
-- 3  | Asimov

-- Input: books
-- title                | author_id
-- ---------------------+----------
-- The Dispossessed     | 1
-- Kindred              | 2
-- Parable of the Sower | 2
-- Beowulf              | NULL

SELECT a.name, b.title
FROM authors a
INNER JOIN books b ON a.id = b.author_id;

-- Output:
-- name    | title
-- --------+---------------------
-- Le Guin | The Dispossessed
-- Butler  | Kindred
-- Butler  | Parable of the Sower

Asimov non ha libri e Beowulf non ha un autore, quindi nessuno dei due compare.

LEFT JOIN

Diagramma di Left Join

Un LEFT JOIN (o LEFT OUTER JOIN) restituisce tutte le righe della tabella di sinistra, quella dopo FROM. Dove la tabella di destra non ha corrispondenze, le sue colonne tornano NULL.

-- Input: the authors and books tables above

SELECT a.name, b.title
FROM authors a
LEFT JOIN books b ON a.id = b.author_id;

-- Output:
-- name    | title
-- --------+---------------------
-- Le Guin | The Dispossessed
-- Butler  | Kindred
-- Butler  | Parable of the Sower
-- Asimov  | NULL

RIGHT JOIN

Diagramma di Right Join

Un RIGHT JOIN (o RIGHT OUTER JOIN) è l'immagine speculare: tutte le righe della tabella di destra, con NULL dove la tabella di sinistra non ha corrispondenze.

-- Input: the authors and books tables above

SELECT a.name, b.title
FROM authors a
RIGHT JOIN books b ON a.id = b.author_id;

-- Output:
-- name    | title
-- --------+---------------------
-- Le Guin | The Dispossessed
-- Butler  | Kindred
-- Butler  | Parable of the Sower
-- NULL    | Beowulf

FULL OUTER JOIN

Diagramma di Full Outer Join

Un FULL OUTER JOIN restituisce tutte le righe di entrambe le tabelle, abbinate dove possibile e completate con NULL dove no. MySQL non lo supporta: vedi “Sintassi alternativa” più sotto.

-- Input: the authors and books tables above

SELECT a.name, b.title
FROM authors a
FULL OUTER JOIN books b ON a.id = b.author_id;

-- Output:
-- name    | title
-- --------+---------------------
-- Le Guin | The Dispossessed
-- Butler  | Kindred
-- Butler  | Parable of the Sower
-- NULL    | Beowulf
-- Asimov  | NULL

CROSS JOIN

Diagramma di Cross Join

Un CROSS JOIN abbina ogni riga di una tabella a ogni riga dell'altra, quindi non ha ON. Due taglie e due colori danno quattro combinazioni; 1.000 righe per 1.000 righe ne danno un milione, quindi usalo su tabelle piccole.

-- Input: sizes
-- size
-- ----
-- S
-- M

-- Input: colors
-- color
-- -----
-- Red
-- Blue

SELECT s.size, c.color
FROM sizes s
CROSS JOIN colors c;

-- Output:
-- size | color
-- -----+------
-- S    | Red
-- S    | Blue
-- M    | Red
-- M    | Blue

LEFT ANTI JOIN

Diagramma di Left Anti Join

Un left anti join restituisce le righe della tabella di sinistra che non hanno corrispondenze nella tabella di destra. Non esiste una parola chiave apposita: fai un LEFT JOIN e tieni le righe in cui il lato destro è tornato NULL.

-- Input: the authors and books tables above

SELECT a.name
FROM authors a
LEFT JOIN books b ON a.id = b.author_id
WHERE b.author_id IS NULL;

-- Output:
-- name
-- ------
-- Asimov

RIGHT ANTI JOIN

Diagramma di Right Anti Join

Un right anti join restituisce le righe della tabella di destra che non hanno corrispondenze nella tabella di sinistra.

-- Input: the authors and books tables above

SELECT b.title
FROM authors a
RIGHT JOIN books b ON a.id = b.author_id
WHERE a.id IS NULL;

-- Output:
-- title
-- -------
-- Beowulf

Regole da conoscere

  • NULL non corrisponde mai a niente, nemmeno a un altro NULL. L'author_id NULL di Beowulf non corrisponde a nessun autore, quindi solo i join che mantengono i libri senza corrispondenza lo restituiscono.
  • Una riga compare una volta per ogni riga a cui corrisponde. Butler compare due volte qui sopra perché corrisponde a due libri. Se poi sommi una colonna della tabella degli autori, i suoi valori vengono contati due volte.
  • Con un outer join, una condizione sull'altra tabella va messa in ON. In WHERE viene applicata dopo il join e scarta le righe NULL che il join aveva appena mantenuto:
-- Input: the authors and books tables above

-- Only Butler is left: the WHERE drops every author whose title is NULL
SELECT a.name, b.title
FROM authors a
LEFT JOIN books b ON a.id = b.author_id
WHERE b.title LIKE 'K%';

-- Output:
-- name   | title
-- -------+--------
-- Butler | Kindred

-- Every author is kept, with a book only where it starts with K
SELECT a.name, b.title
FROM authors a
LEFT JOIN books b ON a.id = b.author_id AND b.title LIKE 'K%';

-- Output:
-- name    | title
-- --------+--------
-- Le Guin | NULL
-- Butler  | Kindred
-- Asimov  | NULL

Sintassi alternativa

MySQL non ha FULL OUTER JOIN. Combina invece un LEFT JOIN con le righe senza corrispondenza di un RIGHT JOIN:

-- MySQL
-- Input: the authors and books tables above

SELECT a.name, b.title
FROM authors a LEFT JOIN books b ON a.id = b.author_id
UNION ALL
SELECT a.name, b.title
FROM authors a RIGHT JOIN books b ON a.id = b.author_id
WHERE a.id IS NULL;

-- Output:
-- name    | title
-- --------+---------------------
-- Le Guin | The Dispossessed
-- Butler  | Parable of the Sower
-- Butler  | Kindred
-- Asimov  | NULL
-- NULL    | Beowulf

SQLite prima della versione 3.39 non ha RIGHT JOIN né FULL OUTER JOIN: inverti le tabelle e usa un LEFT JOIN.

Un anti join si può scrivere anche con NOT EXISTS. Evita NOT IN in questo caso: se la subquery restituisce anche un solo NULL, come l'author_id di Beowulf qui, NOT IN non restituisce proprio nulla:

-- Input: the authors and books tables above

SELECT a.name
FROM authors a
WHERE NOT EXISTS (SELECT 1 FROM books b WHERE b.author_id = a.id);

-- Output:
-- name
-- ------
-- Asimov

SELECT a.name
FROM authors a
WHERE a.id NOT IN (SELECT author_id FROM books);

-- Output:
-- name
-- ----
-- (0 rows)

Quando le colonne da confrontare hanno lo stesso nome in entrambe le tabelle, puoi scrivere JOIN books USING (author_id) al posto della condizione ON, tranne che in SQL Server.

Il codice più vecchio spesso elenca le tabelle separate da virgole e mette la corrispondenza in WHERE. È un INNER JOIN scritto in un altro modo. Se togli il WHERE, diventa un CROSS JOIN:

-- Input: the authors and books tables above

SELECT a.name, b.title
FROM authors a, books b
WHERE a.id = b.author_id;

-- Output:
-- name    | title
-- --------+---------------------
-- Le Guin | The Dispossessed
-- Butler  | Kindred
-- Butler  | Parable of the Sower

Quando usare i JOIN SQL?

Usa un join ogni volta che i dati che ti servono sono divisi tra più tabelle. Quale join usare dipende da cosa deve succedere alle righe senza corrispondenza, e la maggior parte dei problemi rientra in uno di questi cinque casi.

La riga di un dipendente contiene l'ID del reparto, ma il report ha bisogno del nome del reparto. Un INNER JOIN lo recupera e scarta le righe che non hanno nulla da cercare.

Mantenere le righe senza corrispondenza

Quando i dati collegati sono facoltativi, come un dipendente non ancora assegnato a un reparto, un INNER JOIN lo nasconderebbe senza dirti niente. Un LEFT JOIN mantiene tutte le righe della tabella principale e riempie i vuoti con NULL.

Elencare tutti gli elementi, anche quelli vuoti

Spesso i report devono mostrare ogni reparto, categoria o cliente, compresi quelli ancora vuoti. Fai in modo che quella sia la tabella di cui si mantengono tutte le righe: prima con un LEFT JOIN, oppure seconda con un RIGHT JOIN.

Trovare le righe senza corrispondenza

Clienti che non hanno mai ordinato, prodotti mai venduti, dipendenti senza reparto: un anti join trova ciò che manca, ed è quindi anche un ottimo controllo sulla qualità dei dati.

Generare tutte le combinazioni

Un CROSS JOIN genera righe che ancora non esistono: ogni taglia in ogni colore, ogni dipendente per ogni corso di formazione, oppure ogni negozio per ogni giorno di un report.

Esempi reali di JOIN SQL

Studenti e i loro corsi

Una relazione molti-a-molti richiede due INNER JOIN, passando per la tabella di collegamento che associa gli studenti ai corsi. Mei non è iscritta a nessun corso, quindi non compare:

-- Input: students
-- id | name
-- ---+------
-- 1  | Amara
-- 2  | Lucas
-- 3  | Mei

-- Input: enrollments
-- student_id | course_id | grade
-- -----------+-----------+------
-- 1          | 10        | A
-- 1          | 20        | B
-- 2          | 10        | B

-- Input: courses
-- id | course_name
-- ---+------------
-- 10 | Databases
-- 20 | Statistics

SELECT s.name, c.course_name, e.grade
FROM students s
INNER JOIN enrollments e ON s.id = e.student_id
INNER JOIN courses c ON e.course_id = c.id;

-- Output:
-- name  | course_name | grade
-- ------+-------------+------
-- Amara | Databases   | A
-- Amara | Statistics  | B
-- Lucas | Databases   | B

Utenti con profilo facoltativo

Non tutti gli utenti hanno compilato il profilo. Un LEFT JOIN li mantiene tutti:

-- Input: users
-- id | username
-- ---+---------
-- 1  | rosa_m
-- 2  | devon
-- 3  | kai_l

-- Input: user_profiles
-- user_id | bio
-- --------+------------------------
-- 1       | Data engineer in Lisbon
-- 3       | Loves SQL puzzles

SELECT u.username, p.bio
FROM users u
LEFT JOIN user_profiles p ON u.id = p.user_id;

-- Output:
-- username | bio
-- ---------+------------------------
-- rosa_m   | Data engineer in Lisbon
-- devon    | NULL
-- kai_l    | Loves SQL puzzles

Numero di ordini per ogni cliente

Per includere i clienti senza ordini, fai un LEFT JOIN partendo da customers. Poi conta una colonna della tabella degli ordini: COUNT(*) conta la riga creata dal LEFT JOIN per Omar, che quindi avrebbe 1 invece di 0:

-- Input: customers
-- id | name
-- ---+------------
-- 1  | Harper Lee
-- 2  | Omar Haddad
-- 3  | Yuki Tanaka

-- Input: orders
-- id  | customer_id | total
-- ----+-------------+-------
-- 101 | 1           | 59.90
-- 102 | 3           | 120.00
-- 103 | 1           | 14.50

-- Wrong: COUNT(*) counts Omar's NULL row
SELECT c.name, COUNT(*) AS order_count
FROM customers c
LEFT JOIN orders o ON c.id = o.customer_id
GROUP BY c.id, c.name
ORDER BY c.id;

-- Output:
-- name        | order_count
-- ------------+------------
-- Harper Lee  | 2
-- Omar Haddad | 1
-- Yuki Tanaka | 1

-- Right: COUNT(o.id) skips it
SELECT c.name, COUNT(o.id) AS order_count
FROM customers c
LEFT JOIN orders o ON c.id = o.customer_id
GROUP BY c.id, c.name
ORDER BY c.id;

-- Output:
-- name        | order_count
-- ------------+------------
-- Harper Lee  | 2
-- Omar Haddad | 0
-- Yuki Tanaka | 1

Fatture e pagamenti che non tornano

Un FULL OUTER JOIN che mantiene solo le righe senza corrispondenza confronta due elenchi in una sola query: la fattura 502 non è stata pagata e il pagamento 9003 si riferisce a una fattura che non esiste:

-- Input: invoices
-- invoice_id | amount
-- -----------+-------
-- 501        | 120.00
-- 502        | 75.50
-- 503        | 300.00

-- Input: payments
-- payment_id | invoice_id | paid
-- -----------+------------+-------
-- 9001       | 501        | 120.00
-- 9002       | 503        | 300.00
-- 9003       | 504        | 42.00

-- MySQL has no FULL OUTER JOIN: use the UNION ALL form under "Alternative syntax"
SELECT i.invoice_id, i.amount, p.payment_id, p.invoice_id AS paid_invoice
FROM invoices i
FULL OUTER JOIN payments p ON p.invoice_id = i.invoice_id
WHERE i.invoice_id IS NULL OR p.payment_id IS NULL;

-- Output:
-- invoice_id | amount | payment_id | paid_invoice
-- -----------+--------+------------+-------------
-- 502        | 75.50  | NULL       | NULL
-- NULL       | NULL   | 9003       | 504

Incassi giornalieri di ogni negozio, compresi i giorni senza vendite

Fai un CROSS JOIN tra negozi e giorni per ottenere una riga per negozio al giorno, poi fai un LEFT JOIN delle vendite su questa griglia. York non ha venduto nulla il 6 ottobre, ma ha comunque la sua riga:

-- Input: stores
-- store
-- -----
-- Leeds
-- York

-- Input: days
-- day
-- ----------
-- 2026-10-05
-- 2026-10-06

-- Input: sales
-- store | day        | amount
-- ------+------------+-------
-- Leeds | 2026-10-05 | 410.00
-- Leeds | 2026-10-06 | 385.50
-- York  | 2026-10-05 | 220.00

SELECT st.store, d.day, COALESCE(SUM(s.amount), 0.00) AS takings
FROM stores st
CROSS JOIN days d
LEFT JOIN sales s ON s.store = st.store AND s.day = d.day
GROUP BY st.store, d.day
ORDER BY st.store, d.day;

-- Output:
-- store | day        | takings
-- ------+------------+--------
-- Leeds | 2026-10-05 | 410.00
-- Leeds | 2026-10-06 | 385.50
-- York  | 2026-10-05 | 220.00
-- York  | 2026-10-06 | 0.00

Altre letture sui JOIN SQL

Su questo blog:

Documentazione ufficiale:

Beekeeper Studio è una GUI per database gratuita & open source

Il miglior strumento per query & editor SQL che abbia mai usato. Offre tutto ciò che mi serve per gestire il mio database. - ⭐⭐⭐⭐⭐ Mit

Beekeeper Studio è veloce, intuitivo e facile da usare. Beekeeper supporta tantissimi database e funziona alla grande su Windows, Mac e Linux.

La versione Linux di Beekeeper è completa al 100%, senza tagli e senza compromessi sulle funzionalità.

Cosa dicono gli utenti di Beekeeper Studio

★★★★★
"Beekeeper Studio ha sostituito completamente il mio vecchio workflow SQL. È veloce, intuitivo e rende di nuovo piacevole lavorare con i database."
— Alex K., Database Developer
★★★★★
"Ho provato tante GUI per database, ma Beekeeper trova il perfetto equilibrio tra funzionalità e semplicità. Funziona e basta."
— Sarah M., Full Stack Engineer

Pronto a migliorare il tuo workflow SQL?

download Scarica gratis