Exemple avec les tableaux A et B :
A (parent) B (child)
============ =============
id | name pid | name
------------ -------------
1 | Alex 1 | Kate
2 | Bill 1 | Lia
3 | Cath 3 | Mary
4 | Dale NULL | Pan
5 | Evan
Si vous voulez trouver des parents et leurs enfants, vous faites un INNER JOIN
:
SELECT id, parent.name AS parent
, pid, child.name AS child
FROM
parent INNER JOIN child
ON parent.id = child.pid
Le résultat est que chaque correspondance d'un parent
L'id
de du tableau de gauche et un child
le pid
de du deuxième tableau apparaîtra sous forme de ligne dans le résultat :
+----+--------+------+-------+
| id | parent | pid | child |
+----+--------+------+-------+
| 1 | Alex | 1 | Kate |
| 1 | Alex | 1 | Lia |
| 3 | Cath | 3 | Mary |
+----+--------+------+-------+
Maintenant, ce qui précède ne montre pas les parents sans enfants (parce que leurs identifiants n'ont pas de correspondance dans les identifiants des enfants, alors que faites-vous ? Vous faites une jointure externe à la place. Il existe trois types de jointures externes, la gauche, la droite et la jointure externe complète. Nous avons besoin de celle de gauche car nous voulons les lignes "supplémentaires" de la table de gauche (parent) :
SELECT id, parent.name AS parent
, pid, child.name AS child
FROM
parent LEFT JOIN child
ON parent.id = child.pid
Le résultat est qu'en plus des correspondances précédentes, tous les parents qui n'ont pas de correspondance (lire :n'ont pas d'enfant) sont également affichés :
+----+--------+------+-------+
| id | parent | pid | child |
+----+--------+------+-------+
| 1 | Alex | 1 | Kate |
| 1 | Alex | 1 | Lia |
| 3 | Cath | 3 | Mary |
| 2 | Bill | NULL | NULL |
| 4 | Dale | NULL | NULL |
| 5 | Evan | NULL | NULL |
+----+--------+------+-------+
D'où viennent tous ces NULL
viens de? Eh bien, MySQL (ou tout autre SGBDR que vous pourriez utiliser) ne saura pas quoi y mettre car ces parents n'ont pas de correspondance (enfant), donc il n'y a pas de pid
ni child.name
pour correspondre avec ces parents. Donc, il met cette non-valeur spéciale appelée NULL
.
Mon point est que ces NULLs
sont créés (dans le jeu de résultats) lors de la LEFT OUTER JOIN
.
Donc, si nous voulons montrer uniquement les parents qui n'ont PAS d'enfant, nous pouvons ajouter un WHERE child.pid IS NULL
à la LEFT JOIN
au dessus de. Le WHERE
la clause est évaluée (vérifiée) après le JOIN
est fait. Ainsi, il ressort clairement du résultat ci-dessus que seules les trois dernières lignes où le pid
est NULL sera affiché :
SELECT id, parent.name AS parent
, pid, child.name AS child
FROM
parent LEFT JOIN child
ON parent.id = child.pid
WHERE child.pid IS NULL
Résultat :
+----+--------+------+-------+
| id | parent | pid | child |
+----+--------+------+-------+
| 2 | Bill | NULL | NULL |
| 4 | Dale | NULL | NULL |
| 5 | Evan | NULL | NULL |
+----+--------+------+-------+
Maintenant, que se passe-t-il si nous déplaçons ce IS NULL
vérifier à partir de WHERE
à l'adhésion ON
clause ?
SELECT id, parent.name AS parent
, pid, child.name AS child
FROM
parent LEFT JOIN child
ON parent.id = child.pid
AND child.pid IS NULL
Dans ce cas, la base de données essaie de trouver les lignes des deux tables qui correspondent à ces conditions. Autrement dit, les lignes où parent.id = child.pid
ET child.pid IN NULL
. Mais il ne peut trouver aucune correspondance de ce type car pas de child.pid
peut être égal à quelque chose (1, 2, 3, 4 ou 5) et être NULL en même temps !
Donc, la condition :
ON parent.id = child.pid
AND child.pid IS NULL
est équivalent à :
ON 1 = 0
qui est toujours False
.
Alors, pourquoi renvoie-t-il TOUTES les lignes de la table de gauche ? Parce que c'est une JOINTURE GAUCHE ! Et les jointures gauches renvoient les lignes qui correspondent (aucune dans ce cas) ainsi que les lignes du tableau de gauche qui ne correspondent pas le chèque (tous dans ce cas ):
+----+--------+------+-------+
| id | parent | pid | child |
+----+--------+------+-------+
| 1 | Alex | NULL | NULL |
| 2 | Bill | NULL | NULL |
| 3 | Cath | NULL | NULL |
| 4 | Dale | NULL | NULL |
| 5 | Evan | NULL | NULL |
+----+--------+------+-------+
J'espère que l'explication ci-dessus est claire.
Sidenote (pas directement liée à votre question) :Pourquoi diable ne fait-il pas Pan
apparaître dans aucun de nos JOINs ? Parce que son pid
est NULL
et NULL dans la logique (non commune) de SQL n'est pas égal à quoi que ce soit, il ne peut donc correspondre à aucun des identifiants parents (qui sont 1, 2, 3, 4 et 5). Même s'il y avait un NULL ici, il ne correspondrait toujours pas car NULL
n'est égal à rien, pas même NULL
lui-même (c'est une logique très étrange, en effet !). C'est pourquoi nous utilisons la vérification spéciale IS NULL
et non un = NULL
vérifier.
Donc, va Pan
apparaît si nous faisons un RIGHT JOIN
? Oui, il sera! Parce qu'un RIGHT JOIN affichera tous les résultats qui correspondent (le premier INNER JOIN que nous avons fait) plus toutes les lignes de la table RIGHT qui ne correspondent pas (qui dans notre cas est un, le (NULL, 'Pan')
rangée.
SELECT id, parent.name AS parent
, pid, child.name AS child
FROM
parent RIGHT JOIN child
ON parent.id = child.pid
Résultat :
+------+--------+------+-------+
| id | parent | pid | child |
+---------------+------+-------+
| 1 | Alex | 1 | Kate |
| 1 | Alex | 1 | Lia |
| 3 | Cath | 3 | Mary |
| NULL | NULL | NULL | Pan |
+------+--------+------+-------+
Malheureusement, MySQL n'a pas FULL JOIN
. Vous pouvez l'essayer dans d'autres SGBDR, et il affichera :
+------+--------+------+-------+
| id | parent | pid | child |
+------+--------+------+-------+
| 1 | Alex | 1 | Kate |
| 1 | Alex | 1 | Lia |
| 3 | Cath | 3 | Mary |
| 2 | Bill | NULL | NULL |
| 4 | Dale | NULL | NULL |
| 5 | Evan | NULL | NULL |
| NULL | NULL | NULL | Pan |
+------+--------+------+-------+