La plupart des variables manipulées dans un langage informatique ont une valeur par défaut (par exemple zéro pour un nombre, chaîne vide pour une chaîne de caractères...). Or, dans le cadre d’une base de données, certains champs de tables ne sont PAS renseignés : affecter une valeur par défaut à une date est par exemple le meilleur moyen de corrompre les données.

Autre exemple : dans une moyenne de notes d’élèves, un zéro signifiant « pas de note » n’est pas du tout la même chose qu’une bulle pour copie blanche (du moins pour l’élève ; l’ordinateur, lui, s’en contrefiche). On pourrait utiliser un -1 pour signifier que la note n’existe pas, mais on complique la logique et le traitement, on mélange « technique » et « fonctionnel » (signification des données). De plus, la table d’à côté contenant des températures qui sont parfois négatives demandera -99999 comme valeur « absente » , qui ne conviendra pas à une base manipulant le déficit budgétaire français ou américain, sans parler des valeurs astronomiques utilisées par les astronomes.

Bref, il fallait une valeur explicitement « absente », c’est le sens de NULL. Le SQL l’utilise abondamment, et l’ABAP comme le PL/SQL le connaissent.

Par contre, la manière de le prendre en compte dans le code diffère.

SQL et PL/SQL

En SQL et en PL/SQL, le NULL ne peut être comparé à quoi que ce soit ; une comparaison l’impliquant est toujours fausse :

SELECT * FROM table WHERE champ = NULL

ne renverra jamais rien. Dans ce cas il faut oublier l’égalité et utiliser un opérateur différent (IS NULL).

Plus vicieux, l’exemple suivant ne ramènera jamais les lignes dont la colonne champ est NULL :

SELECT * FROM table WHERE champ <> 4

alors que dans l’esprit du développeur il s’agissait juste de filtrer une valeur précise - effet de bord du fait que la machine ne peut décider d’une valeur « par défaut » pour cette valeur vide.

Pour parer à ce cela, il faut explicitement traiter les cas NULL ou affecter une valeur par défaut (fonction NVL sur Oracle) :

SELECT * FROM table WHERE champ IS NULL OR champ <>4

équivaut à :

SELECT * FROM table WHERE NVL(champ, 0) <> 4

ABAP

L’ABAP est techniquement capable de gérer la valeur NULL. Un critère WHERE ... IS NULL est syntaxiquement correct. Mais SAP cherchant à supporter des bases de données anciennes et exotiques, il ne tire pas parti de la fonctionnalité : toutes les colonnes de toutes les tables sont flaggées NOT NULL, interdisant la présence d’une valeur NULL, avec une valeur par défaut (un espace[1], ou zéro).

L’ABAP se voit donc obligé de réémuler la fonctionnalité et introduit la notion d’INITIAL, en fait cette même valeur par défaut, zéro ou espace selon le type !

En conséquence, un critère d’une requête ABAP du genre SELECT ... WHERE champ = valeur est tout le temps valide, même pour une valeur « vide » (en fait par défaut). En informatique de gestion, cela ne gêne pas tant que ça. C’est le point positif.

C’est même particulièrement pratique quand on utilise des SELECT-OPTIONS, à savoir une des astuces sympathiques de SAP : l’utilisateur peut alors choisir comme critère de sélection une liste assez complexe (du genre : « je veux les factures 2000 à 2999, et 7000 à 7999, sauf de 7500 à 7599, et puis les factures 8002 et 9003 en sus. »), et ce critère est stocké dans un range, une simple variable structurée (je passe sur les détails techniques).
L’utilisation est basique :

SELECT ... FROM table WHERE ... champ IN range.

Et ça marche aussi quand le range est vide : pas de critère de filtre, donc on veut tout, et la requête ramène tout dans la table interne. Superbe !

Et le piège se referme quand on enchaîne deux critères, qu’au lieu d’un range on utilise une table interne déjà remplie - ou pas :

Par exemple, on choisit une liste de commandes ainsi :

SELECT vbeln FROM vbak INTO t_liste_commande
WHERE
... critère IN range.

Puis on va chercher les lignes de commande en prenant le résultat (t_liste_commande) comme critère, avec la très pratique syntaxe FOR ALL ENTRIES[2] qui permet de balayer une table et d’enchaîner les SELECT sur chaque ligne :

SELECT ... FROM vbap INTO t_liste_lignes
FOR ALL ENTRIES IN t_liste_commande.

Si le critère est vide ou pas trop restrictif, on récupère à la première requête, disons dix commandes, et à la deuxième les lignes associées.

MAIS si le critère est trop restrictif, la première requête ne ramène rien...
et dans la seconde, le FOR ALL ENTRIES, interprète cette table vide comme une absence de critère de filtrage... et ramène toute la table des lignes de commandes !!! Et il y en a, des lignes de commande dans un SAP qui a quelques années d’existence...
Moralité : session figée, mémoire saturée, déconnexion automatique... dans le meilleur des cas[3].

Ce comportement en fait, est parfaitement cohérent, et inverse de celui d’Oracle. Là où Oracle dit « pensez que cette valeur peut ne pas être remplie », SAP rétorque « votre critère de tri peut ne pas être rempli ». Il faut vérifier ses données sous Oracle, vérifier ses filtres sous SAP. Un IF... ENDIF à rajouter, rien de méchant.

Ah oui, une dernière vacherie pour finir[4] : dans la transaction SE16N, qui, c’est limpide, permet de voir le contenu d’une table de manière assez conviviale[5], les valeur à INITIAL sont affichées comme étant vides.
Ça ne tire pas à conséquence pour les chaînes de caractère, mais voir vide un champ numérique NOT NULL (en fait égal à zéro) fait un petit choc la première fois !

PS : Je ne sais pas pas, et suis curieux de savoir, comment des langages modernes comme Java, le C++, ObjectiveC ou .net gèrent la chose... Je n’ai jamais eu à les manier en liaison avec des bases de données.

Notes

[1] Un espace et non une chaîne vide, car pour la base de donnée Oracle généralement sous-jacente, une chaîne vide '' est équivalente à NULL ! Ce qui n’est pas le cas d’autres bases, par exemple PostgreSQL, qui distingue '' et NULL - ça surprend au premier changement de système.

[2] Enfin, j’en ai déjà dit ici pas mal de mal, sur le plan des perfs.

[3] Dans le pire des cas, la production est bloquée par ce programme bogué, et le rythme cardiaque du consultant atteint son maximum annuel.

[4] Je n’avais pas encore dit de mal de SAP dans ce billet, ça manquait.

[5] Sous forme d’un bête tableau qu’on peut scroller, dont on peut agrandir ou réduire les colonnes... Le summum de la convivialité SAP ! À coder, c'est moins drôle.