W3docs

MySQL Join

Impara a combinare tabelle MySQL in Python con INNER JOIN, LEFT JOIN, RIGHT JOIN e FULL OUTER JOIN. Include esempi con gestione degli errori.

Un JOIN SQL consente di combinare righe di due o più tabelle in base a una colonna correlata. Questa pagina illustra ogni tipo di join supportato quando si lavora con MySQL da Python, mostra esempi completi ed eseguibili per ciascuno, e tratta le best practice come le query parametrizzate e la corretta liberazione delle risorse.

Prima di leggere questo capitolo, assicurati di essere a tuo agio con il collegamento a MySQL, la creazione di tabelle e la selezione di righe.

Prerequisiti

Installa il connettore se non l'hai già fatto:

pip install mysql-connector-python

Tabelle di esempio usate in questo capitolo

Tutti gli esempi seguenti presuppongono che due tabelle — customers e orders — esistano in un database chiamato mydatabase. Esegui questo SQL una volta per crearle e popolarle:

CREATE TABLE IF NOT EXISTS customers (
  id      INT AUTO_INCREMENT PRIMARY KEY,
  name    VARCHAR(100) NOT NULL,
  address VARCHAR(200)
);

CREATE TABLE IF NOT EXISTS orders (
  id           INT AUTO_INCREMENT PRIMARY KEY,
  customer_id  INT,
  order_date   DATE,
  order_total  DECIMAL(10, 2),
  FOREIGN KEY (customer_id) REFERENCES customers(id)
);

INSERT INTO customers (name, address) VALUES
  ('Alice',   '123 Maple St'),
  ('Bob',     '456 Oak Ave'),
  ('Charlie', '789 Pine Rd');

INSERT INTO orders (customer_id, order_date, order_total) VALUES
  (1, '2024-01-10', 99.99),
  (1, '2024-02-14', 45.00),
  (2, '2024-03-05', 210.50);
-- Charlie has no orders, so he will appear only in LEFT/FULL joins.

Nota che Charlie non ha ordini corrispondenti. Questo dettaglio rende evidente la differenza tra i tipi di join nell'output.

Tipi di join tra tabelle

Tipo di joinCosa restituisce
INNER JOINSolo le righe che corrispondono in entrambe le tabelle
LEFT JOINTutte le righe dalla tabella sinistra; NULL dove non c'è corrispondenza a destra
RIGHT JOINTutte le righe dalla tabella destra; NULL dove non c'è corrispondenza a sinistra
FULL OUTER JOIN (tramite UNION)Tutte le righe da entrambe le tabelle; NULL sul lato privo di corrispondenza

MySQL non dispone di una parola chiave FULL OUTER JOIN. Usa una UNION di una LEFT JOIN e una RIGHT JOIN per ottenere lo stesso risultato.

INNER JOIN

Un INNER JOIN restituisce solo le righe in cui la condizione di join è soddisfatta in entrambe le tabelle. Usalo quando ti interessano solo i clienti che hanno effettivamente degli ordini.

import mysql.connector
from mysql.connector import Error

try:
    mydb = mysql.connector.connect(
        host="localhost",
        user="yourusername",
        password="yourpassword",
        database="mydatabase"
    )
    mycursor = mydb.cursor()

    sql = """
        SELECT customers.name, customers.address,
               orders.order_date, orders.order_total
        FROM customers
        INNER JOIN orders ON customers.id = orders.customer_id
    """
    mycursor.execute(sql)
    results = mycursor.fetchall()

    for row in results:
        print(row)

except Error as e:
    print(f"Error: {e}")
finally:
    if mycursor:
        mycursor.close()
    if mydb.is_connected():
        mydb.close()

Output atteso (usando i dati di esempio sopra):

('Alice', '123 Maple St', datetime.date(2024, 1, 10), Decimal('99.99'))
('Alice', '123 Maple St', datetime.date(2024, 2, 14), Decimal('45.00'))
('Bob',   '456 Oak Ave',  datetime.date(2024, 3,  5), Decimal('210.50'))

Charlie è assente perché non ha righe di ordini corrispondenti.

LEFT JOIN

Un LEFT JOIN restituisce ogni riga dalla tabella sinistra (customers) e le righe corrispondenti dalla tabella destra (orders). Quando non c'è corrispondenza, le colonne della tabella destra sono None in Python.

Usa un LEFT JOIN quando vuoi vedere tutti i clienti, anche quelli che non hanno ancora effettuato ordini.

import mysql.connector
from mysql.connector import Error

