I often need to assign (and de-assign) temporary librefs/filerefs as part of utility macros - and thus I need to avoid naming conflicts with any existing librefs/filerefs. I know I could query the sashelp or dictionary tables and use conditional logic to iterate until I find one that isn't assigned, but I wondered if there was some easier way?
For instance, the following will create a uniquely named dataset in the work library:
data;run;
Is there some equivalent for librefs / filerefs?
The FILENAME() function already provides a method for this. When you call it with a missing value for the fileref it generates one using format #LNnnnnn.
6 data test;
7 length fileref $8 ;
8 rc=filename(fileref,,'temp');
9 put rc= fileref=;
10 run;
rc=0 fileref=#LN00056
NOTE: The data set WORK.TEST has 1 observations and 2 variables.
NOTE: DATA statement used (Total process time):
real time 0.02 seconds
cpu time 0.01 seconds
11 %global fileref ;
12 %let rc=%sysfunc(filename(fileref,,temp));
13 %put &=rc &=fileref ;
RC=0 FILEREF=#LN00058
The undocumented monotonic function, invoked in the open execution space, is very handy for obtaining an unused value.
libname mylib "C:\temp\sandbox";
data mylib.data%sysfunc(monotonic());
…
run;
Or code a macro to deliver a name for a libref. The macro can also check for existence if so desired:
%macro nextName(lib=,base=data,check=1);
%local index name;
%if %length(&lib) %then %let lib=&lib..;/* handle non-empty lib */
%do %until (&check and not %sysfunc(exist(&name,data)));
%let name = &lib.&base.%sysfunc(monotonic());
%end;
&name
%mend;
data data3;run;
data data4;run;
%put %nextName();
%put %nextName();
%put %nextName();
%put %nextName();
proc sort data=sashelp.class out=%nextname();
by age;
run;
You could go robust macro implementation and test for lib existence and valid check value.
The LIBNAME function has a similar feature as FILENAME but it does not populate the reference name variable as with FILENAME. The only way I can think of would be to compare SASHELP.VLIBNAM before and after. The librefs are in the form WC00000n.
79 data _null_;
80 length libref $32.;
81 libref = ' ';
82 rc = libname(libref,'.');
83 msg = sysmsg();
84 put _all_;
85 run;
libref= rc=-70004 msg=NOTE: Libref refers to the same physical library as WC000004. _ERROR_=0 _N_=1
actually writing one was fairly trivial, and didn't involve querying the (expensive) dictionary table:
libname mcore0 (work);
libname mcore1 (work);
libname mcore2 (work);
%macro mf_getuniquelibref(prefix=mcore,maxtries=1000);
%local x;
%let x=0;
%do x=0 %to &maxtries;
%if %sysfunc(libref(&prefix&x)) ne 0 %then %do;
%put Libref &prefix&x is available!;
&prefix&x
%return;
%end;
%end;
%put unable to find available libref in range &prefix.0-&maxtries;
%mend;
%let libref=%mf_getuniquelibref();
%put &=libref;
which returns;
UPDATE:
I've added macros for both of these to the MacroCore library and they can be utilised as follows:
filename mc url "https://raw.githubusercontent.com/sasjs/core/main/macrocore.sas";
%inc mc;
%let libref=%mf_getuniquelibref();
%let fileref=%mf_getuniquefileref();
Related
I have searched some info about this error,but it seems none match mine,may someone familiar with this error take a look.
"Code generated by a SAS macro, or submitted with a "submit selected" operation in your editor, can leave off a semicolon inadvertently." it is still abstruse for me to explore in my code by this comment.although I got this error,the outcomes is right.may someone give any advice..thanks!
%let cnt=500;
%let dataset=fund_sellList;
%let sclj='~/task_out_szfh/fundSale/';
%let wjm='sz_fundSale_';
%macro write_dx;
options spool;
data _null_;
cur_date=put(today(),yymmdd10.);
cur_date=compress(cur_date,'-');
cnt=&cnt;
retain i;
set &dataset;
if _n_=1 then i=cnt;
if _n_<=i then do;
abs_filename=&sclj.||&wjm.||cur_date||'.dat';
abs_filename=compress(abs_filename,'');
file anyname filevar=abs_filename encoding='utf8' nobom ls=32767 DLM='|';
put cst_id #;
put '#|' #;
put cust_name #;
put '#|' ;
end;
run;
%mend write_dx;
%write_dx();
and if I am not using macro,there is no error.
data _null_;
options spool;
cur_date=put(today(),yymmdd10.);
cur_date=compress(cur_date,'-');
cnt=&cnt;
retain i;
set &dataset;
if _n_=1 then i=cnt;
if _n_<=i then do;
abs_filename=&sclj.||&wjm.||cur_date||'.dat';
abs_filename=compress(abs_filename,'');
file anyname filevar=abs_filename encoding='utf8' nobom ls=32767 DLM='|';
put cst_id #;
put '#|' #;
put cust_name #;
put '#|' ;
end;
run;
--------------------------------update----------------------------------
I add % to the keyword,but still get the same error
%macro write_dx;
options spool;
data _null_;
cur_date=put(today(),yymmdd10.);
cur_date=compress(cur_date,'-');
cnt=&cnt;
retain i;
set &dataset;
%if _n_=1 %then i=cnt;
%if _n_<=i %then %do;
abs_filename=&sclj.||&wjm.||cur_date||'.dat';
abs_filename=compress(abs_filename,'');
file anyname filevar=abs_filename encoding='utf8' nobom ls=32767 DLM='|';
put cst_id #;
put '#|' #;
put cust_name #;
put '#|' ;
%end;
run;
%mend write_dx;
%write_dx();
Why did you add () to the macro call when the macro is not designed to accept any parameters? If you do that then the () are NOT processed by the macro processor and so are passed along to SAS to interpret. That is the same error message as you would get if you submitted (); by itself.
1 %macro xx ;
2 data _null_;
3 put 'Running data step in macro';
4 run;
5 %mend xx;
6 %xx();
Running data step in macro
NOTE: DATA statement used (Total process time):
real time 0.02 seconds
cpu time 0.00 seconds
6 %xx();
-
180
ERROR 180-322: Statement is not valid or it is used out of proper order.
7 ****;
8 ();
-
180
ERROR 180-322: Statement is not valid or it is used out of proper order.
But if you define it with 0 or more parameters.
%macro param();
generated code
%mend ;
%put |%param()|;
The macro processor will use the () and so they are not passed onto SAS to use.
|generated code|
Change
%macro write_dx;
to
%macro write_dx();
When creating a macro, you have to include the () even if no values are passed.
Hi I am trying to rename variables using SAS Macro loop.
%Let t1=12Mth;
%Let t2=20;
%Let t3=30;
%Let t4=40;
%Let t5=50;
%Let t6=60;
%macro Re(time);
%Do I = 1 %to &time.;
data MilkNew;
set Milk;
rename MT&&t&I..Sp=MTSp&&t&I.;
run;
%end;
%mend Re;
%Re(6)
This loop is mean to rename MT...Sp to MTSp.... Eg:MT20SP to MTSp20.
When I run my loop, there was no error but the variable names were not changed in MilkNew at all.
Where does the problem come? Thanks!
If the only purpose of the macro is to rename the variables in the data set, then why read the data with a set statement. Your data set is probably really small so you don't even realize the inefficiency of doing that. Instead use the modify statement in proc datasets to accomplish the same thing, but more efficiently. Here's an alternative macro for you.
%macro renamevar(dsname, time);
%local lib ds i;
%let lib = %sysfunc(coalescec(%scan(&dsname, -2, %str(.)), work));
%let ds = %scan(&dsname, -1, %str(.));
proc datasets lib=&lib nolist;
modify &ds;
rename
%do i = 1 %to &time;
mt&&t&i..Sp=MTSp&&t&i.
%end;
;
quit;
%mend;
%renamevar(milk, 6);
Here's the log after the macro call:
NOTE: Renaming variable mt12MthSp to MTSp12Mth.
NOTE: Renaming variable mt20Sp to MTSp20.
NOTE: Renaming variable mt30Sp to MTSp30.
NOTE: Renaming variable mt40Sp to MTSp40.
NOTE: Renaming variable mt50Sp to MTSp50.
NOTE: Renaming variable mt60Sp to MTSp60.
NOTE: MODIFY was successful for WORK.MILK.DATA.
NOTE: PROCEDURE DATASETS used (Total process time):
real time 0.00 seconds
cpu time 0.01 seconds
You should move the loop so that it only generates just the RENAME statement (or even just the old=new name pairs). What is happening now is that you keep overwriting MilkNew so only the last RENAME has any effect.
%macro Re(time);
data MilkNew;
set Milk;
%do I = 1 %to &time.;
rename MT&&t&I..Sp=MTSp&&t&I.;
%end;
run;
%mend Re;
%Re(6)
You should have seen the last variable name in the loop (so the 6th) changed. That's because you repeated the same data step with the same source dataset but a different destination - so each time you 'forgot' the changes made in the earlier step.
So, this would've worked, though I'll get in a minute to why this isn't a good way to do this.
%Let t1=12Mth;
%Let t2=20;
%Let t3=30;
%Let t4=40;
%Let t5=50;
%Let t6=60;
%macro Re(time);
%Do I = 1 %to &time.;
data Milk;
set Milk;
rename MT&&t&I..Sp=MTSp&&t&I.;
run;
%end;
%mend Re;
data milk;
input
MT12mthSP
MT20SP
MT30SP
MT40SP
MT50SP
MT60SP
;
datalines;
12 20 30 40 50 60
;;;;
run;
%Re(6)
Here I had it make all changes to Milk and save them back in that dataset. If you want to preserve Milk then first make Milk_New then have that in both set and data statements.
Second, you should not do a new data step for each change. Macros don't have to have a data step in them; they can be run inside the datastep.
So for example:
%macro Re(time);
%Do I = 1 %to &time.;
rename MT&&t&I..Sp=MTSp&&t&I.;
%end;
%mend Re;
data milk_new;
set milk;
%Re(6);
run;
Even better would be generating this list outside of a macro entirely - look up "generating code SAS" for suggestions on that.
If you didn't see any renames at all, you also may have an issue where a label is present on the column(s). That won't affect your usage of the variable name, but it will make it confusing. Use
label _all_;
Or include a label-clearing statement (label <varname>; where you pop in the same variable name as the original variable name before rename) inside your macro loop to fix that.
I have a very large number of datasets that are not consistently formatted - I am trying to read them into SAS and normalize them.
The basic need here is to locate a 'key column' that contains a certain string - from there I know what to do with all the variables to the left and right of that column.
The 'GREP' macro from the sas website (http://support.sas.com/kb/33/078.html) seems like it can handle this, but I need help adapting the code in the following ways:
1 - I only need to search one dataset at a time, already in the 'work' library.
2 - I need to capture the name of the variable (and the position number of it) that prints to the log at the end of this macro. This seems like it would be easy but it just returns the last column in the dataset instead of the (correct) column that prints to the log at the end.
Current code below:
%macro grep(librf,string); /* parameters are unquoted, libref name, search string */
%let librf = %upcase(&librf);
proc sql noprint;
select left(put(count(*),8.)) into :numds
from dictionary.tables
where libname="&librf";
select memname into :ds1 - :ds&numds
from dictionary.tables
where libname="&librf";
%do i=1 %to &numds;
proc sql noprint;
select left(put(count(*),8.)) into :numvars
from dictionary.columns
where libname="&librf" and memname="&&ds&i" and type='char';
/* create list of variable names and store in a macro variable */
%if &numvars > 0 %then %do;
select name into :var1 - :var&numvars
from dictionary.columns
where libname="&librf" and memname="&&ds&i" and type='char';
quit;
data _null_;
set &&ds&i;
%do j=1 %to &numvars;
if &&var&j = "&string" then
put "String &string found in dataset &librf..&&ds&i for variable &&var&j";
%end;
run;
%end;
%end;
%mend;
%grep(work,Source Location);
The log returns: "String Source Location found in dataset WORK.RAW_IMPORT for variable C" (the third), which is correct.
I just need usable macro variables equal to "C" and "3" at the end. This macro will be part of a larger macro (or a prelude to it) so the two macro variables need to reset with each dataset I run through it. Thanks for any help offered.
Please find the modification below, basically what I have done was to create global macro variables for dataset name and variable name which will feed as input to get the variable position using VARNUM function as below, ( change identified by **** )
%macro grep(librf,string);
%let librf = %upcase(&librf);
proc sql noprint;
select left(put(count(*),8.)) into :numds
from dictionary.tables
where libname="&librf";
select memname into :ds1 - :ds&numds
from dictionary.tables
where libname="&librf";
%do i=1 %to &numds;
proc sql noprint;
select left(put(count(*),8.)) into :numvars
from dictionary.columns
where libname="&librf" and memname="&&ds&i" and type='char';
/* create list of variable names and store in a macro variable */
%if &numvars > 0 %then %do;
select name into :var1 - :var&numvars
from dictionary.columns
where libname="&librf" and memname="&&ds&i" and type='char';
quit;
%global var_pos var_nm var_ds;
data _null_;
set &&ds&i;
%do j=1 %to &numvars;
**** ADDED NEW CODE HERE ****;
if &&var&j = "&string" then do; /* IF-DO nesting */;
call symputx("var_nm","&&var&j"); /*Global Macro variable for Variable Name */
call symputx("var_ds","&&ds&i"); /*Global Macro variable for Dataset Name */
put "String &string found in dataset &librf..&&ds&i for variable &&var&j";
%end;
run;
**** ADDED NEW CODE HERE ****;
%let dsid=%sysfunc(open(&var_ds,i)); /* Open Data set */
%let var_pos=%sysfunc(varnum(&dsid,&var_nm)); /* Variable Position */
%let rc=%sysfunc(close(&dsid)); /* Close Data set */;
%end;
%end;
%mend;
%grep(work,Source Location);
%put &=var_nm &=var_ds &=var_pos;
I am working on SAS in UNIX env and I want to view only the column name of a dataset. I have tried proc contents and proc print but both of them list a lot of other irrevelant information that I do not want as it fills up my putty screen and the information ultimately is lost.
I also tried to get this thing frm the sas metadata but that is not working either.
I tried :
2? proc sql;
select *
from dictionary.tables
where libname='test' and memname='sweden_elig_file_jul';
quit;
5?
NOTE: No rows were selected.
6?
NOTE: PROCEDURE SQL used (Total process time):
real time 0.27 seconds
cpu time 0.11 seconds
You're using the wrong dictionary table to get column names...
proc sql ;
select name
from dictionary.columns
where memname = 'mydata'
;
quit ;
Or using PROC CONTENTS
proc contents data=mydata out=meta (keep=NAME) ;
run ;
proc print data=meta ; run ;
Here's one I've used before to get a list of columns with a little bit more information, you can add the keep option as in the previous answer. This just demonstrates how to create a connection to the metadata server, in case that is useful to anyone viewing this post.
libname fetchlib meta
library="libraryName" metaserver="metaDataServerAddress"
password="yourPassword" port=1234
repname="yourRepositoryName" user="yourUserName";
proc contents data=fetchlib.YouDataSetName
memtype=DATA
out=outputDataSet
nodetails
noprint;
run;
For a pure macro approach, try the following:
%macro mf_getvarlist(libds
,dlm=%str( )
)/*/STORE SOURCE*/;
/* declare local vars */
%local outvar dsid nvars x rc dlm;
/* open dataset in macro */
%let dsid=%sysfunc(open(&libds));
%if &dsid %then %do;
%let nvars=%sysfunc(attrn(&dsid,NVARS));
%if &nvars>0 %then %do;
/* add first dataset variable to global macro variable */
%let outvar=%sysfunc(varname(&dsid,1));
/* add remaining variables with supplied delimeter */
%do x=2 %to &nvars;
%let outvar=&outvar.&dlm%sysfunc(varname(&dsid,&x));
%end;
%End;
%let rc=%sysfunc(close(&dsid));
%end;
%else %do;
%put unable to open &libds (rc=&dsid);
%let rc=%sysfunc(close(&dsid));
%end;
&outvar
%mend;
Usage:
%put List of Variables=%mf_getvarlist(sashelp.class);
Returns:
List of Variables=Name Sex Age Height Weight
source: https://github.com/sasjs/core/blob/main/base/mf_getvarlist.sas
proc sql;
select *
from dictionary.tables
where libname="TEST" and memname="SWEDEN_ELIG_FILE_JUL";
quit;
I wonder if there is a way of detecting whether a data set is empty, i.e. it has no observations.
Or in another saying, how to get the number of observations in a specific data set.
So that I can write an If statement to set some conditions.
Thanks.
It's easy with PROC SQL. Do a count and put the results in a macro variable.
proc sql noprint;
select count(*) into :observations from library.dataset;
quit;
There are lots of different ways, I tend to use a macro function with open() and attrn(). Below is a simple example that works great most of the time. If you are going to be dealing with data views or more complex situations like having a data set with records marked for deletion or active where clauses, then you might need more robust logic.
%macro nobs(ds);
%let DSID=%sysfunc(OPEN(&ds.,IN));
%let NOBS=%sysfunc(ATTRN(&DSID,NOBS));
%let RC=%sysfunc(CLOSE(&DSID));
&NOBS
%mend;
/* Here is an example */
%put %nobs(sashelp.class);
Here's the more complete example that #cmjohns was talking about. It will return 0 if it is empty, -1 if it is missing, and has options to handle deleted observations and where clauses (note that using a where clause can make the macro take a long time on very large datasets).
Usage Notes:
This macro will return the number of observations in a dataset. If the dataset does not exist then -1 will be returned. I would not recommend this for use with ODBC libnames, use it only against SAS tables.
Parameters:
iDs - The libname.dataset that you want to check.
iWhereClause (Optional) - A where clause to apply
iNobsType (Optional) - Either NOBS OR NLOBSF. See SASV9 documentation for descriptions.
Macro definition:
%macro nobs(iDs=, iWhereClause=1, iNobsType=nlobsf, iVerbose=1);
%local dsid nObs rc;
%if "&iWhereClause" eq "1" %then %do;
%let dsID = %sysfunc(open(&iDs));
%end;
%else %do;
%let dsID = %sysfunc(open(&iDs(where=(&iWhereClause))));
%end;
%if &dsID %then %do;
%let nObs = %sysfunc(attrn(&dsID,nlobsf));
%let rc = %sysfunc(close(&dsID));
%end;
%else %do;
%if &iVerbose %then %do;
%put WARNING: MACRO.NOBS.SAS: %sysfunc(sysmsg());
%end;
%let nObs = -1;
%end;
&nObs
%mend;
Example Usage:
%put %nobs(iDs=sashelp.class);
%put %nobs(iDs=sashelp.class, iWhereClause=height gt 60);
%put %nobs(iDs=this_dataset_doesnt_exist);
Results
19
12
-1
Installation
I recommend setting up a SAS autocall library and placing this macro in your autocall location.
Proc sql is not efficient when we have large dataset. Though using ATTRN is good method but this can accomplish within base sas, here is the efficient solution that can give number of obs of even billions of rows just by reading one row:
data DS1;
set DS nobs=i;
if _N_ =2 then stop;
No_of_obs=i;
run;
The trick is producing an output even when the dataset is empty.
data CountObs;
i=1;
set Dataset_to_Evaluate point=i nobs=j; * 'point' avoids review of full dataset*;
No_of_obs=j;
output; * Produces a value before "stop" interrupts processing *;
stop; * Needed whenever 'point' is used *;
keep No_of_obs;
run;
proc print data=CountObs;
run;
The above code is the simplest way I've found to produce the number of observations even when the dataset is empty. I've heard NOBS can be tricky, but the above can work for simple applications.
A slightly different approach:
proc contents data=library.dataset out=nobs;
run;
proc summary data=nobs nway;
class nobs;
var delobs;
output out=nobs_summ sum=;
run;
This will give you a dataset with one observation; the variable nobs has the value of number of observations in the dataset, even if it is 0.
I guess I am trying to reinvent the wheel here with so many answers already. But I do see some other methods trying to count from the actual dataset - this might take a long time for huge datasets. Here is a more efficient method:
proc sql;
select nlobs from sashelp.vtable where libname = "library" and memname="dataset";
quit;