how to get macro variable to evaluate math? - sas

I have the following sas marco snippet:
%macro processLink(uuid=, name=, cluster_external_ipaddress=);
%let unix_starttime = 1000000*(&starttime - '01JAN1970:00:00'dt);
%let unix_endtime = 1000000*(&endtime - '01JAN1970:00:00'dt);
...
when this runs it just creates the variable as a string ie
=1000000*(dhms(today()-1,0,0,0) - '01JAN1970:00:00'dt)
instead of the unix timestamp in usecs.
using unix_starttime = 1000000*(&starttime - '01JAN1970:00:00'dt); outside the macro in a data step works
do i need a null datastep in the macro for this to work as intended ?
Thanks

In general if you want to work with DATA you are better off using SAS code and not MACRO code. You can use CALL SYMPUTX() to generate a macro variable if you need it later.
data _null_;
call symputx('unix_starttime',1000000*(&starttime - '01JAN1970:00:00'dt));
...
run;
You can use %eval() to do simple integer arithmetic and comparisons. If you need to use floating point numbers (or date/time/datetime literals) then you need to use %sysevalf().
%let unix_starttime=%sysevalf(1000000*(&starttime - '01JAN1970:00:00'dt));

In general, anything after a %let statement is treated as pure text. However, there are functions available to wrap around the text which tell SAS to perform a mathematical operation.
These are %eval, used for integer calculations, or %sysevalf where calculations involving decimals are required.
So you could put %let unix_starttime = %eval(1000000*(&starttime - '01JAN1970:00:00'dt));
It's not applicable here, but if you ever need to include a function in a %let statement, then precede the function name with %sysfunc

Related

Evaluate string variable that contains macro reference

I've got a table with DataSet names in, some of which contain macro references in the name.
e.g. Monthly_Data_&YYMM (where YYMM is the latest month)
I want to keep the Table with this string, but then have a new variable with the evaluated DataSet name.
e.g. Monthly_Data_&YYMM, Monthly_Data_1612
I can't work out a way to do this. If I read the dataset as a macro variable it returns as the required name, but I can't then join it on the same row as the non evaluated reference.
I'm sure this must be possible, and probably quite easy, but I just can't get my head around how to do this.
Many thanks
You can use the resolve function to do this, e.g.
%let YYMM = 1601;
data mydata;
dsname = 'Monthly_Data_&YYMM';
dsname_resolved = resolve(dsname);
run;
N.B. all macro variables used in your column of names must be defined in your session with the correct values at the point when the resolve function executes. If two different data sets used the same macro variable in their name, but it took different values at different times, you will need to redefine the macro variable and run your logic separately, possibly via separate data steps or call symput + symget.

Passing a substring (may contains quotes and so on) to a macro variable

As we know, special characters should be masked during macro compilation. But what if I wanna assign a dynamic substring to a macro variable? Like this:
%let mvSubstr = %substr(&mvString, 1, 1);
mvString can contain any symbols including unmatched single or double quotation marks.
So, in this example program works correctly:
%lev mvString = Test;
%let mvSubstr = %substr(&mvString, 1, 1);
And in the following case program doesn't work and SAS reports ERROR: Literal contains unmatched quote.:
%lev mvString = %str(%'Test%');
%let mvSubstr = %substr(&mvString, 1, 1);
How can I defeat this problem (make program works independently of mvString value)?
Use the %QSUBSTR() function if you expect that it is possible the value of the substring will contain unmatched quotes or other characters that require macro quoting. There is also the %QSCAN() function to use when the result of using %SCAN() might need quoting. And there is the %QSYSFUNC() function for when calling other SAS functions from within macro code.
This is why macro quoting exists. You have a lot of different options, depending on exactly what you're doing.
%quote, %nrquote, %bquote, and %nrbquote - all do roughly the same thing: mask quote characters and some other special characters. See for example the documentation for %bquote/nrbquote. They tell SAS not to pay attention to ' and similar, so it does not worry about matching things. I've never seen a reason to use %quote over %bquote - the B stands for 'better' - so I would use that. They work during execution, not compilation. %nrbquote masks the macro characters & and %, meaning it will prevent a macro inside the macro variable from resolving.
%str and %nrstr mask during compilation. Otherwise they are similar to %bquote and %nrbquote. If it's important that it not have the quote during compilation, use these.
%superq masks a macro variable only (not open text) and prevents all resolution from occurring. It's often the best way to assign the value of one macro variable to another variable. It importantly does not take the & - you pass the name of the macro variable, with no ampersands or whatnot (unless the name of the macro variable is stored in another macro variable).
In your case, you would need to use %bquote to quote the results of the substring assignment, so:
%let mvString = %str(%'Test%');
%put &=mvString;
%let mvSubstr = %bquote(%substr(&mvString, 1, 1));
%put &=mvString &=mvSubstr;
What about the scenario where MVSTRING contains unmasked characters that need special treatment. This requires quoting the argument of SUBSTR.
data _null_;
call symputx('mvString',"'Test",'G');
run;
%put %nrbquote(&=mvString);
%let mvSubstr = %bquote(%substr(%superq(mvString), 1, 1));
%put %nrbquote(&=mvString) %nrbquote(&=mvSubstr);

