D'expérience, beaucoup d'erreurs inattendues dans les bibliothèques, outils et langages sont liées à la présence de valeurs manquantes.
Par exemple, considère un array NumPy 1D contenant des valeurs NaN.

Quand on agrège cet array NumPy pour calculer, disons, sa somme, on obtient le résultat suivant :

Étrange, non ?
Alors qu'un résultat de 6 aurait eu plus de sens ici, NumPy produit une valeur NaN à la place.
Bref, ce n'est pas le sujet du jour, mais tu vois l'idée.
Une erreur silencieuse similaire existe aussi en SQL, plus précisément dans l'utilisation de la clause NOT IN, et beaucoup d'utilisateurs SQL ne la connaissent pas.
Voyons ça !
Imaginons qu'on a les tables suivantes (students et names) dans notre base de données :

L'objectif est de sélectionner les enregistrements de la table students où first_name n'est pas dans la table names.
Une façon de faire est d'utiliser la clause NOT IN, comme ci-dessous :

Et on obtient bien les résultats attendus.
Tout va bien jusque-là !
Maintenant, supposons que notre table names contienne une valeur NULL :

Si on relance la même requête, on n'obtient aucun résultat cette fois :

Petite parenthèse : si on avait utilisé la clause IN pour sélectionner les lignes de la table students où first_name est dans la table names, on constate que ça fonctionne comme prévu :

C'est étrange, non ?
Pourquoi n'obtient-on aucun résultat avec NOT IN ?
La raison pour laquelle NOT IN ne retourne aucun résultat alors que IN fonctionne comme prévu tient au fonctionnement interne de ces deux clauses.
Pour simplifier, imaginons qu'on vérifie le premier enregistrement (où first_name est "John") de la table students :

La clause WHERE a besoin d'une valeur booléenne pour déterminer si un enregistrement doit être retenu ou non.
Quand on utilise IN, cette valeur booléenne est évaluée avec l'opérateur OR comme suit :

Si n'importe quelle condition est TRUE, la ligne est retenue.
En revanche, quand on utilise NOT IN, la valeur booléenne est évaluée avec l'opérateur AND comme suit :

Pour que cette expression soit TRUE, toutes les conditions individuelles DOIVENT être TRUE.
Mais la condition (JOHN != None) pose problème car, en règle générale, elle produit une valeur UNKNOWN.
L'expression entière s'évalue donc à UNKNOWN, et aucun enregistrement n'est retourné.
C'est le cas pour chaque enregistrement de la table students. Résultat : la requête ne retourne aucun enregistrement :

Que faire à la place ?
Il existe plusieurs façons d'éviter ça.
Puisque notre objectif est de sélectionner les enregistrements de la table students où first_name n'est pas dans la table names, on peut :
Filtrer les valeurs NULL dans la sous-requête :

Utiliser un Anti Join : un anti join retourne uniquement les lignes de la table de gauche qui n'ont aucune correspondance dans la table de droite. C'est exactement ce qu'il nous faut ici, et voici comment l'implémenter :

Ce genre d'erreurs peut parfois prendre un temps considérable à débugger si tu n'en as pas conscience au préalable.
J'espère que ça t'a été utile !
Si tu veux expérimenter avec ce qu'on a vu sur la clause NOT IN aujourd'hui, télécharge ce Jupyter Notebook : Notebook Clause NOT IN.
Envie d'aller plus loin ? DataXp t'attend : formations et parcours pour progresser en data science, machine learning et IA.
👉 À toi : peux-tu proposer une autre solution au problème ci-dessus ?

