SAS: How do I skip an error in a do loop? - loops

I am trying to skip a selection in the do loop if there is an error.
I am running the following code:
%macro date_loop(start,end);
%let start=%sysfunc(inputn(&start,anydtdte9.));
%let end=%sysfunc(inputn(&end,anydtdte9.));
%let dif=%sysfunc(intck(weekday,&start,&end));
%do i=0 %to &dif;
%let date=%sysfunc(intnx(weekday,&start,&i,b),yymmddn8.);
%put &date;
MY CODE TO OPEN FILE WITH THAT DATE
%end;
%mend date_loop;
%date_loop(01jan2015,02jan2015)
There isn't a file with the January 1, 2015 date, as I expect. I get the following error.
ERROR: File 20150101.DATA does not exist
Rather than take out holidays, I would prefer to have SAS skip that date and go to the next one. Oddly, it seems to capture the January 2, 2015 file. Then the January 2, 2015 part of the loop runs, and I get the January 2, 2015 file again. Is there a way for it to skip the date if the file doesn't exist?

To check if a SAS dataset exists then use the EXIST() function. To test if a disk file exists use the FILEEXIST() function.
...
%do i=0 %to &dif;
%let date=%sysfunc(intnx(weekday,&start,&i,b),yymmddn8.);
%let dataset = MYLIB.MYFILE&date ;
%if %sysfunc(exist(&dataset)) %then %do;
MY CODE THAT uses &dataset
%end;
%end;

Related

sas macro variable in loop thru end of month

