Evaluate string variable that contains macro reference - sas

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.

Related

Brings sets together into one data. (in macro) in SAS

i want to brings sets together into one data in macro. I have 1064 sets from zm_&next_name and i want to brings them into one data for example ----> data CramerSet;
I want to do it in macro
You could do it without a macro. Just use the : operator when defining your data sets.
This feature is nice when you have a constant string that represents the beginning strings of the data set. Even if the strings change, as long as your target strings are constant (like your zm_ data sets) this is a good solution.
data CramerSet;
set zm_:;
run;
Once you run this, check your log. You will see a readout of every zm_% data set that has been concatenated.
If you are in fact hellbent on doing this with a macro - just use the data step above and use the string constant as your macro argument. Then if your string constant changes (maybe you have 1,025 data sets that start with ym_..., just use the new string constant as your macro string.

SAS: adding character variables in data step without setting the lenghth in advance

In a SAS data step, if one creates a character variable he has to be careful in choosing the right length in advance. The following data step returns a wrong result when var1=case2, since 'var2' is truncated to 2 characters and is equal to 'ab', which is obviously not what we want. The same happens replacing var2=' ' with length var2 $2. This kind of procedure is quite prone to errors.
data b; set a;
var2 = ' ';
if var1 = 'case1' then var2='xy';
if var1 = 'case2' then var2='abcdefg';
run;
I was unable to find a way to just define 'var2' as a character, without having to care for its length (side note: if left unspecified, the length is 8).
Do you know if it is possible?
If not, can you perhaps suggest a more robust turnoround, something similar to an sql "case", "decode", etc, to allocate different values to a new string variable that does not suffer from this length issue?
SAS data step code is very flexible compared to most computer languages (and certainly compared to other languages created in the early 1970s) in that you are not forced to define variables before you start using them. The data step compiler waits to define the variable until it needs to. But like any computer program it has rules that it follows. When it cannot tell anything about the variable then it is defined as numeric. If it sees that the variable should be character it bases the decision on the length of the variable on the information available at the first reference. So if the first place you use the variable in your code is assigning it a string constant that is 2 bytes long then the variable has a length of 2. If it is the result of character function where the length is unknown then the default length is 200. If the reference is using a format or informat then the length is set to the appropriate length for the width of the format/informat. If there is no additional information then the length is 8.
You can also use PROC SQL code if you want. In that case the rules of ANSI SQL apply for how variable types are determined.
In your particular example the assignment of blanks to the variable is not needed since all newly created variables are set to missing (all blanks in the case of character variables) when the data step iteration starts. Note that if VAR2 is not new (ie it is already defined in dataset A) then you cannot change its length anyway.
So just replace the assignment statement with a length statement.
data b;
set a;
length var2 $20;
if var1 = 'case1' then var2='ab';
if var1 = 'case2' then var2='abcdefg';
run;
SAS is not going the change the language at this point, they have too many users with existing code bases. Perhaps they will make a new language at some point in the future.

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

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 ;

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.