I wanna get the label of dataset which is in the set statement but I haven't find a beautiful method.
I have tried this:
data test;
set sashelp.class;
rc = open("sashelp.class");
label = attrc(rc,"label");
rc = close(rc);
run;
It works but also have a weak point that I have to write the name of dataset in open() function.
I am looking for a better way to replace writing it manually since I have dozens of similar steps.
I have tried &syslast too, but it doesn't work. May there is some way else?
Maybe INDSNAME
18 data _null_;
19 set sashelp.class(obs=2 drop=_all_) sashelp.shoes(obs=2 drop=_all_)indsname=indsname;
20 retain label;
21 if indsname ne lag(indsname) then do;
22 rc = open(indsname); label=attrc(rc,"label"); rc=close(rc);
23 end;
24 put _all_;
25
26 run;
indsname=SASHELP.CLASS label=Student Data rc=0 _ERROR_=0 _N_=1
indsname=SASHELP.CLASS label=Student Data rc=. _ERROR_=0 _N_=2
indsname=SASHELP.SHOES label=Fictitious Shoe Company Data rc=0 _ERROR_=0 _N_=3
indsname=SASHELP.SHOES label=Fictitious Shoe Company Data rc=. _ERROR_=0 _N_=4
Would statement style macros allowed by IMPLMAC qualify for a SAS beauty pageant ?
Repurpose the SET statement as a statement style macro that emits source code containing a normal SET statement and a data step variable assignment of the data set label retrieved via ATTRC.
Example:
%macro SET(data) / STMT;
options IMPLMAC = 0; %* turn off implmac so following SET is normal token;
SET &data;
options IMPLMAC = 1; %* turn on implac so subsequent SET invoke this macro;
%local id;
%let id = %sysfunc(open(&data));
%if (&id) %then %do;
DSLABEL = %sysfunc(quote(%sysfunc(ATTRC(&ID,LABEL))));
%let id = %sysfunc(close(&id));
%end;
%mend;
data have(label="This is the data set I ""have""");
x=1;
run;
options IMPLMAC=1 MPRINT;
data _null_;
SET HAVE;
run;
options IMPLMAC=0;
Related
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();
How can I check if the values are obtained by this condition?
data ad01(keep=str);
length str $1024;
set Address(where=(Type_="1"));
///if resultat not null do something
run;
As #Quentin suggests this becomes a macro question. one way to do this is capturing count into macro variable.
/* an example where count is zero*/
proc sql noprint;
select count(*) into :cnt from sashelp.class
where name = 'unknown';
%put &cnt;
/* Here datastep gets aborted if count = 0 */
data class;
if &cnt = 0 then abort;
set sashelp.class;
run;
/* above query works when there is count gt 0 see the example below*/
proc sql noprint;
select count(*) into :cnt from sashelp.class
where trim(name) = 'Alfred';
%put &cnt;
data class;
if &cnt = 0 then abort;
set sashelp.class (where =(trim(name) = 'Alfred'));
run;
I’m assuming you mean if there are any records that meet your WHERE condition you want to process them somehow.
You can do that with just:
data ad01(keep=str);
length str $1024;
set Address(where=(Type_="1"));
*do something;
run;
If the dataset has no records with TYPE_=“1” the step will end when the SET statement executes and hits the (logical) end of file mark. If there are records that meet the condition, then any statements after the SET statement will be executed.
If by “do something” you mean execute additional steps (not just statements) this could become a macro language question. Where you would want a macro with an %IF statement, something like:
%if %anyobs(Address(where=(Type_=“1”))) %then %do;
*data steps and PROC steps and whatever here;
%end;
For examples of function-style %anyobs macros see e.g. http://www2.sas.com/proceedings/sugi26/p095-26.pdf or https://www.devenezia.com/downloads/sas/macros/index.php?m=anyobs or http://www.datasavantconsulting.com/roland/Spectre/utilmacros/nlobs.sas
If you want to do something when the SET statement return 0 observations you have to do it BEFORE the SET. With 0 obs the SET returns and data step ends. Consider this code where an observation is output when the SET returns 0 obs.
data class;
if 0 then set sashelp.class;
if _n_ eq 1 and eof then do;
name = 'NODATA';
output;
end;
set sashelp.class(where=(sex='X')) end=eof;
run;
proc print; run;
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.
I'm trying to test different covariance structures inside macro with Proc Mixed.
%macro cov(type);
proc mixed data=tmp order=data;
class sub trt visit;
model var = trt visit trt*visit / S cl;
repeated visit /subject=sub type=&type.;
FitStatistics=min_var_&type.;
run;
%mend;
Some of the covariance structures I need to fit in model causes errors and I'm trying to find a way to execute this proc mixed statement only, if it doesn't cause error with value of &type.
I have been working with %sysfunc and but haven't been able to resolve this yet.
%IF %SYSFUNC(EXIST(min_var_&type.)) %THEN %DO;
data help_&type.;
set min_var_&type.;
run;
%end;
This produces these datasets correctly, but still log errors exists in log for those macro variables that can not be fitted.
You can redirect the log to a file like that :
filename logfile "\\SERVER\LOG\mylog.log";
proc printto log=logfile new;
run;
And then when your PROC MIXED is finished, you can filter on the log file for the string "ERROR" :
....YOUR PROC MIXED...
/*come back to normal log*/
proc printto;
run;
/*check the log file*/
DATA CHECKLOG;
LENGTH ROWS $200;
LABEL ROWS = 'Messages from LOG';
INFILE "\\SERVER\LOG\mylog.log" TRUNCOVER;
INPUT ROWS &;
LINE = _N_;
IF SUBSTR(ROWS,1,5)='ERROR' /*OR SUBSTR(ROWS,1,7)='WARNING'*/ THEN
OUTPUT;
RUN;
You will have all the ERROR and (or WARNING if needed) in a dataset.
Then you have to check if the table is empty.
If YES, you can continue your script.
You can do it via this method
proc sql;
select * from checklog;
run;
%put n=&sqlobs;
If sqlobs is greater than 0, then you have errors.
You can check the sqlobs via a macro function like this :
%macro checklog;
proc sql;
select * from checklog;
run;
%if (&sqlobs>0) %then ...
%else ...
%mend checklog;
I am using the below code to put the 75 percent quantile into a macro variable quant75. I want to do this using one data step only, omitting the extra dataset cap_val created.
proc univariate data=site_visits;
VAR total_visits ;
;
output out=cap_val
pctlpts = 75
pctlpre = pcap
run
;
data _null_;
set cap_val;
call symput("quant75",pcap75);
run;
%put &quant75;
Not sure why you want to do this within one data step. But you could try this:
data _null_;
if _n_=1 then do;
call execute('proc univariate data=sashelp.class;
var weight;
output out=cap_val pctlpts = 75 pctlpre = pcap; ');
call execute('data _null_;
set cap_val;
call symput("quant75",pcap75);
run;');
end;
run;
%put &quant75;
You could do this without an additional data step, by reading the dataset directly using macro:
proc univariate data=site_visits;
var total_visits ;
output out=cap_val
pctlpts = 75
pctlpre = pcap
run;
%let dsid = %sysfunc(open(work.cap_val));
%syscall set(dsid);
%let rc = %sysfunc(fetch(&dsid));
%let rc = %sysfunc(close(&dsid));
%let quant75 = &pcap75;
%put &=quant75;
Be warned that this will create a macro variable for each variable in your input dataset (may or may not be a problem).
In your case though I'd recommend sticking with the approach you are using, as it is easier to read.
Also (as #superfluous mentions), it is not possible for you to create this macro variable directly in the univariate procedure. Instead you will need to create and then query an output dataset.
Not sure why this should be a problem, but to alleviate any concerns, you could try writing out to _DATA_ (creating a uniquely named dataset, eg DATA1) and then use &syslast to delete - as follows:
proc univariate data=site_visits;
var total_visits ;
output out=_DATA_
pctlpts = 75
pctlpre = pcap
run;
proc sql noprint;
select pcap75 into: quant75 from &syslast;
drop table &syslast;