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;
Related
I have some problems with getting basic file information send to me in an email.
From various scripts on the internet I have ended up with the following macro which works for outputting the information I want.
%macro list_files(dir);
%local filrf rc did memcnt name i;
%let rc=%sysfunc(filename(filrf,&dir));
%let did=%sysfunc(dopen(&filrf));
%let ymd = %sysfunc(putn(%sysfunc(today()).,yymmdd6.));
%if &did eq 0 %then %do;
%put Directory &dir cannot be open or does not exist;
%return;
%end;
%do i = 1 %to %sysfunc(dnum(&did));
%let name=%qsysfunc(dread(&did,&i));
%if %qscan(%qscan(&name,1,_,b),1,.) eq &ymd. %then %do;
%let rc=%sysfunc(filename(fref,&dir\&name));
%let fid=%sysfunc(fopen(&fref));
%let CreatedDT=%qsysfunc(finfo(&fid,Create Time));
%let ModifiedDT=%qsysfunc(finfo(&fid,Last Modified));
%put &dir\&name,&CreatedDT,&ModifiedDT;
%let fid=%sysfunc(fclose(&fid));
%let rc=%sysfunc(filename(fref));
%end;
%end;
%let rc=%sysfunc(dclose(&did));
%let rc=%sysfunc(filename(filrf));
%mend list_files;
%list_files(C:\logs\);
Notice: The macro does expect a certain file name format in order to work. If I for example have the following log files:
ProgramA_210215.log
ProgramA_210214.log
ProgramA_210213.log
ProgramB_210214.log
ProgramB_210213.log
ProgramC_210215.log
ProgramC_210214.log
ProgramC_210213.log
... and I run this on 2021-02-15, then I only get:
ProgramA_210215.log
ProgramC_210215.log
... which is perfect for my needs.
My problem is, that I can't figure out how to either send the output to me as an email or dump the output in a file, which I can then attach to an email.
Notice 2: I initially wrote the macro as "normal data calls"(?) and everything worked except for the CreateDT and ModifiedDT, which were static for all files. The macro version at least works and shows the correct (and different) CreateDT and ModifiedDT.
What I am trying to achieve in the end is, to send me an email with an attachment of todays log files and their creation datetime and last modified datetime.
I guess I'm just missing a simple step, but can't figure out which :(.
Calling all of those functions using %SYSFUNC() is just making it harder to to do what you want. Just call the functions in a data step instead so that you have an actual dataset with the files. Then you can use any of multiple methods to direct the list where ever you want, including sending an email.
I am pretty sure that FINFO() always returns character strings so make sure that the target variables are long enough for the value returned. Note that the information available from FINFO() depends on the operating system where SAS is running. So make sure the names you use are appropriate for your SAS session.
%let dir=c:\logs\;
data file_list;
length filrf fref $8 name $256 ymd $6 CreatedDT ModifiedDT $40;
ymd = put(today(),yymmdd6.);
rc=filename(filrf,"&dir");
did=dopen(filrf);
if did=0 then do;
put "Directory &dir cannot be open or does not exist.";
stop;
end;
do i = 1 to dnum(did);
name=dread(did,i);
if scan(scan(name,1,'_','b'),1,'.') eq ymd then do;
rc=filename(fref,"&dir\"||name);
fid=fopen(fref);
CreatedDT=finfo(fid,'Create Time');
ModifiedDT=finfo(fid,'Last Modified');
output;
fid=fclose(fid);
rc=filename(fref);
end;
end;
rc=dclose(did);
rc=filename(filrf);
keep name CreatedDT ModifiedDT ;
run;
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
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;
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;
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));