try:
    mydb = mysql.connector.connect(
        host="localhost",
        user="yourusername",
        password="yourpassword",
        database="mydatabase"
    )
    mycursor = mydb.cursor()

    sql = """
        SELECT customers.name, customers.address,
               orders.order_date, orders.order_total
        FROM customers
        LEFT JOIN orders ON customers.id = orders.customer_id
    """
    mycursor.execute(sql)
    results = mycursor.fetchall()

    for row in results:
        print(row)

except Error as e:
    print(f"Error: {e}")
finally:
    if mycursor:
        mycursor.close()
    if mydb.is_connected():
        mydb.close()

Output atteso:

('Alice',   '123 Maple St', datetime.date(2024, 1, 10), Decimal('99.99'))
('Alice',   '123 Maple St', datetime.date(2024, 2, 14), Decimal('45.00'))
('Bob',     '456 Oak Ave',  datetime.date(2024, 3,  5), Decimal('210.50'))
('Charlie', '789 Pine Rd',  None,                       None)

Charlie appare con None per le colonne degli ordini perché non ha ordini.

RIGHT JOIN

Un RIGHT JOIN è l'immagine speculare di un LEFT JOIN. Restituisce ogni riga dalla tabella destra (orders) e le righe corrispondenti dalla tabella sinistra (customers). Le righe in orders prive di un cliente corrispondente mostrano None per le colonne del cliente.

In pratica, RIGHT JOIN è meno comune di LEFT JOIN perché lo si può sempre riscrivere come LEFT JOIN scambiando l'ordine delle tabelle.

import mysql.connector
from mysql.connector import Error

try:
    mydb = mysql.connector.connect(
        host="localhost",
        user="yourusername",
        password="yourpassword",
        database="mydatabase"
    )
    mycursor = mydb.cursor()

    sql = """
        SELECT customers.name, customers.address,
               orders.order_date, orders.order_total
        FROM customers
        RIGHT JOIN orders ON customers.id = orders.customer_id
    """
    mycursor.execute(sql)
    results = mycursor.fetchall()

    for row in results:
        print(row)

except Error as e:
    print(f"Error: {e}")
finally:
    if mycursor:
        mycursor.close()
    if mydb.is_connected():
        mydb.close()

Output atteso (con i dati di esempio, tutti gli ordini hanno un cliente corrispondente, quindi il risultato è uguale a INNER JOIN):

('Alice', '123 Maple St', datetime.date(2024, 1, 10), Decimal('99.99'))
('Alice', '123 Maple St', datetime.date(2024, 2, 14), Decimal('45.00'))
('Bob',   '456 Oak Ave',  datetime.date(2024, 3,  5), Decimal('210.50'))

FULL OUTER JOIN (tramite UNION)

MySQL non dispone della parola chiave FULL OUTER JOIN, ma puoi ottenere lo stesso risultato combinando una LEFT JOIN e una RIGHT JOIN con UNION. UNION rimuove automaticamente le righe duplicate.

import mysql.connector
from mysql.connector import Error

try:
    mydb = mysql.connector.connect(
        host="localhost",
        user="yourusername",
        password="yourpassword",
        database="mydatabase"
    )
    mycursor = mydb.cursor()

    sql = """
        SELECT customers.name, customers.address,
               orders.order_date, orders.order_total
        FROM customers
        LEFT JOIN orders ON customers.id = orders.customer_id

        UNION

        SELECT customers.name, customers.address,
               orders.order_date, orders.order_total
        FROM customers
        RIGHT JOIN orders ON customers.id = orders.customer_id
    """
    mycursor.execute(sql)
    results = mycursor.fetchall()

    for row in results:
        print(row)

except Error as e:
    print(f"Error: {e}")
finally:
    if mycursor:
        mycursor.close()
    if mydb.is_connected():
        mydb.close()

Output atteso:

('Alice',   '123 Maple St', datetime.date(2024, 1, 10), Decimal('99.99'))
('Alice',   '123 Maple St', datetime.date(2024, 2, 14), Decimal('45.00'))
('Bob',     '456 Oak Ave',  datetime.date(2024, 3,  5), Decimal('210.50'))
('Charlie', '789 Pine Rd',  None,                       None)

Appaiono tutti i clienti (incluso Charlie senza ordini) e tutti gli ordini (inclusi quelli che potrebbero non avere un cliente corrispondente).

Filtrare i risultati JOIN con WHERE

Puoi aggiungere una clausola WHERE a qualsiasi join per restringere il set di risultati. Usa sempre le query parametrizzate (il segnaposto %s) invece della formattazione di stringhe per evitare l'SQL injection.

L'esempio seguente recupera solo gli ordini di un cliente specifico per nome:

import mysql.connector
from mysql.connector import Error

