0
votes

Is it possible to make a macro of this form work?

    %macro tableMath(input1,input2);
    %local result;
    proc sql; ---some code here using inputs--- quit;
    proc sql; ---more code here--- quit;
    proc sql;
    select something into: result
    quit;
    &result
    %mend;

I want to run some fairly complicated logic on each observation of a dataset, and in any other language I've used before the way to do this would be to encapsulate it in a function that returns a result each time it's called--I'm not sure how to do this logic in SAS however.

EDIT: input1 and input2 would be columns of a dataset and result would be used to create a new column in some other macro in another part of the program. I don't need a specific code solution I just literally don't get how you're supposed to do traditional function logic where you need a return value in SAS...

4
stackoverflow.com/questions/33817518/… In this question it's brought up that you can't use anything without a % or else it will pollute the processing stream and you won't get the right return value. Does this mean you literally can't use PROC SQL or a DATA step in macros where you need a return value??S420L
There are ways to return a value from a macro . There are also ways to write “function-style macros” but they aren’t true functions. The macro language is a pre-processor, so if you have PROC SQL code that calls a macro, the macro will execute before the SQL code executes. If you can make a little example of how you hope to use your function, that would make this question more specific/answerable.Quentin
You can try using a data step to code your complicated logic, which would return a result for every row it iterates. After that, use a call execute() to run some step, if the result matches your wished criteria. As Quentin said, there are ways... But you'd have to be a bit more specific for us.samkart
I want to run some fairly complicated logic on each observation of a dataset If you're newer to SAS I'd say explain that logic. Usually it's not as complex as it seems or there are better methods to use in SAS, even if it's a few steps.Reeza

4 Answers

3
votes

As Richard wrote, function-style macros emit SAS code. The general rule of developing function-style macros is that they contain only macro language statements. Any SAS code they contain will be emitted. Historically, this made it difficult/annoying to write a function-style macro that would process data like you would with a DATA step. Luckily, SAS has added a function, DOSUBL, which makes it easier to write function-style macros that execute SAS code in a "side session" and emit a result. See Rick Langston's paper.

Here is an example of a function-style macro which used DOSUBL to count the number of records in a table, and emits the count. (This is a very inefficient way to get a record count, just an example of doing something in SQL).

%macro SQLcount(table);
  %local rc emit; 

  %let rc=%sysfunc(dosubl(%nrstr(
     proc sql noprint;
      select count(*) into :emit trimmed
      from &table
     quit;
  )));

  &emit 
%mend ;

It can be used like:

proc sql ;
  select name
        ,%SQLcount(sashelp.shoes) as ShoeCount  /*emits 395*/
  from sashelp.class
  ;
quit ;

When the above step runs, it will return 19 rows of names from sashelp.class, and the value of ShoeCount will be 395 on every row. Note that the macro SQLcount only executed once. While the PROC SQL step is being compiled/interpreted the call to SQLcount is seen and the macro is executed and emits 395. The step becomes:

proc sql ;
  select name
        ,395 as ShoeCount  /*emits 395*/
  from sashelp.class
  ;
quit ;

DOSUBL uses a "side session" to execute code, which allows you to execute a PROC SQL step in the side session while the main session is interpreting a PROC SQL step.

I can't tell from your question if that sort of use case is what you want. It's possible you want a function-style macro where you could pass values to it from a table, and have the macro execute on each value and return something. Suppose you had a table which was a list of table names, and wanted to use SQL to get the count of records in each table:

data mytables ;
  input table $20. ;
  cards ;
sashelp.shoes
sashelp.class
sashelp.prdsale
;
quit ;

You can do that by using the resolve() function to build macro calls from data, delaying the execution of the macro until the SELECT statement executes:

proc sql ;
  select table
        ,resolve('%SQLcount('||table||')') as count
  from mytables
  ;
quit ;

With that, SQLcount will be called three times, and will return the number of records in each dataset.

table                 count
---------------------------
sashelp.shoes         395
sashelp.class         19
sashelp.prdsale       1440

The macro call is not seen when the PROC SQL step is interpreted, because it is hidden by the single quotes. The resolve function then calls the macro when the SELECT statement executes, passing the value of table as a parameter value, and the macro emits the record count. This is similar to a CALL EXECUTE approach for using data to drive macro calls.

1
votes

You state you want to:

run some fairly complicated logic on each observation of a dataset

To do that you should use the SAS language instead of the macro processor or PROC SQL. You can use a data step. Or for even more complicated logic you should look at PROC DS2.

1
votes

Sounds like you may want to create an FCMP function using proc fcmp. This is basically a way to create your own SAS functions that can be used within proc sql and data steps. For example:

/******************************************************************************
** PROGRAM:  COMMON.FCMP_DIV.SAS
**
** DESCRIPTION: PERFORMS A MATHEMATICAL DIVISION BUT WILL RETURN NULL IF THE
**              NUMERATOR OR DENOMINATOR IS MISSING (OR IF THE DIVISOR IS 0).
**
******************************************************************************/

proc fcmp outlib=common.funcs.funcs;

  function div(numerator, denominator);

    if numerator eq . or denominator in (0,.) then do;
      return(.);
    end;
    else do;
      return(numerator / denominator);
    end;

  endsub;
run;

Example Usage (example is data step but works equally well within SQL):

data x;
  x1  = div(1,0);
  x2  = div(1,.);
  x3  = div(1,1);

  x4  = div(0,0);
  x5  = div(0,.);
  x6  = div(0,1);

  x7  = div(.,0);
  x8  = div(.,.);
  x9  = div(.,1);

  put _all_;
run;
0
votes

Macro functions do not return values. A macro function can 'emit' source code that

  • that are one or more steps,
  • that is a snippet that code be incorporated in a statement,
  • that is one or more statements that are part of a step,
  • etc

For your case of wanting to 'do' things in SQL, you could write SQL views that are then

  • opened with %sysfunc(open()) and
  • processed with
    • %sysfunc(set()) and
    • %sysfunc(getvarn()) and
    • %sysfunc(getvarc()).

Not all SQL functionality can utilized by this technique -- the select something into :result, would have to be a view with the select something and the macro would getvarc to read the result.

Access done in the open/set/get manner does not cause a step boundary to occur, so the macro processing can proceed with it's logic and eventually emit source code for snippet level consumption. (The consumer is the SAS executor that processes macro code, and implicitly compiles and runs SAS steps)