explain what is happening in Proc Sql - sas

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;

Related

If condition and a proc sql inside a macro program

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.

How to use call symput on a specific observation in SAS

I'm trying to convert a SAS dataset column to a list of macro variables but am unsure of how indexing works in this language.
DATA _Null_;
do I = 1 to &num_or;
set CondensedOverrides4 nobs = num_or;
call symputx("Item" !! left(put(I,8.))
,"Rule", "G");
end;
run;
Right now this code creates a list of macro variables Item1,Item2,..ItemN etc. and assigns the entire column called "Rule" to each new variable. My goal is to put the first observation of "Rule" in Item1, the second observation in that column in Item2, etc.
I'm pretty new to SAS and understand you can't brute force logic in the same way as other languages but if there's a way to do this I would appreciate the guidance.
Much easier to create a series of macro variables using PROC SQL's INTO clause. You can save the number of items into a macro variable.
proc sql noprint;
select rule into :Item1-
from CondensedOverrides4
;
%let num_or=&sqlobs;
quit;
If you want to use a data step there is no need for a DO loop. The data step iterates over the inputs automatically. Put the code to save the number of observations into a macro variable BEFORE the set statement in case the input dataset is empty.
data _null_;
if eof then call symputx('num_or',_n_-1);
set CondensedOverrides4 end=eof ;
call symputx(cats('Item',_n_),rule,'g');
run;
SAS does not need loops to access each row, it does it automatically. So your code is really close. Instead of I, use the automatic variable _n_ which can function as a row counter though it's actually a step counter.
DATA _Null_;
set CondensedOverrides4;
call symputx("Item" || put(_n_,8. -l) , Rule, "G");
run;
To be honest though, if you're new to SAS using macro variables to start isn't recommended, there are usually multiple ways to avoid it anyways and I only use it if there's no other choice. It's incredibly powerful, but easy to get wrong and harder to debug.
EDIT: I modified the code to remove the LEFT() function since you can use the -l option on the PUT statement to left align the results directly.
EDIT2: Removing the quotes around RULE since I suspect it's a variable you want to store the value of, not the text string 'RULE'. If you want the macro variables to resolve to a string you would add back the quotes but that seems incorrect based on your question.

Issue Creating a Table based on Macro's Parameter Name

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, &current_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.

Dynamically create define in a PROC REPORT

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.

Cycling through all variables

I started to learn SAS here fairly recently and am getting the basics down pretty well, but have a question regarding something that is a little outside of my current realm of knowledge. Does anyone happen to know of a way to cycle through all variables in a SAS dataset? I know how to run a do loop/array on variables in a range (x1-x99), but ideally would like to look at every variable without having to rename any variables. Basically, I'm looking to run through a dataset and change variable values when the current value = 'True'/'False'. My guess is that I'll need to use proc contents in someway here, but not really sure how to go about using it correctly. Any tips/insight would be greatly appreciated. Thanks!
You can create an array of non-similarly-named variables. You're on the right track with PROC CONTENTS, although you also can use dictionary.columns or sashelp.vcolumn, which contain basically the same information.
proc sql;
select name into :collist separated by ' '
from dictionary.columns
where memname='DATASETNAME' and libname='LIBNAME' and <other criteria>;
quit;
The variables have to be all of the same type (char/numeric) so you may want to include a criterion of variable type in your query, plus any other limiting factor you may need.
That will create a list, &collist., in a macro variable you can use in your array
array vars &collist.;
and now you can loop over the array.
You may also be able to cheat things, if all of your variables are the same type, and you know the order is fixed . The double dash list (x1--x99) is 'in variable order, all variables from x1 to x99' and doesn't require numeric suffixes or anything like that.
Finally, you also might be able to write a format in PROC FORMAT to accomplish what you need, depending on what you are intending to do (mapping TRUE to 1 and FALSE to 0 or something like that).
Adding to Joe's answer: you can overcome the requirement that all variables should be of the same type. For that you can use macro loop instead of array. Firstly you need to define the macro:
%macro loop;
%do i=1 %to %sysfunc(countw(&collist));
....
<here goes your code for changing values, where instead of a variable name
you use macro function %scan(&collist,&i)>
....
%end;
%mend loop;
and now you can paste %loop into the DATA step where you're going to process all variables.