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-pythonTables 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 jointure | Ce qu'elle retourne |
|---|---|
INNER JOIN | Uniquement les lignes correspondantes dans les deux tables |
LEFT JOIN | Toutes les lignes de la table gauche ; NULL là où il n'y a pas de correspondance à droite |
RIGHT JOIN | Toutes 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_idn'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 JOINpour la lisibilité. UnRIGHT JOINpeut toujours être réécrit enLEFT JOINen échangeant les positions des tables, ce que la plupart des développeurs trouvent plus facile à suivre.
Référence rapide
| Scénario | Jointure à utiliser |
|---|---|
| Uniquement les enregistrements correspondants dans les deux tables | INNER JOIN |
| Tous les enregistrements de la table principale (gauche), correspondants ou non | LEFT JOIN |
| Tous les enregistrements de la table secondaire (droite), correspondants ou non | RIGHT JOIN |
| Tous les enregistrements des deux tables, correspondants ou non | LEFT JOIN ... UNION ... RIGHT JOIN |
| Affiner les lignes jointes | Ajouter une clause WHERE avec des valeurs paramétrées |
| Contrôler l'ordre de sortie | Ajouter ORDER BY column ASC|DESC |
| Paginer les résultats | Ajouter LIMIT n (voir MySQL Limit) |