Pourquoi JOIN multiplie les lignes : clés, contraintes et granularité
Diagnostiquer les lignes répétées après JOIN en comptant les clés correspondantes et en choisissant la granularité attendue.
Dans ce guide
La réponse courte
Une jointure produit des paires de lignes correspondantes. Une ligne source peut donc apparaître plusieurs fois sans constituer une erreur. Définissez d'abord ce que représente une ligne du résultat : une commande, un article commandé ou un total par commande. Comptez ensuite les occurrences des véritables clés de jointure dans les deux entrées, après leurs filtres. Pour une clé non NULL présente L fois à gauche et R fois à droite, une jointure d'égalité produit L × R paires. Vérifiez séparément les contraintes d'unicité : les observations actuelles ne garantissent pas les données futures.
Ce que compte une jointure
Avec INNER JOIN, chaque paire satisfaisant la condition ON contribue une ligne au résultat. Une jointure externe peut aussi conserver une ligne sans partenaire, avec des valeurs NULL du côté manquant. Le produit cartésien explique la logique SQL, pas une obligation de construire physiquement toutes les paires. Examinez donc la condition de jointure puis le reste de la requête. Un filtre WHERE appliqué ensuite peut supprimer certaines lignes et modifier le nombre final d'enregistrements.
Produit cartésien et clés répétées
CROSS JOIN associe chaque ligne de gauche à chaque ligne de droite. Deux entrées de trois lignes produisent ainsi neuf paires. Une jointure d'égalité ne conserve que les clés correspondantes. Lorsqu'une même clé est répétée dans les deux entrées, son nombre de paires est le produit des deux nombres d'occurrences : une multiplication plusieurs à plusieurs. Une relation un à plusieurs n'implique pas, à elle seule, un résultat plus grand que la plus grande table d'entrée.
WITH t1(num) AS (VALUES (1), (2), (3)),
t2(num) AS (VALUES (1), (3), (5))
SELECT t1.num AS left_num, t2.num AS right_num
FROM t1 CROSS JOIN t2;
-- Nine pairs in this illustrative dataset.Une relation un à plusieurs
Dans l'exemple, la commande 1 possède deux articles et la commande 2 en possède un. La jointure donne trois lignes, dont deux portent le même identifiant de commande mais des identifiants d'article différents. Ces lignes représentent des relations utiles ; les supprimer automatiquement ferait perdre des informations. Si le rapport attend une ligne par article, cette granularité convient. S'il attend une ligne par commande, agrégez d'abord les articles ou choisissez une autre manière de récupérer les informations demandées.
WITH orders(order_id) AS (VALUES (1), (2)),
order_items(item_id, order_id) AS (VALUES (10, 1), (11, 1), (12, 2))
SELECT o.order_id, i.item_id
FROM orders AS o
JOIN order_items AS i ON i.order_id = o.order_id
ORDER BY o.order_id, i.item_id;
-- Three pairs: order 1 appears with two different items.LEFT JOIN et lignes sans correspondance
LEFT JOIN conserve une ligne gauche sans correspondance et remplit les colonnes droites avec NULL. Une ligne ayant plusieurs partenaires apparaît néanmoins plusieurs fois. Dans le petit exemple, les clés 1 et 3 correspondent, tandis que la clé 2 n'a pas de partenaire : trois lignes sont produites. Cela décrit la jointure avant les filtres suivants. Un WHERE portant sur une colonne droite peut éliminer les lignes sans correspondance ; analysez la requête entière plutôt que son seul type de jointure.
WITH t1(num) AS (VALUES (1), (2), (3)),
t2(num) AS (VALUES (1), (3), (5))
SELECT t1.num AS left_num, t2.num AS right_num
FROM t1 LEFT JOIN t2 ON t1.num = t2.num
ORDER BY t1.num;
-- Three rows; the right value for left key 2 is NULL.Compter les clés réellement utilisées
Comptez les occurrences de chaque clé effectivement utilisée, après les filtres pertinents. L'exemple regroupe les articles par order_id et révèle la commande ayant deux articles. Pour une égalité ordinaire, écartez les clés NULL : NULL n'est pas égal à un autre NULL dans cette condition. Une occurrence par clé dans l'échantillon ne prouve ni une contrainte d'unicité ni une relation un à un permanente. Consultez aussi le schéma, car les prochains ajouts peuvent changer la distribution observée.
WITH order_items(item_id, order_id) AS (VALUES (10, 1), (11, 1), (12, 2))
SELECT order_id, COUNT(*) AS item_count
FROM order_items
WHERE order_id IS NOT NULL
GROUP BY order_id
HAVING COUNT(*) > 1;
-- Order 1 has two items in this illustrative dataset.Clé étrangère, unicité et NULL
Une clé étrangère relie les colonnes enfants à une clé référencée dont l'unicité est garantie. Elle ne rend pas les valeurs enfants uniques : plusieurs articles peuvent référencer la même commande. Les valeurs NULL peuvent aussi être autorisées ; la clé étrangère seule n'impose pas un identifiant de parent renseigné. Le schéma illustratif ajoute donc explicitement NOT NULL à order_id. Comparez les colonnes de ON aux contraintes déclarées, notamment pour une clé composée ou une condition supplémentaire.
CREATE TABLE orders (
order_id integer PRIMARY KEY
);
CREATE TABLE order_items (
item_id integer PRIMARY KEY,
order_id integer NOT NULL REFERENCES orders(order_id)
);Choisir la granularité attendue
Plusieurs tables de détail jointes ensemble peuvent répéter les valeurs d'une collection pour chaque élément d'une autre collection. Les sommes deviennent alors trompeuses. Si le résultat doit contenir une ligne par commande, agrégez les détails à ce niveau avant la jointure. L'exemple compte les articles puis joint ce résumé aux commandes. Son INNER JOIN conserve seulement les commandes ayant des articles. Pour conserver les autres, utilisez LEFT JOIN et décidez explicitement comment représenter le nombre absent, par exemple par zéro.
SELECT o.order_id, i.item_count
FROM orders AS o
JOIN (
SELECT order_id, COUNT(*) AS item_count
FROM order_items
GROUP BY order_id
) AS i ON i.order_id = o.order_id;Comparer trois types de jointure
Le dernier exemple définit les petites entrées avec VALUES : les clés sont visibles dans la requête. Seules 1 et 3 sont communes, donc INNER JOIN produit deux lignes. L'exemple précédent avec LEFT JOIN conserve également la clé 2 et produit trois lignes. CROSS JOIN forme les neuf paires possibles. Ces nombres sont déduits des valeurs illustratives, sans mesure de performances. Imaginez une occurrence supplémentaire d'une clé correspondante et identifiez les nouvelles paires qu'elle ajouterait.
WITH t1(num) AS (VALUES (1), (2), (3)),
t2(num) AS (VALUES (1), (3), (5))
SELECT t1.num
FROM t1 INNER JOIN t2 ON t1.num = t2.num
ORDER BY t1.num;
-- Two rows: matching keys 1 and 3.Points à vérifier
- Compter les clés des deux entrées après leurs filtres, en tenant compte de NULL.
- Distinguer PRIMARY KEY, UNIQUE et FOREIGN KEY des seules observations sur les données.
- Pour L occurrences à gauche et R à droite, compter L × R paires pour une clé d'égalité non NULL.
- Consulter EXPLAIN pour les estimations, mais diagnostiquer la multiplication avec les clés : l'algorithme seul ne la prouve pas.
Champ d’application
Les exemples utilisent une égalité et la syntaxe PostgreSQL. D'autres conditions, des clés composées, WHERE, l'agrégation et la projection peuvent changer les résultats. Les nombres concernent les VALUES illustratifs ; les requêtes et leur vitesse n'ont pas été testées.