SAS Macro Works Standalone, But Not When Looped

I have a large dataset where I am storing macro parameters. The macro is itself used to call a number of other macros, each of which runs a number of operations.
Ideally, I'd like to use another macro to loop over each row of the dataset, construct (using PROC SQL) a macro call, store it in a macro variable :CALL, and call the variable at every iteration of the loop (with a PUT &CALL.;) That is:
%macro OUTER_LOOP(DS);
%let K = ;
%COUNT_ROWS(DS, K); /* This stores the number of rows in DS in K. */
%do i = 1 %to &K.;
proc sql noprint; ...; quit; /* Create the macro call, and store it in :CALL. */
%put &CALL.;
%end;
%mend;
%OUTER_LOOP;
This doesn't work as expected: some of the internal checks that exist in my macro indicate several datasets created by the macro are missing. Curiously, when I don't run this in a macro loop (i.e. I manually create a macro call, row-by-row, and execute it), no error occurs.
Has anyone experienced this issue? If so, is anyone familiar with a solution that would still allow me to loop over macro calls? I know that CALL EXECUTE(); (in the data step) runs different parts of the macro at different times--is that what is occurring in this case, as well?
I would add %put Loop iterating: i=&i k=&k ; inside the DO loop. That will let you see how many times the loop iterates. One possibility is the loop is exiting earlier than you intend it to. If that is the case, the cause could be a collision between the macro variable i you use for the looping in %Outer_Loop and another macro variable i you use in one of the inner macros you call. As a general rule, it's a good idea to define macro variables as %LOCAL to the macro they are defined in. Doing that will prevent such macro variable collisions. But without seeing the inner macros, that's just one possibility.
You could also add %put %superq(Call) ; inside the do loop. That will show you the macro calls that are being generated, so you can check you are getting the expected parameter values in each call.
Most likely a scoping issue. Your sub-macros are likely overwriting the values of your macro variables in your calling-macros.
You can fix this by declaring all your variables as local variables using the %local statement. If there are macro variables that you need to access after the macros have run, explicitly declare them as %global.
So for the macro you have listed above you will need the below line:
%local k i;
Don't forget you need to do this for any sub-macros that are called, and so on...
You can avoid a lot of these types of problems by generating the code yourself. For your example you could move the logic that generates the code from SQL to a data step and then instead of a macro you just need a data step. You don't even need know the number of observations in the dataset in advance.
filename code temp ;
data _null_;
set DS ;
file code ;
put '.... generated code based on values in current data ... ;
run;
%include code / source2 ;

Changing Value of Macro Variable inside SAS macro

