Get Data from SAS table to SAS Macro Variable - macros

I want to create a macro that reads a value from a SAS table and stores that value in a Global variable.
The purpose is to use this value in SAS DIS JOBs.
I have tested following:
%GLOBAL &myMVar.;
%Macro Get_data(myDataset,myLine,myColumn,myMVar);
data _null_;
set &myDataset.;
if _N_ = &myLine.
then do;
call symputx(symget('myMVar'),&myColumn.);
end;
run;
%MEND Get_data;
*Calling a Macro program;
LIBNAME dtvault BASE "/sasdata/DataVault";
%Get_data(dtvault.codes,1,cod,myMVar);
%put &myMVar;
run;
**I do not have any results. The log: **
*Calling a Macro program;
38 LIBNAME dtvault BASE "/sasdata/DataVault";
NOTE: Libref DTVAULT was successfully assigned as follows:
Engine: BASE
Physical Name: /sasdata/DataVault
39 %Get_data(dtvault.codes,1,cod,myMVar);
NOTE: There were 2 observations read from the data set DTVAULT.CODES.
NOTE: DATA statement used (Total process time):
real time 0.00 seconds
cpu time 0.00 seconds
40 %put &myMVar;
41 run;
42
43 GOPTIONS NOACCESSIBLE;
44 %LET _CLIENTTASKLABEL=;
45 %LET _CLIENTPROCESSFLOWNAME=;
46 %LET _CLIENTPROJECTPATH=;
2 The SAS System 10:06 Wednesday, October 25, 2017
47 %LET _CLIENTPROJECTPATHHOST=;
48 %LET _CLIENTPROJECTNAME=;
49 %LET _SASPROGRAMFILE=;
50 %LET _SASPROGRAMFILEHOST=;
51
52 ;*';*";*/;quit;run;
53 ODS _ALL_ CLOSE;
54
55
56 QUIT; RUN;
57
Thanks

The way you are initializing the global macro variable if what you want is a global macro variable called myVar. The %global statement does not require the ampersand.
Also, if you're going to instantiate that macro variable outisde of your macro definition anyway, then there's no need to specify it as a parameter of you macro definition:
%GLOBAL myMVar.;
%Macro Get_data(myDataset,myLine,myColumn);
data _null_;
set &myDataset.;
if _N_ = &myLine.
then do;
call symputx('myMVar',&myColumn.);
end;
run;
%MEND Get_data;
*Calling a Macro program;
LIBNAME dtvault BASE "/sasdata/DataVault";
%Get_data(dtvault.codes,1,cod);
%put &myMVar;
Note that the symputx call routine allows for specifying the symbol table in which you want your variable created. You can then do away with the %global statement:
%Macro Get_data(myDataset,myLine,myColumn);
data _null_;
set &myDataset.;
if _N_ = &myLine.
then do;
call symputx('myMVar',&myColumn.,'G');
end;
run;
%MEND Get_data;
*Calling a Macro program;
LIBNAME dtvault BASE "/sasdata/DataVault";
%Get_data(dtvault.codes,1,cod);
%put &myMVar;
EDIT: in answer to your comment, I used the above code on a test dataset:
libname dtvault (work);
data dtvault.codes;
cod='test';
run;
%Macro Get_data(myDataset,myLine,myColumn);
data _null_;
set &myDataset.;
if _N_ = &myLine.
then do;
call symputx('myMVar',&myColumn.,'G');
end;
run;
%MEND Get_data;
%Get_data(dtvault.codes,1,cod);
%put &myMVar;
The log produces this output:
42 %Get_data(dtvault.codes,1,cod);
NOTE: There were 1 observations read from the data set DTVAULT.CODES.
NOTE: DATA statement used (Total process time):
real time 0.00 seconds
2 The SAS System 10:04 Wednesday, October 25, 2017
cpu time 0.00 seconds
43 %put &myMVar;
test
If the %put statement, on your data, produces nothing; could it be that the value for of cod on line 1 of your dataset is blank?

