Struggling to use INTNX feature for date logic. I have the following macro variable created -
%LET BDATE1 = '2015-07-12';*ACTIVITY BEGIN DATE;
I need to use this static date (whatever it may be at the time) and create an additional macro var. off of the condition of &BDATE1 + 7 ...attempting to resolve the value of '2015-07-19' so it can be passed in a query (RSUBMIT in a DB2 env.)
My attempt/assumption was that the following would give me what I needed to pass in the query -
DATA _NULL_;
%GLOBAL NEWDATE;
CALL SYMPUT('NEWDATE',PUT(INTNX('DAY',&BDATE1,+7),YYMMDD10.));
RUN;
This execution provides an error -
NOTE: Invalid numeric data, '2015-07-12' , at line 1 column 1.
NOTE: Argument 2 to function INTNX('DAY',.,7) at line 979 column 27 is invalid.
_ERROR_=1 _N_=1
My assumption is that since I am creating &BDATE1 as a string date (for the DB2), the INTNX is reading this as a char. string, not a numeric value. Can anyone suggest another approach/resolution to this? I attempted to reformat the var, but the fact that it needs to be in string format causes an additional problem.
So the problem is because you are effectively running this code:
%LET BDATE1 = '2015-07-12';
data _null_;
oops = INTNX('DAY',&BDATE1,+7);
run;
This is failing because the bdate1 macro variable resolves to a string, rather than a date literal. It's not resolving to a date literal because you forgot to specify the d suffix at the end of the string:
%LET BDATE1 = '2015-07-12'd; /* HAS D SUFFIX */
That change should be sufficient to fix the code.
My personal preferred way of working with these kinds of requirements is to create a date value in a macro variable, and do the manipulations using %let statemnets.
%let bdate1 = %sysfunc(mdy(7,12,2015));
%let newdate = %sysfunc(intnx(day,&bdate1,7),yymmdd10.);
%put &newdate;
Gives:
2015-07-19
You can see the secret here is that the %sysfunc() function as a second parameter that allows you apply a format to the value to be returned.
EDIT : In case you are unfamiliar with the %sysfunc() function you are best off searching for some whitepapers on it. But basically, all it does is allows you to use functions that you would normally use in a data step, within the macro environment. In this case, we're using them within a %let statement.
I should also mention that your %global statement is not required unless that particular piece of code is executing within a %macro block. Any variables created outside of of macro, will effectively be global.
Related
I have a control to verify.
I want to write a macro variable who includes an SQL proc in case the condition is verified.
Exemple:
%Macro Control1(List) / minoperator mindelimiter=' ';
%IF &Control1 in &List. %Then %do;
proc sql;
create table work.&Control1 as
Select id, count(id) as number
From data.&Produit.
group by id
having calculated number > 1
;
quit;
%end;
%mend;
%let list = &control1. &control2 &Control4 ;
%Control1(&List);
If we do the same process to control3 the proc sql doesn't run because control3 doesn't belong to the list.
I might have some mistakes in the syntax
/*CONTROL1 and PRODUIT ARE ALREADY DECLARED */
Your main problem is that you need to let SAS know that you want to use in as an operator in your macro logic.
To enable use of IN as an operator in macro language then you need set the MINOPERATOR option. To insure it always works for your macro independent of how the system option is set add it to the %macro statement.
%macro Control1(List) / minoperator mindelimiter=' ';
But you also have a number of other issues.
When defining a parameter to the macro you just want to type the name you want to parameter to have. Your example has an & before the name which would be invalid syntax and that is used to reference the value of macro variable and you cannot use the value to macro variable to name your parameter. So when defining the macro just list the name
%macro control1(list) .....
Note than when you CALL the macro is fine to reference a macro variable when passing in the parameter value to the macro, as you have done in your example call.
The %if statement has multiple problems. The first appears to just be a typo with an extra e added to the end of the list macro variable's name. The second is a logical mistake. The value the macro variable CONTROL1 is never going to be the same as that value with double quote characters around it. Also the () around the list of values for the in operator are not needed, but they won't cause any actual problems.
%if &control1 in &list %then %do;
Your SELECT statement is missing a comma between the two variables you are selecting.
select id,count(id) as number
The condition in your HAVING clause is not valid syntax. Are you trying to test if the count is larger than 1? Plus if you want to reference a variable you have calculated you need to use the CALCULATED keyword.
having calculated number > 1
Finally it is potentially confusing to reference two macro variables, CONTROL1 and PROUDUIT, in your macro that are not defined by the code (or even documented in a comment). They are not input parameters. You have not declared them as local macro variables. Or done anything to insure that they will exist.
So I will briefly explain my code structure before I dive into the issue.
I've a macro
%Sales (Outdata= , dt =, Outdata2= , Outdata3= );
(
I create a table &outdata by (Select * from XYZ);
Proc SQL;
Create table &Outdata._1 as
(
)
%mend Sales
Now I call the macro
%Sales (Outdata = sales_final_Oct17, dt='2017-10-01');
Libname ABCDEFG
I Create a data set
Data ABCDEFG.all_sales_test;
Set ABCDEFG. all_Sales
sales_final_Oct17_1;
incur_month = month(rept_dt);
run;
Above (1 to 3) is the original code flow and it works fine.
My Problem:
I'm using a dynamic way of generating file name for each month (so that each month I do not manually enter file_name_month and date.
File name code
%let Last_Month = intnx('month', current_date,-1, "beginning");
Name = 'Sales_final';
Last_Month_Name = name|| put(&last_month, monyy7.);
Call SYMPUTX('Last_Month_Name_v', Last_Month_Name);
run;
Call Macro
%Sales(outdata=&Last_Month_Name, dt = 'Dynamic date');
Till this point everything works fine. The moment I create a data set similar to step 3 (above), the code breaks.
Libname ABCDEFG
Data ABCDEFG.all_sales_test;
Set ABCDEFG.all_Sales
Last_Month_Name_1;
incur_month = month(rept_dt);
run;
> Error Message: File ABCDEFG.LAST_MONTH_NAME_1.DATA does not exist.
What should I do to get rid of this error? It seems, if I pass a static name in the macro and then use the same name with "_1" it works fine but when I pass dynamic reference, then the data Set step fails with the above error message.
Any help is much appreciated. I'm new to SAS so excuse me if it's a silly question. Thanks.
In (1) the macro code is using the value of the macro parameter (or local macro variable) OUTDATA to create a dataset. In (2) you are supplying a value for OUTDATA in your call and in (3) you are using the same value again in the set statement.
One way to not have to type the value twice is to store the value into a macro variable then just reference that macro variable's value in steps (2) and (3).
So in (4) you did create a macro variable,Last_Month_Name_v , but you then used the value of a different macro variable, &Last_Month_Name in the macro call. But instead of using the macro variable in the set statement you just referenced some other dataset,Last_Month_Name_1 , that you never mentioned before at all.
Here are the simplified key steps in the process you want for how to create and use the macro variable. I have put in ... to show where I have left out parts of a statement or statements so we can concentrate on the flow of the macro variable and its value.
First you set the macro variable to some name that you want to use. Let's just use anything as the name for this example.
%let last_month_name= anything;
Then you use the value in the macro call to create the dataset. Notice the & before the name, that is what tells the macro processor to replace the name with the value. The period after the name tells the macro processor that is the end of the macro variable name.
%sales(outdata=&last_month_name. .... )
Then you can use the value again later when you want to tell the set statement which dataset to read.
set .... &last_month_name. ;
Now your posted macro %sales does not actually create the dataset named anything. Instead it appears to create a dataset named anything_1. Personally I don't know why that is there, but if you keep it that way then you need add the _1 back to the end of the macro variable's value in the set statement. Just do it in the same way that you did in the macro's code.
set .... &last_month_name._1 ;
Working with macros is one of the harder parts of SAS and can be very confusing to newcomers. Below is a simplified working example that demonstrates the approach I would take.
First we dynamically calculate the name of the table we want to save the results to:
%let current_date = %sysfunc(date());
%let last_month = Sales_final_%sysfunc(intnx(month, ¤t_date, -1, beginning), monyy7.);
%put &=last_month;
The output from the put statement in the above step is:
LAST_MONTH=Sales_final_OCT2017
Note that in the above code, I'm passing two parameters to %sysfunc(). The first parameter is the call to the intnx() function. The second parameter (monyy7.) is what format to apply to the result being returned from the function call.
Also note that there is no need to concatenate the prefix (Sales_final_) to the %sysfunc() result because when working with the macro language, the result of %sysfunc() is subsituted in place. There is no concatenation operator in the macro language at all - everything is based off of macro substitution.
Then it's a simple case of passing that value into the macro as shown below:
%macro sales(outdata=);
proc sql;
create table &outdata._1 as select * from sashelp.class;
quit;
%mend;
%sales(outdata=&last_month);
You should be able to modify the above into what you need.
I'm new in SAS. I'd like to know what the lines below do. Couldn't figure out what it does to the program because I didn't encounter any of the defined variables in the succeeding parts after declaration.
%let cutofftime =%sysfunc(time());
%let currdt = %sysfunc(putn(&cutofftime.,time5.)) ;
%put &cutofftime. &currdt.;
The %let statement is used to create a macro variable.
The first statement:
%let cutofftime =%sysfunc(time());
uses the time() function to determine the current time. It returns the current time as a numeric value which is number of seconds since midnight.
The second statement:
%let currdt = %sysfunc(putn(&cutofftime.,time5.)) ;
uses the PUTN() to convert the numeric time value (which Is now stored in the macro variable CUTOFFTIME) to a pretty formatted value like 22:30.
So after the two %let statements have run, you created two macro variables. Then the %PUT statement is used to write the values of the two macro variables to the log:
%put &cutofftime. &currdt.;
Using the %PUT statement to write the value of macro variables to the log is a useful way to debug macro code, in the same way that the PUT statement can be used to write the value of data step variables to the log as a data step debugging tool. When I run the code at 9:32 PM, the log shows:
3 %put &cutofftime. &currdt.;
77537.809 21:32
That said, if you're new to SAS, you should probably avoid the trying to learn the macro language at the same time as you are learning the SAS language.
I am struggling with this compress function in the code below that I am trying to convert.
Old code: (This code works and returns the results below)
data _null_;
%let startdt='2015/11/1';
date_num=compress(&startdt,"'");
call symputx('date_num',date_num);
%put &startdt;
%put &date_num;
run;
This code returns values for the macro variable startdt as 2015/11/1 and datenum as 2015/11/1.
I am trying to achieve similar functionality using macro variables for dates.
New code: (This code gives me an error and I am not able to figure out why)
data _null_;
dt = date();
last_mth_beg = intnx('month',dt,-1,'beginning');
call symput('startdt',put(last_mth_beg,YYMMDDS10.));
date_num=compress(&startdt,"'");
call symputx('date_num',date_num);
%put &startdt;
%put &date_num;
run;
I am getting an error when I run this new code. I would like to get results as in the old code.
Please help. Thank you!
Your first data step is just removing the quotes from your macro variable. You can do this in macro code by either using %sysfunc() to call the compress() function. Or even just use the %scan() function.
%let date_num = %scan(&startdt,1,%str(%'));
In the second one it looks like you are trying to use INTNX() function on the date value. But your macro variables do not contain date values. If you want to treat '2015/11/1' as if it was a date then you will need to use an input function to convert it first.
%let last_mth_beg = %sysfunc(intnx(month,%sysfunc(inputn(&date_num,yymmdd10)),-1,b),yymmdd10);
I am not sure why your old code is not giving you an error. The first time you run it, you should see the error,
WARNING: Apparent symbolic reference DATE_NUM not resolved.
Then, after the data step has run, &DATE_NUM will have a value. You cannot use %PUT inside a data step to display a macro variable you are creating within the data step.
In your second set of code, the value of the STARTDT macro variable you are creating does not have single quotes around it in the first place. If you just run the following, you will get the correct result:
data _null_;
dt = date();
last_mth_beg = intnx('month',dt,-1,'beginning');
call symput('startdt',put(last_mth_beg,YYMMDDS10.));
run;
%put &startdt;
When I run it, I see
2016/11/01
I posted a question a while back about trimming a macro variable down that I am using to download a CSV from Yahoo Finance that contains variable information on each pass to the site. The code that was suggested to me to achieve this was as follows:
data _null_;
a = "&testvar.";
call symputx('svar',trim(input(a,$8.)));
run;
That worked great, however I have since needed to redesign the code so that I am declaring multiple macro variables and submitting multiple ones at the same time.
To declare multiple macros at the same time I have used the following lines of code:
%let svar&e. = &svar.;
%put stock_ticker = &&svar&e.;
The varible &e. is an iterative variable that goes up by one everytime. This declares what looks to be an identical macro to the one called &svar. everytime they are put into the log, however the new dynamic macro is now throwing up the original warning message of:
WARNING: The quoted string currently being processed has become more than 262 characters long. You
may have unbalanced quotation marks.
That i was getting before i started using the symputx option suggested in my original problem.
The full code for this particular nested macro is listed below:
%macro symbol_var;
/*here the start row and end row created in the macro above are passed to this nested macro and then passed through the*/
/*source dataset. at the end of the loop each ticker macro variable is defined in turn for use in the following nested*/
/*macro, symbol by metric.*/
%do e = &beg_point. %to &end_point. %by 1;
%put stock row in dataset nasdaq ticker = &e.;
%global svar&e;
proc sql noprint;
select symbol
into :testvar
from nasdaq_ticker
where monotonic() = &e.;
quit;
/*convert value to string here*/
data _null_;
a = "&testvar.";
call symputx('svar',trim(input(a,$8.)));
run;
%let svar&e. = &svar.;
%put stock_ticker = &&svar&e.;
%end;
%mend;
%symbol_var;
Anyone have any suggestions how I could declare the macro &&svar&e. directly into the call synputx step? It currently throws up an error saying that the macro variable being created cannot contain any special characters. Ive tried using "E, %NRQUOTE and %NRBQUOTE but either I have used the function in an invalid context or I haven't got the syntax exactly right.
Thanks
Isn't this as simple as the following two line data step?
%macro symbol_var;
/*here the start row and end row created in the macro above are passed to this nested macro and then passed through the*/
/*source dataset. at the end of the loop each ticker macro variable is defined in turn for use in the following nested*/
/*macro, symbol by metric.*/
data _null_;
set nasdaq_ticker(firstobs=&beg_point. obs=&end_point.);
call symputx('svar' || strip(_n_), symbol);
run;
%mend;
%symbol_var;
Or the following (which includes debugging output)
%macro symbol_var;
/*here the start row and end row created in the macro above are passed to this nested macro and then passed through the*/
/*source dataset. at the end of the loop each ticker macro variable is defined in turn for use in the following nested*/
/*macro, symbol by metric.*/
data _null_;
set nasdaq_ticker(firstobs=&beg_point. obs=&end_point.);
length varname $ 32;
varname = 'svar' || strip(_n_);
call symputx(varname, symbol);
put varname '= ' symbol;
run;
%mend;
%symbol_var;
When manipulating macro variables and desiring bullet-proof code I often find myself reverting to using a data null step. The original post included the problem about a quoted string warning. This happens because the SAS macro parser does not hide the value of your macro variables from the syntax scanner. This means that your data (stored in macro vars) can create syntax errors in your program because SAS attempts to interpret it as code (shudder!). It really makes the hair on the back of my neck stand up to risk my program at the hands of what might be in the data. Using the data step and functions protects you from this completely. You will note that my code never uses an ampersand character other than the observation window points. This makes my code bullet proof regarding what dirty data there may be in the nasdaq_ticker data set.
Also, it is important to point out that both Dom and I wrote code that makes one pass over the nasdaq_ticker data set. Not to bash the original posted code, but looping in that way causes a proc sql invocation for every observation in the result set. This will create very poor performance for large result sets. I recommend developing an awareness of how many times a macro loop is going to cause you to read a data set. I have been bitten by this many times in my own code.
Try
call symputx("svar&e",trim(input(a,$8.)));
You need double quotes ("") to resolve the e macro.
As an aside, I am not sure you need the input statement if $testvar is a string and not a number.
I would have written this as
%macro whatever();
proc sql noprint;
select count(*)
into :n
from nasdaq_ticker;
select strip(symbol)
into :svar1 - :svar%left(&n)
from nasdaq_ticker;
quit;
%do i=1 %to &n;
%put stock_ticker = &&svar&i;
%end;
%mend;