I have a set of code that I am manually adjusting month end dates. The query which runs and uses the dates has left joins to pull data for each declared date. I've been looking at macros because this is labor intensive to change all the dates by one month all the way until the most recent completed month. There's got to be a way to loop through and change each declared month end date by one month and do a loop to run the query until it gets to the most recent completed month end date.
So after the below runs entirely, it would up each declared date by one month and show the month end date. Each data set that runs would be be BEDT. So the first run would be jan2018, so on and so forth.
Can someone assist or point me into the right direction? I've read through so much documentation and have gotten no where. Thanks in advance.
%Let BEdt='01JAN2018'd
%let dt1='28FEB2018'
%let dt2='31MAR2018'
%let dt3='30APR2018'
%let dt4='31MAY2018'
%let dt5='30JUN2018'
%let dt6='31JUL2018'
%let dt7='31AUG2018'
%let dt8='30SEP2018'
%let dt9='31OCT2018'
%let dt10='30NOV2018'
%let dt12='31DEC2018'
%let dt12='31JAN2019'
Let's use something that has a start date and a number of months to pull. We'll use intnx() to increment each month one by one until we reach the last month. intnx() will also help us calculate the start and end dates.
%macro get_data(start=, months=12);
/* Convert start/end values into SAS dates */
%let start_dt = %sysfunc(inputn(&start., date9.));
%do i = 0 %to &months.;
/* Calculate the start and end months for the month of data in the loop */
%let month_s = %sysfunc(intnx(month, &start_dt., &i., B) );
%let month_e = %sysfunc(intnx(month, &start_dt., &i., E) );
%put Pulling data for %cmpres(%sysfunc(putn(&month_s., date9.)) - %sysfunc(putn(&month_e., date9.)));
/***********************************/
/***** SQL/DATA Step goes here *****/
/***********************************/
%end;
%mend;
%get_data(start=01JAN2018);
In the area where you'd add SQL or DATA step code, it could look like this:
data want_&month_s.;
set have;
where date BETWEEN &month_s. AND &month_e.;
run;
Output:
Pulling data for 01JAN2018 - 31JAN2018
Pulling data for 01FEB2018 - 28FEB2018
Pulling data for 01MAR2018 - 31MAR2018
Pulling data for 01APR2018 - 30APR2018
Pulling data for 01MAY2018 - 31MAY2018
Pulling data for 01JUN2018 - 30JUN2018
Pulling data for 01JUL2018 - 31JUL2018
Pulling data for 01AUG2018 - 31AUG2018
Pulling data for 01SEP2018 - 30SEP2018
Pulling data for 01OCT2018 - 31OCT2018
Pulling data for 01NOV2018 - 30NOV2018
Pulling data for 01DEC2018 - 31DEC2018
Pulling data for 01JAN2019 - 31JAN2019
Depending on the complexity of the statement you want to run for each month, you can also use call execute instead of a macro loop.
data _null_;
start_date = "&start."d;
do i = 0 to &months.;
month_start = intnx("month", start_date, i, "B");
month_end = intnx("month", start_date, i, "E");
call execute(
'data want_'||strip(i)||';'||
'set have;'||
'where date between '||month_start||' and '||month_end||';'||
'run;'
);
end;
run;
You only have to be careful about when macro variables get resolved. I nice article on this can be found here
* Do whatever you would do for one month in a macro;
%macro do_a_month(begin_d, end_d);
data want_&begin_d;
set have;
where data between "&begin_d"d and "&end_d"d;
run;
%mend;
* call the macro as much as needed;
data _null_;
begin_d = '01JAN2018'd;
do while (begin_d le '31JAN2019'd);
end_d = intnx("month", begin_d, 0, "E");
call execute ( '&do_a_month('
|| put(begin_d, date9.) ||','
|| put(end_d, date9.) ||')';
* go to next month;
begin_d = end_d + 1;
end;
run;
Related
Good afternoon,
I am decent with SAS but I've never written macros.
I have a DB where I need to break out in separate datasets ID's where the date in a field occurs. E.g. All ID's with a date in Jan 2018 would be one dataset, All ID's with a date in Feb 2018 would be another data set, so on and so forth. Field name is ZDate.
I found this which seems to do exactly what I want. However I think my date isn't in the correct format. The date I'm pulling is in a timestamp in snowflake and I'm converting it to a date with to_date. It's showing as formatted date (16FEB2020) in the original vintagedata data set but the subsequent data sets are completely blank.
%macro month;
%local mindate maxdate i date month ;
proc sql noprint;
select min(ZDate),max(ZDate)
into :mindate , :maxdate
from vintagedata
;
quit;
data
%do i=0 %to %sysfunc(intck(month,&mindate,&maxdate));
%let date=%sysfunc(intnx(month,&mindate,&i));
%let month=%sysfunc(putn(&date,monyy7.));
&month
%end;
;
set vintagedata;
%do i=0 %to %sysfunc(intck(month,&mindate,&maxdate));
%let date=%sysfunc(intnx(month,&mindate,&i));
%let month=%sysfunc(putn(&date,monyy7.));
if intnx('month',date,0)=&date then output &month ;
%end;
run;
%mend;
%month;
I have a table with observations from the date 01.08.2016 to 30.08.2016.
How to create 12 tables in the following way:
the first one contains observations from the date 01.08.2016 to 20.08.2016;
the second one contains observations from the date 01.08.2016 to 21.08.2016;
...
the 12th one contains observations from the date 01.08.2016 to 30.08.2016.
I think that it is better to do using loops, but dont know how.
This assumes that the date is in SAS date format. You can use character comparison if your date is in character format.
The data vector still contains the observation after the output statement is executed. So as long as the condition is true, the data step will write the same observation to multiple datasets. Also, I think you will need the date comparisons till 31st August if you want 12 datasets.
data want1 want2 want3 ... want12;
set have;
if date <= '20AUG2016'd then output want1;
if date <= '21AUG2016'd then output want2;
if date <= '22AUG2016'd then output want3;
.
.
.
if date <= '31AUG2016'd then output want12;
run;
It is probably better to use WHERE statements than to make separate tables. But to do either without hardcoding you need to use code generation. That is normally done using macro logic.
%macro split(start,stop);
%local i n;
%let n=%sysfunc(intck(day,&start,&stop));
%let n=%eval(&n+1);
DATA
%do i=1 %to &n;
WANT&i
%end;
;
set have ;
%do i=1 %to &n ;
if date <= %sysfunc(intnx(day,&start,&i-1)) then output WANT&i ;
%end;
run;
%mend split;
%split('20AUG2016'd,'31AUG2016'd);
I am looking to automate a daily report for my company but I have run in to a bit of trouble. The report gets updated only on the 2nd working day of each month. I found some code on the SAS website which works out what the 2nd working day of any month is.
data scdwrk;
/* advance date to the first day of the month using the INTNX function */
second=intnx('month',today(),0);
/* determine the day of the week using the WEEKDAY function */
day=weekday(second);
/* if day=Monday then advance by 1 */
if day=2 then second+1;
/* if day=Sunday then advance by 2 */
else if day=1 then second+2;
format second date9.;
run ;
I have also set a flag that compares todays date to the date from this generated by this piece of code.
I now need to find a way that if the code is run on the first working day of the month then it runs a particular set of macro date variables
%let start_date="&prevmnth;
%let end_date= &endprevmnth;
%let month= &prevyearmnth;
and then when its run on the 2nd working day of the month it uses the other set of macro date variables (calender month)
%let start_date="&currmnth;
%let end_date= &endcurrmnth;
%let month= &curryearmnth;
Any help on this would be greatly appreciated.
I have some recent code that does just this. Here is how I tackled it.
First, create a table of holidays. This can be maintained yearly.
Second, create a table with the first 5 days of the month that are not weekend days.
Third, delete holidays.
Finally, get the second value in the data set.
data holidays;
format holiday_date date9.;
informat holiday_date date9.;
input holiday_date;
datalines;
01JAN2015
19JAn2015
16FEB2015
03APR2015
25MAY2015
03JUL2015
07SEP2015
26NOV2015
25DEC2015
;
data _dates;
firstday = intnx('month',today(),0);
format firstday date date9.;
do date=firstday to firstday+5;
if 1 < weekday(date) < 7 then
output;
end;
run;
proc sql noprint;
delete from _dates
where date in (select holiday_date from holidays);
quit;
data _null_;
set _dates(firstobs=2);
call symput("secondWorkDay",put(date,date9.));
stop;
run;
%put &secondWorkDay;
I am an intermediate user of SAS, but I have limited knowledge of arrays and macros. I have a set of code that prompts the user to enter a date range. For example, the user might enter December 1, 2015-December 5,2015. For simplicity, imagine the code looks like:
data new; set old;
if x1='December 1, 2015'd then y="TRUE";
run;
I need to run this same code for every day in the date prompt range, so for the 1st, 2nd, 3rd, 4th, and 5th. My thought was to create an array that contains the dates, but I am not sure how I would do that. My second thought was to create a macro, but I can't figure out out to feed a list through a macro.
Also, just FYI, the code is a lot longer and more complicated than just a data step.
The following macro can be used as a framework for your code:
%MACRO test(startDate, endDAte);
%DO i=&startDate %to &endate;
/* data steps go here */
/* example */
DATA test;
SET table;
IF x1 = &i THEN y = "true";
RUN;
%END;
%MEND;
Look into call execute to call your macro and a data null step using a do loop to loop through the days. Getting the string correct for the call execute can sometimes be tricky, but worth the effort overall.
data sample;
do date='01Jan2014'd to '31Jan2014'd;
output;
end;
run;
%macro print_date(date);
proc print data=sample;
where date="&date"d;
format date date9.;
run;
%mend;
%let date_start=05Jan2014;
%let date_end=11Jan2014;
data _null_;
do date1="&date_start"d to "&date_end"d by 1;
str='%print_date('||put(date1, date9.)||');';
call execute(str);
end;
run;
Hello I am trying to build a macro process that allows me to pull data from the most current month to a x amount of months(historically), however it seems to only pull for one month any thoughts ?
%LET START_DATE = 20140731;
DATA _NULL_;
START_DATE = input(cat(&START_DATE),YYMMN6.);
FORMAT START_DATE date9.;
MON_START_DT=put(START_DATE,date9.);
CALL SYMPUTX('MON_START_DT',put(MON_START_DT,9.));
RUN;
%MACRO DO_FOR(NUM_OF_MONTHS);
%DO I = 0 %TO &NUM_OF_MONTHS-1;
DATA _NULL_;
END_DATE=intnx('month',"&MON_START_DT"d,&I,'end');
FORMAT END_DATE yymmdd10.;
MON_END_DATE=put(END_DATE,yymmdd10.);
MON_END_DATE=COMPRESS(MON_END_DATE," (-)");
FORMAT MON_END_DATE $8.;
CALL SYMPUTX('BATCH_ID',put(MON_END_DATE,$8.));
RUN;
PROC SQL;
sysecho;
CREATE TABLE work.table AS
SELECT FROM WHERE
/*ex This table contains months of data*/
%END;
%MEND DO_FOR;
%DO_FOR(NUM_OF_MONTHS=03);
Shouldn't &I have a minus sign in front of it to go backwards in time rather than forwards in time? If you start at the current month and only go forwards you will only get one month.
END_DATE=intnx('month',"&MON_START_DT"d,-&I,'end');