SAS - How to return a value from a SAS macro?

I would like to return the value from the SAS macro I created, but I'm not sure how to do this. A macro calculates the number of observations in a dataset. I want the number of observations to be returned.

%macro nobs(library_name, table_name); proc sql noprint; select nlobs into :nobs from dictionary.tables where libname = UPCASE(&library_name) and memname = UPCASE(&table_name); quit; *return nobs macro variable; &nobs %mend; %let num_of_observations = %nobs('work', 'patients'); 

In addition, I would like the &nobs macro used in the macro to be local to this macro, not global. How can i do this?

+10
source share
5 answers

I will answer the main question that Bambi asked in the comments:

My main task here is how to return a value from a macro.

I'm going to argue with Dirk here in an important way. He says:

SAS macro inserts code. It will never be able to return a value, although in some cases you can mimic functions

I do not agree. The SAS macro returns the text inserted into the workflow. Returns is an absolutely suitable term for this. And when the text turns out to be a single number, we can say that it returns a value.

However, a macro can only return one value if it has only macro operators in addition to that value. That is, each line should begin with % . Anything that does not start with % will be returned (and some things that start with % can also be returned).

Therefore, the important question is: how can I return only the value from the macro.


In some cases, such as this one, this is only possible with macro codes. In fact, in many cases this is technically possible - although in many cases it is more than necessary.

A related article by Jack Hamilton includes an example that is relevant here. He rejects this example, but mainly because his article is devoted to counting cases in cases where NOBS is incorrect - either with the WHERE clause or in some other cases when the data sets were changed without updating the NOBS metadata.

In your case, you seem to be completely happy to trust NOBS - this example will do.

A macro that returns a value must have exactly one statement, which is either not a macro syntax operator or a macro syntax operator that returns a value to the processing flow. %sysfunc is an example of a statement that does this. Things like %let , %put , %if , etc., are syntax operators that return nothing (by themselves); so that you can have as much as you want.

You should also have one statement that puts the value in the processing flow: otherwise you will not get anything from your macro at all.

Here is a stripped down version of Jack's macro at the end of page 3, simplified to remove nlobsf :

  %macro check; %let dsid = %sysfunc(open(sashelp.class, IS)); %if &DSID = 0 %then %put %sysfunc(sysmsg()); %let nlobs = %sysfunc(attrn(&dsid, NLOBS)); %put &nlobs; %let rc = %sysfunc(close(&dsid)); %mend; 

This macro is not a function style macro. It does not return anything to the processing thread! This is useful for viewing a log, but not useful for getting a value with which you can program. However, this is a good start for function style macro, because you really want this &nlobs , right?

  %macro check; %let dsid = %sysfunc(open(sashelp.class, IS)); %if &DSID = 0 %then %put %sysfunc(sysmsg()); %let nlobs = %sysfunc(attrn(&dsid, NLOBS)); &nlobs %let rc = %sysfunc(close(&dsid)); %mend; 

Now this is a function style macro: it has one statement that is not a macro syntax operator, &nlobs. on a simple line all by itself.

This is actually more than just a statement; Remember how I said that %sysfunc returns a value to the processing thread? You can remove the %let part of this statement, leaving you with

  %sysfunc(attrn(&dsid, NLOBS)) 

And then the value will be placed directly in the processing flow itself, which allows you to use it directly. Of course, it's not so easy to debug if something goes wrong, but I'm sure you can get around this if you need to. Also pay attention to the absence of a semicolon at the end of the statement - this is because semicolons are not required to perform macro functions, and we do not want to return extra semicolons.

Let's behave ourselves, add a few %local to get it nice and safe, and make the name of the data set a parameter, because nature does not tolerate a macro without parameters:

  %macro check(dsetname=); %local dsid nlobs rc; %let dsid = %sysfunc(open(&dsetname., IS)); %if &DSID = 0 %then %put %sysfunc(sysmsg()); %let nlobs = %sysfunc(attrn(&dsid, NLOBS)); &nlobs %let rc = %sysfunc(close(&dsid)); %mend; %let classobs= %check(dsetname=sashelp.class); %put &=classobs; 

There you have it: a function style macro that uses the nlobs function to find out how many rows there are in a particular data set.

+10
source

What is the problem of writing function type macros?

i.e. macros that you can use as %let myVar = %myMacro(myArgument)

  • You can use your user-written macro as if it were a function, if all you do is
    • calling some %doSomething(withSometing) macro functions
    • assign values ​​to macro variables using the %let someVar = operator
    • "return" your result, usually by writing &myResult. in the last line before your %mend
  • Once you include the proc or data step in your macro, this no longer works
  • Fortunately,% sysFunc () comes to the rescue, so we can use any data step function
  • This includes low-level features like open , fetch and close , which can even access your data.
  • Enchanted people can do a lot with him, but even if you're more alert, your boss will rarely give you time to do so.

How to solve this? , i.e. What blocks are used to solve this problem?

  • proc fcmp allows proc fcmp to pack some data step instructions in a routine or function
  • This function, intended for use in a data step, can be used in %sysfunc()
  • In this function, you can call run_macro to execute any macro IN THE IMMEDIATE LIST

Now we are ready for a practical solution.

Step 1: write a helper macro

  • without parameters,
  • using some global macro variables
  • "returns" its result in a global macro variable

