BookmarkSubscribeRSS Feed
Reese00
Calcite | Level 5

Hello all,

 

Please forgive me as I am very very new to SAS and have been tasked to learn SAS without formal training. I have a macro that currently pulls data from a very large file (a list of employee expenditures by employee id) and separates the data into 4 different groups depending on the kind of expenditure it is (ex: ID#100 - #212 = clothing, ID#220 - #411 = food, ID#440 - #575 = housing, ID#600 - #722 = supplies). Currently, I have a %LET statement (%LET MONYR = SEP21) that allows me to run it for each month. I now need to change that %LET so that I can run it for an entire fiscal year. I have researched for a few days to try and understand the best way to do this but I am at a loss (this is probably due to my very low level beginner SAS knowledge). Any guidance would be greatly appreciated!!!

18 REPLIES 18
PaigeMiller
Diamond | Level 26

So if you run the program on a specific day (let's say today, January 5, 2022), how do you determine what value to assign? How do you determine what value to assign on other days?

--
Paige Miller
Reese00
Calcite | Level 5
Hi Paige,

We determine the value to assign it by what our analysts need at that time. For example, as of today January 5, 2022 we are currently doing a large testing effort and the analysts need this data for all of FY20-FY21 so I need to be able to run this program today for October 2019 - September 2021 at one time instead of having to run the file 24 times and having 24 separate files. Essentially, on any day we need to be able to run it for any specified time but it's always by month since that is how the data is organized in the larger source file. I hope this answers your question!
SASKiwi
PROC Star

Without knowing how your code handles date processing and also how you want your multiple date data organised, it is near impossible to offer any useful advice. Can you post your program so we can see what it does?

 

We don't know what your definition of FY20-FY21 is either and whether you are dealing with daily, weekly or monthly data. 

Patrick
Opal | Level 21

@Reese00 Without at least seeing the code but ideally also a portion of the data it's going to be close to impossible to really give you an answer. A SAS macro variable can only ever have a single value (a string) and all SAS macro processing is textual only.

 

Based on what you write you have somewhere in your code...

%LET MONYR = SEP21;

...and this macro variable then gets used to subset your source data. If so then there must be somewhere later in your code something like:

where <some variable>= &monyr

SAS macro language is pre-processed meaning that when you run the program SAS will first execute everything macro and resolve this. After this the "normal" SAS will compile and execute what remains.

 

In above case the macro variable would resolve to...

where <some variable>= SEP12

....and "normal" SAS would then compile and execute this syntax. 

 

Using above example to not sub-set the data if macro variable &monnr is blank one could extend the code within the SAS macro to something like...

%if %nrbquote(&monyr) ne %nrbquote() %then
%do;
  where <some variable>= &monyr
%end;

...where the SAS macro only "writes" the where condition if &monyr isn't empty. If it's empty then the macro won't write this where clause and though "normal" SAS won't get it for compilation and execution.

 

It's a bit tough - and a bit unreasonable imho - to expect someone without training to start right with SAS macros. You should first learn the Base SAS language. Macro language comes later.

There is some free e-learning but major part is not for free - but still available as e-learning. You really should convince your employer to let you do this.

 

What's your coding background? Which language(s) do have you experience with?

PaigeMiller
Diamond | Level 26

@Reese00 wrote:
Hi Paige,

We determine the value to assign it by what our analysts need at that time. For example, as of today January 5, 2022 we are currently doing a large testing effort and the analysts need this data for all of FY20-FY21 so I need to be able to run this program today for October 2019 - September 2021 at one time instead of having to run the file 24 times and having 24 separate files. Essentially, on any day we need to be able to run it for any specified time but it's always by month since that is how the data is organized in the larger source file. I hope this answers your question!

I'm sorry, but I'm still not understanding. What is the logic (in a person's mind, not in SAS) that identifies that this time we want these months in the analysis, and the next time we want different months?

 

Also, how will the computer program know what this logic is and what months it should use? Somehow, this information from a person's mind has to be provided to the computer program, and you haven't explained that at all. Does this information come entirely from the day the program is run, or are there other pieces of information needed to figure out what the proper months are?

--
Paige Miller
Reese00
Calcite | Level 5
@PaigeMiller - My apologies for not providing sufficient information to answer your question. It's determined by the type of testing we are doing and how much data the analysts need to review in order to have enough data to support the testing effort. So for this particular round of testing, the analysts need to look at the previous two fiscal years to have enough information to be able to validate expenditures and provide supporting data/documentation if necessary. The next round of testing may have different requirements and only need to look at the last 6 months of data.
PaigeMiller
Diamond | Level 26

So again, how does the computer program know if it should work on 24 months or 6 months or x months?

--
Paige Miller
Reese00
Calcite | Level 5

@PaigeMiller - For the %LET MONYR = SEP21 and %LET MONFLG = SEPTEMBER-2021 in the program, I have to manually go in and update the the bolded values any time we need to run it. 

PaigeMiller
Diamond | Level 26

@Reese00 wrote:

For the %LET MONYR = SEP21 and %LET MONFLG = SEPTEMBER-2021 in the program, I have to manually go in and update the the bolded values any time we need to run it. 


And you'd like to make a program such that you don't have to do that manually. I understand that part.

 

The question you are not answering: is what is the logic? A program has to run on logic. It has to know what to do in the current situation. I don't see an answer.

--
Paige Miller
Reese00
Calcite | Level 5
I do not mind having to manually input the date values that the program should run for. The timeframes that we need to pull the data for are decided on a case by case basis and can change at any time so it's really up to the analysts and what timeframe they want the data for and they could change their mind tomorrow and say they need a different timeframe. The date entry part does not have to be automatic. I just need to be able to input any timeframe (1 month, 5 months, 2 years) and be able run the program and pull the data for that timeframe.
PaigeMiller
Diamond | Level 26

@Reese00 wrote:
I do not mind having to manually input the date values that the program should run for. The timeframes that we need to pull the data for are decided on a case by case basis and can change at any time so it's really up to the analysts and what timeframe they want the data for and they could change their mind tomorrow and say they need a different timeframe. The date entry part does not have to be automatic. I just need to be able to input any timeframe (1 month, 5 months, 2 years) and be able run the program and pull the data for that timeframe.

You could create a macro variable for start month (or end month) and a macro variable for the number of months to use. The user then simply change these values of these two variables and runs the program.

 

As far as exactly how to do this, I see others have made the point that its really hard to give advice without seeing the program. I haven't read those posts and your replies carefully, so I will answer at a later time.

--
Paige Miller
Reeza
Super User

@Reese00 wrote:

Hello all,

 

Please forgive me as I am very very new to SAS and have been tasked to learn SAS without formal training. I have a macro that currently pulls data from a very large file (a list of employee expenditures by employee id) and separates the data into 4 different groups depending on the kind of expenditure it is (ex: ID#100 - #212 = clothing, ID#220 - #411 = food, ID#440 - #575 = housing, ID#600 - #722 = supplies). Currently, I have a %LET statement (%LET MONYR = SEP21) that allows me to run it for each month. I now need to change that %LET so that I can run it for an entire fiscal year. I have researched for a few days to try and understand the best way to do this but I am at a loss (this is probably due to my very low level beginner SAS knowledge). Any guidance would be greatly appreciated!!!


Unfortunately you cannot just change the %LET and expect it to work correctly. Most likely the filters are designed for a single date/month and you would also have to make changes in the program to account for years. 

 

However, it's very trivial to call a macro 24 times with different parameters and combine the results using CALL EXECUTE or a macro. I would suggest one of those approaches instead of diving into the code. That being said, there may also be business reasons that doing that isn't appropriate - you need to know the data to be able to answer that one. If it is, show how you call it and how the output is generated and we can help you automate the 24 calls easily. 

Reese00
Calcite | Level 5

Here is the code I'm trying to update - 

 

"LIBNAME DTA "/opt/app/sas/WS_files/";
Proc Import Datafile="/opt/app/sas/WS_files/CATID Listing.csv"
OUT=SAF_TABLE DBMS=CSV REPLACE;
Run;
Proc Sort Data=CAT_TABLE;
BY CATID;
Proc Import Datafile="/opt/app/sas/WS_files/FY22 EMPID Samples.csv"
OUT=EMPID_TABLE DBMS=CSV REPLACE;
Run;

Data EMPID_TABLE_ID;
Set EMPID_TABLE;
Length EMPID2 $9.;
EMPID2 = SUBSTR(cats('000000000',TRIM(EMPID)), Length(cats('000000000',TRIM(EMPID)))- 8, 9);
Drop EMPID
Rename EMPID2 = EMPID;
Run;

Proc Sort Data=EMPID_TABLE_ID;
BY EMPID
Run;

/**************************/
/*MACRO FOR CONTROL TOTALS*/
/**************************/
%MACRO CONTROL_TOTALS(DSN, VAR_LIST);
PROC SUMMARY DATA=&DSN. NWAY MISSING;
VAR &VAR_LIST.;
OUTPUT OUT=SUMM SUM=;
RUN;
%LET CNT = %EVAL(%SYSFUNC(LENGTH(%SYSFUNC(COMPBL("&VAR_LIST."))))-%SYSFUNC(LENGTH(%SYSFUNC(COMPRESS("&VAR_LIST."))))+1);
%PUT "COUNT OF VARIABLES: &CNT.";
DATA _NULL_;
SET SUMM;
FORMAT _FREQ_ COMMA20.;
FORMAT &VAR_LIST. COMMA30.2;
PUT @3 '----------------------------------------------------'/;
PUT @5 "CONTROL TOTAL AND TOTAL NUMBER OF RECORDS IN &DSN."/;
PUT @10 'TOTAL NUMBER OF RECORDS = ' _FREQ_/;
%DO I = 1 %TO &CNT.;
%LET VAR = %SCAN(&VAR_LIST.,&I.);
PUT @10 "TOTAL OF &VAR. = " &VAR./;
%END;
PUT @3 '----------------------------------------------------'/;
RUN;
%MEND;

%LET MONYR = SEP21;
%LET MONFLG = SEPTEMBER-2021;

/************************************************************/
/** MACRO TO READ IN EXPENDITURE FILES AND CREATE PERMANENT **/
/** DATA SET FOR EACH MONTH. **/
/************************************************************/

DATA GROUPE_FORMAT1_&monyr. (KEEP=EMPID CATID AI LEVEL YEARS FY MONTH CURR_MO_AMT NXT_MO_AMT CURMONODAYS NXTMONODAYS CURMONNOPMT NXTMONNOPMT NODAYSENTMH TRAINING_YRS CNTRY_CODE STATUS AIC ADSN SVC_COMP SOURCE FILE_MONTH_FLAG)
GROUPE_FORMAT3_&monyr. (KEEP=EMPID CATID AI LEVEL FY MONTH CURR_MO_AMT NXT_MO_AMT BAL_DUE ORG_DEBTAMT INDBTNS_TYP AMT_FORGVN CNTRY_CODE STATUS AIC ADSN SVC_COMP SOURCE FILE_MONTH_FLAG)
GROUPE_FORMAT4_&monyr. (KEEP=EMPID CATID AI LEVEL FY MONTH AMT_OF_COLL CNTRY_CODE STATUS AIC ADSN SVC_COMP SOURCE FILE_MONTH_FLAG)
GROUPE_FORMAT6_&monyr. (KEEP=EMPID CATID AI LEVEL YEARS FY MONTH CURR_MO_AMT NXT_MO_AMT NOMTHPURBND MTHBNDISD BNS_SR_TYP CNTRY_CODE STATUS AIC ADSN SVC_COMP SOURCE FILE_MONTH_FLAG)
;
INFILE "/opt/app/sas/WS_files/EXPEAPR&monyr._final.txt";
LENGTH SOURCE $15.;
SOURCE = "EXPENDITURES";
LENGTH FILE_MONTH_FLAG $15.;
FILE_MONTH_FLAG = "&monflg.";
INPUT @1 EMPID $9.
@10 CATID $2. @;
IF CATID IN ("AW","RX","R0","D1","D1","D2","D3","D4","D5","D6","D7","D8","D1","D3","D5","D7","X9") THEN
DO;
INPUT @13 AI $1.
@14 LEVEL $3.
@17 YEARS $3.
@20 FY $1.
@21 MONTH $3.
@24 CURR_MO_AMT $7.
@31 NXT_MO_AMT $7.
@38 CURMONODAYS $3.
@41 NXTMONODAYS $3.
@44 CURMONNOPMT $1.
@45 NXTMONNOPMT $1.
@46 NODAYSENTMH $3.
@53 TRAINING_YRS $3.
@79 CNTRY_CODE $2.
@81 STATUS $1.
@82 AIC $4.
@86 ADSN $4.
@90 SVC_COMP $1.
;
OUTPUT GROUPE_FORMAT1_&monyr.;
END;
ELSE IF CATID IN ("E1","E2","E3","G1","K1","M1","P1","Q1","Q2","R1","S1","V1","V2","W1") THEN
DO;
INPUT @13 AI $1.
@14 LEVEL $3.
@17 FILLER $2.
@19 FY $1.
@20 MONTH $3.
@23 CURR_MO_AMT $9.
@32 NXT_MO_AMT $9.
@41 BAL_DUE $9.
@50 ORG_DEBTAMT $9.
@59 INDBTNS_TYP $1.
@60 AMT_FORGVN $9.
@88 CNTRY_CODE $2.
@90 STATUS $1.
@91 AIC $4.
@95 ADSN $4.
@99 SVC_COMP $1.
;
OUTPUT GROUPE_FORMAT3_&monyr.;
END;
ELSE IF CATID IN ("A4","A5","CA","C1","CC","C3","C5","E1","P1") THEN
DO;
INPUT @13 AI $1.
@14 LEVEL $3.
@20 FY $1.
@21 MONTH $3.
@24 AMT_OF_COLL $9.
@73 CNTRY_CODE $2.
@75 STATUS $1.
@76 AIC $4.
@80 ADSN $4.
@84 SVC_COMP $1.
;
OUTPUT GROUPE_FORMAT4_&monyr.;
END;
ELSE IF CATID IN ("AC","AD","AF","AH","AI","AJ","AL","AN","AS","AT","AV","A1","SN") THEN
DO;
INPUT @13 AI $1.
@14 LEVEL $3.
@19 FY $1.
@20 MONTH $3.
@23 CURR_MO_AMT $7.
@30 NXT_MO_AMT $7.
@39 NOMTHPURBND $3.
@42 MTHBNDISD $3.
@45 BNS_SR_TYP $1.
@76 CNTRY_CODE $2.
@78 STATUS $1.
@79 AIC $4.
@83 ADSN $4.
@87 SVC_COMP $1.
;
OUTPUT GROUPE_FORMAT6_&monyr.;
END;
RUN;

/** GROUP E - FORMAT 1 **/

DATA GROUPE_FORMAT1_&monyr._MOD;
SET GROUPE_FORMAT1_&monyr.;
LEVEL_NUM = LEVEL * 1;
CUR_AMOUNT = CURR_MO_AMT * .01;
IF AI IN ("0","1") THEN DO;
CURR_AMOUNT = CUR_AMOUNT*1;
END;
ELSE DO;
CURR_AMOUNT = -(CUR_AMOUNT)*1;
END;
LENGTH EMP_LEVEL $8.;
IF LEVEL_NUM < 26 THEN EMP_LEVEL = "STAFF";
ELSE IF LEVEL_NUM < 40 THEN EMP_LEVEL = "MANAGEMENT";
ELSE IF LEVEL_NUM < 55 THEN EMP_LEVEL = "EXECUTIVE";
LENGTH APPN $4.;
IF SVC_COMP IN("S","M","E") THEN APPN = "1343";
IF SVC_COMP IN("T") THEN APPN = "1657";
/* KEEP EMPID CATID LEVEL ADSN FY MONTH FILE_MONTH_FLAG CURR_AMOUNT SOURCE EMP_LEVEL;*/
RUN;

/** GROUP E - FORMAT 3 **/

DATA GROUPE_FORMAT3_&monyr._MOD;
SET GROUPE_FORMAT3_&monyr.;
LEVEL_NUM = GRADE * 1;
CUR_AMOUNT = CURR_MO_AMT * .01;
IF AI IN ("0","1") THEN DO;
CURR_AMOUNT = CUR_AMOUNT*1;
END;
ELSE DO;
CURR_AMOUNT = -(CUR_AMOUNT)*1;
END;
IF CATID IN("D1","D2") THEN DO CURR_AMOUNT = CURR_AMOUNT * 100;
END;
LENGTH EMP_LEVEL $8.;
IF LEVEL_NUM < 26 THEN EMP_LEVEL = "STAFF";
ELSE IF LEVEL_NUM < 40 THEN MEMBER_GRADE = "MANAGEMENT";
ELSE IF LEVEL_NUM < 55 THEN MEMBER_GRADE = "EXECUTIVE";
LENGTH APPN $4.;
IF SVC_COMP IN("S","M","E") THEN APPN = "1343";
IF SVC_COMP IN("T") THEN APPN = "1657";
/* KEEP EMPID CATID LEVEL ADSN FY MONTH FILE_MONTH_FLAG CURR_AMOUNT SOURCE EMP_LEVEL;*/
RUN;

/** GROUP E - FORMAT 4 **/

DATA GROUPE_FORMAT4_&monyr._MOD;
SET GROUPE_FORMAT4_&monyr.;
LEVEL_NUM = GRADE * 1;
CUR_AMOUNT = AMT_OF_COLL * .01;
IF AI IN ("0","1") THEN DO;
CURR_AMOUNT = CUR_AMOUNT*1;
END;
ELSE DO;
CURR_AMOUNT = -(CUR_AMOUNT)*1;
LENGTH EMP_LEVEL $8.;
IF LEVEL_NUM < 26 THEN EMP_LEVEL = "STAFF";
ELSE IF LEVEL_NUM < 40 THEN EMP_LEVEL = "MANAGEMENT";
ELSE IF LEVEL_NUM < 55 THEN EMP_LEVEL = "EXECUTIVE";
LENGTH APPN $4.;
IF SVC_COMP IN("S","M","E") THEN APPN = "1343";
IF SVC_COMP IN("T") THEN APPN = "1657";
END;
/* KEEP EMPID CATID LEVEL ADSN FY MONTH FILE_MONTH_FLAG CURR_AMOUNT SOURCE EMP_LEVEL;*/
RUN;

/** GROUP E - FORMAT 6 **/

DATA GROUPE_FORMAT6_&monyr._MOD;
SET GROUPE_FORMAT6_&monyr.;
LEVEL_NUM = GRADE * 1;
CUR_AMOUNT = CURR_MO_AMT * .01;
IF AI IN ("0","1") THEN DO;
CURR_AMOUNT = CUR_AMOUNT*1;
END;
ELSE DO;
CURR_AMOUNT = -(CUR_AMOUNT)*1;
LENGTH MEMBER_GRADE $8.;
IF LEVEL_NUM < 26 THEN EMP_LEVEL = "STAFF";
ELSE IF LEVEL_NUM < 40 THEN EMP_LEVEL = "MANAGEMENT";
ELSE IF LEVEL_NUM < 55 THEN EMP_LEVEL = "EXECUTIVE";
LENGTH APPN $4.;
IF SVC_COMP IN("S","M","E") THEN APPN = "1343";
IF SVC_COMP IN("T") THEN APPN = "1657";
END;
/* KEEP EMPID CATID LEVEL ADSN FY MONTH FILE_MONTH_FLAG CURR_AMOUNT SOURCE EMP_LEVEL;*/
RUN;

Data DTA.Expenditures_&monyr.;
Set GROUPE_FORMAT1_&monyr._MOD
GROUPE_FORMAT3_&monyr._MOD
GROUPE_FORMAT4_&monyr._MOD
GROUPE_FORMAT6_&monyr._MOD;
Run;

%Macro Detail_EMP_Exp();

Proc Sort Data= DTA.Expenditures_&monyr.;
BY EMPID;
Run;

Data Detail_EMP_Exp;
Merge DTA.Expenditures_&monyr. (in=A) EMPID_TABLE_ID (in=B);
BY EMPID;
If B;
Run;

Proc Sort Data=Detail_EMP_Exp;
By CATID;
Run;

Data Detail_EMP_Exp_mod;
Merge Detail_EMP_Exp (in=A) SAF_TABLE (in=B);
BY CATID;
If A;
Run;

Proc Sort Data=Detail_EMP_Exp_mod;
By EMPID CATID FY MONTH EMPID;
Run;

Proc Export Data=Detail_EMP_Exp_mod
Outfile="/opt/app/sas/WS_files/FY22 Emp Samples Expenditures.csv"
DBMS=CSV REPLACE;
Run;

%MEND;
%Detail_EMP_Exp;
Reeza
Super User
How do you call this program and where do you put in the month parameter?
Do you want a CSV file in the end or a SAS data set?

I would:

1. change the current program to take a macro parameter so its cleaner
2. Change the current program to output a SAS data set with a unique name
3. Create a new control program that took a start and end date and then would run it for that interval and combine and export the results. If you need it for one month the start/end is the same.

Because your data sets are uniquely named, rewriting the code to handle multiple periods would require a decent amount of testing to ensure you get the correct results. This approach assumes your base code is correct and would minimize errors.

sas-innovate-2024.png

Available on demand!

Missed SAS Innovate Las Vegas? Watch all the action for free! View the keynotes, general sessions and 22 breakouts on demand.

 

Register now!

How to Concatenate Values

Learn how use the CAT functions in SAS to join values from multiple variables into a single value.

Find more tutorials on the SAS Users YouTube channel.

Click image to register for webinarClick image to register for webinar

Classroom Training Available!

Select SAS Training centers are offering in-person courses. View upcoming courses for:

View all other training opportunities.

Discussion stats
  • 18 replies
  • 1113 views
  • 2 likes
  • 6 in conversation