my idea is to get a data set from tables where the variable is dynamically created based on the end of the month date. I know I'm missing sth with format/informat of a variable, but I'm super lost, please help me understood what I'm missing:
I need variable YYYYMMDD to look like ie20200229 to be sure to get leap year right.
I have database with no partion that's why each end of month is in different table.
%MACRO MEH;
%DO n=2018 %TO 2022;
&LET yyyy = n;
%DO i=1 %TO 12;
%LET mm= i;
%LET YYYYMMDD= %sysfunc(putn(intnx(month,mdy(( input(&mm.),28,input(&yyyy.)),0,e), YYMMDDN8.)); /*IE. 20200229 */
RUN;
/*this part works*/
PROC SORT DATA=XYZ.NAME_OF_TABLE_&YYYYMMDD.
out=work.NEW_NAME&YYYYMMDD. nodupkey;
by SOME_ID;
RUN;
/* MORE CODE HERE*/
DATA work.TEMP2;
MERGE work.TEMP1
work.NEW_NAME&YYYYMMDD;
BY SOME_ID;
RUN;
DATA work.WIN&YYYYMMDD.;
MERGE work.TEMP6
work.TEMP2;
BY SOME_ID;
RUN;
/* MORE CODE Here this code do sorting merging and filtering specific vales i did on SQL but I'm missing some function (like lag, to do in on SQL only) */
/*END OF WORKING CODE*/
/*CEAN UP*/
proc datasets nolist lib=work;
DELETE NEW_NAME: ;
DELETE TEMP: ;
RUN;
%END;
%END;
%MEND MEH;
%MEH;
To loop over date intervals it is usually easier to use an integer index. You can then use INTNX() function to to calculate the next interval's start date. You can use INTCK() to calculate how many iterations are required.
%let start='01JAN2018'd;
%let end='01DEC2012'd;
%do offset=0 %to %sysfunc(intck(month,&start,&end));
%let ymd = %sysfunc(intnx(month,&start,&offset),yymmddn8.);
....
%end;
Drop the YYYY and mm macro variables as they're redundant
Remove the RUN as well, not required
Then you can replace the top of your macro with the following:
%DO n=2018 %TO 2022;
%DO i=1 %TO 12;
%LET YYYYMMDD= %sysfunc(intnx(month, %sysfunc(inputn(&n.&i., yymmn6.)), 0, e), yymmddn8.); /*IE. 20200229 */
%put &yyyymmdd.; * for testing;

Is there a way to use a nested loop in a SAS macro, one for year, one for month, and start and stop mid-year?

I've used nested loops in SAS for years to pull small bits of data at a time, then process/subset the data, then append to a data set at the end of the macro, but I've always had to pull all months (or quarters), then subset my final data set later. What I want to do is to leave off the beginning and end portions of data automagically. In my example below, I want to leave off January of 2017 and December of 2020. The date variable in my data I'm using to subset is stored as a character variable in the format "yyyy-mm-dd". When I run this code, it still shows the months I don't want to see. Does anyone see anything obvious that I'm missing?
%let year1=2017-02; *I'm currently using dates in this format, but the data is too big to pull long periods of data;
%let year2=2020-11;
data _null_; *taking apart the pieces and getting start/stop year and months assigned as macro vars.;
year1="&year1";
year2="&year2";
y1=substr(year1,1,4);
y2=substr(year2,1,4);
m1=substr(year1,6,2);
m2=substr(year2,6,2);
call symput('m1',m1);
call symput('m2',m2);
call symput('y1',y1);
call symput('y2',y2);
put "&y1";
put "&y2";
put "&m1";
put "&m2";
run;
%macro test1234;
%do yr=&y1 %to &y2; *starting with year;
%do mo=1 %to 12; * nested do loop is for month, and trying to only keep month/year combos within the start/stop dates;
%if ((yr ne &y1) AND (yr ne &y2)) OR (yr=&y1 and mo ge &m1) OR (yr=&y2 AND mo le &m2) %then %do;
** for line above: 1st condition: if not a start or stop year, 2nd: start year and not before start month, 3rd, stop year, and not after stop month.;
data _null_; * viewing all the macro variables;
put "LoopYear" "&yr";
put "LoopMonth" "&mo";
put "FirstMonth" "&m1";
put "FirstYear" "&y1";
put "LastMonth" "&m2";
put "LastYear" "&y2";
run;
%end;
%else %do; %end;
%end; %end;
%mend test1234;
%test1234;
You are missing the ampersands in front of &yr and &mo in your subsetting statement. That is, you should change:
%if ((yr ne &y1) AND (yr ne &y2)) OR (yr=&y1 and mo ge &m1) OR (yr=&y2 AND mo le &m2)
to
%if (&yr ne &y1 and &yr ne &y2) OR (&yr = &y1 and &mo ge &m1) OR (&yr = &y2 and &mo le &m2)
Then you will exclude the first and last month, as you expect.
That being said, the program could undoubtedly be simplified greatly if you used numerical dates instead of character variables.
My recommendation would to refactor your code to use the two parameters and loop by the number of months. Then to change your code to exclude the end points, you can adjust your %DO loop as necessary by changing the intervals.
Convert year1 to a SAS date
Convert year2 to a SAS date
Calculate the number of months between those two dates
Loop through the number of months
%macro loop_dates(year1=, year2=);
*Convert dates to start and end dates in the SAS format;
%let start_date = %sysfunc(inputn(&year1.-01, yymmdd10.)); /*1*/
%let end_date = %sysfunc(inputn(&year2.-01, yymmdd10.)); /*2*/
*Calculate the number of months between the two dates;
%let nMonths = %sysfunc(intck(month, &start_date, &end_date, c)); /*3*/
*check macro variables, not really needed;
%put %sysfunc(putn(&start_date, date9.));
%put %sysfunc(putn(&end_date, date9.));
%put &nMonths;
*loop over the number of months and create macro variables as needed;
%do i=0 %to &nMonths; /*4*/
*increment date by loop counter i;
%let newDate = %sysfunc(intnx(month, &start_date, &i, s));
*calculate your macro variables needed for loop, not sure why???;
%let yr = %sysfunc(year(&newDate.));
%let mo = %sysfunc(month(&newDate.));
%let m1 = %sysfunc(month(&start_date.));
%let y1 = %sysfunc(year(&start_date.));
%let m2 = %sysfunc(month(&end_date.));
%let y2 = %sysfunc(year(&end_date.));
*test macro variables, not needed;
%put "LoopYear" "&yr";
%put "LoopMonth" "&mo";
%put "FirstMonth" "&m1";
%put "FirstYear" "&y1";
%put "LastMonth" "&m2";
%put "LastYear" "&y2";
%end;
%mend loop_dates;
*Call macro with the date intervals rather than use an un-parameterized macro
%loop_dates(year1=2017-02, year2=2020-11);
This includes the start, 2017-02 and end, 2020-11. To change this to not include the end points, change your %DO by incrementing the start and end. So it now starts at 1 and ends at nMonths - 1.
From:
%do i=0 %to &nMonths;
To:
%do i=1 %to &nMonths-1;

Loop and join table

Let say I want to loop for i = 2002 to 2006 and in each loop, I create a key word like:
w2006
w2005
w2004
w2003
w2002
And each keyword will be the input for a macro, let say mymacro(keyword) which create a table named "result&key" (for example: resultw2006), so that I want to execute 5 time : %mymacro(w2006), %mymacro(w2005)... %mymacro(w2002) to have 5 tables and join them after that.
Do you have any idea of how to do this? In fact I tried but it doesn't work, here is my code:
%macro tojoin;
%do i=2002 %to 2006;
key=w&i;
%mymacro(&key);
%data result;
set result result&key;
%run;
output;
%end;
%mend;
You need to provide more information on what SAS code you are tying to use the macro processor to generate to get a real answer.
But assuming that %MYMACRO will generate a dataset named result&key then it looks like you want to run something like this to call it 5 times and append the results into a single dataset.
%macro tojoin;
%local key;
%do i=2002 %to 2006;
%let key=w&i;
%mymacro(&key);
proc append base=result data=result&key;
run;
%end;
%mend;
You don't need % in front of data or run, you need a %let statement, and you don't need the output command. So:
%macro tojoin;
%do i=2002 %to 2006;
%let key=w&i;
%mymacro(&key);
data result;
set result result&key;
run;
%end;
%mend;
Tom's method is cleaner, but your code was basically correct - just a bit of cleanup needed.

Sas loop for start date and end date

I have a code here, where I take a start date and end date from the user and execute a macro for those 2 dates.
%let mon_last=31MAR2019;
%let mon_first=01JAN2019;
%macro call();
data _null_;
array datelist {2} "&mon_first"d "&mon_last"d;
%do i=1 %to 2;
%let yrmon1=%sysfunc(inputn(datelist{i},mmddyy10.), date9.));
%let yrmon=%sysfunc(putn(&yrmon1,date9.),yymmn6.);
%let yr=%sysfunc(year(&yrmon1),4);
%let mon=%sysfunc(month(&yrmon1),z2);
%datapull(&yrmon., &yr., &mon.);
%end;
%mend;
%call();
But I end up getting the following error whichever way I try:
WARNING: Argument 1 to function INPUTN referenced by the %SYSFUNC or %QSYSFUNC macro function is out of range.
NOTE: Mathematical operations could not be performed during %SYSFUNC function execution. The result of the operations have been set
to a missing value.
ERROR: The function PUTN referenced by the %SYSFUNC or %QSYSFUNC macro function has too few arguments.
The macro processor considers everything as a string. You cannot convert the string datelist{i} into a date value.
Looks like you want to have a macro that can take as input a list of strings that are in a format that can be converted into date values and use those to call another macro.
%macro call(date_list);
%local i yrmon yr mon;
%do i=1 %to %sysfunc(countw(&date_list));
%let yrmon=%sysfunc(inputn(%scan(&date_list,&i),date11.),yymmn6.);
%let yr=%substr(&yrmon,1,4);
%let mon=%substr(&yrmon,5);
%datapull(&yrmon., &yr., &mon.);
%end;
%mend;
%call(31MAR2019 01JAN2019);
If instead you wanted to process every month from start to end then you want a different macro with different inputs. In that case you just need two inputs that each can only have one value.
This time let's code it so that the burden of supplying valid date values falls onto the caller of the macro instead of accepting character strings that need to be converted into dates.
%macro call(start,end);
%local i yrmon yr mon;
%do i=0 %to %sysfunc(intck(month,&start,&end));
%let yrmon=%sysfunc(intnx(month,&start,&i),yymmn6.);
%let yr=%substr(&yrmon,1,4);
%let mon=%substr(&yrmon,5);
%datapull(&yrmon., &yr., &mon.);
%end;
%mend;
%call("01JAN2019"d,"31MAR2019"d);

SAS Macro Do LOOP

I want to have a sas macro loop by even years, so the do loop would jump from 2006 to 2008 to 2010...all the way to 2018 and not from 2006 to 2007.
When I do a %by = 2, SAS doesn't work and gives me an error message. What is the best solution?
I have the following Code:
%macro import;
%do I = 2006 %to 2018;
data BTI&I;
set edited.Bti_&I;
year=&I;
run;
%end;
%mend import;
%import;
Add the %by 2 keyword to increment intervals of 2. I would also recommend passing the start and end years as parameters to your function and give defaults values of 2006 and 2018.
%macro import(start=2006, end=2018);
%do I = &start. %to &end. %by 2;
data BTI&I;
set edited.Bti_&I;
year=&I;
run;
%end;
%mend import;
%import;
Usage:
%import(); which will use the default values 2006 & 2018
%import(start=2009, end=2018); specify the date range you want to use

Resources