web-dev-qa-db-fra.com

Utilisation de IS NULL ou IS NOT NULL sur les conditions de jointure - Question théorique

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?

35
JoshG

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   |
+------+--------+------+-------+
81
ypercubeᵀᴹ

La partie NULL est calculée APRÈS la jointure réelle, c'est pourquoi elle doit être dans la clause where.

6
Sabeen Malik

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.

3
Muhammad Usama

La clause WHERE est évaluée après le traitement des conditions JOIN.

2
Joe Stefanelli

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.

2
Suroot

Votre plan d'exécution devrait le préciser; le JOIN a priorité, après quoi les résultats sont filtrés.

1
Paul Sonier