Programmation SAS Avancée : Pourquoi et quand utiliser les Entrées/Sorties (I/O) à Bas Niveau ?

En tant que développeurs SAS, nous passons le plus clair de notre temps à manipuler des données avec des instructions que nous connaissons par cœur : SET, MERGE, INFILE, ou PUT. Ces instructions sont fantastiques : elles sont concises, gèrent la boucle de l'étape DATA automatiquement et accomplissent des tâches complexes en quelques lignes.

Cependant, cette simplicité a un prix : la perte de contrôle granulaire.

Il existe dans SAS un framework d'I/O (Entrées/Sorties) dit "à bas niveau", construit non pas sur des instructions globales, mais sur des appels de fonctions. Ce framework est souvent méconnu, mais il est indispensable pour créer des applications avancées, des macros utilitaires dynamiques ou pour optimiser les performances de vos traitements.


Dans cet article, nous allons démystifier cette approche, comprendre quand l'utiliser, et explorer un exemple de code concret.

1. Haut Niveau vs Bas Niveau : De quoi parle-t-on ?

Les termes "haut niveau" et "bas niveau" ne sont pas des indicateurs de la qualité de votre code ou de votre niveau de compétence. C'est une question de point de vue :


  • L'approche Haut Niveau (Abstraite et Puissante) : Vous utilisez des instructions comme SET ou FILE. Une équipe d'experts chez SAS a conçu ces instructions pour couvrir 95% des cas d'usage courants de la manière la plus efficace possible. La plomberie interne (ouverture du fichier, lecture ligne par ligne, gestion des buffers, fermeture) est gérée pour vous.

  • L'approche Bas Niveau (Précise et Granulaire) : Vous utilisez des fonctions comme OPEN, FETCH, GETVARC ou CLOSE. Vous devez gérer vous-même chaque étape de l'accès au fichier. Cela demande plus de lignes de code et plus de variablesColonnes d'une table SAS contenant des données spécifiques (numériques ou caractères). Elles possèdent des attributs comme le nom, le type, la longueur, l'étiquette et le format d'affichage., ce qui multiplie théoriquement les risques d'erreurs , mais cela vous offre une flexibilité absolue.

2. Pourquoi s'embêter avec le bas niveau ? Les cas d'usage.

Pourquoi écrire 20 lignes de code quand un simple SET en nécessite 2 ? La réponse tient en un mot : Flexibilité. Les instructions de haut niveau sont statiques. Les fonctions de bas niveau permettent de s'adapter dynamiquement.

Voici les scénarios où les fonctions I/O brillent particulièrement :


  1. Gérer des noms de fichiers dynamiques : Lire ou écrire dans un fichier dont le nom est déterminé en temps réel par la logique de votre programme.


  2. Analyser des tables SAS inconnues : Traiter une table (SAS Data Set) sans connaître à l'avance le nombre de colonnes, leurs noms ou leurs types. Idéal pour créer des macros génériques.


  3. Lecture conditionnelle : Vérifier l'existence d'une table et s'assurer qu'elle contient bien des observations avant de déclencher un traitement lourd.


  4. Parcourir des répertoires : Lire le contenu d'un dossier système et traiter dynamiquement tous les fichiers qu'il contient, un par un.


  5. Utilisation hors de l'étape DATA : Les fonctions I/O peuvent être enveloppées dans un %SYSFUNC pour lire des fichiers ou des tables directement depuis le code Macro.

3. Les concepts fondamentaux des I/O Bas Niveau

Pour coder avec ces fonctions, vous devez maîtriser quelques concepts clés de l'architecture SAS.

Ouverture et Fermeture

Dans la vraie vie, vous ne pouvez pas lire un livre sans l'ouvrir. C'est pareil ici. Vous devez explicitement ouvrir votre table (fonction OPEN) et la fermer (fonction CLOSE). Garder un fichier fermé le protège contre les corruptions accidentelles et libère la mémoireGemini said
Espace de stockage temporaire (RAM) utilisé par le moteur CAS pour charger et traiter les données à haute vitesse, minimisant les accès disque pour optimiser les performances de SAS Viya.
du système d'exploitation.


Les Identifiants (ID)

Quand vous ouvrez une table, la fonction ne renvoie pas la donnée, mais un Code Identifiant numérique (Data Set ID). Vous devez stocker ce nombre dans une variable (souvent appelée dsid). Si l'ouverture échoue, ce code vaut 0.


Le Buffer DDV (Data Set Data Vector) vs le PDV

