dov7139
dov7139

Reputation: 23

how to correctly read error code in SAS macro loop?

I'm attempting to build SAS code that looks through past logs, reruns failed reports and informs about rerun status. I'm using &syscc. macro to check whether there was an error within my rerun attempt and resetting it between each attempt. However I'm running into a problem using syscc within a macro %do loop as syscc does not show the correct error code. I.e. it didn't show any error for the first loop although it should've and then it failed to reset back to 0. ( shared the piece of code below ) . Is there alternative approach to using syscc or another way of writing the macro?

%macro rerunner();
    %do i=1 %to &numrecs;
    %put &i;
    

        /*selecting a log and code*/
        proc sql;
            select cname into :  selected_code separated by ','
            from code_logs
            where rown=&i;
        quit;   
        proc sql;
            select lname into :  selected_log separated by ','
            from code_logs
            where rown=&i;
        quit;

        /*counting errors*/
        data _null_; 
            infile "&loglocation./&selected_log."  end=last;
            input;
            if _infile_  =: 'ERROR' then error_count+1;
            if last and error_count >=1 then CALL EXECUTE('%rerun(&selected_log. ,         &selected_code.)') ;
            if last and error_count < 1 then CALL EXECUTE('%no_rerun(&selected_log.)') ;
        run;


    %end;



%mend;


%macro rerun(log,code);


    %let syscc = 0;
    %include "&codelocation./&code." ;

    %if &syscc. <= 6 %then %do;
            %success(&log.);
        %end;

    %else %do;
            %failure(&log.)
        %end;

%mend;


...
    
%rerunner();

Upvotes: 2

Views: 245

Answers (2)

Tom
Tom

Reputation: 51566

As Joe mentions you seem to be falling into a classic misuse of CALL EXECUTE() to run macro code. Since the code that CALL EXECUTE() submits does not run until after the data step you also want the macro calls in the code to not run until after the data step. Otherwise the macro logic will be evaluated before the code that the macro generates has a chance to run.

So wrap the macro call with %NRSTR(). You will notice the difference immediately in the lines of code that call execute echoes to the SAS log. Without the macro quoting the CODE that macro generates is echoed. With the macro quoting the actual macro call itself is echoed.

Example:

92   %macro print(ds);
93   proc print data=&ds; run;
94   %mend print;
95
96   data _null_;
97    call execute('%print(sashelp.class)');
98    call execute('%nrstr(%print)(sashelp.class)');
99   run;

NOTE: DATA statement used (Total process time):
      real time           0.00 seconds
      cpu time            0.00 seconds


NOTE: CALL EXECUTE generated line.
1   + proc print data=sashelp.class; run;

NOTE: There were 19 observations read from the data set SASHELP.CLASS.
NOTE: PROCEDURE PRINT used (Total process time):
      real time           0.00 seconds
      cpu time            0.00 seconds


2   + %print(sashelp.class)

NOTE: There were 19 observations read from the data set SASHELP.CLASS.
NOTE: PROCEDURE PRINT used (Total process time):
      real time           0.00 seconds
      cpu time            0.00 seconds

It does not matter for this trivial example because the macro does not have any macro logic that makes decisions about what code to run based on the results of code that the macro had generated earlier. But your %RERUN() macro does.

Another easy way to avoid this trap is not use CALL EXECUTE. Instead write the code to a file and then use %INCLUDE to run the code at the end of the data step.

So something like this:

filename code temp;
data _null_;
  set somedataset;
  file code;
  if (some condition) then put '%rerun(' somevar ')' ;
run;
%include code / source2;

Upvotes: 3

Joe
Joe

Reputation: 63424

You've got a timing issue here. Your macro %rerun is using the &syscc defined as it is before anything happens!

The way to think of this is to see this as having 2 passes. First the "macro" pass, then the "SAS" pass.

  • Macro pass: do all of the macro language things (stuff that starts with a % mostly), and create a larger SAS code
  • SAS pass: run as SAS code

So %rerun works like this.

Starting code:

%macro rerun(log,code);    
    %let syscc = 0;
    %include "&codelocation./&code." ;
    %if &syscc. <= 6 %then %do;
            %success(&log.);
        %end;
    %else %do;
            %failure(&log.)
        %end;
%mend;

Macro unroll step:

%let syscc=0;

-- syscc=0

%include "&codelocation./&code.";

-- include the code from that spot into the SAS code (but don't run it)

%if &syscc <= 6 %then %do;
     %success(&log.);
%end;

-- syscc=0, so it is less than six, so do this part -- unroll macro success into SAS code and include it here

%else %do;
%end;

-- this does nothing since if was true

So now, the %rerun macro goes into the SAS code step, and actually does things. Except... it doesn't matter whether the code sets an error or not, because there's no longer a check of syscc anywhere - it's already going through the syscc=0 path.

If you want to do something like this, you need to have a way to interactively look up the value of syscc during macro excecution. That's possible, but it's not the best design.

Better design here would be to store the value of &syscc into a dataset at the end of the execution of your macro (or whatever), and check that, probably calling the rerun from that.

data syscc_status;
input prog $ timestamp :datetime17. syscc;
datalines;
prog1 01JAN2024:01:01:01 3
prog2 01JAN2024:01:01:02 8
prog3 01JAN2024:01:01:03 6
;;;;
run;

Something like that. Then your programs when they run can be in a wrapper like so:

%macro runprogram(program=,log=);
  %let syscc=0;
  proc printto log="&log";
  run;
  %include "&program";
  proc printto;
  run;

  data syscc_append;
    prog="&program";
    timestamp = now();
    syscc=&syscc;
  run;

  proc append base=syscc_status data=syscc_append;
  run;
%mend runprogram;

Now you can do stuff like this:

proc sql;
  select cats('%rerun(program=',prog,')') into :proglist separated by ' '
   from syscc_status
    where timestamp gt dhms(today(),0,0,0)
  ;
quit;

&proglist;

and your %rerun macro just calls the runprogram macro (it probably could just be straight up the runprogram macro directly called, unless you wanted to do something else). Obviously you may need to send the log name also if it's not simple.

Upvotes: 2

Related Questions