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.
Related
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.
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
I have this macro that doesn't resolve even if the logic seems to work. I'm a beginner sas user so the code might look a little messy.
%let macro1 = 1600;
%let macro2 = 1300;
%if '¯o1.' > '¯o2.' %then %do;
%let macro3 = increase;
%else %if '¯o1.' < '¯o2.' %then %do;
%let macro3 = decrease;
%else %if '¯o1.' = '¯o2.' %then %do;
%let macro3 = stability;
%end;
when I run the code, there are no errors but macro3 does not resolve and it looks something like:
"There is a ¯o3. between 1600 and 1300"
You are missing %END; statements for the %DO; statements. Do you really need the %DO; statements? You don't appear to be trying to run multiple statements when the condition is true.
The macro processor will ignore macro triggers that are inside quoted strings that are bounded by single quote characters. So the result of your tests will always be that the string '¯o1' is less than the string '¯o2' because the digit 1 comes before the digit 2 in lexicographical ordering.
Either remove the quotes completely or replace them with double quote characters.
Without the quotes the implied %EVAL() macro function call will compare the strings 1600 and 1300 as integer numbers. With the quotes then %EVAL() will compare the string "1600" and "1300" as character strings.
So if the values of MACRO1 and MACRO2 are supposed to be numbers then do not include the quotes in the %IF conditions. Otherwise values like "1200" will be less that values like "800" becuase 8 is larger than 1.
You also have to wrap the whole sequence of %IF/%THEN/%ELSE/%IF inside a macro definition, if it is not already inside of a macro definition, because you cannot have nested %IF in open code.
%macro testit;
%let macro1 = 1600;
%let macro2 = 1300;
%if ¯o1. > ¯o2. %then %let macro3 = increase;
%else %if ¯o1. < ¯o2. %then %let macro3 = decrease;
%else %let macro3 = stability;
%put &=macro3 ;
%mend testit;
%testit;
And if &MACRO1 and &MACRO2 are not INTEGER values then you will need to explicitly use %SYSEVALF() to compare them.
%let macro1 = 16.50;
%let macro2 = 13.00;
%if %sysevalf(¯o1. > ¯o2.) %then ....
%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.
I am writing a macro variable that aims at producing a valid SQL WHERE clause as specified by user in prompts. Assume we have 3 variables X, Y, Z, and so on. User may specify the filter for every variable and macro variable looks like:
a = x eq 1 and y eq 1 and z eq 1;
which is then proceeded to the WHERE clause.
But if user specifies only, let's say' filter for Y it looks like:
a = and y eq 1 and
And I would like it to look like:
a = y eq 1
That is why I would like to somehow remove the operand 'AND' when it starts or ends the expression (it may start or end it multiple times, e.g. if we fitler only Z variable it looks like):
a = and and and z eq 1
I suppose it could be easily done with regular expressions but since I'm new to it, is there anyone willing to help me ? ;)
Slight rework of #DirkHorsten's technique. This simply organizes the code in a slightly different manner so that the SQL statement can be more easily read. In my opinion, the SQL statement is the important piece of code that you would like readers to understand (so let's keep it simple!), while the building of the where clause is just a side-note. This can be a valuable approach, especially as your SQL statements become more complex.
Approach 1, a single variable for all filters:
%macro getMyData(xValue=, yValue=, zValue=);
%local and_filters;
* THE BORING IMPLEMENTATION DETAILS ARE KEPT SEPARATE;
%let and_filters = ;
%if "&xValue" ne "" %then %do;
%let and_filters = &and_filters and sex eq "&xValue";
%end;
%if "&yValue" ne "" %then %do;
%let and_filters = &and_filters and age eq &yValue;
%end;
%if "&zValue" ne "" %then %do;
%let and_filters = &and_filters and height eq &zValue;
%end;
* THE IMPORTANT PIECE OF CODE IS EASY TO READ;
proc sql;
select *
from sashelp.class
where 1 &and_filters
;
quit;
%mend;
Approach 2, individual variables for each filter:
%macro getMyData(xValue=, yValue=, zValue=);
%local and_sex_filter and_age_filter and_height_filter;
* THE BORING IMPLEMENTATION DETAILS ARE KEPT SEPARATE;
%let and_sex_filter = ;
%let and_age_filter = ;
%let and_height_filter = ;
%if "&xValue" ne "" %then %do;
%let and_sex_filter = and sex eq "&xValue";
%end;
%if "&yValue" ne "" %then %do;
%let and_age_filter = and age eq &yValue;
%end;
%if "&zValue" ne "" %then %do;
%let and_height_filter = and height eq &zValue;
%end;
* THE IMPORTANT PIECE OF CODE IS EASY TO READ;
proc sql;
select *
from sashelp.class
where 1
&and_sex_filter
&and_age_filter
&and_height_filter
;
quit;
%mend;
Assuming you're doing this via macro parameters, this is easier to do by supplying a default.
%macro filter(x=1,y=1,z=1);
where &x. and &y. and &z.;
%mend filter;
1 is "true", so it acts (along with "AND") as a left-out argument.
If you only want to pass values (not the full equality), then you can also do this:
%macro filter(x=x, y=y, z=z);
where x=&x. and y=&y. and z=&z.;
%mend filter;
x=x is always true in SAS, but if you're passing through to SQL Server or Oracle and might have nulls this will not work (as null=null is false in SQL Server or Oracle).
%macro getMyData(xValue=, yValue=, zValue=);
proc sql;
select *
from sashelp.class
where
%if %length(&xValue) %then %do;
sex = "&xValue." and
%end;
%if %length(&yValue) %then %do;
age = &yValue. and
%end;
%if %length(&zValue) %then %do;
height >= &zValue. and
%end;
1;
quit;
%mend;
Title 'Females';
%getMyData(xValue=F);
Title '12 year';
%getMyData(yValue=12);
Title 'Large males';
%getMyData(xValue=M, zValue=60);
You can use a little known "where also" expression. Appended "where also" expressions logically equal to AND operator for each WHERE clause and you can user "where also" as your first WHERE clause without any issues to your code.
If you have a macro like that:
%MACRO get_data;
data want;
set have;
where a = x eq 1 and y eq 1 and z eq 1;
RUN;
%MEND;
You can rewrite to someting like:
%MACRO get_data;
data want;
set have;
%IF &X ne %THEN
%DO;
where also &x eq 1;
%END;
%IF &Y ne %THEN
%DO;
where also &y eq 1;
%END;
%IF &Z ne %THEN
%DO;
where also &z eq 1;
%END;
RUN;
%MEND;
Before you test the code, you need to at least initialise the macro variables.
You could do it with something like that:
%IF %symexist(&Z)=0 %THEN %LET Z = ;
Thanks all,
I have already figured out a similar structure as #Robert Penridge, but I appriciate all of the answers :) Thanks!
PS. I also was not familiar with WHERE ALSO - may prove useful in the future:)