Changing Value of Macro Variable inside SAS macro - sas

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.

Related

difference between using if _n_ =1 then do; and not using it with PRXPARSE() in SAS

Created a dataset :
data x;
infile datalines truncover;
input name $100.;
datalines;
Deepanshu
How are you, deepanshu
dipanshu
deepanshu is a good boy
My name is deepanshu
Deepanshu Bhalla
Deepanshuuu
DeepanshuBhalla
Bhalla Deepanshu
;
run;
Wrote the following code :
data test;
set x;
if _n_ =1 then do;
retain re;
re = prxparse("s/(Deepanshu\s?Bhalla|bhalla\s?Deepanshu|Deepanshu)/Soumya Pandey/i");
end;
new_data = prxchange(re, -1, name);
proc print;
run;
and a similar one but without the
if _n_ =1 then do; end; retain;
data test;
set x;
re = prxparse("s/(Deepanshu\s?Bhalla|bhalla\s?Deepanshu|Deepanshu)/Soumya Pandey/i");
new_data = prxchange(re, -1, name);
proc print;
run;
Both of the testing codes gave the same result. What is the difference between them?
DATA Step code blocks with the construct
if _n_ = 1 then do;
...
end;
cause the interior statements to occur during only the 1st iteration of the implicit loop.
Retaining a (non DATA SET) variable prevents its value from being reset to missing at the top of the implicit loop. Retain can be used to initialize a variable with a literal value at compilation time and do not need a if _n_=1 guard. Initializations from an computation assignment or INPUT necessarily require the guard (except special situations such a prxparse).
For the case of interior statement re = prxparse(...)
As stated by #whymath the DATA Step compiler is being improved in each release of SAS and there are now some implicit guards against recompiling a static regular expression pattern.
You will see the same code construct used for initializing hash objects.
Tip of the day:
If you do not specify an argument, the RETAIN statement causes the values of all variables that are created with INPUT or assignment statements to be retained from one iteration of the DATA step to the next.
The first one uses if _n_ = 1 then ...; retain ; statements, which is a very good and practical programming technique, called initialization block. It will only executing at the first row when reading data and avoids compiling the regular expression for each iteration of data step.
However, This skill may considered old-fashioned. In the very new version of SAS(mine is SAS9.4M5), we don't need to write this initialization block any more, there is some internal parser optimization. Here is specification from SAS Help Center:
If perl-regular-expression is a constant or if it uses the /o option, the Perl regular expression is compiled only once. Successive calls to PRXPARSE do not cause a recompile, but returns the regular-expression-id for the regular expression that was already compiled. This behavior simplifies the code because you do not need to use an initialization block (IF _N_ =1) to initialize Perl regular expressions.
So I prefer to use your second way, it is a true SASor's way.

how to get macro variable to evaluate math?

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

error in do loop SAS

I need my data temp dataset to generate 2 columns.
word1 and word2. Both will have blank values. The value in the do loop will change. 2 is just a dummy number.
Can some one tell me how to interpret this error ?
data temp(drop=k);
do k=1 to 2;
word&k=.;
output;
end;
run;
Logs -
180
WARNING: Apparent symbolic reference K not resolved.
ERROR 180-322: Statement is not valid or it is used out of proper order.
You need to use an array, not a macro variable; you're misunderstanding how macro variables work.
data temp(drop=k);
array word[2];
do k=1 to 2;
word[k]=.;
output;
end;
run;
Macro variables are an entirely different system, and require a different kind of loop (and, to be inside a macro, the way you're trying to do it).

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 ;