CPak
CPak

Reputation: 13581

conditionally execute macro function

I want to conditionally execute macro function dependent on existence of table

Data

data have;
    do i = 1 to 5;
        output;
    end;
run;

Table I want to condition on

data counttbl;
    infile datalines delimiter='::'; 
    format variable $char400. condition $char400.;
    input variable $ condition $;
    datalines;
runcount::i>1
;
run;

Some tests to show that I can condition on counttbl existence (works as expected)

data _null_;
    call execute("data want;");
    call execute("set have;");
    if exist("work.counttbl") then
        call execute("tmp = 1;");
    call execute('run;');
stop;
run;

The above creates column tmp = 1

proc delete data=work.counttbl;
run;

data _null_;
    call execute("data want;");
    call execute("set have;");
    if exist("work.counttbl") then
        call execute("tmp = 1;");
    call execute('run;');
stop;
run;

After deleting the table, the above does not create the column tmp

Macro function to execute

%macro apply_change();
    call execute('if _n_ eq 1 then do;');
    do until (eof);
        set counttbl (keep=variable) end=eof;
        call execute(strip(variable) || ' = 0;');
    end;
    call execute('end;');
    call missing(eof);
    do until (eof);
        set counttbl end=eof;
        call execute(strip(variable) || ' + (' || strip(condition) || ');');
    end;
    call missing(eof);
%mend apply_change;

Works fine when counttbl exists

data _null_;
    call execute("data want;");
    call execute("set have;");
    if exist("work.counttbl") then
        %apply_change()
    call execute('run;');
stop;
run;

Throws error when counttbl is deleted - I want it to simply skip executing the macro function

proc delete data=work.counttbl;
run;

data _null_;
    call execute("data want;");
    call execute("set have;");
    if exist("work.counttbl") then
        %apply_change()
    call execute('run;');
stop;
run;

ERROR: File WORK.COUNTTBL.DATA does not exist.

Thanks in advance for your help

Upvotes: 0

Views: 649

Answers (3)

Tom
Tom

Reputation: 51566

You cannot use a test at run time to prevent SAS from compiling some lines of code in the same data step. Instead you need to use macro logic to not generate the lines of code.

It looks like you want to use the dataset to generate a series of variables that count how many times a condition is met. I find that it is much easier to debug if that type of data driven code generation is done by writing the code to a file. Then you can stop after generating the file and look at the generated code and make sure your code generation step is working properly.

It looks like you want the new dataset generated whether or not the file with the list of VARIABLE/CONDITION pairs exists or not. So just hard code that part of the data step and only conditionally generate the part that calculates the new variables. Since you are generating sum statements there is not need for the IF _N_=1 block to set the initial values to zero. SAS will automatically set them to zero and retain them. (Assuming that HAVE doesn't already have variables with those names, in which the sum statement won't work right either.)

filename code temp;
data _null_;
  file code ;
%if %sysfunc(exist(&dsname)) %then %do;
  set &dsname end=eof;
  put '  ' variable '+ ( ' condition ');' ;
%end ;
run;

So either the temp file CODE is empty or it has code like:

VAR1 + ( dx='123' );
VAR2 + ( sex='M' );

Then to make your dataset just run this step with a %INCLUDE to add in the conditionally generated code.

data want;
  set have;
%include code /source2;
run;

If you are using an old version of SAS you will need to wrap that %IF statement into a macro. But the newest releases of SAS allow that type of simple %IF/%THEN/%DO/%END construct in open code.

Upvotes: 1

Richard
Richard

Reputation: 27498

Your problem area is

if exist("work.counttbl") then
    %apply_change()

Macro is processed, and generates source code, prior to the SAS system implicitly compiling the data step and running it.

I would not recommend pursuing this avenue because it mixes scopes (macro/data step)

If you persist, there are a couple of tips

  • place all the code generation in a macro
  • place the existential check in the macro, and do NOT codegen source that has a SET counttbl when not present

For example:

%macro apply_change();
    %if not %sysfunc(EXISTS(WORK.COUNTTBL)) %then %return; 

    %* This code gen only done when COUNTTBL present;
    call execute('if _n_ eq 1 then do;');
    do until (eof);
        set counttbl (keep=variable) end=eof;
        call execute(strip(variable) || ' = 0;');
    end;
    call execute('end;');
    call missing(eof);
    do until (eof);
        set counttbl end=eof;
        call execute(strip(variable) || ' + (' || strip(condition) || ');');
    end;
    call missing(eof);
%mend apply_change;

Replace

if exist("work.counttbl") then %apply_change()

With

%apply_change()

Upvotes: 2

Lee
Lee

Reputation: 1427

First

if exist("work.counttbl") then

will only apply to the first line of your macro

 call execute('if _n_ eq 1 then do;');

This is because the macro is evaluated before the datastep is executed. So sas will simple paste the macro content to the location of the macro invocation. But even when it would apply to the whole macro it will not work. Take for example the following code:

data x;
  if 0 then do;
    set y;
  end;
  set z;
run;

Her y and z has to exist. However no observation will be read from y only structure is taken.

Upvotes: 1

Related Questions