macro SAS9

Running Conditional Reports and Avoiding Macro Pitfalls

Simon 24/03/2022 3 vues

Automation is at the heart of SAS© programming. A classic scenario involves checking the contents of a table before running a process: "If my table contains data, run the standard report; otherwise, run an anomaly report."

Although the logic seems simple, implementing it using the macro language can run into two major obstacles: variable scope and the misleading behavior of certain automatic variables like SQLOBS.

The Problem: Why Doesn't My Code See the Error?

Imagine code structured into two macros:

  1. %Check_Data: Checks the data and creates an &error flag.

  2. %Run_Report: Reads &error and decides which report to print.

A common mistake is defining the &error variable inside the first macro without precaution.

1. The Scope Trap By default, a macro variable created inside a macro (via %LET) is local. It only exists for the duration of that macro's execution. As soon as %Check_Data finishes, the &error variable is destroyed. When %Run_Report tries to read &error, it finds nothing, and the program crashes (error "Apparent symbolic reference not resolved").

The Solution: The variable must be declared as global at the beginning of the program or before its use:

1%GLOBAL error;
2%LET error = 0;

The Technical Pitfall: SQLOBS and Views

Another subtle error relates to the counting method. The developer was trying to detect if a table was empty by checking the automatic variable &SQLOBS right after a CREATE VIEW statement.

1PROC SQL;
2 CREATE VIEW work.ma_vue AS SELECT * FROM SOURCE;
3QUIT;
4/* Le développeur pense que &SQLOBS contient le nombre de lignes... */

This is a logical error. Creating a view does not read the data; it only defines a structure. Therefore, SAS© always returns SQLOBS = 0 after creating a view, even if the underlying table contains millions of rows. The error report was thus systematically triggered incorrectly.

The Good Practice

To conditionally execute your reports (e.g., PRINT, REPORT procedures), here is the procedure to follow:

  1. Do not wrap your macro calls containing procedures (Proc Print, etc.) inside a DATA _NULL_ step. Macros are called directly in open code.

  2. Count the rows correctly: To check if a table is empty without reading it entirely (which is resource-intensive), query the metadata via SAS© dictionaries.

Here is a robust example:

1/* 1. Récupérer le nombre d'observations depuis les métadonnées */
2PROC SQL NOPRINT;
3 SELECT (nobs - delobs) INTO :num_obs
4 FROM dictionary.tables
5 WHERE LIBNAME = 'WORK' AND memname = 'MA_TABLE';
6QUIT;
7 
8/* 2. Logique conditionnelle */
9%MACRO Choisir_Rapport;
10 %IF &num_obs > 0 %THEN %DO;
11 %Put --- Lancement du Rapport Standard ---;
12 /* %Rapport_Standard; */
13 %END;
14 %ELSE %DO;
15 %Put --- TABLE vide : Lancement du Rapport d'Erreur ---;
16 /* %Rapport_Erreur; */
17 %END;
18%MEND;
19 
20%Choisir_Rapport;
This method is both more performant (no unnecessary data reading) and more reliable for managing your production workflows.