C'est la mécanique la plus importante à comprendre. SAS possède un buffer mémoireGemini said
Espace de stockage temporaire (RAM) utilisé par le moteur CAS pour charger et traiter les données à haute vitesse, minimisant les accès disque pour optimiser les performances de SAS Viya.
spécial appelé DDV (Data Set Data Vector).


  • La fonction FETCH ne lit pas la donnée directement dans votre programme. Elle copie une ligne (observation) de la table physique vers le DDV.

  • Le PDV (Program Data Vector) est l'espace mémoireGemini said
    Espace de stockage temporaire (RAM) utilisé par le moteur CAS pour charger et traiter les données à haute vitesse, minimisant les accès disque pour optimiser les performances de SAS Viya.
    de votre étape DATA en cours d'exécution.

  • Vous devez ensuite utiliser une fonction comme GETVARC (pour le texte) ou GETVARN (pour les nombres) pour extraire la valeur du DDV et la rapatrier dans votre PDV (dans une variable de votre choix).

Les Codes de Retour

Chaque action (lire, ouvrir, se déplacer) génère un code de retour numérique généré par le système. Par convention, un code de 0 indique un succès. Un code de -1 renvoyé par la fonction FETCH signifie que vous avez atteint la fin du fichier (End of File).


4. Exemple Pratique : Lire une table séquentiellement

Imaginons que nous voulions lire une table contenant des informations clients, mais en utilisant uniquement les fonctions à bas niveau. Nous cherchons à extraire spécifiquement le "NOM" (caractère) et le "SCORE" (numérique) du client.

Voici comment structurer cette logique (avec gestion des erreurs) :

SAS


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
/* Utilisation de _NULL_ car nous ne créons pas de nouvelle table SAS classique */
data _null_;
/* 1. Définition des variables du programme (PDV) */
length nom_client $ 60
message_erreur $ 75;

/* 2. Ouverture de la table en mode 'Input' (I) */
id_table = open('WORK.CLIENTS', 'I');

/* Si id_table = 0, l'ouverture a échoué */
if id_table = 0 then goto gestion_erreur;

/* 3. Recherche du numéro d'index des colonnes ciblées */
num_col_nom = varnum(id_table, 'NOM');
num_col_score = varnum(id_table, 'SCORE');

/* Vérification que les colonnes existent bien (> 0) */
if not (num_col_nom and num_col_score) then do;
putlog "ERREUR: Les variables NOM ou SCORE sont introuvables.";
goto fermeture_table;
end;

/* Vérification des types de données (C = Caractère, N = Numérique) */
if not (vartype(id_table, num_col_nom) = 'C' and vartype(id_table, num_col_score) = 'N') then do;
putlog "ERREUR: Le type de données des variables est incorrect.";
goto fermeture_table;
end;

/* 4. Boucle de lecture des observations */
do while (1);
/* FETCH charge la ligne courante dans le buffer DDV */
code_retour = fetch(id_table);

/* Un code de retour non nul signifie une erreur OU la fin du fichier (-1) */
if code_retour ne 0 then leave; /* On quitte la boucle */

/* GETVARC/GETVARN extraient les données du DDV vers nos variables */
nom_client = getvarc(id_table, num_col_nom);
score_client = getvarn(id_table, num_col_score);

/* Action métier : affichage dans la log */
putlog "Traitement en cours : " nom_client " - Score : " score_client;
end;

/* 5. Routines de fermeture et d'erreur */
fermeture_table:
/* On ferme proprement la table pour libérer la mémoire */
code_retour = close(id_table);
stop;

gestion_erreur:
code_erreur = sysrc();
message_erreur = sysmsg();
putlog "ERREUR CRITIQUE. Impossible d'ouvrir la table. Code: " code_erreur;
if message_erreur ne '
' then putlog message_erreur;
stop;
run;

Décryptage de l'exemple :

  • Sécurité et Typage : Contrairement à un SET, nous vérifions l'existence des colonnes avec VARNUM et leur type avec VARTYPE avant même de lire la première ligne. Cela rend le code extrêmement robuste.

  • La boucle infinie : Le bloc do while (1) tourne en boucle jusqu'à ce que la fonction FETCH renvoie un code de retour différent de 0 (le plus souvent -1 pour dire "plus de lignes à lire"), ce qui déclenche l'instruction leave.


  • Bonne pratique : La présence du label fermeture_table garantit que, même si une erreur métier survient, l'identifiant id_table est correctement détruit via la fonction CLOSE.

Conclusion

Le code à bas niveau est plus verbeux, c'est indéniable. Mais maîtriser le trio OPEN, FETCH, et GETVAR vous donne un contrôle total sur l'interaction entre votre programme et vos données. C'est la porte d'entrée incontournable pour développer des outils SAS dynamiques, portables et hautement sécurisés.

Dans nos prochains articles, nous verrons comment utiliser ces concepts pour explorer dynamiquement l'intégralité des attributs d'une table sans en connaître la structure à l'avance, ou encore comment parser des fichiers textes complexes. Restez connectés !

Nicolas Housset

Passionné d'informatique, je suis Consultant et expert technique SAS VIYA, également co-fondateur de la société Flexcelite. Spécialisé dans les technologies SAS (Viya, 9.4) et les infrastructures associées (Linux, Hadoop, Azure), ce blog est mon espace pour partager mes mémos techniques et retours d'expérience.