W3docs

MySQL Join

Combinez des tables MySQL en Python avec INNER JOIN, LEFT JOIN, RIGHT JOIN et FULL OUTER JOIN. Exemples complets avec gestion des erreurs.

Un JOIN SQL permet de combiner des lignes de deux tables ou plus en fonction d'une colonne liée. Cette page explique chaque type de jointure pris en charge lors de l'utilisation de MySQL avec Python, présente des exemples complets et exécutables pour chacun, et couvre les bonnes pratiques telles que les requêtes paramétrées et la libération correcte des ressources.

Avant de lire ce chapitre, assurez-vous d'être à l'aise avec la connexion à MySQL, la création de tables et la sélection de lignes.

Prérequis

Installez le connecteur si ce n'est pas déjà fait :

pip install mysql-connector-python

Tables d'exemple utilisées dans ce chapitre

Tous les exemples ci-dessous supposent que deux tables — customers et orders — existent dans une base de données appelée mydatabase. Exécutez ce SQL une fois pour les créer et les remplir :

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.

Notez que Charlie n'a aucune commande. Ce détail rend évidente la différence entre les types de jointures dans les résultats.

Types de jointures de tables

Type de jointureCe qu'elle retourne
INNER JOINUniquement les lignes correspondantes dans les deux tables
LEFT JOINToutes les lignes de la table gauche ; NULL là où il n'y a pas de correspondance à droite
RIGHT JOINToutes les lignes de la table droite ; NULL là où il n'y a pas de correspondance à gauche
FULL OUTER JOIN (via UNION)Toutes les lignes des deux tables ; NULL du côté qui n'a pas de correspondance

MySQL ne dispose pas du mot-clé FULL OUTER JOIN. Utilisez un UNION d'un LEFT JOIN et d'un RIGHT JOIN pour obtenir le même résultat.

INNER JOIN

Un INNER JOIN retourne uniquement les lignes pour lesquelles la condition de jointure est satisfaite dans les deux tables. Utilisez-le lorsque vous ne vous intéressez qu'aux clients qui ont effectivement des commandes.

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()

Résultat attendu (avec les données d'exemple ci-dessus) :

('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 est absent car il n'a aucune ligne de commande correspondante.

LEFT JOIN

Un LEFT JOIN retourne chaque ligne de la table de gauche (customers) et les lignes correspondantes de la table de droite (orders). Lorsqu'il n'y a pas de correspondance, les colonnes de la table de droite ont la valeur None en Python.

Utilisez un LEFT JOIN lorsque vous souhaitez voir tous les clients, même ceux qui n'ont encore passé aucune commande.

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()

Résultat attendu :

('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 apparaît avec None pour les colonnes de commandes car il n'a aucune commande.

RIGHT JOIN

Un RIGHT JOIN est le reflet d'un LEFT JOIN. Il retourne chaque ligne de la table de droite (orders) et les lignes correspondantes de la table de gauche (customers). Les lignes dans orders qui n'ont pas de client correspondant affichent None pour les colonnes client.

En pratique, RIGHT JOIN est moins courant que LEFT JOIN car on peut toujours le réécrire en LEFT JOIN en échangeant l'ordre des tables.

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()

Résultat attendu (avec les données d'exemple, toutes les commandes ont un client correspondant, donc le résultat est identique à 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 (via UNION)

MySQL ne dispose pas du mot-clé FULL OUTER JOIN, mais vous pouvez obtenir le même résultat en combinant un LEFT JOIN et un RIGHT JOIN avec UNION. UNION supprime automatiquement les lignes dupliquées.

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()

Résultat attendu :

('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)

Tous les clients apparaissent (y compris Charlie sans commandes) et toutes les commandes apparaissent (y compris celles qui n'auraient pas de client correspondant).

Filtrer les résultats d'un JOIN avec WHERE

Vous pouvez ajouter une clause WHERE à n'importe quelle jointure pour affiner l'ensemble de résultats. Utilisez toujours des requêtes paramétrées (l'espace réservé %s) plutôt que la mise en forme de chaînes pour éviter les injections SQL.

L'exemple suivant récupère uniquement les commandes d'un client spécifique par son nom :

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()

Résultat attendu :

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

Trier les résultats d'un JOIN avec ORDER BY

Combinez une jointure avec ORDER BY pour contrôler l'ordre de sortie. Cet exemple liste toutes les commandes des clients triées par commande la plus récente en premier :

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()

Résultat attendu :

('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'))

Limiter les résultats d'un JOIN avec LIMIT

Associez une jointure à une clause LIMIT pour paginer efficacement les grands ensembles de résultats :

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()

Résultat attendu (uniquement les deux commandes les plus récentes) :

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

Bonnes pratiques

  • Utilisez des requêtes paramétrées. Passez les valeurs fournies par l'utilisateur comme second argument à cursor.execute() avec des espaces réservés %s. N'utilisez jamais le formatage de chaînes Python ou les f-strings pour construire du SQL — cela expose votre application aux injections SQL.
  • Encapsulez le code de base de données dans try...except...finally. Cela garantit que les connexions et les curseurs sont toujours fermés, même en cas d'erreur.
  • Sélectionnez uniquement les colonnes dont vous avez besoin. Utiliser SELECT * sur des tables jointes peut ramener de nombreuses colonnes redondantes et nuire aux performances sur les grandes tables.
  • Ajoutez des index sur les colonnes de jointure. Si orders.customer_id n'est pas indexé, MySQL parcourra l'intégralité de la table pour chaque jointure. Une contrainte de clé étrangère (comme indiqué dans le script de configuration ci-dessus) crée automatiquement un index.
  • Préférez LEFT JOIN à RIGHT JOIN pour la lisibilité. Un RIGHT JOIN peut toujours être réécrit en LEFT JOIN en échangeant les positions des tables, ce que la plupart des développeurs trouvent plus facile à suivre.

Référence rapide

ScénarioJointure à utiliser
Uniquement les enregistrements correspondants dans les deux tablesINNER JOIN
Tous les enregistrements de la table principale (gauche), correspondants ou nonLEFT JOIN
Tous les enregistrements de la table secondaire (droite), correspondants ou nonRIGHT JOIN
Tous les enregistrements des deux tables, correspondants ou nonLEFT JOIN ... UNION ... RIGHT JOIN
Affiner les lignes jointesAjouter une clause WHERE avec des valeurs paramétrées
Contrôler l'ordre de sortieAjouter ORDER BY column ASC|DESC
Paginer les résultatsAjouter LIMIT n (voir MySQL Limit)
Was this page helpful?