How to call a macro inside a macro depending on conditions - sas

I have a macro called "Comparison" that compares values from current period with the previous period, and it's working fine.
Edit to explain better: The macro Comparison will compare values from a specific account (revenues, for example) for the month T and T-1. All inside that macro works fine.
Say the current period is T. If the current month is March, June, September or December (Q1, Q2, Q3 or Q4), then I want to compare values from period T with T-1, T-1 with T-2 and T-2 with T-3. If the current month is not in the first condition, then I will only compare T with T-1. There's a variable called YEARMONTH (that can be 202210, for example) that I declare in another part of the code.
So basically I'm trying to run the Comparison macro 1 time if it's not the end of a quarter, or 3 times if it's a quarter.
I'm trying to do it as follows:
%MACRO TEST(YEARMONTH); /*20XXYY*/
%LET MONTH = %SUBSTR(&YEARMONTH,5,2);
%LET CP = &YEARMONTH.;
%LET CP_1 = &YEARMONTH. - 1;
%LET CP_2 = &YEARMONTH. - 2;
%IF &MONTH. = 3 %THEN %DO; %LET CP_3 = &YEARMONTH. - 91; %END
%ELSE %DO; %LET CP_3 = &YEARMONTH. - 3; %END;
%IF &MONTH. IN (3, 6, 9, 12) %THEN %DO;
%Comparison(CP,CP_1);
%Comparison(CP_1,CP_2);
%Comparison(CP_2,CP_3);
%END;
%ELSE %DO;
%Comparison(CP,CP_1);
%END;
%MEND TEST;
Basically I can't test it in SAS as my profile was mistakenly blocked by IT (they were meant to revoke my access to some libraries, but they revoked everything linked to SAS). Considering that the macro "Comparison" is working, will that new Macro work or are there flaws in my code?

It works a lot easier if you convert your YYYYMM string into an actual date. You have to use & before the macro variable name to pass in the values. You never defined CP_3 macro variable. You can just use the MOD() function to test if it is the last month of a quarter.
%macro test(yearmonth);
%local date month cp cp_1 cp_2 cp_3 ;
%let date=%sysfunc(inputn(&yearmonth,yymmn6.));
%let month=%sysfunc(month(&date));
%let cp = %sysfunc(putn(&date,yymmn6.));
%let cp_1 = %sysfunc(intnx(month,&date,-1),yymmn6.);
%let cp_2 = %sysfunc(intnx(month,&date,-2),yymmn6.);
%let cp_3 = %sysfunc(intnx(month,&date,-3),yymmn6.);
%comparison(&cp,&cp_1);
%if %sysfunc(mod(&month,3)) = 0 %then %do;
%comparison(&cp_1,&cp_2);
%comparison(&cp_2,&cp_3);
%end;
%mend test;
Let's make a dummy %COMPARISON() macro and test it;
317 %macro comparison(one,two);
318 %put &=one &=two;
319 %mend;
320
321 %test(202201)
ONE=202201 TWO=202112
322 %test(202203)
ONE=202203 TWO=202202
ONE=202202 TWO=202201
ONE=202201 TWO=202112

Related

Check whether item is in SAS list

I have a SAS list. This SAS list is stored in a macro variable. Please assume that I have no table to derive this SAS list.
The SAS list contains names separated by commas. An example of the SAS list macro variable:
%LET sas_list = name1,name2,name3;
I want to check whether macro-variable “item” is present in the list.
Something like:
%IF &item. IN &sas_list. %THEN %DO;
Whatever;
%END;
For some reason, I get the error:
“A character operand was found in the %EVAL function or %IF condition where a numeric operand is required. The condition was: name1 IN name1,name2,name3”
Help is appreciated.
You need to use two options here:
MINOPERATOR, this will allow the use of the IN operator
MINDELIMITER, this will allow you to set the delimiter
%let sas_list = name1,name2,name3;
options minoperator mindelimiter=',';
%Macro want(item);
%if &item. in &sas_list. %then %put i = 1;
%else %put i = 0;
%mend;
%want(name1);
i = 1
Add the minoperator and mindelimiter system options. These stand for Macro IN Operator and Macro IN Delimiter.
options minoperator mindelimiter=',';
%LET sas_list = name1,name2,name3;
%LET item = name1;
%IF &item. IN &sas_list. %THEN %DO;
%put &item is in &sas_list;
%END;
Output:
name1 is in name1,name2,name3
If you want to find values that are not in a list, pass it through %eval.
options minoperator mindelimiter=',';
%LET sas_list = name1 name2 name3;
%LET item = name4;
%IF %eval(&item. IN &sas_list.) = 0 %THEN %DO;
%put &item NOT in &sas_list;
%END;
Output:
name4 is NOT in name1,name2,name3
Note that you can also supply these options directly in a macro if you only want in to work selectively. For example:
%macro foo / minoperator mindelimiter=',';
...
%mend;
You can also use the FINDW funtion to ascertain the presence of an item in a list.
%if %sysfunc(FINDW(%upcase(%superq(saslist)), %upcase(&item), %str(,))) %then %do;
...
%end;
Supporting
%LET sas_list = name1,name2,name3;
you can find if name2 is in the list this way.
data test;
it = findw("&sas_list", 'name2',',');
run;
but I assume you want to find it out without using a data step, so
%let het = %sysfunc(findw(%quote(&sas_list),name2,%quote(,)));
%put NOTE: het is &het;
does the job. Note
you find is not a macro function, so you need %sysfunc() to call it in a macro statement
you have to mask the comma's with %quote(). Otherwise they would be considered argument separators.

