Question théorique ici:
Pourquoi la spécification de table.field IS NULL ou table.field IS NOT NULL ne fonctionne pas sur une condition de jointure (jointure gauche ou droite par exemple) mais uniquement dans l'état où?
Exemple non fonctionnel:
- cela devrait retourner tous les envois avec tous les retours (valeurs non nulles) filtrés. Cependant, cela renvoie tous les envois, même si quelque chose correspond à l'instruction [r.id is null].
SELECT
*
FROM
shipments s
LEFT OUTER JOIN returns r
ON s.id = r.id
AND r.id is null
WHERE
s.day >= CURDATE() - INTERVAL 10 DAY
Exemple de travail:
-Cela retourne la quantité correcte de lignes qui est le total des expéditions, moins celles liées à un retour (valeurs non nulles).
SELECT
*
FROM
shipments s
LEFT OUTER JOIN returns r
ON s.id = r.id
WHERE
s.day >= CURDATE() - INTERVAL 10 DAY
AND r.id is null
Pourquoi est-ce le cas? Toutes les autres conditions de filtrage entre deux tables jointes fonctionnent très bien, mais pour une raison quelconque IS NULL et IS les filtres NOT NULL ne fonctionnent pas sauf dans l'instruction where) .
Quelle est la raison pour ça?
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
's id
du tableau de gauche et d'un child
's pid
du deuxième tableau s'affichera comme une 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 effectuez 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 la 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
Résultat: outre les matchs précédents, tous les parents qui n'ont pas de match (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
? Eh bien, MySQL (ou tout autre SGBDR que vous pouvez utiliser) ne saura pas quoi y mettre car ces parents n'ont pas de correspondance (kid), donc il n'y a ni pid
ni child.name
pour correspondre avec ces parents. Ainsi, il place cette non-valeur spéciale appelée NULL
.
Mon point est que ces NULLs
sont créés (dans le jeu de résultats) pendant le 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
au LEFT JOIN
au dessus de. La clause WHERE
est évaluée (vérifiée) une fois la JOIN
terminée. Ainsi, il est clair d'après le résultat ci-dessus que seules les trois dernières lignes où pid
est NULL seront affichées:
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érification de la clause WHERE
à la clause ON
jointe?
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 des lignes des deux tables qui correspondent à ces conditions. Autrement dit, les lignes où parent.id = child.pid
ET child.pid IN NULL
. Mais il peut trouver pas une telle correspondance 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 du tableau de gauche? Parce que c'est un LEFT JOIN! Et les jointures de gauche retournent lignes qui correspondent (aucune dans ce cas) et aussi lignes du tableau de gauche qui ne correspondent pas = le chèque (tout 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é à votre question): Pourquoi diable Pan
n'apparaît dans aucun de nos JOINs? Parce que son pid
est NULL
et NULL dans la logique (non courante) de SQL n'est égal à rien, il ne peut donc correspondre à aucun des ID parents (qui sont 1,2, 3,4 et 5). Même s'il y avait un NULL, il ne correspondrait toujours pas car NULL
ne correspond à 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, Pan
apparaîtra 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) ainsi que toutes les lignes de la table RIGHT qui ne correspondent pas (ce 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 cela montrera:
+------+--------+------+-------+
| 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 |
+------+--------+------+-------+
La partie NULL
est calculée APRÈS la jointure réelle, c'est pourquoi elle doit être dans la clause where.
En fait, le filtre NULL n'est pas ignoré. La chose est la façon dont la jonction de deux tables fonctionne.
Je vais essayer de parcourir les étapes effectuées par le serveur de base de données pour le faire comprendre. Par exemple, lorsque vous exécutez la requête dont vous avez dit qu'elle ignore la condition NULL. SELECT * FROM envois s LEFT OUTER JOIN renvoie r
ON s.id = r.id AND r.id is null WHERE s.day> = CURDATE () - INTERVAL 10 DAY
La première chose qui s'est produite est que toutes les lignes de la table SHIPMENTS sont sélectionnées
à l'étape suivante, le serveur de base de données commencera à sélectionner un enregistrement un par un dans la deuxième table (RETOURS).
à la troisième étape, l'enregistrement de la table RETURNS sera qualifié par rapport aux conditions de jointure que vous avez fournies dans la requête qui dans ce cas est (s.id = r.id et r.id est NULL)
notez que cette qualification appliquée à la troisième étape décide uniquement si le serveur doit accepter ou rejeter l'enregistrement actuel de la table RETOURS à ajouter à la ligne sélectionnée de la table EXPÉDITION. Il ne peut en aucun cas affecter la sélection de l'enregistrement dans la table SHIPMENT.
Et une fois que le serveur a fini de joindre deux tables qui contiennent toutes les lignes de la table SHIPMENT et les lignes sélectionnées de la table RETURNS, il applique la clause where sur le résultat intermédiaire. donc lorsque vous mettez (r.id est NULL) la condition dans la clause where, tous les enregistrements du résultat intermédiaire avec r.id = null sont filtrés.
La clause WHERE
est évaluée après le traitement des conditions JOIN
.
Vous faites un LEFT OUTTER JOIN
qui indique que vous voulez que chaque Tuple de la table à GAUCHE de l'instruction ait un enregistrement correspondant dans la table DROITE. Cela étant le cas, vos résultats sont élagués à partir de la table RIGHT mais vous vous retrouvez avec les mêmes résultats que si vous n'aviez pas du tout inclus AND dans la clause ON.
L'exécution de AND dans la clause WHERE provoque le pruneau après le LEFT JOIN.
Votre plan d'exécution devrait le préciser; le JOIN a priorité, après quoi les résultats sont filtrés.