Une jointure SQL combine des lignes de deux tables selon une condition de correspondance — le plus souvent une clé étrangère. Sans jointure, une base relationnelle se résume à des silos : les données vivent dans des tables séparées et ne se parlent pas. Les jointures, c'est ce qui les fait parler.
MySQL propose cinq types : INNER JOIN, LEFT JOIN, RIGHT JOIN, FULL OUTER JOIN simulé et CROSS JOIN. Chacun traite différemment les lignes qui n'ont pas de correspondance dans l'autre table — c'est là que réside toute la différence. On va les parcourir dans l'ordre du plus restrictif au plus large, avec des exemples exécutables depuis Node.js à chaque étape.
Étape 1 — Créer les deux tables de test
Tous les exemples reposent sur deux tables : members et committees. David et Amelia apparaissent dans les deux. John, Jane et Mary n'existent que dans members ; Joe et Sarah uniquement dans committees. Ce déséquilibre est voulu — sans lui, toutes les jointures donneraient le même résultat et l'exercice ne servirait à rien.
CREATE TABLE members (
member_id INT AUTO_INCREMENT,
name VARCHAR(100),
PRIMARY KEY (member_id)
);
INSERT INTO members(name)
VALUES('John'), ('Jane'), ('Mary'), ('David'), ('Amelia');
AUTO_INCREMENT: MySQL attribue unmember_idunique à chaque insertion — inutile de le préciser manuellement.- La syntaxe multi-valeurs de
INSERT INTOinsère les cinq membres en une seule requête.
CREATE TABLE committees (
committee_id INT AUTO_INCREMENT,
name VARCHAR(100),
PRIMARY KEY (committee_id)
);
INSERT INTO committees(name)
VALUES('Joe'), ('Sarah'), ('Amelia'), ('David');
Résultat : 5 membres, 4 membres de comités, 2 noms en commun. Les jointures qui suivent vont traiter ces 2 noms communs très différemment selon le type choisi.
Étape 2 — INNER JOIN : uniquement les lignes présentes dans les deux tables
L'INNER JOIN est la jointure par défaut — la plus stricte. Elle exclut tout ce qui n'a pas de correspondance des deux côtés. Pas de NULL, pas de lignes orphelines. Si un nom n'existe que dans une table, il disparaît purement du résultat.
En résumé : INNER JOIN = intersection. Seul ce qui est commun aux deux tables passe.
Requête SQL
SELECT m.member_id, m.name AS member, c.committee_id, c.name AS committee FROM members m INNER JOIN committees c ON c.name = m.name;
La condition ON c.name = m.name est le filtre : seules les lignes où le nom existe dans les deux tables passent. Les alias m et c évitent l'ambiguïté sur la colonne name, présente dans les deux tables.
Avec Node.js et mysql2
Le module mysql2/promise permet d'écrire des requêtes avec async/await, sans callbacks imbriqués. Si vous ne l'avez pas encore :
npm install mysql2
const mysql = require('mysql2/promise');
(async () => {
const connection = await mysql.createConnection({
host: "localhost",
user: "root",
password: "votre_mot_de_passe",
database: "votre_base_de_donnees"
});
try {
// execute() prépare la requête côté serveur — plus sûr que query() contre les injections SQL
const [rows] = await connection.execute(`
SELECT
m.member_id,
m.name AS member,
c.committee_id,
c.name AS committee
FROM
members m
INNER JOIN committees c ON c.name = m.name;
`);
rows.forEach((row) => {
console.log(JSON.stringify(row));
});
} catch (error) {
console.error("Erreur :", error);
} finally {
// finally garantit la fermeture de connexion même si la requête échoue
await connection.end();
}
})();
⚠ execute() prépare la requête avant de l'envoyer — contrairement à query() qui l'envoie telle quelle. Pour des requêtes statiques sans paramètres, les deux donnent le même résultat. Mais execute() protège automatiquement contre les injections SQL quand on ajoute des paramètres plus tard, c'est l'habitude à prendre.
Résultat attendu :
{"member_id":4,"member":"David","committee_id":4,"committee":"David"}
{"member_id":5,"member":"Amelia","committee_id":3,"committee":"Amelia"}
Deux lignes. David et Amelia uniquement — les seuls noms communs aux deux tables. John, Jane, Mary, Joe et Sarah ont disparu.
Étape 3 — LEFT JOIN : tous les membres, comité ou non
Le LEFT JOIN retourne toutes les lignes de la table de gauche (members), avec les correspondances de droite quand elles existent. Quand il n'y en a pas, MySQL remplit les colonnes manquantes avec NULL. La table gauche pilote — aucune de ses lignes n'est exclue.
En résumé : LEFT JOIN = tout ce qui est à gauche, complété à droite quand c'est possible.
Requête SQL
SELECT m.member_id, m.name AS member, c.committee_id, c.name AS committee FROM members m LEFT JOIN committees c ON c.name = m.name;
Pour chaque membre, MySQL cherche un comité portant le même nom. S'il n'en trouve pas — comme pour John, Jane ou Mary — il insère NULL dans les colonnes de committees et conserve quand même la ligne.
Avec Node.js et mysql2
const mysql = require('mysql2/promise');
(async () => {
const connection = await mysql.createConnection({
host: "localhost",
user: "root",
password: "votre_mot_de_passe",
database: "votre_base_de_donnees"
});
try {
const [rows] = await connection.execute(`
SELECT
m.member_id,
m.name AS member,
c.committee_id,
c.name AS committee
FROM
members m
LEFT JOIN committees c ON c.name = m.name;
`);
rows.forEach((row) => {
console.log(JSON.stringify(row));
});
} catch (error) {
console.error("Erreur :", error);
} finally {
await connection.end();
}
})();
Résultat attendu :
{"member_id":1,"member":"John","committee_id":null,"committee":null}
{"member_id":2,"member":"Jane","committee_id":null,"committee":null}
{"member_id":3,"member":"Mary","committee_id":null,"committee":null}
{"member_id":4,"member":"David","committee_id":4,"committee":"David"}
{"member_id":5,"member":"Amelia","committee_id":3,"committee":"Amelia"}
Cinq lignes — tous les membres. John, Jane et Mary ont null côté comité ; David et Amelia sont complets. Joe et Sarah, eux, n'apparaissent pas — ils ne sont pas dans members, la table qui pilote.
Étape 4 — RIGHT JOIN : tous les comités, membre ou non
Le RIGHT JOIN inverse la logique : c'est la table de droite (committees) qui pilote. Toutes ses lignes apparaissent, même sans correspondance dans members. Les colonnes manquantes côté gauche reçoivent NULL.
En résumé : RIGHT JOIN = tout ce qui est à droite. Notez qu'on peut toujours réécrire un RIGHT JOIN en LEFT JOIN en inversant l'ordre des tables — beaucoup de développeurs préfèrent cette convention, plus lisible.
Requête SQL
SELECT m.member_id, m.name AS member, c.committee_id, c.name AS committee FROM members m RIGHT JOIN committees c ON c.name = m.name;
Cette fois committees est la table pilote. Joe et Sarah n'ont aucun équivalent dans members — leurs colonnes gauches seront NULL, mais ils apparaissent quand même.
Avec Node.js et mysql2
const mysql = require('mysql2/promise');
(async () => {
const connection = await mysql.createConnection({
host: "localhost",
user: "root",
password: "votre_mot_de_passe",
database: "votre_base_de_donnees"
});
try {
const [rows] = await connection.execute(`
SELECT
m.member_id,
m.name AS member,
c.committee_id,
c.name AS committee
FROM
members m
RIGHT JOIN committees c ON c.name = m.name;
`);
rows.forEach((row) => {
console.log(JSON.stringify(row));
});
} catch (error) {
console.error("Erreur :", error);
} finally {
await connection.end();
}
})();
Résultat attendu :
{"member_id":null,"member":null,"committee_id":1,"committee":"Joe"}
{"member_id":null,"member":null,"committee_id":2,"committee":"Sarah"}
{"member_id":5,"member":"Amelia","committee_id":3,"committee":"Amelia"}
{"member_id":4,"member":"David","committee_id":4,"committee":"David"}
Quatre lignes — tous les comités. Joe et Sarah ont null côté membres. Amelia et David sont complets. John, Jane et Mary ont disparu — ils ne sont pas dans committees.
Étape 5 — FULL OUTER JOIN simulé : toutes les lignes, des deux côtés
MySQL n'a pas de FULL OUTER JOIN natif — contrairement à PostgreSQL ou SQL Server. On le simule avec UNION : un LEFT JOIN fournit toutes les lignes de gauche, un RIGHT JOIN toutes celles de droite, et UNION fusionne les deux en supprimant les doublons. David et Amelia, présents dans les deux requêtes, n'apparaissent qu'une fois.
En résumé : FULL OUTER JOIN simulé = vue complète des deux tables. Utile quand vous voulez identifier les lignes orphelines des deux côtés en une seule requête.
Requête SQL
SELECT m.member_id, m.name AS member, c.committee_id, c.name AS committee FROM members m LEFT JOIN committees c ON c.name = m.name UNION SELECT m.member_id, m.name AS member, c.committee_id, c.name AS committee FROM members m RIGHT JOIN committees c ON c.name = m.name;
UNION — et non UNION ALL — déduplique automatiquement. Si vous utilisez UNION ALL, David et Amelia apparaîtront deux fois dans le résultat.
Avec Node.js et mysql2
const mysql = require('mysql2/promise');
(async () => {
const connection = await mysql.createConnection({
host: "localhost",
user: "root",
password: "votre_mot_de_passe",
database: "votre_base_de_donnees"
});
try {
const [rows] = await connection.execute(`
SELECT
m.member_id,
m.name AS member,
c.committee_id,
c.name AS committee
FROM members m
LEFT JOIN committees c ON c.name = m.name
UNION
SELECT
m.member_id,
m.name AS member,
c.committee_id,
c.name AS committee
FROM members m
RIGHT JOIN committees c ON c.name = m.name;
`);
rows.forEach((row) => {
console.log(JSON.stringify(row));
});
} catch (error) {
console.error("Erreur :", error);
} finally {
await connection.end();
}
})();
Résultat attendu :
{"member_id":1,"member":"John","committee_id":null,"committee":null}
{"member_id":2,"member":"Jane","committee_id":null,"committee":null}
{"member_id":3,"member":"Mary","committee_id":null,"committee":null}
{"member_id":4,"member":"David","committee_id":4,"committee":"David"}
{"member_id":5,"member":"Amelia","committee_id":3,"committee":"Amelia"}
{"member_id":null,"member":null,"committee_id":1,"committee":"Joe"}
{"member_id":null,"member":null,"committee_id":2,"committee":"Sarah"}
Sept lignes. Les cinq membres (dont trois sans comité) plus Joe et Sarah, qui n'ont aucun équivalent dans members. Tout le monde est là — c'est le seul type de jointure qui ne sacrifie personne.
Étape 6 — CROSS JOIN : toutes les combinaisons possibles
Le CROSS JOIN produit le produit cartésien des deux tables : chaque ligne de members est combinée avec chaque ligne de committees. Pas de condition ON, pas de filtre — toutes les paires sont générées. 5 membres × 4 comités = 20 lignes.
C'est rarement ce qu'on veut. Le CROSS JOIN sert surtout à générer des matrices de test, des grilles horaires exhaustives, ou des combinaisons de paramètres. Sur de vraies tables de production, il peut produire des millions de lignes sans avertissement.
Requête SQL
SELECT m.member_id, m.name AS member, c.committee_id, c.name AS committee FROM members m CROSS JOIN committees c;
⚠ Deux tables de 1 000 lignes chacune → 1 000 000 de lignes en sortie. À réserver à des jeux de données petits et maîtrisés.
Avec Node.js et mysql2
const mysql = require('mysql2/promise');
(async () => {
const connection = await mysql.createConnection({
host: "localhost",
user: "root",
password: "votre_mot_de_passe",
database: "votre_base_de_donnees"
});
try {
const [rows] = await connection.execute(`
SELECT
m.member_id,
m.name AS member,
c.committee_id,
c.name AS committee
FROM
members m
CROSS JOIN committees c;
`);
console.log(`Nombre de lignes : ${rows.length}`);
// Afficher seulement les 4 premières lignes pour la lisibilité
rows.slice(0, 4).forEach((row) => {
console.log(JSON.stringify(row));
});
} catch (error) {
console.error("Erreur :", error);
} finally {
await connection.end();
}
})();
Résultat attendu :
Nombre de lignes : 20
{"member_id":1,"member":"John","committee_id":1,"committee":"Joe"}
{"member_id":1,"member":"John","committee_id":2,"committee":"Sarah"}
{"member_id":1,"member":"John","committee_id":3,"committee":"Amelia"}
{"member_id":1,"member":"John","committee_id":4,"committee":"David"}
20 lignes. Les quatre premières : John avec chacun des comités. Le même schéma se répète ensuite pour Jane, Mary, David et Amelia — dans l'ordre, sans exception.
Récapitulatif — Quelle jointure choisir ?
Le choix se résume à une seule question : que voulez-vous faire des lignes qui n'ont pas de correspondance dans l'autre table ?
| Jointure | Ce qu'elle retourne | Cas d'usage typique |
|---|---|---|
| INNER JOIN | Uniquement les lignes présentes dans les deux tables | Lister les membres qui font partie d'un comité — sans lignes incomplètes |
| LEFT JOIN | Toutes les lignes de gauche + correspondances de droite (NULL si absent) |
Lister tous les membres, y compris ceux sans comité |
| RIGHT JOIN | Toutes les lignes de droite + correspondances de gauche (NULL si absent) |
Lister tous les comités, même ceux sans membres inscrits |
| FULL OUTER JOIN (simulé) | Toutes les lignes des deux tables, avec NULL là où une correspondance manque |
Vue d'ensemble complète : membres sans comité et comités sans membres |
| CROSS JOIN | Toutes les combinaisons possibles entre les deux tables | Générer une matrice exhaustive — à éviter sur de grandes tables |
INNER JOIN et LEFT JOIN couvrent la grande majorité des cas courants. RIGHT JOIN est souvent remplacé par un LEFT JOIN avec les tables inversées — c'est équivalent et plus lisible pour qui lit le code après vous. FULL OUTER JOIN et CROSS JOIN restent des cas spéciaux.
Par carabde : mai 2026 | Mis à jour le 14 juin 2026