I see two main mistakes that will make your program not work.
The & is not part of the macro variable name. It is the trigger that lets the macro processor know that you want it to replace the macro variable reference with the value of the macro variable referenced. So if you want to define a macro variable named mymvar your %global statement needs to look like this.
%global mymvar;
You are also getting impacted by the scoping of macro variables. If your program starts with these lines
%global mymvar;
%macro get_data(mydataset,myline,mycolumn,mymvar);
then you have defined a LOCAL macro variable mymvar that will hide the global macro variable mymvar while the macro get_data is running. So if you set mymvar to some value it will disappear when the macro finishes running.
Since you want your macro to generate a macro variable that will be available after the macro finishes running the you will want to make sure that the macro variable that it creates either already exists or is created in the GLOBAL macro space.
For added flexibility you might want to pass the NAME of the macro variable that you want the macro to use as a parameter to the macro. You could then use the %SYMEXIST() function to decide if you need to create a global macro variable or not. Notice that now we do want the & in the %global statement since we want to create a macro variable whose name is the value of another macro variable.
%macro get_data(dataset,line,column,mvar);
%if not %symexist(&mvar) %then %global &mvar ;
data _null_;
set &dataset ;
if _n_=&line then do;
call symputx("&mvar",&column);
stop;
end;
run;
%mend get_data;
Now when you call the macro you pass in the values that you want for the parameters. Note that one nice feature of SAS macros is that even for parameters that are defined as positional arguments you can use named parameters in the macro call.
%get_data(dataset=myDataset,line=1,column=myVar,mvar=myMvar)
%put &=myMvar;

Related

SAS macro loop to read csv files with proc import

I have a directory of csv files, each with names that begin with the letter m and end with a number. There are twelve files - m6 to m17.
I'd like to read them in and process them as separate data sets. I've written two macros attempting to do so. Macro1 works. Macro2 breaks. I would prefer Macro2 if I can get it to work, to avoid unnecessary bits like my creation of %rawfiles, invocation of %sysfunc, etc.
Macro 1:
%let rawcsv = C:\ALL\dat\;
%let rawfiles = m6 m7 m8 m9 m10 m11 m12 m13 m14 m15 m16 m17;
%macro1;
%do i = 1 %to %sysfunc(countw(&rawfile));
%let rawfile = %scan(&rawfiles, &i);
proc import datafile="&&rawcsv.&&rawfile.csv"
out=&rawfile replace
dbms=csv;
guessingrows=500;
run;
%end;
%mend;
%macro1;
Macro 2:
%let rawcsv = C:\ALL\dat\;
%macro macro2(first=6, last=19);
%do i=&first. %to &last. %by 1;
proc import datafile="&&rawcsv..m&&i.csv"
out=m&i replace
dbms=csv;
guessingrows=500;
run;
%end;
%mend;
%macro2;
%macro2 is my bad imitation of this solution. It returns the following errors:
MPRINT(MACRO2): proc import datafile="C:\ALL\dat\m.6.csv" out=m.6 replace
dbms=csv;
MPRINT(MACRO2): ADLM;
MPRINT(MACRO2): guessingrows=500;
MPRINT(MACRO2): run;
ERROR: Library name is not assigned. /*repeats this error 14 times, once per file*/
Two questions:
What am I missing in %macro2?
Do you see a better solution that I am not using? The files are structured differently and not stackable, just a heads up.
From your log we can see a period is being inserted into the output dataset name. Just remove that extra period in your macro definition.
MPRINT(MACRO2): proc import datafile="C:\ALL\dat\m.6.csv" out=m.6 replace dbms=csv;
The extra & in the code is probably confusing you. When the macro processor sees two & it converts them to one and then reprocesses the string to further resolve the resulting macro variable references.
The period after a macro variable name is not required when the macro processor can tell that the name has ended. But the periods are needed in some places.
One place in your code is where it is required to make sure the macro processor knows where the name ends (the macro variable is named readcsv not readcsvm ). Another is where you want to place an actual period after the value of a macro variable. You will need to place two periods there since the first will be used by the macro processor when it evaluates the macro variable value.
In this version of macro2 I have removed the periods after the macro variable names in the places where they are not required just to emphasize the places where the period is required.
%let rawcsv = C:\ALL\dat\;
%macro macro2(first, last);
%local i ;
%do i=&first %to &last ;
proc import dbms=csv
datafile="&rawcsv.m&i..csv"
out=m&i replace
;
guessingrows=500;
run;
%end;
%mend macro2;
%macro2(first=6, last=19)
Small typo here, you need to use an & in front of LAST not the %.
%do i=&first. %to %last. %by 1;
Should be:
%do i=&first. %to &last. %by 1;
Unless you're using a separate macro called last to determine your end of the loop. But in that case you likely wouldn't also have a parameter called last.
If you're looking for alternate options I usually recommend reading all at once using a data step or CALL EXECUTE instead of macro loops as they're infinitely easier to debug in my opinion.
https://communities.sas.com/t5/SAS-Communities-Library/How-do-I-write-a-macro-to-import-multiple-text-files-that-have/ta-p/223627