I am defining a macro variable inside a macro. Then, I am feeding it into a second macro. Inside macro2 counter changes value to 200. However, when I check what is inside the macro variable that I put in after macro 2 runs, it still says 0. I would like it to store the value 200? is this possible?
%macro macro1();
%let variable1= 0;
macro2(counter=&variable1)
%put &variable1;
%mend macro1;
%macro1;
You have a couple of issues here. First of all, you are missing the % before your call to macro2, but I suspect that's just a typo. The main issue is that you are trying to do what is referred to in other languages as call-by-reference. You can do this in SAS macro by passing the name of your variable rather than the value of your variable, and then use some funky & syntax to set the variable of that name to a new value.
Here is some sample code that does this:
%macro macro2(counter_name);
/* The following code translates to:
"Let the variable whose name is stored in counter_name equal
the value of the variable whose name is stored in counter_name
plus 1." */
%LET &counter_name = %EVAL (&&&counter_name + 1);
%mend;
%macro macro1();
%let variable1= 0;
/* Try it once - see a 1 */
/* Notice how we're passing 'variable1', not '&variable1' */
%macro2(counter_name = variable1)
%put &variable1;
/* Try it twice - see a 2 */
/* Notice how we're passing 'variable1', not '&variable1' */
%macro2(counter_name = variable1)
%put &variable1;
%mend macro1;
%macro1;
I actually have another post on StackOverflow that has an explanation of the &&& syntax; you can have a look at it here. Note that the %EVAL call has nothing to do with call-by-reference, it is just there to do the addition.
Sparc_Spread explains how to "call by reference" in the SAS macro language, which may solve your problem.
In this particular case though, it's not necessarily crucial to use call by reference, and I'd argue it's not idiomatic to SAS macro language to use it (though certainly nothing wrong with it - it just looks a bit odd, and is a bit harder since it's not really a native concept, though certainly intentionally supported to be used that way if desired). There are two ways to get around this that both are very easy to use.
First of all, let's say you know the variable name you want to increment, and the starting value is the only interesting thing. Thanks to how SAS macro language handles scoping, with something not exactly lexical scoping and not exactly functional, it automatically will use the variable that already exists in the most local scope, when it does already exist (with some minor caveats, such as macros using DOSUBL).
So this works as expected:
%macro macro2(counter=);
%do variable1 =&counter. %to 200;
%if %sysfunc(mod(&variable1.,50))=0 %then %put &=variable1;
%end;
%mend macro2;
%macro macro1();
%let variable1= 0;
%macro2(counter=&variable1.);
%put &=variable1;
%mend macro1;
%macro1;
(Of course, that is if you expect &variable1 to have the value of 201 - because %do loops, like do loops, always get incremented one higher than their ending value. I assume your real procedure works differently.)
That's because the &variable1. referred to in %macro2 automatically is the one present in the most local scope - which in this case is the scope of %macro1.
Alternatively, if you're using this %macro2 for the purpose of incrementing a counter, I would use a function-style macro method.
A function-style macro by definition is one that returns only a single value - and by returns I mean has a single value at the end of the macro's code that is presented in plain text (since a macro is, after all, only intended to create text that will then be parsed by the normal SAS language parser).
This can then be used on the right side of an equal sign in an assignment statement. The key is that it uses only macro language elements - %do loops and such - and no data step, proc, etc., language that would prevent it from being on the right side of an equal sign in an assignment statement (ie, x=%macrostuff(); cannot be x=proc sql(select...)).
So the following accomplishes the goal: increment a counter some amount, return the value (201, in this case, just like before), and then that can be assigned to a macro variable.
%macro macro2(counter=);
%do internal_counter =&counter. %to 200;
%if %sysfunc(mod(&internal_counter.,50))=0 %then %put &=internal_counter.;
%end;
&internal_counter.
%mend macro2;
%macro macro1();
%let variable1= %macro2(counter=0);
%put &=variable1;
%mend macro1;
%macro1;
I would suggest that this is the most idiomatic way to accomplish this, and the most simple: you pass the value you want as input, function operates on it, returns value, which you then assign to a variable in your macro however you want.

How to run/not run SAS or SQL code based on conditional output?

I have a SAS program with a macro that will output a different list of variables based on the input criteria. For example, with %MACRO(OPTION1), I get three variables, but with %MACRO(OPTION2), I get four variables. The name of all of the variables is fixed, but it's just a matter of if they are created or not (based on the option).
How can I adjust the macro so that any option inputted by the user will still allow the macro to run? In other words, how can I tell it to ignore some variables if they don't exist.
Fortunately, I am not restricted to any specific procedure, but it would probably have to be either in a DATA step (macro language) or a PROC SQL statement (where clause or some other conditional statement).
This is answerable in the general, as an approach to programming.
The first rule:
Use macro parameters explicitly when the amount of code is small.
This means, if you want to (say) do a PROC MEANS on something, but the variable differed, you could do:
%macro run_means(var=);
proc means data=sashelp.class;
var &var.;
run;
%mend run_means;
%run_means(var=height);
%run_means(var=weight);
etc. Don't put some conditional logic in the macro, make them external. This includes lists of variables; make the whole list of variables parameters. Don't write them into your macro. If it's a long list, make it a macro variable in your main program, and pass that macro variable. Your macro itself should strive to accept what's given; today you have two sets of variables, tomorrow you might have three, or a slightly different set of one or the other. It's easier to change what you pass to the macro than to change the macro.
This concept will feel comfortable to folks used to object oriented programming, in particular the modular approach, although the separation of data is a bit different.
The second rule:
When substantial parts of a macro vary based on a parameter, separate that code into multiple macros.
In this case, let's say you have two things you want to do: run a PROC MEANS, or run a PROC FREQ, depending on if it's a character or numeric variable. Here, I suggest a general rule of not putting all of that into one macro. It's possible, but it's generally a bad idea. Adding to the previous macro, if you wanted to do this for sashelp.class, I'd do it like this:
%macro run_freq(var=);
proc freq data=sashelp.class;
tables &var.;
run;
%mend run_freq;
%run_means(var=height);
%run_means(var=weight);
%run_freq (var=sex);
How you create these may be programmatic. A lot depends on what you're doing and how you're generating the code; and sometimes in the middle of your macro, you generate the value that determines which of the two things you do. I would still write the portion that varies as a separate macro, though; you can then add logic to call the appropriate macro, and allow it to be more legible.