I know this is a bad coding habit, but to reduce the risk, we qualify these variables with a prefix. Applies to example in question

 ** macro nobsHelper retrieves the number of observations in a dataset Uses global macro variables: nobsHelper_lib: the library in which the dataset resides, enclosed in quotes nobsHelper_mem: the name of the dataset, enclosed in quotes Writes global macro variable: nobsHelper_obs: the number of observations in the dataset Take care nobsHelper exists before calling this macro, or it will be ost **; %macro nobsHelper(); ** Make sure nobsHelper_obs is a global macro variable**; %global nobsHelper_obs; proc sql noprint; select nobs into :nobsHelper_obs from sashelp.vtable where libname = %UPCASE(&nobsHelper_lib) and memname = %UPCASE(&nobsHelper_mem); quit; %* uncomment these put statements to debug **; %*put NOTE: inside nobsHelper, the following macro variables are known; %*put _user_; %mend; 

Step 2: write an auxiliary function ;

 **Functions need to be stored in a compilation library; options cmplib=sasuser.funcs; ** function nobsHelper, retrieves the number of observations in a dataset Writes global macro variables: nobsHelper_lib: the library in which the dataset resides, enclosed in quotes nobsHelper_mem: the name of the dataset, enclosed in quotes Calls the macro nobsHelper Uses macro variable: nobsHelper_obs: the number of observations in the dataset **; proc fcmp outlib=sasuser.funcs.trial; ** Define the function and specity it should be called with two character vriables **; function nobsHelper(nobsHelper_lib $, nobsHelper_mem $); ** Call the macro and pass the variables as global macro variables ** The macro variables will be magically qouted **; rc = run_macro('nobsHelper', nobsHelper_lib, nobsHelper_mem); if rc then put 'ERROR: calling nobsHelper gave ' rc=; ** Retreive the result and pass it on **; return (symget('nobsHelper_obs')); endsub; quit; 

Step 3: write a convenient macro for using helpers ;

 ** macro nobs retrieves the number of observations in a dataset Parameters: library_name: the library in which the dataset resides member_name: the name of the dataset Inserts in your code: the number of observations in the dataset Use as a function **; %macro nobs(library_name, member_name); %sysfunc(nobsHelper(&library_name, &member_name)); %* Uncomment this to debug **; %*put _user_; %mend; 

Finally, use ;

 %let num_carrs = %nobs(sasHelp, cars); %put There are &num_carrs cars in sasHelp.Cars; Data aboutClass; libname = 'SASHELP'; memname = 'CLASS'; numerOfStudents = %nobs(sasHelp, class); run; 

I know this is complicated , but at least all the nimble work is done. You can copy, paste and modify this in the time that your boss will take. ;

+8
source

SAS mask inserts code. It will never be able to return a value, although in some cases you can imitate functions, usually you need work , for example

 %nobs(work, patients, toReturn=num_of_observations ) 

** To help you understand what will happen, I advise you to print the code inserted by the macro into your log :;

 options mprint; 

We pass the name of the macro variable to populate the macro,. I find it most practical for

  • doesn't require my macro user to put quotes around the library name and members
  • enter the name of the variable with the named macro variable, so we can specify it by default;

    % macro nobs (library_name, table_name, toReturn = nobs);

Make sure the returned variable exists

  • If it exists, it is known outside this macro.
  • Otherwisse, if we create it here, it will by default be local and lost when we leave the macro;

     %if not %symexist(&toReturn.) %then %global &toReturn.; 

In SQL , I

  • use SASHELP.VTABLE, the view provided by SAS by its metadata.
  • add quotes that I missed in the macro call ("", not '': macro variables are not replaced in one Qoutes)
  • use the macro save function instead of the SAS wait function, as it sometimes improves performance;

     proc sql noprint; select nobs into :&toReturn. from sashelp.vtable where libname = %UPCASE("&library_name.") and memname = %UPCASE("&table_name."); quit; 

    % latai;

Note that if you invoke a macro in a macro , run this code and read the log to see why:

 %macro test_nobs(); %nobs(sashelp, class); ** will return the results in nobs **; %nobs(sashelp, shoes, toReturn=num_of_shoes); %let num_of_cars = ; %nobs(sashelp, cars, toReturn=num_of_cars); %put NOTE: inside test_nobs, the following macro variables are known; %put _user_; %mend; %test_nobs; %put NOTE: outside test_nobs, the following macro variables are known; %put _user_; 
+3
source

You cannot "return" a value from a macro file in the style of a function if you did not write it using only macros. The Quentin link provides an example of how to do this.

For example, you cannot use your macro in this way because proc sql cannot be executed in the middle of the %put instruction (this is possible with other more complex workarounds, for example dosubl , but not the way you wrote it).

 %put %nobs(mylib,mydata); 

The best thing you can do without significant changes is to create a global macro variable and use it in subsequent operations.

To create a macro that is local to the source macro, you must first declare it using the %local statement in the macro definition.

+2
source

I know that I was very late for this discussion, but thought about commenting, since I came across this. This is another way to do this, I think:

 %macro get_something_back(input1, input2, output); &output = &input1 + &input2; %mend; data _test_; invar1 = 1; invar2 = 2; %get_something_back(invar1, invar2, outvar); end; 

This will also work outside the data step.

 %global sum; %macro get_something_back(input1, input2, outvar); %let &outvar = &sysevalf(&input1 + &input2); %mend; %get_something(1, 2, sum); 
0
source

All Articles