Add a variable (column) in data set (SAS)

I can't find the solution for this simple problem: I want to add a colum/variable in my data set. This variable will always have the same value, stored in the macro variable &value. And I am in a macro so I don't know if it change anything... This is the step before merging 2 data step.
So far, here's what I have:
%do i=1 %to 10;
data &new_data_set;
set &new_data_set;
Nom_controle=&Nom_Controle;
Partenaire=&Partenaire;
run;
%end;
I'm trying to add to my data-set (which was previously defined in the macro as &new_data_set) a column/variable named "Nom_Controle" which always takes the value stored in the macro variable &Nom_controle (previously defined too). I'm also trying to add a second column/variable named "Partenaire" which always takes the value stored in the macro variable &Partenaire (previously defined too).
Of course, as I'm posting here, my code doesn't work. Can you help me?
EDIT: after some ask me to in order to help me, here is the macro this code is from (the full thing):
%macro presence_mouvement (data_set_detail_mouvement, data_set_mouvement);
%if %sysfunc(exist(&data_set_mouvement)) AND %sysfunc(exist(&data_set_detail_mouvement)) %then %do; *Check if my data set actually exist;
%let suffix=_2;
%let new_data_set=&data_set_detail_mouvement&suffix; *Create the name of the new data set I'm going to save the result of the next proc sql in;
proc SQL noprint; *Proc to look for errors in a previous data set and print it in the new data set;
create table &new_data_set as
insert into &new_data_set
SELECT num_mouvement
FROM &data_set_detail_mouvement
EXCEPT
SELECT num_mouvement
FROM &data_set_mouvement);
%let Nom_controle=Presence_mouvement; *Creation of a new variable;
%if %sysfunc(length(&data_set_detail_mouvement))=29 %then %do; *Creation of a second variable (value conditional to the size of a previous variable);
%let Partenaire=%sysfunc(substr(&data_set_detail_mouvement, 9, 3)); %end;
%else %if %sysfunc(length(&data_set_detail_mouvement))=30 %then %do;
%let Partenaire=%sysfunc(substr(&data_set_detail_mouvement, 9, 4)); %end;
%else %do;
%let Partenaire=%sysfunc(substr(&data_set_detail_mouvement, 9, 6)); %end;
%do i=1 %to 10;
data &new_data_set;
set &new_data_set;
Nom_controle=&Nom_Controle;
Partenaire=&Partenaire;
run;
%end;
%end;*End of the actions to do in case the two data set in parameters exist;
%else %do; *Actions to do in case the two data set in parameters don't exist;
data _null_;
file print;
put #3 #10 "At least one of the data set does not exist";
run;
%end;
*This macro is aiming at pointing error in a previous data set, print them in a new data set and add two new variables/columns to this new data set (indicating their origin). The next set is going to be to merge this new data set to another one;
%mend presence_mouvement;
%presence_mouvement (sasuser.bgpi__detail_mouvement, sasuser.bgpi__mouvement);
I also wanted to say that I tested the rest of the macro before trying to add new variable so the rest of the macro shouldn't have any problem. But who knows...
Run a single data step, setting the new variables to the values setup in macro variables. If the values setup are character in nature the data step variables need to resolve those macro variables within double quotes.
data &new_data_set;
set &new_data_set;
retain
Nom_controle "&Nom_Controle"
Partenaire "&Partenaire"
;
* also works;
* Nom_controle = "&Nom_Controle";
* Partenaire = "&Partenaire";
run;
Note: The new data set variables lengths will be set to the length of the values stored in the macro variables.
A data set is a rectangle of values. It will have a certain number of rows and columns of numeric and / or character types. The SET statement in a DATA step reads one row of the table's column values into the running program data vector -- which are essentially the variables in the DATA step. A DATA step loops automatically and halts automatically on various conditions, such as the last row of a SET table being read.
I don't know why you have a macro loop %DO I=1 %TO 10. I might speculate you think you need to do this in order to 'update' 10 rows in &new_data_set.
What is it really doing ? Running the same code 10 times! Without macro the actual code run is akin to the following
data x; do r = 1 to 10; output; end; run; %* an original new_data_set;
data x; set x; z=1; run;
data x; set x; z=1; run;
data x; set x; z=1; run;
...
One additional concern is the code such as
%if %sysfunc(length(&data_set_detail_mouvement))=29 %then %do; *Creation of a second variable (value conditional to the size of a previous variable);
%let Partenaire=%sysfunc(substr(&data_set_detail_mouvement, 9, 3)); %end;
It appears you are grabbing the first 3, 4, or 6th letters of the data set name from a fully qualified libname.dataset where libname is presumed to be sasuser. A safer and more robust version could be
%let syslast = &data_set_detail_mouvement;
%let libpart = %scan(&syslast,1,.);
%let datapart = %scan(&syslast,2,.);
… extract 3, 4, or 6 preface of datapart …
%* this might be helpful;
%let Partenaire = %scan(&datapart,1,_);
Nothing seems to be wrong with the part of the code that creates the variables. There might be other issues that are difficult to tell from this extract without seeing the entire code or log. For example, if Nom_controle and Partenaire are meant to be character variables because the macro variables are characters but without quotes then there will definitely be errors. You should use symbolgen and mprint options and then post the log to help solve the problem.