Iterate date in loop in SAS

need help on one query , I have to iterate date in do loop that is in format of yymmd6.(202112) so that once the month reach to 12 then its automatically change to next year first month.
///// code////////
%let startmo=202010 ;
%let endmo= 202102;
%macro test;
%do month= &startmo %to &endmo;
Data ABC_&month;
Set test&month;
X=&month ;
%end;
Run;
%mend;
%test;
//////////
Output should be 5 dataset as
ABC_202010
ABC_202011
ABC_202012
ABC_202101
ABC_20210
I need macro variable month to be resolved 202101 once it reached to 202012
Those are not actual DATE values. Just strings that you have imposed your own interpretation on so that they LOOK like dates to you.
Use date values instead and then it is easy to generate strings in the style you need by using a FORMAt.
%macro test(startmo,endmo);
%local offset month month_string;
%do offset = 0 to %sysfunc(intck(month,&startmo,&endmo));
%let month=%sysfunc(intnx(month,&startmo,&offset));
%let month_string=%sysfunc(putn(&month,yymmn6.));
data ABC_&month_string;
set test&month_string;
X=&month ;
format X monyy7.;
run;
%end;
%mend;
%test(startmo='01OCT2020'd , endmo='01FEB2021'd)
And if you need to convert one of those strings into a date value use an INFORMAT.
%let date=%sysfunc(inputn(202010,yymmn6.));
I would prefer to use a do while loop.
check whether the last 2 characters are 12, if so, change the month part to 01.
code
%let startmo=202010 ;
%let endmo= 202102;
%macro test;
%do %while(&startmo <= &endmo);
Data ABC_&startmo;
Set test&startmo;
X=&startmo ;
Run;
%end;
%let mon = %substr(&startmo, 5, 2);
%let yr = %substr(&startmo, 1, 4);
%if &mon = 12 %then %do;
%let m = 01;
%let startmo = %sysfunc(cat(%eval(&yr + 1), &m));
%end;
%else %do;
%let startmo = %eval(&startmo + 1);
%end;
%mend;
%test;

Why is the %IF statement not executing?

%macro SASIsAwful();
%let LastEnt = 12;
%let i = 1;
%do i = 1 %to &LastEnt.;
proc sql noprint;
select Diff into :CheckDiff from Trial01 where RowNum = &i.;
select ACTV_DTM into :CheckDate from Trial01 where RowNum = &i.;
create table Eval001 as select * from Trial01 where RowNum = &i.;
quit;
%if &CheckDiff. = 'N' %then %do; %put &CheckDiff.; %end;
%end;
%mend SASIsAwful;
I cannot for the life of me figure out why the %if statement did not work. Proc SQL worked fine, and I manually confirmed that it saved the value N into &CheckDiff. twelve times. Yet, for some reason, the %if statement never executed, and there was no error message. What went wrong here? Thank you.
The correct macro statement should be
%IF &CHECKDIFF = N %then ... ;
If you examine an unconditional %put, you will most likely see a log lines such as
%put INFO: &=CHECKDIFF;
--- log ---
INFO: CHECKDIFF=N;
INFO: CHECKDIFF=Y;
Richard is right.
Macrovars are always strings.
CheckDiff is just the 1-char-string N, which is different from the 3-char-string "N".
You could also write
%IF "&CHECKDIFF" = "N" %then ...
but not
%IF '&CHECKDIFF' = 'N' %then ...
because Macrovars are only replaced within double quotes, not single quotes.

SAS - conditional macro variable

