Tony Hoare, qui est surtout considéré comme l'inventeur de la référence NULL, l'appelle maintenant une erreur d'un milliard de dollars dont presque tous les langages "souffrent" maintenant, y compris SQL.
Citant Tony (de son article Wikipedia):
J'appelle ça mon erreur d'un milliard de dollars. C'était l'invention de la référence nulle en 1965. A cette époque, je concevais le premier système de type complet pour les références dans un langage orienté objet (ALGOL W). Mon objectif était de m'assurer que toute utilisation de références soit absolument sûre, avec une vérification effectuée automatiquement par le compilateur. Mais je n'ai pas pu résister à la tentation de mettre une référence nulle, simplement parce que c'était si facile à mettre en œuvre. Cela a conduit à d'innombrables erreurs, vulnérabilités et plantages du système, qui ont probablement causé un milliard de dollars de douleur et de dommages au cours des quarante dernières années.La chose intéressante ici est que Tony a été tenté d'implémenter cette référence parce que c'était facile à faire. Mais pourquoi avait-il même besoin d'une telle référence ?
Les différentes significations de NULL
Dans un monde parfait, nous n'aurions pas besoin de NULL. Chaque personne a un prénom et un nom de famille. Chaque personne a une date de naissance, un travail, etc. Ou bien ?
Malheureusement, ce n'est pas le cas.
Tous les pays n'utilisent pas le concept de prénom et de nom.
Tous les gens n'ont pas un emploi. Ou parfois, nous ne connaissons pas leur travail. Ou on s'en fiche.
C'est là que NULL est extrêmement utile. NULL peut modéliser tous ces états que nous ne voulons pas vraiment modéliser. NULL peut être :
- La valeur "indéfinie" , c'est-à-dire la valeur qui n'est pas encore définie (probablement pour des raisons techniques) mais qui pourrait bien l'être plus tard. Pensez à une personne que nous voulons ajouter à la base de données afin de l'utiliser dans d'autres tables. À un stade ultérieur, nous ajouterons le travail de cette personne.
- La valeur "inconnue" , c'est-à-dire la valeur que nous ne connaissons pas (et que nous ne connaîtrons peut-être jamais). Peut-être ne pouvons-nous plus demander à cette personne ou à ses proches sa date de naissance – l'information sera à jamais perdue. Mais nous voulons toujours modéliser la personne, nous utilisons donc NULL dans le sens de UNKNOWN (qui est sa véritable signification en SQL, comme nous le verrons plus tard).
- La valeur "facultatif" , c'est-à-dire la valeur qui n'a pas besoin d'être définie. Notez que la valeur "facultative" apparaît également dans le cas d'une OUTER JOIN, lorsque la jointure externe ne produit aucune valeur d'un côté de la relation. Ou également lors de l'utilisation de GROUPING SETS, où différentes combinaisons de colonnes GROUP BY sont combinées (ou laissées vides).
- La valeur "supprimé" ou "évité" , c'est-à-dire la valeur que nous ne voulons pas spécifier. Peut-être enregistrons-nous généralement l'état civil d'une personne comme cela se fait dans certaines juridictions, mais pas dans d'autres, où il n'est pas légal d'enregistrer des données personnelles de ce type. Par conséquent, nous ne voulons pas connaître cette valeur dans certains cas.
- La valeur "spéciale" dans un contexte donné , c'est-à-dire la valeur que l'on ne peut pas modéliser autrement dans la plage des valeurs possibles. Cela se fait souvent lorsque vous travaillez avec des plages de dates. Supposons que le travail d'une personne est limité par deux dates, et si la personne occupe actuellement ce poste, nous utiliserons NULL pour dire que la période est illimitée à la fin de la plage de dates.
- Le NULL "accidentel" , c'est-à-dire la valeur NULL qui est simplement NULL parce que les développeurs n'y ont pas prêté attention. En l'absence d'une contrainte NOT NULL explicite, la plupart des bases de données supposent que les colonnes acceptent les valeurs NULL. Et une fois que les colonnes sont nullables, les développeurs peuvent juste "accidentellement" mettre des valeurs NULL dans leurs lignes, là où ils n'avaient même pas l'intention de le faire.
Comme nous l'avons vu ci-dessus, il ne s'agit que de quelques-unes des 50 nuances de NULL .
L'exemple suivant affiche différentes significations de NULL dans un exemple SQL concret :
CREATE TABLE company ( id int NOT NULL, name text NOT NULL, CONSTRAINT company_pk PRIMARY KEY (id) ); CREATE TABLE job ( person_id int NOT NULL, start_date date NOT NULL, -- If end_date IS NULL, the “special value” of an unbounded -- interval is encoded end_date date NULL, description text NOT NULL, -- A job doesn’t have to be done at a company. It is “optional”. company_id int NULL, CONSTRAINT job_pk PRIMARY KEY (person_id,start_date), CONSTRAINT job_company FOREIGN KEY (company_id) REFERENCES company (id) ); CREATE TABLE person ( id int NOT NULL, first_name text NOT NULL, -- Some people need to be created in the database before we -- know their last_names. It is “undefined” last_name text NULL, -- We may not know the date_of_birth. It is “unknown” date_of_birth date NULL, -- In some situations, we must not define any marital_status. -- It is “deleted” marital_status int NULL, CONSTRAINT person_pk PRIMARY KEY (id), CONSTRAINT job_person FOREIGN KEY (person_id) REFERENCES person (id) );
Les gens ont toujours argumenté sur l'absence de valeur
Lorsque NULL est une valeur si utile, pourquoi les gens continuent-ils à la critiquer ?
Tous ces cas d'utilisation précédents pour NULL (et d'autres) sont présentés dans cette intéressante et récente conférence de C.J. Date sur "Le problème des informations manquantes" (regardez la vidéo sur YouTube).
Le SQL moderne peut faire beaucoup de choses géniales que peu de développeurs de langages à usage général comme Java, C#, PHP ignorent. Je vais vous montrer un exemple plus bas.
D'une certaine manière, C.J. Date est d'accord avec Tony Hoare sur le fait que (ab)utiliser NULL pour tous ces différents types "d'informations manquantes" est un très mauvais choix.
Par exemple, en électronique, des techniques similaires sont appliquées pour modéliser des choses comme 1, 0, "conflit", "non attribué", "inconnu", "ne s'en soucie pas", "haute impédance". Remarquez cependant qu'en électronique, différentes valeurs spéciales sont utilisés pour ces choses, plutôt qu'une seule valeur NULL spéciale . Est-ce vraiment mieux ? Que pensent les programmeurs JavaScript de la distinction entre différentes valeurs "fausses", comme "null", "undefined", "0", "NaN", la chaîne vide '' ? Est-ce vraiment mieux ?
En parlant de zéro :lorsque nous quittons un instant l'espace SQL et que nous entrons dans les mathématiques, nous verrons que les cultures anciennes comme les Romains ou les Grecs avaient les mêmes problèmes avec le nombre zéro. En fait, ils n'avaient même aucun moyen de représenter le zéro contrairement à d'autres cultures, comme on peut le voir dans l'article de Wikipedia sur le nombre zéro. Citation de l'article :
Les archives montrent que les anciens Grecs semblaient incertains du statut du zéro en tant que nombre. Ils se sont demandé :« Comment rien ne peut-il être quelque chose ? », conduisant à des arguments philosophiques et, à l'époque médiévale, religieux sur la nature et l'existence du zéro et du vide.Comme nous pouvons le voir, les "arguments religieux" s'étendent clairement à l'informatique et aux logiciels, où nous ne savons toujours pas exactement quoi faire de l'absence de valeur.
Retour à la réalité :NULL en SQL
Bien que les gens (y compris les universitaires) ne soient toujours pas d'accord sur le fait que nous ayons besoin d'un encodage pour "indéfini", "inconnu", "facultatif", "supprimé", "spécial", revenons à la réalité et aux mauvais côtés de SQL est NULL.
Une chose qui est souvent oubliée lorsqu'il s'agit de SQL NULL est qu'il implémente formellement le cas UNKNOWN, qui est une valeur spéciale qui fait partie de la logique dite à trois valeurs, et il le fait de manière incohérente, par ex. dans le cas d'opérations UNION ou INTERSECT.
Si nous revenons à notre modèle :
Si, par exemple, nous voulons trouver toutes les personnes qui ne sont pas enregistrées comme étant mariées, intuitivement, nous aimerions écrire la déclaration suivante :
SELECT * FROM person WHERE marital_status != 'married'
Malheureusement, en raison de la logique à trois valeurs et de la valeur NULL de SQL, la requête ci-dessus ne renverra pas les valeurs qui n'ont pas de marital_status explicite. Par conséquent, nous devrons écrire un prédicat explicite supplémentaire :
SELECT * FROM person WHERE marital_status != 'married' OR marital_status IS NULL
Ou, nous contraignons la valeur à une valeur NOT NULL avant de la comparer
SELECT * FROM person WHERE COALESCE(marital_status, 'null') != 'married'
La logique à trois valeurs est difficile. Et ce n'est pas le seul problème avec NULL en SQL. Voici d'autres inconvénients liés à l'utilisation de NULL :
- Il n'y a qu'un seul NULL, alors que nous voulions vraiment encoder plusieurs valeurs "absentes" ou "spéciales" différentes. La plage de valeurs spéciales utiles dépend fortement du domaine et des types de données utilisés. Pourtant, la connaissance du domaine est toujours nécessaire pour interpréter correctement la signification d'une colonne acceptant les valeurs nulles, et les requêtes doivent être conçues avec soin afin d'éviter que des résultats erronés ne soient renvoyés, comme nous l'avons vu ci-dessus.
- Encore une fois, la logique à trois valeurs est très difficile à maîtriser. Bien que l'exemple ci-dessus soit encore assez simple, que pensez-vous que la requête suivante donnera ?
SELECT * FROM person WHERE marital_status NOT IN ('married', NULL)
Exactement. Cela ne donnera rien du tout, comme expliqué dans cet article ici. En bref, la requête ci-dessus est la même que celle ci-dessous :
SELECT * FROM person WHERE marital_status != 'married' AND marital_status != NULL -- This is always NULL / UNKNOWN
-
La base de données Oracle traite NULL et la chaîne vide '' comme la même chose. C'est très délicat car vous ne remarquerez pas immédiatement pourquoi la requête suivante renvoie toujours un résultat vide :
SELECT * FROM person WHERE marital_status NOT IN ('married', '')
-
Oracle (encore une fois) ne met pas les valeurs NULL dans les index. C'est la source de nombreux problèmes de performances désagréables, par exemple, lorsque vous utilisez une colonne nullable dans un prédicat NOT IN en tant que tel :
SELECT * FROM person WHERE marital_status NOT IN ( SELECT some_nullable_column FROM some_table )
Avec Oracle, l'anti-jointure ci-dessus entraînera une analyse complète de la table, que vous ayez ou non un index sur some_nullable_column. En raison de la logique à trois valeurs et parce qu'Oracle ne met pas de valeurs NULL dans les index, le moteur devra accéder à la table et vérifier chaque valeur juste pour s'assurer qu'il n'y a pas au moins une valeur NULL dans l'ensemble, ce qui rendrait le tout le prédicat INCONNU.
Conclusion
Nous n'avons pas encore résolu le problème NULL dans la plupart des langues et des plates-formes. Bien que je prétende que NULL n'est PAS l'erreur d'un milliard de dollars pour laquelle Tony Hoare essaie de s'excuser, NULL est certainement loin d'être parfait non plus.
Si vous voulez rester du bon côté avec la conception de votre base de données, évitez à tout prix les valeurs NULL, à moins que vous n'ayez absolument besoin de l'une de ces valeurs spéciales pour encoder à l'aide de NULL. N'oubliez pas que ces valeurs sont :"indéfini", "inconnu", "facultatif", "supprimé" et "spécial", et plus :Les 50 nuances de NULL . Si vous n'êtes pas dans une telle situation, ajoutez toujours par défaut une contrainte NOT NULL à chaque colonne de votre base de données. Votre conception sera beaucoup plus propre et vos performances bien meilleures.
Si seulement NOT NULL était la valeur par défaut dans DDL, et NULLABLE le mot-clé qui devait être défini explicitement…
Quelles sont vos prises et expériences avec NULL ? Comment fonctionnerait un meilleur SQL à votre avis ?
Lukas Eder est fondateur et PDG de Data Geekery GmbH, situé à Zurich, en Suisse. Data Geekery vend des produits et services de base de données autour de Java et SQL depuis 2013.
Depuis ses études de Master à l'EPFL en 2006, il est fasciné par l'interaction de Java et SQL. Il a acquis la majeure partie de cette expérience dans le domaine de l'e-banking suisse à travers diverses variantes (JDBC, Hibernate, principalement avec Oracle). Il est heureux de partager ces connaissances lors de diverses conférences, JUG, présentations internes et sur son blog d'entreprise.