Passing SAS dataset column as macro parameter

I have a SAS dataset with values
yyyymm
201605
201606
201607
201608
201609
I am trying to find a way to pass these values one at a time to macro such that
do while dataset still has value
%macro passdata(yyyymm);
end
How can I do this in SAS. Can someone please help with a sample/code snippet.
As mentioned by the prior comment, a way to pass parameters is through call execute routine. Note that this must be done in datastep environment. The lines are read from the set you input.
You can input multiple variables. Just add more variables in '||' separators. Note that the variables may have a lot of whitespaces in them. (==Do comparisons with care.)
Here is a small sample code. Tested.
data start_data;
input date_var ;
datalines;
201605
201606
201607
201608
201609
;
run;
%macro Do_stuff(input_var);
%put 'Line generates value ' &input_var;
%mend do_stuff;
data _null_;
set start_data;
call execute('%do_stuff('||date_var||')' );
run;
Try this example and try modifying to meet your needs... From the "source" dataset we can use call symput() to assign a macro token to each observation (differentiated by the SAS automatic dataset variable n so My_token1, My_token2, etc.) Once you have a set of macro variables defined, just loop through them! This program will print all the individual records from source to the SAS log:
data source;
do var=1000 to 1010;
output;
end;
run;
data _null_;
set source;
call symput(compress("My_token"||_n_),var);
run;
%put &my_token1 &my_token4;
%Macro neat;
%do this=1 %to 11;
*Check the log.;
%put &&My_token&this;
%end;
%mend;
%neat;

resolve macro variables for saving/naming a dataset in SAS

