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.
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.
select Name into :Dataset1-:Dataset%trim(%left(&DatasetNum)) from MEM;
I am not able to interpret what is happening here in this statement can anyone give me an explanation.
I understand this stament
select count(Name) into :DatasetNum from MEM
But not the above one.
It is attempting to use the value of the macro variable DATASETNUM as the upper bound on the macro variable names that are being created by the SELECT statement. Because the previous variable was created with leading spaces the %LEFT() macro is called to remove them. The call to the macro %trim() is not needed as trailing spaces would not cause any trouble.
It is much easier to just build the macro variable array first and then set the counter variable from the value of the automatic macro variable SQLOBS. Plus then it will not have the leading blanks.
select name into :Dataset1- from mem ;
%let DatasetNum=&sqlobs;
If you have an older version of SAS that doesn't support the new :varname- syntax then just use a large value for the upper bound. SAS will only create the number of macro variables it needs.
select name into :Dataset1-:Dataset99999 from mem;
This is creating an array of SAS macro variables (DATASET1, DATASET2, DATASET3) etc, populated from the Name column of the MEM dataset.
It is analagous to:
data _null_;
set MEM;
call symputx(cats('Dataset',_n_),Name);
run;
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.
I have a dataset(liste_institution) that contain all the name of the variable that I want to "define" in my proc report statement. Here is my code that work when I call my macro not dynamically(%create_institution(815);). If I use the data statement with the call execute(in comment in my code) it not working. The reason seem to be that when I use the call execute the code is not interpreted in a PROC REPORT that is why it give me error.
proc report data = ventes_all_inst4
missing split = "*" nowd
style(header)=[font_weight=bold background = #339966 foreground = white]
style(column)=[cellwidth=15cm];
%macro create_institution(institution);
define TOTAL_&institution. / display "TOTAL*($)" style(column)=[cellwidth=4cm];
%mend;
/* Give error when I use this data step */
/*data _null_;
set liste_institution;
call execute('%create_institution(' || INS || ');');
run;*/
%create_institution(815);
run;
Is there an easy way to create dynamically define statement in a PROC REPORT from a dataset that contain the column name.
Basically, you have a misunderstanding of how macros work and timing. You need to compile the macro list previous to the proc report, but you can't use call execute because that actually executes code. You need to create a macro variable.
Easiest way to do it is like so:
proc sql;
select cats('%create_institution(',ins,')')
into :inslist separated by ' '
from liste_institution
;
quit;
which makes &inslist which is now the list of institutions (with the macro call).
You also may be able to use across variables to allow this to be easier; what you'd have is one row per ins, with a single variable with that value (which defines the column name) and another single variable with the value that goes in the data table portion. Then SAS will automatically create columns for each across value. Across variables are one of the things that makes proc report extremely powerful.
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;