try:
    mydb = mysql.connector.connect(
        host="localhost",
        user="yourusername",
        password="yourpassword",
        database="mydatabase"
    )
    mycursor = mydb.cursor()

    sql = """
        SELECT customers.name, orders.order_date, orders.order_total
        FROM customers
        INNER JOIN orders ON customers.id = orders.customer_id
        WHERE customers.name = %s
    """
    val = ("Alice",)
    mycursor.execute(sql, val)
    results = mycursor.fetchall()

    for row in results:
        print(row)

except Error as e:
    print(f"Error: {e}")
finally:
    if mycursor:
        mycursor.close()
    if mydb.is_connected():
        mydb.close()

Output atteso:

('Alice', datetime.date(2024, 1, 10), Decimal('99.99'))
('Alice', datetime.date(2024, 2, 14), Decimal('45.00'))

Ordinare i risultati JOIN con ORDER BY

Combina un join con ORDER BY per controllare l'ordine dell'output. Questo esempio elenca tutti gli ordini dei clienti ordinati dal più recente al più vecchio:

import mysql.connector
from mysql.connector import Error

try:
    mydb = mysql.connector.connect(
        host="localhost",
        user="yourusername",
        password="yourpassword",
        database="mydatabase"
    )
    mycursor = mydb.cursor()

    sql = """
        SELECT customers.name, orders.order_date, orders.order_total
        FROM customers
        INNER JOIN orders ON customers.id = orders.customer_id
        ORDER BY orders.order_date DESC
    """
    mycursor.execute(sql)
    results = mycursor.fetchall()

    for row in results:
        print(row)

except Error as e:
    print(f"Error: {e}")
finally:
    if mycursor:
        mycursor.close()
    if mydb.is_connected():
        mydb.close()

Output atteso:

('Bob',   datetime.date(2024, 3,  5), Decimal('210.50'))
('Alice', datetime.date(2024, 2, 14), Decimal('45.00'))
('Alice', datetime.date(2024, 1, 10), Decimal('99.99'))

Limitare i risultati JOIN con LIMIT

Abbina un join a una clausola LIMIT per scorrere in modo efficiente grandi set di risultati:

import mysql.connector
from mysql.connector import Error

try:
    mydb = mysql.connector.connect(
        host="localhost",
        user="yourusername",
        password="yourpassword",
        database="mydatabase"
    )
    mycursor = mydb.cursor()

    sql = """
        SELECT customers.name, orders.order_date, orders.order_total
        FROM customers
        INNER JOIN orders ON customers.id = orders.customer_id
        ORDER BY orders.order_date DESC
        LIMIT 2
    """
    mycursor.execute(sql)
    results = mycursor.fetchall()

    for row in results:
        print(row)

except Error as e:
    print(f"Error: {e}")
finally:
    if mycursor:
        mycursor.close()
    if mydb.is_connected():
        mydb.close()

Output atteso (solo i due ordini più recenti):

('Bob',   datetime.date(2024, 3,  5), Decimal('210.50'))
('Alice', datetime.date(2024, 2, 14), Decimal('45.00'))

Best practice

  • Usa le query parametrizzate. Passa i valori forniti dall'utente come secondo argomento a cursor.execute() con i segnaposto %s. Non usare mai la formattazione di stringhe Python o le f-string per costruire SQL — questo espone l'applicazione all'SQL injection.
  • Racchiudi il codice del database in try...except...finally. Questo garantisce che le connessioni e i cursori vengano sempre chiusi, anche in caso di errore.
  • Seleziona solo le colonne di cui hai bisogno. L'uso di SELECT * su tabelle unite può estrarre molte colonne ridondanti e penalizzare le prestazioni su tabelle grandi.
  • Aggiungi indici sulle colonne di join. Se orders.customer_id non è indicizzato, MySQL eseguirà una scansione completa della tabella per ogni join. Un vincolo di chiave esterna (come mostrato nello script di configurazione sopra) crea automaticamente un indice.
  • Preferisci LEFT JOIN a RIGHT JOIN per la leggibilità. Un RIGHT JOIN può sempre essere riscritto come LEFT JOIN scambiando le posizioni delle tabelle, il che risulta più facile da seguire per la maggior parte degli sviluppatori.

Riferimento rapido

ScenarioJoin da usare
Solo record con corrispondenze in entrambe le tabelleINNER JOIN
Tutti i record dalla tabella principale (sinistra), con o senza corrispondenzaLEFT JOIN
Tutti i record dalla tabella secondaria (destra), con o senza corrispondenzaRIGHT JOIN
Ogni record da entrambe le tabelle, con o senza corrispondenzaLEFT JOIN ... UNION ... RIGHT JOIN
Restringere le righe uniteAggiungi una clausola WHERE con valori parametrizzati
Controllare l'ordine dell'outputAggiungi ORDER BY column ASC|DESC
Paginare i risultatiAggiungi LIMIT n (vedi MySQL Limit)
Was this page helpful?