I have a variable date=201611 and I need to create the first day of the next month in the following format '2016-12-01'. The following code works fine for the months up till 11:
%let date = 201611;
%let rok = %sysfunc(substr(&date,1,4));
%let month = %sysfunc(substr(&date,5,2));
%let xdat2_ii = &rok-%eval(&month + 1)-01;
%let xdat1 = %str(%')&xdat2_ii.%str(%');
%put &xdat1;
'2016-12-01'
I need to add some improvement to make the code working for the month 12, i.e. when the date is 201612 then to obtain '2017-01-01'.
My idea was to do it using macro, but it does not work.
%macro promenne;
%if &month < 12 %then %let xdat2_ii = &rok-%eval(&month + 1)-01
%else %if &month= 12 %then %let xdat2_ii = %eval(&rok + 1)-01-01;
%mend promenne;
Thank you for any suggestions which way to go.
When working with dates, is often easiest to use the built in date shifting functions - in this case, intnx.
/* define variable (this is a macro STRING) */
%let date=201612;
/* convert to SAS date value (numeric, num of days since 01JAN1960) */
%let dateval=%sysfunc(mdy(%substr(&date,5,2),1,%substr(&date,1,4)));
/* finally - shift to beginning of following month and format output */
%let xdat2_ii=%sysfunc(intnx(MONTH,&dateval,1,B),yymmddd10.);
%put &xdat2_ii; /* 2017-01-01 */

SAS Global date comparison

I'm trying to do a date comparison but I'm not getting the correct results. Does anyone know what's going on?
%macro ttt;
%let check_start = 28APR2014;
%if "&check_start."d < "25may2014"d %then %let true = 1;
%else %if "&check_start."d > "25may2014"d %then %let true = 2;
%put &true;
%mend;
%ttt;
14 %macro ttt;
15 %let check_start = 28APR2010;
16 %if "&check_start."d < "25may2014"d %then %let true = 1;
17 %else %if "&check_start."d > "25may2014"d %then %let true = 2;
18 %put &true;
19 %mend;
20 %ttt;
true = 2
Macro-variable true should equal 1
You need to use %sysevalf() to evaluate the comparison in this case. The following works.
%macro ttt;
%let check_start = 28APR2015;
%if %sysevalf("&check_start"d < '25may2014'd) %then %let true=1;
%else %if %sysevalf("&check_start."d > '25may2014'd) %then %let true=2;
%put &true.;
%mend;
%ttt;
Reeza has provided a good solution but I thought I'd add a few suggestions as well.
The problem you are having is the reason I recommend never using date literals when working in the macro language. Instead, of date literals (i.e. "01jan2000"d) I recommend using macro variables that contain date values (i.e. %let start_of_21st_century = %sysfunc(mdy(1,1,2000)); ). By using macro variables, not only do you avoid your above issue, but you also get the benefit of being able to self-document your code.
Currently I have no idea what significance the 25th May 2014 has in your code, but if you had this line:
%let product_launch_date = %sysfunc(mdy(5,25,2014));
... then it would be clear to anyone reading it what the significance is.
Your code would then become:
%macro ttt;
%local check_start compare_date;
%let check_start = %sysfunc(mdy(4,28,2014));
%let compare_date = %sysfunc(mdy(5,25,2014));
%if &check_start < &compare_date %then %let true = 1;
%else %if &check_start > &compare_date %then %let true = 2;
%put &true;
%mend;
There's still a few more things I'd consider changing. One thing I noticed is that if the 2 date values are equal, than true will not be assigned a value. So that should probably be remedied.
Also, in SAS, the typical concept of true/false is typically represented as follows:
A value of zero represents FALSE
Any non-zero number (including negatives) represents TRUE
So having a macro variable named true with either a value of 1 or 2 (both values would normally represent a value of true) may be confusing to some. I'd consider either renaming the macro variable, or using values of 0, and 1 (or other non-zero number).
Incorporating all of this, the macro would become something like:
%macro check_dates;
%local check_start compare_date;
%let check_start = %sysfunc(mdy(4,28,2014));
%let compare_date = %sysfunc(mdy(5,25,2014));
%let check_start_compared_higher = &check_start > &compare_date;
%if &check_start_compared_higher %then %do;
%put It was higher =) ;
%end;
%else %do;
%put It was equal to or lower =( ;
%end;
%mend;
%check_dates;
A few comments on the final macro... The macro variable named true has been replaced with a more descriptive variable named check_start_compared_higher. Because we just need a boolean value stored in it, we can simply assigning it the result of evaluating the expression &check_start > &compare_date which will return either a 0 (if false) or a 1 (if true). This is easier to read than using %if...%else... statements to do an assignment, as it is immediately clear that the line of code is simply performing an assignment and nothing more.
The line %if &check_start_compared_higher %then %do; shows how we can use the newly saved value to control program flow. Because the value in &check_start_compared_higher resolves to either TRUE or FALSE we can easily use it this way to make easy-to-read if-statements.