I have problem saving a dataset using macro variables to a desired directory.
Basically, I want to save the dataset "_est" to library "sret" according to the values of &var and &age. I wrote the following code:
%let var=k;
%let age=2;
...
...
data sret.est_&var&age._b3;
set _est;
run;
What I want is a dataset named as "est_k2_b3.sas7bdat" in "sret". But what the code gives me is a dataset "est_k2.sas7bdat" saved in the folder I want and another dataset "_b3" in the working library. Both datasets are identical. I'm quite puzzled how to solve this.
As itzy pointed out you have a space after "2" that splits your dataset name in two.
I can replicate the issue only defining the macro variable age with a call symput:
data _null_;
age='2 ';
call symput('age',age);
run;
If this is the case you can solve it by removing the space in the data step with a strip(), using a call symputx() (to be used with numbers) or re-declaring your variable after the data step with a %let, that automatically removes spaces:
%let age= &age.;
Had a very similar problem. Somehow a space is added until you use strip(). Here's the example below.
data test;
input numdays;
datalines;
31
;
%macro monthly(months);
%let count=%sysfunc(countw(&months.));
%do i=1 %to &count.;
%let value=%qscan(&months.,&i,%str(,));
%let month=%sysfunc(strip(&value.));
%put &value.;
%put &month.;
data value_&value.;
set test;
run;
data month_&month.;
set test;
run;
%end;
%mend;
%monthly(%str(oct,jan));

SAS Macro to export to csv

I'm trying to use code from here and not sure how to adapt it correctly.
https://communities.sas.com/thread/33654?start=0&tstart=0
I'm trying to export 51 SAS datasets to CSV files so that I can import them into R. Here's the macro code I have (that's not working):
libname g 'C:\mylibrary';
%macro export(libname, numvars= &numvars.);
%do i= 1 %to &numvars.;
proc export data=&libname.StateZip&i
DBMS=CSV REPLACE
outfile= "&libname.StateZip&i.csv";
run;
%end;
%mend;
%export(&g, numvars= 51);
Here is the error message I'm getting:
NOTE: Writing TAGSETS.SASREPORT12(EGSR) Body file: EGSR
13
14 GOPTIONS ACCESSIBLE;
ERROR: Invalid macro name (. It should be a valid SAS identifier no longer than 32 characters.
ERROR: A dummy macro will be compiled.
15 %macro export(libname, numvars= &numvars.);
16 %do i= 1 %to &numvars.;
17 proc export data=&libname.StateZip&i
18 DBMS=CSV REPLACE
19 outfile= '&libname.StateZip&i.csv';
20 run;
21 %end;
22 %mend;
23
24 %export(g, numvars= 51);
24 +(libname, numvars= &numvars.);
_
10
ERROR 10-205: Expecting the name of the procedure to be executed.
24 +(libname, numvars= &numvars.);
_
180
ERROR 180-322: Statement is not valid or it is used out of proper order.
2 The SAS System 11:38 Wednesday, September 5, 2012
180: LINE and COLUMN cannot be determined.
NOTE: NOSPOOL is on. Rerunning with OPTION SPOOL may allow recovery of the LINE and COLUMN where the
error has occurred.
Any help you can provide would be appreciated.
You have a recursive reference to &numvars, which is a problem in and of itself, but not a critical one - just means that the default value will be null, not the value you intend it to be. Otherwise your code works fine for me. I would guess you have something else going wrong just before the %macro definition; the error you post makes it look like you have a token that's incorrectly resolving, so instead of
%macro export(
you have
%macro (
Anyway, this is what works for me:
%let g=c:\temp;
libname g "&g.";
%macro export(libname, numvars=);
%do i= 1 %to &numvars.;
proc export data=sashelp.class
DBMS=CSV REPLACE
outfile= "&g.\StateZip&i.csv";
run;
%end;
%mend;
%export(&g, numvars= 51);
The things I changed:
* You need a %let g= to go along with your libname. You don't have that defined, but presumably you do elsewhere.
* Sashelp.class instead of using your library as I don't have those datasets.
The better answer, though, is probably to do it like this, using FILEVAR option and writing them out yourself. Even if you have a ton of variables, you can either programmatically determine the put statement, or just do one proc export and copy from the log. This avoids all of the macro stuff, and avoids you having to make 51 different datasets. Just make one, with an indicator variable. Pretty much no matter what you're doing, this will be faster (using BY group processing with PROCs, etc.) and less error-prone than having a bunch of different datasets and making up for it with macros.
data have_51;
set sashelp.class;
do _n = 1 to 51;
output;
end;
run;
*I assume you have have_51 already;
data want;
set have_51;
filename=cats("&g.\StateZip",_n,".csv");
run;
proc sort data=want;
by filename;
run;
data _null_;
set want;
file a filevar=filename dlm=',' lrecl=500;
put
name $
age
height
weight
;
run;