mirror of
https://github.com/sasjs/core.git
synced 2026-01-14 03:50:06 +00:00
feat(*): recreate library as scoped package
This commit is contained in:
141
base/mf_abort.sas
Normal file
141
base/mf_abort.sas
Normal file
@@ -0,0 +1,141 @@
|
||||
/**
|
||||
@file
|
||||
@brief abort gracefully according to context
|
||||
@details Do not use directly! See bottom of explanation for details.
|
||||
|
||||
Configures an abort mechanism according to site specific policies or the
|
||||
particulars of an environment. For instance, can stream custom
|
||||
results back to the client in an STP Web App context, or completely stop
|
||||
in the case of a batch run.
|
||||
|
||||
For the sharp eyed readers - this is no longer a macro function!! It became
|
||||
a macro procedure during a project and now it's kinda stuck that way until
|
||||
that project is updated (if it's ever updated). In the meantime we created
|
||||
`mp_abort` which is just a wrapper for this one, and so we recomend you use
|
||||
that for forwards compatibility reasons.
|
||||
|
||||
@param mac= to contain the name of the calling macro
|
||||
@param type= deprecated. Not used.
|
||||
@param msg= message to be returned
|
||||
@param iftrue= supply a condition under which the macro should be executed.
|
||||
|
||||
@version 9.2
|
||||
@author Allan Bowe
|
||||
**/
|
||||
|
||||
%macro mf_abort(mac=mf_abort.sas, type=, msg=, iftrue=%str(1=1)
|
||||
)/*/STORE SOURCE*/;
|
||||
|
||||
%if not(%eval(%unquote(&iftrue))) %then %return;
|
||||
|
||||
%put NOTE: /// mf_abort macro executing //;
|
||||
%if %length(&mac)>0 %then %put NOTE- called by &mac;
|
||||
%put NOTE - &msg;
|
||||
|
||||
/* Stored Process Server web app context */
|
||||
%if %symexist(_metaperson) or "&SYSPROCESSNAME"="Compute Server" %then %do;
|
||||
options obs=max replace nosyntaxcheck mprint;
|
||||
/* extract log err / warn, if exist */
|
||||
%local logloc logline;
|
||||
%global logmsg; /* capture global messages */
|
||||
%if %symexist(SYSPRINTTOLOG) %then %let logloc=&SYSPRINTTOLOG;
|
||||
%else %let logloc=%qsysfunc(getoption(LOG));
|
||||
proc printto log=log;run;
|
||||
%if %length(&logloc)>0 %then %do;
|
||||
%let logline=0;
|
||||
data _null_;
|
||||
infile &logloc lrecl=5000;
|
||||
input; putlog _infile_;
|
||||
i=1;
|
||||
retain logonce 0;
|
||||
if (_infile_=:"%str(WARN)ING" or _infile_=:"%str(ERR)OR") and logonce=0 then do;
|
||||
call symputx('logline',_n_);
|
||||
logonce+1;
|
||||
end;
|
||||
run;
|
||||
/* capture log including lines BEFORE the err */
|
||||
%if &logline>0 %then %do;
|
||||
data _null_;
|
||||
infile &logloc lrecl=5000;
|
||||
input;
|
||||
i=1;
|
||||
stoploop=0;
|
||||
if _n_ ge &logline-5 and stoploop=0 then do until (i>12);
|
||||
call symputx('logmsg',catx('\n',symget('logmsg'),_infile_));
|
||||
input;
|
||||
i+1;
|
||||
stoploop=1;
|
||||
end;
|
||||
if stoploop=1 then stop;
|
||||
run;
|
||||
%end;
|
||||
%end;
|
||||
|
||||
/* send response in SASjs JSON format */
|
||||
data _null_;
|
||||
file _webout mod lrecl=32000;
|
||||
length msg $32767;
|
||||
sasdatetime=datetime();
|
||||
msg=cats(symget('msg'),'\n\nLog Extract:\n',symget('logmsg'));
|
||||
/* escape the quotes */
|
||||
msg=tranwrd(msg,'"','\"');
|
||||
/* ditch the CRLFs as chrome complains */
|
||||
msg=compress(msg,,'kw');
|
||||
/* quote without quoting the quotes (which are escaped instead) */
|
||||
msg=cats('"',msg,'"');
|
||||
if symexist('_debug') then debug=symget('_debug');
|
||||
if debug ge 131 then put '>>weboutBEGIN<<';
|
||||
put '{"START_DTTM" : "' "%sysfunc(datetime(),datetime20.3)" '"';
|
||||
put ',"sasjsAbort" : [{';
|
||||
put ' "MSG":' msg ;
|
||||
put ' ,"MAC": "' "&mac" '"}]';
|
||||
put ",""SYSUSERID"" : ""&sysuserid"" ";
|
||||
if symexist('_metauser') then do;
|
||||
_METAUSER=quote(trim(symget('_METAUSER')));
|
||||
put ",""_METAUSER"": " _METAUSER;
|
||||
_METAPERSON=quote(trim(symget('_METAPERSON')));
|
||||
put ',"_METAPERSON": ' _METAPERSON;
|
||||
end;
|
||||
_PROGRAM=quote(trim(resolve(symget('_PROGRAM'))));
|
||||
put ',"_PROGRAM" : ' _PROGRAM ;
|
||||
put ",""SYSCC"" : ""&syscc"" ";
|
||||
put ",""SYSERRORTEXT"" : ""&syserrortext"" ";
|
||||
put ",""SYSJOBID"" : ""&sysjobid"" ";
|
||||
put ",""SYSWARNINGTEXT"" : ""&syswarningtext"" ";
|
||||
put ',"END_DTTM" : "' "%sysfunc(datetime(),datetime20.3)" '" ';
|
||||
put "}" @;
|
||||
%if &_debug ge 131 %then %do;
|
||||
put '>>weboutEND<<';
|
||||
%end;
|
||||
run;
|
||||
%let syscc=0;
|
||||
%if %symexist(SYS_JES_JOB_URI) %then %do;
|
||||
/* refer web service output to file service in one hit */
|
||||
filename _webout filesrvc parenturi="&SYS_JES_JOB_URI" name="_webout.json";
|
||||
%let rc=%sysfunc(fcopy(_web,_webout));
|
||||
%end;
|
||||
%else %do;
|
||||
data _null_;
|
||||
if symexist('sysprocessmode')
|
||||
then if symget("sysprocessmode")="SAS Stored Process Server"
|
||||
then rc=stpsrvset('program error', 0);
|
||||
run;
|
||||
%end;
|
||||
/**
|
||||
* endsas is reliable but kills some deployments.
|
||||
* Abort variants are ungraceful (non zero return code)
|
||||
* This approach lets SAS run silently until the end :-)
|
||||
*/
|
||||
%put _all_;
|
||||
filename skip temp;
|
||||
data _null_;
|
||||
file skip;
|
||||
put '%macro skip(); %macro skippy();';
|
||||
run;
|
||||
%inc skip;
|
||||
%end;
|
||||
%else %do;
|
||||
%put _all_;
|
||||
%abort cancel;
|
||||
%end;
|
||||
%mend;
|
||||
26
base/mf_existds.sas
Executable file
26
base/mf_existds.sas
Executable file
@@ -0,0 +1,26 @@
|
||||
/**
|
||||
@file mf_existds.sas
|
||||
@brief Checks whether a dataset OR a view exists.
|
||||
@details Can be used in open code, eg as follows:
|
||||
|
||||
%if %mf_existds(libds=work.someview) %then %put yes it does!;
|
||||
|
||||
NOTE - some databases have case sensitive tables, for instance POSTGRES
|
||||
with the preserve_tab_names=yes libname setting. This may impact
|
||||
expected results (depending on whether you 'expect' the result to be
|
||||
case insensitive in this context!)
|
||||
|
||||
@param libds library.dataset
|
||||
@return output returns 1 or 0
|
||||
@warning Untested on tables registered in metadata but not physically present
|
||||
@version 9.2
|
||||
@author Allan Bowe
|
||||
**/
|
||||
|
||||
%macro mf_existds(libds
|
||||
)/*/STORE SOURCE*/;
|
||||
|
||||
%if %sysfunc(exist(&libds)) ne 1 & %sysfunc(exist(&libds,VIEW)) ne 1 %then 0;
|
||||
%else 1;
|
||||
|
||||
%mend;
|
||||
42
base/mf_existfeature.sas
Normal file
42
base/mf_existfeature.sas
Normal file
@@ -0,0 +1,42 @@
|
||||
/**
|
||||
@file mf_existfeature.sas
|
||||
@brief Checks whether a feature exists
|
||||
@details Check to see if a feature is supported in your environment.
|
||||
Run without arguments to see a list of detectable features.
|
||||
Note - this list is based on known versions of SAS rather than
|
||||
actual feature detection, as that is tricky / impossible to do
|
||||
without generating errors in most cases.
|
||||
|
||||
%put %mf_existfeature(PROCLUA);
|
||||
|
||||
@param feature the feature to detect. Leave blank to list all in log.
|
||||
|
||||
@return output returns 1 or 0 (or -1 if not found)
|
||||
|
||||
<h4> Dependencies </h4>
|
||||
@li mf_getplatform.sas
|
||||
|
||||
|
||||
@version 8
|
||||
@author Allan Bowe
|
||||
**/
|
||||
|
||||
%macro mf_existfeature(feature
|
||||
)/*/STORE SOURCE*/;
|
||||
%let feature=%upcase(&feature);
|
||||
%local platform;
|
||||
%let platform=%mf_getplatform();
|
||||
|
||||
%if &feature= %then %do;
|
||||
%put Supported features: PROCLUA;
|
||||
%end;
|
||||
%else %if &feature=PROCLUA %then %do;
|
||||
%if &platform=SASVIYA %then 1;
|
||||
%else %if "&sysver"="9.3" or "&sysver"="9.4" %then 1;
|
||||
%else 0;
|
||||
%end;
|
||||
%else %do;
|
||||
-1
|
||||
%put &sysmacroname: &feature not found;
|
||||
%end;
|
||||
%mend;
|
||||
32
base/mf_existvar.sas
Executable file
32
base/mf_existvar.sas
Executable file
@@ -0,0 +1,32 @@
|
||||
/**
|
||||
@file
|
||||
@brief Checks if a variable exists in a data set.
|
||||
@details Returns 0 if the variable does NOT exist, and return the position of
|
||||
the var if it does.
|
||||
Usage:
|
||||
|
||||
%put %mf_existvar(work.someds, somevar)
|
||||
|
||||
@param libds (positional) - 2 part dataset or view reference
|
||||
@param var (positional) - variable name
|
||||
@version 9.2
|
||||
@author Allan Bowe
|
||||
**/
|
||||
|
||||
%macro mf_existvar(libds /* 2 part dataset name */
|
||||
, var /* variable name */
|
||||
)/*/STORE SOURCE*/;
|
||||
|
||||
%local dsid rc;
|
||||
%let dsid=%sysfunc(open(&libds,is));
|
||||
|
||||
%if &dsid=0 or %length(&var)=0 %then %do;
|
||||
%put %sysfunc(sysmsg());
|
||||
0
|
||||
%end;
|
||||
%else %do;
|
||||
%sysfunc(varnum(&dsid,&var))
|
||||
%let rc=%sysfunc(close(&dsid));
|
||||
%end;
|
||||
|
||||
%mend;
|
||||
56
base/mf_existvarlist.sas
Executable file
56
base/mf_existvarlist.sas
Executable file
@@ -0,0 +1,56 @@
|
||||
/**
|
||||
@file
|
||||
@brief Checks if a set of variables ALL exist in a data set.
|
||||
@details Returns 0 if ANY of the variables do not exist, or 1 if they ALL do.
|
||||
Usage:
|
||||
|
||||
%put %mf_existVarList(sashelp.class, age sex name dummyvar)
|
||||
|
||||
<h4> Dependencies </h4>
|
||||
@li mf_abort.sas
|
||||
|
||||
@param libds 2 part dataset or view reference
|
||||
@param varlist space separated variable names
|
||||
|
||||
@version 9.2
|
||||
@author Allan Bowe
|
||||
**/
|
||||
|
||||
%macro mf_existvarlist(libds, varlist
|
||||
)/*/STORE SOURCE*/;
|
||||
|
||||
%if %str(&libds)=%str() or %str(&varlist)=%str() %then %do;
|
||||
%mf_abort(msg=No value provided to libds(&libds) or varlist (&varlist)!
|
||||
,mac=mf_existvarlist.sas)
|
||||
%end;
|
||||
|
||||
%local dsid rc i var found;
|
||||
%let dsid=%sysfunc(open(&libds,is));
|
||||
|
||||
%if &dsid=0 %then %do;
|
||||
%put WARNING: unable to open &libds in mf_existvarlist (&dsid);
|
||||
%end;
|
||||
|
||||
%if %sysfunc(attrn(&dsid,NVARS))=0 %then %do;
|
||||
%put MF_EXISTVARLIST: No variables in &libds ;
|
||||
0
|
||||
%return;
|
||||
%end;
|
||||
|
||||
%else %do i=1 %to %sysfunc(countw(&varlist));
|
||||
%let var=%scan(&varlist,&i);
|
||||
|
||||
%if %sysfunc(varnum(&dsid,&var))=0 %then %do;
|
||||
%let found=&found &var;
|
||||
%end;
|
||||
%end;
|
||||
|
||||
%let rc=%sysfunc(close(&dsid));
|
||||
%if %str(&found)=%str() %then %do;
|
||||
1
|
||||
%end;
|
||||
%else %do;
|
||||
0
|
||||
%put Vars not found: &found;
|
||||
%end;
|
||||
%mend;
|
||||
34
base/mf_getattrc.sas
Normal file
34
base/mf_getattrc.sas
Normal file
@@ -0,0 +1,34 @@
|
||||
/**
|
||||
@file
|
||||
@brief Returns a character attribute of a dataset.
|
||||
@details Can be used in open code, eg as follows:
|
||||
|
||||
%put Dataset label = %mf_getattrc(sashelp.class,LABEL);
|
||||
%put Member Type = %mf_getattrc(sashelp.class,MTYPE);
|
||||
|
||||
@param libds library.dataset
|
||||
@param attr full list in [documentation](
|
||||
https://support.sas.com/documentation/cdl/en/lrdict/64316/HTML/default/viewer.htm#a000147794.htm)
|
||||
@return output returns result of the attrc value supplied, or -1 and log
|
||||
message if error.
|
||||
|
||||
@version 9.2
|
||||
@author Allan Bowe
|
||||
**/
|
||||
|
||||
%macro mf_getattrc(
|
||||
libds
|
||||
,attr
|
||||
)/*/STORE SOURCE*/;
|
||||
%local dsid rc;
|
||||
%let dsid=%sysfunc(open(&libds,is));
|
||||
%if &dsid = 0 %then %do;
|
||||
%put WARNING: Cannot open %trim(&libds), system message below;
|
||||
%put %sysfunc(sysmsg());
|
||||
-1
|
||||
%end;
|
||||
%else %do;
|
||||
%sysfunc(attrc(&dsid,&attr))
|
||||
%let rc=%sysfunc(close(&dsid));
|
||||
%end;
|
||||
%mend;
|
||||
34
base/mf_getattrn.sas
Executable file
34
base/mf_getattrn.sas
Executable file
@@ -0,0 +1,34 @@
|
||||
/**
|
||||
@file
|
||||
@brief Returns a numeric attribute of a dataset.
|
||||
@details Can be used in open code, eg as follows:
|
||||
|
||||
%put Number of observations=%mf_getattrn(sashelp.class,NLOBS);
|
||||
%put Number of variables = %mf_getattrn(sashelp.class,NVARS);
|
||||
|
||||
@param libds library.dataset
|
||||
@param attr Common values are NLOBS and NVARS, full list in [documentation](
|
||||
http://support.sas.com/documentation/cdl/en/lrdict/64316/HTML/default/viewer.htm#a000212040.htm)
|
||||
@return output returns result of the attrn value supplied, or -1 and log
|
||||
message if error.
|
||||
|
||||
@version 9.2
|
||||
@author Allan Bowe
|
||||
**/
|
||||
|
||||
%macro mf_getattrn(
|
||||
libds
|
||||
,attr
|
||||
)/*/STORE SOURCE*/;
|
||||
%local dsid rc;
|
||||
%let dsid=%sysfunc(open(&libds,is));
|
||||
%if &dsid = 0 %then %do;
|
||||
%put WARNING: Cannot open %trim(&libds), system message below;
|
||||
%put %sysfunc(sysmsg());
|
||||
-1
|
||||
%end;
|
||||
%else %do;
|
||||
%sysfunc(attrn(&dsid,&attr))
|
||||
%let rc=%sysfunc(close(&dsid));
|
||||
%end;
|
||||
%mend;
|
||||
44
base/mf_getengine.sas
Executable file
44
base/mf_getengine.sas
Executable file
@@ -0,0 +1,44 @@
|
||||
/**
|
||||
@file
|
||||
@brief Returns the engine type of a SAS library
|
||||
@details Usage:
|
||||
|
||||
%put %mf_getEngine(SASHELP);
|
||||
|
||||
returns:
|
||||
> V9
|
||||
|
||||
A note is also written to the log. The credit for this macro goes to the
|
||||
contributors of Chris Hemedingers blog [post](
|
||||
http://blogs.sas.com/content/sasdummy/2013/06/04/find-a-sas-library-engine/)
|
||||
|
||||
@param libref Library reference (also accepts a 2 level libds ref).
|
||||
|
||||
@return output returns the library engine for the FIRST library encountered.
|
||||
|
||||
@warning will only return the FIRST library engine - for concatenated
|
||||
libraries, with different engines, inconsistent results may be encountered.
|
||||
|
||||
@version 9.2
|
||||
@author Allan Bowe
|
||||
**/
|
||||
|
||||
%macro mf_getEngine(libref
|
||||
)/*/STORE SOURCE*/;
|
||||
%local dsid engnum rc engine;
|
||||
|
||||
/* in case the parameter is a libref.tablename, pull off just the libref */
|
||||
%let libref = %upcase(%scan(&libref, 1, %str(.)));
|
||||
|
||||
%let dsid=%sysfunc(open(sashelp.vlibnam(where=(libname="%upcase(&libref)")),i));
|
||||
%if (&dsid ^= 0) %then %do;
|
||||
%let engnum=%sysfunc(varnum(&dsid,ENGINE));
|
||||
%let rc=%sysfunc(fetch(&dsid));
|
||||
%let engine=%sysfunc(getvarc(&dsid,&engnum));
|
||||
%put &libref. ENGINE is &engine.;
|
||||
%let rc= %sysfunc(close(&dsid));
|
||||
%end;
|
||||
|
||||
&engine
|
||||
|
||||
%mend;
|
||||
47
base/mf_getfilesize.sas
Normal file
47
base/mf_getfilesize.sas
Normal file
@@ -0,0 +1,47 @@
|
||||
/**
|
||||
@file
|
||||
@brief Returns the size of a file in bytes.
|
||||
@details Provide full path/filename.extension to the file, eg:
|
||||
|
||||
%put %mf_getfilesize(fpath=C:\temp\myfile.txt);
|
||||
|
||||
or
|
||||
|
||||
data x;do x=1 to 100000;y=x;output;end;run;
|
||||
%put %mf_getfilesize(libds=work.x,format=yes);
|
||||
|
||||
gives:
|
||||
|
||||
2mb
|
||||
|
||||
@param fpath= full path and filename. Provide this OR the libds value.
|
||||
@param libds= library.dataset value (assumes library is BASE engine)
|
||||
@param format= set to yes to apply sizekmg. format
|
||||
@returns bytes
|
||||
|
||||
@version 9.2
|
||||
@author Allan Bowe
|
||||
**/
|
||||
|
||||
%macro mf_getfilesize(fpath=,libds=0,format=NO
|
||||
)/*/STORE SOURCE*/;
|
||||
|
||||
%if &libds ne 0 %then %do;
|
||||
%let fpath=%sysfunc(pathname(%scan(&libds,1,.)))/%scan(&libds,2,.).sas7bdat;
|
||||
%end;
|
||||
|
||||
%local rc fid fref bytes;
|
||||
%let rc=%sysfunc(filename(fref,&fpath));
|
||||
%let fid=%sysfunc(fopen(&fref));
|
||||
%let bytes=%sysfunc(finfo(&fid,File Size (bytes)));
|
||||
%let rc=%sysfunc(fclose(&fid));
|
||||
%let rc=%sysfunc(filename(fref));
|
||||
|
||||
%if &format=NO %then %do;
|
||||
&bytes
|
||||
%end;
|
||||
%else %do;
|
||||
%sysfunc(INPUTN(&bytes, best.),sizekmg.)
|
||||
%end;
|
||||
|
||||
%mend ;
|
||||
32
base/mf_getkeyvalue.sas
Normal file
32
base/mf_getkeyvalue.sas
Normal file
@@ -0,0 +1,32 @@
|
||||
/**
|
||||
@file
|
||||
@brief retrieves a key value pair from a control dataset
|
||||
@details By default, control dataset is work.mp_setkeyvalue. Usage:
|
||||
|
||||
%mp_setkeyvalue(someindex,22,type=N)
|
||||
%put %mf_getkeyvalue(someindex)
|
||||
|
||||
|
||||
@param key Provide a key on which to perform the lookup
|
||||
@param libds= define the target table which holds the parameters
|
||||
|
||||
@version 9.2
|
||||
@author Allan Bowe
|
||||
**/
|
||||
|
||||
%macro mf_getkeyvalue(key,libds=work.mp_setkeyvalue
|
||||
)/*/STORE SOURCE*/;
|
||||
%local ds dsid key valc valn type rc;
|
||||
%let dsid=%sysfunc(open(&libds(where=(key="&key"))));
|
||||
%syscall set(dsid);
|
||||
%let rc = %sysfunc(fetch(&dsid));
|
||||
%let rc = %sysfunc(close(&dsid));
|
||||
|
||||
%if &type=N %then %do;
|
||||
&valn
|
||||
%end;
|
||||
%else %if &type=C %then %do;
|
||||
&valc
|
||||
%end;
|
||||
%else %put %str(ERR)OR: Unable to find key &key in ds &libds;
|
||||
%mend;
|
||||
62
base/mf_getplatform.sas
Normal file
62
base/mf_getplatform.sas
Normal file
@@ -0,0 +1,62 @@
|
||||
/**
|
||||
@file mf_getplatform
|
||||
@brief Returns platform specific variables
|
||||
@details Enables platform specific variables to be returned
|
||||
|
||||
%put %mf_getplatform();
|
||||
|
||||
returns:
|
||||
SASMETA (or SASVIYA)
|
||||
|
||||
@param switch the param for which to return a platform specific variable
|
||||
|
||||
<h4> Dependencies </h4>
|
||||
@li mf_mval.sas
|
||||
|
||||
@version 9.4 / 3.4
|
||||
@author Allan Bowe
|
||||
**/
|
||||
|
||||
%macro mf_getplatform(switch
|
||||
)/*/STORE SOURCE*/;
|
||||
%local a b c;
|
||||
%if &switch.NONE=NONE %then %do;
|
||||
%if %symexist(sysprocessmode) %then %do;
|
||||
%if "&sysprocessmode"="SAS Object Server"
|
||||
or "&sysprocessmode"= "SAS Compute Server" %then %do;
|
||||
SASVIYA
|
||||
%end;
|
||||
%else %if "&sysprocessmode"="SAS Stored Process Server" %then %do;
|
||||
SASMETA
|
||||
%return;
|
||||
%end;
|
||||
%else %do;
|
||||
SAS
|
||||
%return;
|
||||
%end;
|
||||
%end;
|
||||
%else %if %symexist(_metaport) %then %do;
|
||||
SASMETA
|
||||
%return;
|
||||
%end;
|
||||
%else %do;
|
||||
SAS
|
||||
%return;
|
||||
%end;
|
||||
%end;
|
||||
%else %if &switch=SASSTUDIO %then %do;
|
||||
/* return the version of SAS Studio else 0 */
|
||||
%if %mf_mval(_CLIENTAPP)=%str(SAS Studio) %then %do;
|
||||
%let a=%mf_mval(_CLIENTVERSION);
|
||||
%let b=%scan(&a,1,.);
|
||||
%if %eval(&b >2) %then %do;
|
||||
&b
|
||||
%end;
|
||||
%else 0;
|
||||
%end;
|
||||
%else 0;
|
||||
%end;
|
||||
%else %if &switch=VIYARESTAPI %then %do;
|
||||
%sysfunc(getoption(servicesbaseurl))
|
||||
%end;
|
||||
%mend;
|
||||
46
base/mf_getquotedstr.sas
Executable file
46
base/mf_getquotedstr.sas
Executable file
@@ -0,0 +1,46 @@
|
||||
/**
|
||||
@file
|
||||
@brief Adds custom quotes / delimiters to a delimited string
|
||||
@details Can be used in open code, eg as follows:
|
||||
|
||||
%put %mf_getquotedstr(blah blah blah);
|
||||
|
||||
which returns:
|
||||
> 'blah','blah','blah'
|
||||
|
||||
@param in_str the unquoted, spaced delimited string to transform
|
||||
@param dlm= the delimeter to be applied to the output (default comma)
|
||||
@param indlm= the delimeter used for the input (default is space)
|
||||
@param quote= the quote mark to apply (S=Single, D=Double). If any other value
|
||||
than uppercase S or D is supplied, then that value will be used as the
|
||||
quoting character.
|
||||
@return output returns a string with the newly quoted / delimited output.
|
||||
|
||||
@version 9.2
|
||||
@author Allan Bowe
|
||||
**/
|
||||
|
||||
|
||||
%macro mf_getquotedstr(IN_STR,DLM=%str(,),QUOTE=S,indlm=%str( )
|
||||
)/*/STORE SOURCE*/;
|
||||
%if "e=S %then %let quote=%str(%');
|
||||
%else %if "e=D %then %let quote=%str(%");
|
||||
%else %let quote=%str();
|
||||
%local i item buffer;
|
||||
%let i=1;
|
||||
%do %while (%qscan(&IN_STR,&i,%str(&indlm)) ne %str() ) ;
|
||||
%let item=%qscan(&IN_STR,&i,%str(&indlm));
|
||||
%if %bquote("E) ne %then %let item="E%qtrim(&item)"E;
|
||||
%else %let item=%qtrim(&item);
|
||||
|
||||
%if (&i = 1) %then %let buffer =%qtrim(&item);
|
||||
%else %let buffer =&buffer&DLM%qtrim(&item);
|
||||
|
||||
%let i = %eval(&i+1);
|
||||
%end;
|
||||
|
||||
%let buffer=%sysfunc(coalescec(%qtrim(&buffer),"E"E));
|
||||
|
||||
&buffer
|
||||
|
||||
%mend;
|
||||
40
base/mf_getschema.sas
Normal file
40
base/mf_getschema.sas
Normal file
@@ -0,0 +1,40 @@
|
||||
/**
|
||||
@file mf_getschema.sas
|
||||
@brief Returns the database schema of a SAS library
|
||||
@details Usage:
|
||||
|
||||
%put %mf_getschema(MYDB);
|
||||
|
||||
returns:
|
||||
> dbo
|
||||
|
||||
@param libref Library reference (also accepts a 2 level libds ref).
|
||||
|
||||
@return output returns the library schema for the FIRST library encountered
|
||||
|
||||
@warning will only return the FIRST library schema - for concatenated
|
||||
libraries, with different schemas, inconsistent results may be encountered.
|
||||
|
||||
@version 9.2
|
||||
@author Allan Bowe
|
||||
**/
|
||||
|
||||
%macro mf_getschema(libref
|
||||
)/*/STORE SOURCE*/;
|
||||
%local dsid vnum rc schema;
|
||||
/* in case the parameter is a libref.tablename, pull off just the libref */
|
||||
%let libref = %upcase(%scan(&libref, 1, %str(.)));
|
||||
%let dsid=%sysfunc(open(sashelp.vlibnam(where=(
|
||||
libname="%upcase(&libref)" and sysname='Schema/Owner'
|
||||
)),i));
|
||||
%if (&dsid ^= 0) %then %do;
|
||||
%let vnum=%sysfunc(varnum(&dsid,SYSVALUE));
|
||||
%let rc=%sysfunc(fetch(&dsid));
|
||||
%let schema=%sysfunc(getvarc(&dsid,&vnum));
|
||||
%put &libref. schema is &schema.;
|
||||
%let rc= %sysfunc(close(&dsid));
|
||||
%end;
|
||||
|
||||
&schema
|
||||
|
||||
%mend;
|
||||
36
base/mf_getuniquefileref.sas
Normal file
36
base/mf_getuniquefileref.sas
Normal file
@@ -0,0 +1,36 @@
|
||||
/**
|
||||
@file
|
||||
@brief Assigns and returns an unused fileref
|
||||
@details Use as follows:
|
||||
|
||||
%let fileref1=%mf_getuniquefileref();
|
||||
%let fileref2=%mf_getuniquefileref();
|
||||
%put &fileref1 &fileref2;
|
||||
|
||||
which returns:
|
||||
|
||||
> mcref0 mcref1
|
||||
|
||||
@prefix= first part of fileref. Remember that filerefs can only be 8
|
||||
characters, so a 7 letter prefix would mean that `maxtries` should be 10.
|
||||
@param maxtries= the last part of the libref. Provide an integer value.
|
||||
|
||||
@version 9.2
|
||||
@author Allan Bowe
|
||||
**/
|
||||
|
||||
%macro mf_getuniquefileref(prefix=mcref,maxtries=1000);
|
||||
%local x fname;
|
||||
%let x=0;
|
||||
%do x=0 %to &maxtries;
|
||||
%if %sysfunc(fileref(&prefix&x)) > 0 %then %do;
|
||||
%let fname=&prefix&x;
|
||||
%let rc=%sysfunc(filename(fname,,temp));
|
||||
%if &rc %then %put %sysfunc(sysmsg());
|
||||
&prefix&x
|
||||
%*put &sysmacroname: Fileref &prefix&x was assigned and returned;
|
||||
%return;
|
||||
%end;
|
||||
%end;
|
||||
%put unable to find available fileref in range &prefix.0-&maxtries;
|
||||
%mend;
|
||||
40
base/mf_getuniquelibref.sas
Normal file
40
base/mf_getuniquelibref.sas
Normal file
@@ -0,0 +1,40 @@
|
||||
/**
|
||||
@file
|
||||
@brief Returns an unused libref
|
||||
@details Use as follows:
|
||||
|
||||
libname mclib0 (work);
|
||||
libname mclib1 (work);
|
||||
libname mclib2 (work);
|
||||
|
||||
%let libref=%mf_getuniquelibref();
|
||||
%put &=libref;
|
||||
|
||||
which returns:
|
||||
|
||||
> mclib3
|
||||
|
||||
@prefix= first part of libref. Remember that librefs can only be 8 characters,
|
||||
so a 7 letter prefix would mean that maxtries should be 10.
|
||||
@param maxtries= the last part of the libref. Provide an integer value.
|
||||
|
||||
@version 9.2
|
||||
@author Allan Bowe
|
||||
**/
|
||||
|
||||
|
||||
%macro mf_getuniquelibref(prefix=mclib,maxtries=1000);
|
||||
%local x libref;
|
||||
%let x=0;
|
||||
%do x=0 %to &maxtries;
|
||||
%if %sysfunc(libref(&prefix&x)) ne 0 %then %do;
|
||||
%let libref=&prefix&x;
|
||||
%let rc=%sysfunc(libname(&libref,%sysfunc(pathname(work))));
|
||||
%if &rc %then %put %sysfunc(sysmsg());
|
||||
&prefix&x
|
||||
%*put &sysmacroname: Libref &libref assigned as WORK and returned;
|
||||
%return;
|
||||
%end;
|
||||
%end;
|
||||
%put unable to find available libref in range &prefix.0-&maxtries;
|
||||
%mend;
|
||||
22
base/mf_getuniquename.sas
Normal file
22
base/mf_getuniquename.sas
Normal file
@@ -0,0 +1,22 @@
|
||||
/**
|
||||
@file mf_getuniquename.sas
|
||||
@brief Returns a shortened (32 char) GUID as a valid SAS name
|
||||
@details Use as follows:
|
||||
|
||||
%let myds=%mf_getuniquename();
|
||||
%put &=myds;
|
||||
|
||||
which returns:
|
||||
|
||||
> MCc59c750610321d4c8bf75faadbcd22
|
||||
|
||||
@param prefix= set a prefix for the new name
|
||||
|
||||
@version 9.3
|
||||
@author Allan Bowe
|
||||
**/
|
||||
|
||||
|
||||
%macro mf_getuniquename(prefix=MC);
|
||||
&prefix.%substr(%sysfunc(compress(%sysfunc(uuidgen()),-)),1,32-%length(&prefix))
|
||||
%mend;
|
||||
42
base/mf_getuser.sas
Executable file
42
base/mf_getuser.sas
Executable file
@@ -0,0 +1,42 @@
|
||||
/**
|
||||
@file
|
||||
@brief Returns a userid according to session context
|
||||
@details In a workspace session, a user is generally represented by <code>
|
||||
&sysuserid</code> or <code>SYS_COMPUTE_SESSION_OWNER</code> if it exists.
|
||||
In a Stored Process session, <code>&sysuserid</code>
|
||||
resolves to a system account (default=sassrv) and instead there are several
|
||||
metadata username variables to choose from (_metauser, _metaperson
|
||||
,_username, _secureusername). The OS account is represented by
|
||||
<code> _secureusername</code> whilst the metadata account is under <code>
|
||||
_metaperson</code>.
|
||||
|
||||
%let user= %mf_getUser();
|
||||
%put &user;
|
||||
|
||||
@param type - do not use, may be deprecated in a future release
|
||||
|
||||
@return SYSUSERID (if workspace server)
|
||||
@return _METAPERSON (if stored process server)
|
||||
@return SYS_COMPUTE_SESSION_OWNER (if Viya compute session)
|
||||
|
||||
@version 9.2
|
||||
@author Allan Bowe
|
||||
**/
|
||||
|
||||
%macro mf_getuser(type=META
|
||||
)/*/STORE SOURCE*/;
|
||||
%local user metavar;
|
||||
%if &type=OS %then %let metavar=_secureusername;
|
||||
%else %let metavar=_metaperson;
|
||||
|
||||
%if %symexist(SYS_COMPUTE_SESSION_OWNER) %then %let user=&SYS_COMPUTE_SESSION_OWNER;
|
||||
%else %if %symexist(&metavar) %then %do;
|
||||
%if %length(&&&metavar)=0 %then %let user=&sysuserid;
|
||||
/* sometimes SAS will add @domain extension - remove for consistency */
|
||||
%else %let user=%scan(&&&metavar,1,@);
|
||||
%end;
|
||||
%else %let user=&sysuserid;
|
||||
|
||||
%quote(&user)
|
||||
|
||||
%mend;
|
||||
33
base/mf_getvalue.sas
Normal file
33
base/mf_getvalue.sas
Normal file
@@ -0,0 +1,33 @@
|
||||
/**
|
||||
@file
|
||||
@brief Retrieves a value from a dataset. If no filter supplied, then first
|
||||
record is used.
|
||||
@details Be sure to <code>%quote()</code> your where clause. Example usage:
|
||||
|
||||
%put %mf_getvalue(sashelp.class,name,filter=%quote(age=15));
|
||||
%put %mf_getvalue(sashelp.class,name);
|
||||
|
||||
<h4> Dependencies </h4>
|
||||
@li mf_getattrn.sas
|
||||
|
||||
@param libds dataset to query
|
||||
@param variable the variable which contains the value to return.
|
||||
@param filter contents of where clause
|
||||
|
||||
@version 9.2
|
||||
@author Allan Bowe
|
||||
**/
|
||||
|
||||
%macro mf_getvalue(libds,variable,filter=1
|
||||
)/*/STORE SOURCE*/;
|
||||
%if %mf_getattrn(&libds,NLOBS)>0 %then %do;
|
||||
%local dsid rc &variable;
|
||||
%let dsid=%sysfunc(open(&libds(where=(&filter))));
|
||||
%syscall set(dsid);
|
||||
%let rc = %sysfunc(fetch(&dsid));
|
||||
%let rc = %sysfunc(close(&dsid));
|
||||
|
||||
%trim(&&&variable)
|
||||
|
||||
%end;
|
||||
%mend;
|
||||
32
base/mf_getvarcount.sas
Normal file
32
base/mf_getvarcount.sas
Normal file
@@ -0,0 +1,32 @@
|
||||
/**
|
||||
@file
|
||||
@brief Returns number of variables in a dataset
|
||||
@details Useful to identify those renagade datasets that have no columns!
|
||||
|
||||
%put Number of Variables=%mf_getvarcount(sashelp.class);
|
||||
|
||||
returns:
|
||||
> Number of Variables=4
|
||||
|
||||
@param libds Two part dataset (or view) reference.
|
||||
|
||||
@version 9.2
|
||||
@author Allan Bowe
|
||||
|
||||
**/
|
||||
|
||||
%macro mf_getvarcount(libds
|
||||
)/*/STORE SOURCE*/;
|
||||
%local dsid nvars rc ;
|
||||
%let dsid=%sysfunc(open(&libds));
|
||||
%let nvars=.;
|
||||
%if &dsid %then %do;
|
||||
%let nvars=%sysfunc(attrn(&dsid,NVARS));
|
||||
%let rc=%sysfunc(close(&dsid));
|
||||
%end;
|
||||
%else %do;
|
||||
%put unable to open &libds (rc=&dsid);
|
||||
%let rc=%sysfunc(close(&dsid));
|
||||
%end;
|
||||
&nvars
|
||||
%mend;
|
||||
71
base/mf_getvarformat.sas
Executable file
71
base/mf_getvarformat.sas
Executable file
@@ -0,0 +1,71 @@
|
||||
/**
|
||||
@file
|
||||
@brief Returns the format of a variable
|
||||
@details Uses varfmt function to identify the format of a particular variable.
|
||||
Usage:
|
||||
|
||||
data test;
|
||||
format str1 $1. num1 datetime19.;
|
||||
str2='hello mum!'; num2=666;
|
||||
stop;
|
||||
run;
|
||||
%put %mf_getVarFormat(test,str1);
|
||||
%put %mf_getVarFormat(work.test,num1);
|
||||
%put %mf_getVarFormat(test,str2,force=1);
|
||||
%put %mf_getVarFormat(work.test,num2,force=1);
|
||||
%put %mf_getVarFormat(test,renegade);
|
||||
|
||||
returns:
|
||||
|
||||
$1.
|
||||
DATETIME19.
|
||||
$10.
|
||||
8.
|
||||
NOTE: Variable renegade does not exist in test
|
||||
|
||||
@param libds Two part dataset (or view) reference.
|
||||
@param var Variable name for which a format should be returned
|
||||
@param force Set to 1 to supply a default if the variable has no format
|
||||
@returns outputs format
|
||||
|
||||
@author Allan Bowe
|
||||
@version 9.2
|
||||
**/
|
||||
|
||||
%macro mf_getVarFormat(libds /* two level ds name */
|
||||
, var /* variable name from which to return the format */
|
||||
, force=0
|
||||
)/*/STORE SOURCE*/;
|
||||
%local dsid vnum vformat rc vlen vtype;
|
||||
/* Open dataset */
|
||||
%let dsid = %sysfunc(open(&libds));
|
||||
%if &dsid > 0 %then %do;
|
||||
/* Get variable number */
|
||||
%let vnum = %sysfunc(varnum(&dsid, &var));
|
||||
/* Get variable format */
|
||||
%if(&vnum > 0) %then %let vformat=%sysfunc(varfmt(&dsid, &vnum));
|
||||
%else %do;
|
||||
%put NOTE: Variable &var does not exist in &libds;
|
||||
%let rc = %sysfunc(close(&dsid));
|
||||
%return;
|
||||
%end;
|
||||
%end;
|
||||
%else %do;
|
||||
%put dataset &libds not opened! (rc=&dsid);
|
||||
%return;
|
||||
%end;
|
||||
|
||||
/* supply a default if no format available */
|
||||
%if %length(&vformat)<2 & &force=1 %then %do;
|
||||
%let vlen = %sysfunc(varlen(&dsid, &vnum));
|
||||
%let vtype = %sysfunc(vartype(&dsid, &vnum.));
|
||||
%if &vtype=C %then %let vformat=$&vlen..;
|
||||
%else %let vformat=8.;
|
||||
%end;
|
||||
|
||||
|
||||
/* Close dataset */
|
||||
%let rc = %sysfunc(close(&dsid));
|
||||
/* Return variable format */
|
||||
&vformat
|
||||
%mend;
|
||||
52
base/mf_getvarlen.sas
Normal file
52
base/mf_getvarlen.sas
Normal file
@@ -0,0 +1,52 @@
|
||||
/**
|
||||
@file
|
||||
@brief Returns the length of a variable
|
||||
@details Uses varlen function to identify the length of a particular variable.
|
||||
Usage:
|
||||
|
||||
data test;
|
||||
format str $1. num datetime19.;
|
||||
stop;
|
||||
run;
|
||||
%put %mf_getVarLen(test,str);
|
||||
%put %mf_getVarLen(work.test,num);
|
||||
%put %mf_getVarLen(test,renegade);
|
||||
|
||||
returns:
|
||||
|
||||
1
|
||||
8
|
||||
NOTE: Variable renegade does not exist in test
|
||||
|
||||
@param libds Two part dataset (or view) reference.
|
||||
@param var Variable name for which a length should be returned
|
||||
@returns outputs length
|
||||
|
||||
@author Allan Bowe
|
||||
@version 9.2
|
||||
|
||||
**/
|
||||
|
||||
%macro mf_getVarLen(libds /* two level ds name */
|
||||
, var /* variable name from which to return the length */
|
||||
)/*/STORE SOURCE*/;
|
||||
%local dsid vnum vlen rc;
|
||||
/* Open dataset */
|
||||
%let dsid = %sysfunc(open(&libds));
|
||||
%if &dsid > 0 %then %do;
|
||||
/* Get variable number */
|
||||
%let vnum = %sysfunc(varnum(&dsid, &var));
|
||||
/* Get variable format */
|
||||
%if(&vnum > 0) %then %let vlen = %sysfunc(varlen(&dsid, &vnum));
|
||||
%else %do;
|
||||
%put NOTE: Variable &var does not exist in &libds;
|
||||
%let vlen = %str( );
|
||||
%end;
|
||||
%end;
|
||||
%else %put dataset &libds not opened! (rc=&dsid);
|
||||
|
||||
/* Close dataset */
|
||||
%let rc = %sysfunc(close(&dsid));
|
||||
/* Return variable format */
|
||||
&vlen
|
||||
%mend;
|
||||
64
base/mf_getvarlist.sas
Executable file
64
base/mf_getvarlist.sas
Executable file
@@ -0,0 +1,64 @@
|
||||
/**
|
||||
@file
|
||||
@brief Returns dataset variable list direct from header
|
||||
@details WAY faster than dictionary tables or sas views, and can
|
||||
also be called in macro logic (is pure macro). Can be used in open code,
|
||||
eg as follows:
|
||||
|
||||
%put List of Variables=%mf_getvarlist(sashelp.class);
|
||||
|
||||
returns:
|
||||
> List of Variables=Name Sex Age Height Weight
|
||||
|
||||
%put %mf_getvarlist(sashelp.class,dlm=%str(,),quote=double);
|
||||
|
||||
returns:
|
||||
> "Name","Sex","Age","Height","Weight"
|
||||
|
||||
@param libds Two part dataset (or view) reference.
|
||||
@param dlm= provide a delimiter (eg comma or space) to separate the vars
|
||||
@param quote= use either DOUBLE or SINGLE to quote the results
|
||||
|
||||
@version 9.2
|
||||
@author Allan Bowe
|
||||
|
||||
**/
|
||||
|
||||
%macro mf_getvarlist(libds
|
||||
,dlm=%str( )
|
||||
,quote=no
|
||||
)/*/STORE SOURCE*/;
|
||||
/* declare local vars */
|
||||
%local outvar dsid nvars x rc dlm q var;
|
||||
|
||||
/* credit Rowland Hale - byte34 is double quote, 39 is single quote */
|
||||
%if %upcase("e)=DOUBLE %then %let q=%qsysfunc(byte(34));
|
||||
%else %if %upcase("e)=SINGLE %then %let q=%qsysfunc(byte(39));
|
||||
/* 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=&q.%sysfunc(varname(&dsid,1))&q.;
|
||||
/* add remaining variables with supplied delimeter */
|
||||
%do x=1 %to &nvars;
|
||||
%let var=&q.%sysfunc(varname(&dsid,&x))&q.;
|
||||
%if &var=&q&q %then %do;
|
||||
%put &sysmacroname: Empty column found in &libds!;
|
||||
%let var=&q. &q.;
|
||||
%end;
|
||||
%if &x=1 %then %let outvar=&var;
|
||||
%else %let outvar=&outvar.&dlm.&var.;
|
||||
%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;
|
||||
54
base/mf_getvarnum.sas
Executable file
54
base/mf_getvarnum.sas
Executable file
@@ -0,0 +1,54 @@
|
||||
/**
|
||||
@file
|
||||
@brief Returns the position of a variable in dataset (varnum attribute).
|
||||
@details Uses varnum function to determine position.
|
||||
|
||||
Usage:
|
||||
|
||||
data work.test;
|
||||
format str $1. num datetime19.;
|
||||
stop;
|
||||
run;
|
||||
%put %mf_getVarNum(work.test,str);
|
||||
%put %mf_getVarNum(work.test,num);
|
||||
%put %mf_getVarNum(work.test,renegade);
|
||||
|
||||
returns:
|
||||
|
||||
> 1
|
||||
|
||||
> 2
|
||||
|
||||
> NOTE: Variable renegade does not exist in test
|
||||
|
||||
@param libds Two part dataset (or view) reference.
|
||||
@param var Variable name for which a position should be returned
|
||||
|
||||
@author Allan Bowe
|
||||
@version 9.2
|
||||
|
||||
**/
|
||||
|
||||
%macro mf_getVarNum(libds /* two level ds name */
|
||||
, var /* variable name from which to return the format */
|
||||
)/*/STORE SOURCE*/;
|
||||
%local dsid vnum rc;
|
||||
/* Open dataset */
|
||||
%let dsid = %sysfunc(open(&libds));
|
||||
%if &dsid > 0 %then %do;
|
||||
/* Get variable number */
|
||||
%let vnum = %sysfunc(varnum(&dsid, &var));
|
||||
%if(&vnum <= 0) %then %do;
|
||||
%put NOTE: Variable &var does not exist in &libds;
|
||||
%let vnum = %str( );
|
||||
%end;
|
||||
%end;
|
||||
%else %put dataset &ds not opened! (rc=&dsid);
|
||||
|
||||
/* Close dataset */
|
||||
%let rc = %sysfunc(close(&dsid));
|
||||
|
||||
/* Return variable number */
|
||||
&vnum.
|
||||
|
||||
%mend;
|
||||
48
base/mf_getvartype.sas
Executable file
48
base/mf_getvartype.sas
Executable file
@@ -0,0 +1,48 @@
|
||||
/**
|
||||
@file
|
||||
@brief Returns variable type - Character (C) or Numeric (N)
|
||||
@details
|
||||
Usage:
|
||||
|
||||
data test;
|
||||
length str $1. num 8.;
|
||||
stop;
|
||||
run;
|
||||
%put %mf_getvartype(test,str);
|
||||
%put %mf_getvartype(work.test,num);
|
||||
|
||||
|
||||
|
||||
@param libds Two part dataset (or view) reference.
|
||||
@param var the variable name to be checked
|
||||
@return output returns C or N depending on variable type. If variable
|
||||
does not exist then a blank is returned and a note is written to the log.
|
||||
|
||||
@version 9.2
|
||||
@author Allan Bowe
|
||||
|
||||
**/
|
||||
|
||||
%macro mf_getvartype(libds /* two level name */
|
||||
, var /* variable name from which to return the type */
|
||||
)/*/STORE SOURCE*/;
|
||||
%local dsid vnum vtype rc;
|
||||
/* Open dataset */
|
||||
%let dsid = %sysfunc(open(&libds));
|
||||
%if &dsid. > 0 %then %do;
|
||||
/* Get variable number */
|
||||
%let vnum = %sysfunc(varnum(&dsid, &var));
|
||||
/* Get variable type (C/N) */
|
||||
%if(&vnum. > 0) %then %let vtype = %sysfunc(vartype(&dsid, &vnum.));
|
||||
%else %do;
|
||||
%put NOTE: Variable &var does not exist in &libds;
|
||||
%let vtype = %str( );
|
||||
%end;
|
||||
%end;
|
||||
%else %put dataset &libds not opened! (rc=&dsid);
|
||||
|
||||
/* Close dataset */
|
||||
%let rc = %sysfunc(close(&dsid));
|
||||
/* Return variable type */
|
||||
&vtype
|
||||
%mend;
|
||||
26
base/mf_isblank.sas
Normal file
26
base/mf_isblank.sas
Normal file
@@ -0,0 +1,26 @@
|
||||
/**
|
||||
@file mf_isblank.sas
|
||||
@brief Checks whether a macro variable is empty (blank)
|
||||
@details Simply performs:
|
||||
|
||||
%sysevalf(%superq(param)=,boolean)
|
||||
|
||||
Usage:
|
||||
|
||||
%put mf_isblank(&var);
|
||||
|
||||
inspiration: https://support.sas.com/resources/papers/proceedings09/022-2009.pdf
|
||||
|
||||
@param param VALUE to be checked
|
||||
|
||||
@return output returns 1 (if blank) else 0
|
||||
|
||||
@version 9.2
|
||||
**/
|
||||
|
||||
%macro mf_isblank(param
|
||||
)/*/STORE SOURCE*/;
|
||||
|
||||
%sysevalf(%superq(param)=,boolean)
|
||||
|
||||
%mend;
|
||||
29
base/mf_loc.sas
Normal file
29
base/mf_loc.sas
Normal file
@@ -0,0 +1,29 @@
|
||||
/**
|
||||
@file
|
||||
@brief Returns physical location of various SAS items
|
||||
@details Returns location of the PlatformObjectFramework tools
|
||||
Usage:
|
||||
|
||||
%put %mf_loc(POF); %*location of PlatformObjectFramework tools;
|
||||
|
||||
@version 9.2
|
||||
@author Allan Bowe
|
||||
**/
|
||||
|
||||
%macro mf_loc(loc);
|
||||
%let loc=%upcase(&loc);
|
||||
%local root;
|
||||
|
||||
%if &loc=POF or &loc=PLATFORMOBJECTFRAMEWORK %then %do;
|
||||
%let root=%substr(%sysget(SASROOT),1,%index(%sysget(SASROOT),SASFoundation)-2);
|
||||
%let root=&root/SASPlatformObjectFramework/&sysver;
|
||||
%put Batch tools located at: &root;
|
||||
&root
|
||||
%end;
|
||||
%else %if &loc=VIYACONFIG %then %do;
|
||||
%let root=/opt/sas/viya/config;
|
||||
%put Viya Config located at: &root;
|
||||
&root
|
||||
%end;
|
||||
|
||||
%mend;
|
||||
67
base/mf_mkdir.sas
Executable file
67
base/mf_mkdir.sas
Executable file
@@ -0,0 +1,67 @@
|
||||
/**
|
||||
@file
|
||||
@brief Creates a directory, including any intermediate directories
|
||||
@details Works on windows and unix environments via dcreate function.
|
||||
Usage:
|
||||
|
||||
%mf_mkdir(/some/path/name)
|
||||
|
||||
|
||||
@param dir relative or absolute pathname. Unquoted.
|
||||
@version 9.2
|
||||
|
||||
**/
|
||||
|
||||
%macro mf_mkdir(dir
|
||||
)/*/STORE SOURCE*/;
|
||||
|
||||
%local lastchar child parent;
|
||||
|
||||
%let lastchar = %substr(&dir, %length(&dir));
|
||||
%if (%bquote(&lastchar) eq %str(:)) %then %do;
|
||||
/* Cannot create drive mappings */
|
||||
%return;
|
||||
%end;
|
||||
|
||||
%if (%bquote(&lastchar)=%str(/)) or (%bquote(&lastchar)=%str(\)) %then %do;
|
||||
/* last char is a slash */
|
||||
%if (%length(&dir) eq 1) %then %do;
|
||||
/* one single slash - root location is assumed to exist */
|
||||
%return;
|
||||
%end;
|
||||
%else %do;
|
||||
/* strip last slash */
|
||||
%let dir = %substr(&dir, 1, %length(&dir)-1);
|
||||
%end;
|
||||
%end;
|
||||
|
||||
%if (%sysfunc(fileexist(%bquote(&dir))) = 0) %then %do;
|
||||
/* directory does not exist so prepare to create */
|
||||
/* first get the childmost directory */
|
||||
%let child = %scan(&dir, -1, %str(/\:));
|
||||
|
||||
/*
|
||||
If child name = path name then there are no parents to create. Else
|
||||
they must be recursively scanned.
|
||||
*/
|
||||
|
||||
%if (%length(&dir) gt %length(&child)) %then %do;
|
||||
%let parent = %substr(&dir, 1, %length(&dir)-%length(&child));
|
||||
%mf_mkdir(&parent)
|
||||
%end;
|
||||
|
||||
/*
|
||||
Now create the directory. Complain loudly of any errors.
|
||||
*/
|
||||
|
||||
%let dname = %sysfunc(dcreate(&child, &parent));
|
||||
%if (%bquote(&dname) eq ) %then %do;
|
||||
%put %str(ERR)OR: could not create &parent + &child;
|
||||
%abort cancel;
|
||||
%end;
|
||||
%else %do;
|
||||
%put Directory created: &dir;
|
||||
%end;
|
||||
%end;
|
||||
/* exit quietly if directory did exist.*/
|
||||
%mend;
|
||||
18
base/mf_mval.sas
Normal file
18
base/mf_mval.sas
Normal file
@@ -0,0 +1,18 @@
|
||||
/**
|
||||
@file mf_mval.sas
|
||||
@brief Returns a macro variable value if the variable exists
|
||||
@details Use this macro to avoid repetitive use of `%if %symexist(MACVAR) %then`
|
||||
type logic.
|
||||
Usage:
|
||||
|
||||
%if %mf_mval(maynotexist)=itdid %then %do;
|
||||
|
||||
@version 9.2
|
||||
@author Allan Bowe
|
||||
**/
|
||||
|
||||
%macro mf_mval(var);
|
||||
%if %symexist(&var) %then %do;
|
||||
%superq(&var)
|
||||
%end;
|
||||
%mend;
|
||||
26
base/mf_nobs.sas
Executable file
26
base/mf_nobs.sas
Executable file
@@ -0,0 +1,26 @@
|
||||
/**
|
||||
@file
|
||||
@brief Returns number of logical (undeleted) observations.
|
||||
@details Beware - will not work on external database tables!
|
||||
Is just a convenience macro for calling <code> %mf_getattrn()</code>.
|
||||
|
||||
%put Number of observations=%mf_nobs(sashelp.class);
|
||||
|
||||
<h4> Dependencies </h4>
|
||||
@li mf_getattrn.sas
|
||||
|
||||
@param libds library.dataset
|
||||
|
||||
@return output returns result of the attrn value supplied, or log message
|
||||
if error.
|
||||
|
||||
|
||||
@version 9.2
|
||||
@author Allan Bowe
|
||||
|
||||
**/
|
||||
|
||||
%macro mf_nobs(libds
|
||||
)/*/STORE SOURCE*/;
|
||||
%mf_getattrn(&libds,NLOBS)
|
||||
%mend;
|
||||
21
base/mf_uid.sas
Normal file
21
base/mf_uid.sas
Normal file
@@ -0,0 +1,21 @@
|
||||
/**
|
||||
@file
|
||||
@brief Creates a Unique ID based on system time in a friendly format
|
||||
@details format = YYYYMMDD_HHMMSSmmm_<sysjobid>_<3randomDigits>
|
||||
|
||||
%put %mf_uid();
|
||||
|
||||
@version 9.2
|
||||
@author Allan Bowe
|
||||
|
||||
**/
|
||||
|
||||
%macro mf_uid(
|
||||
)/*/STORE SOURCE*/;
|
||||
%local today now;
|
||||
%let today=%sysfunc(today(),yymmddn8.);
|
||||
%let now=%sysfunc(compress(%sysfunc(time(),time12.3),:.));
|
||||
|
||||
&today._&now._&sysjobid._%sysevalf(%sysfunc(ranuni(0))*999,CEIL)
|
||||
|
||||
%mend;
|
||||
65
base/mf_verifymacvars.sas
Executable file
65
base/mf_verifymacvars.sas
Executable file
@@ -0,0 +1,65 @@
|
||||
/**
|
||||
@file
|
||||
@brief Checks if a set of macro variables exist / contain values.
|
||||
@details Writes ERROR to log if abortType is SOFT, else will call %mf_abort.
|
||||
Usage:
|
||||
|
||||
%let var1=x;
|
||||
%let var2=y;
|
||||
%put %mf_verifymacvars(var1 var2);
|
||||
|
||||
Returns:
|
||||
> 1
|
||||
|
||||
<h4> Dependencies </h4>
|
||||
@li mf_abort.sas
|
||||
|
||||
@param verifyvars space separated list of macro variable names
|
||||
@param makeupcase= set to YES to convert all variable VALUES to
|
||||
uppercase.
|
||||
@param mAbort= Abort Type. Default is SOFT (writes err to log).
|
||||
Set to any other value to call mf_abort (which can be configured to abort in
|
||||
various fashions according to context).
|
||||
|
||||
@warning will not be able to verify the following variables due to
|
||||
naming clash!
|
||||
- verifyVars
|
||||
- verifyVar
|
||||
- verifyIterator
|
||||
- makeUpcase
|
||||
|
||||
@version 9.2
|
||||
@author Allan Bowe
|
||||
|
||||
**/
|
||||
|
||||
|
||||
%macro mf_verifymacvars(
|
||||
verifyVars /* list of macro variable NAMES */
|
||||
,makeUpcase=NO /* set to YES to make all the variable VALUES uppercase */
|
||||
,mAbort=SOFT
|
||||
)/*/STORE SOURCE*/;
|
||||
|
||||
%local verifyIterator verifyVar abortmsg;
|
||||
%do verifyIterator=1 %to %sysfunc(countw(&verifyVars,%str( )));
|
||||
%let verifyVar=%qscan(&verifyVars,&verifyIterator,%str( ));
|
||||
%if not %symexist(&verifyvar) %then %do;
|
||||
%let abortmsg= Variable &verifyVar is MISSING;
|
||||
%goto exit_err;
|
||||
%end;
|
||||
%if %length(%trim(&&&verifyVar))=0 %then %do;
|
||||
%let abortmsg= Variable &verifyVar is EMPTY;
|
||||
%goto exit_err;
|
||||
%end;
|
||||
%if &makeupcase=YES %then %do;
|
||||
%let &verifyVar=%upcase(&&&verifyvar);
|
||||
%end;
|
||||
%end;
|
||||
|
||||
%goto exit_success;
|
||||
%exit_err:
|
||||
%if &mAbort=SOFT %then %put %str(ERR)OR: &abortmsg;
|
||||
%else %mf_abort(mac=mf_verifymacvars,type=&mabort,msg=&abortmsg);
|
||||
%exit_success:
|
||||
|
||||
%mend;
|
||||
54
base/mf_wordsinstr1butnotstr2.sas
Executable file
54
base/mf_wordsinstr1butnotstr2.sas
Executable file
@@ -0,0 +1,54 @@
|
||||
/**
|
||||
@file
|
||||
@brief Returns words that are in string 1 but not in string 2
|
||||
@details Compares two space separated strings and returns the words that are
|
||||
in the first but not in the second.
|
||||
Usage:
|
||||
|
||||
%let x= %mf_wordsInStr1ButNotStr2(
|
||||
Str1=blah sss blaaah brah bram boo
|
||||
,Str2= blah blaaah brah ssss
|
||||
);
|
||||
|
||||
returns:
|
||||
> sss bram boo
|
||||
|
||||
@param str1= string containing words to extract
|
||||
@param str2= used to compare with the extract string
|
||||
|
||||
@warning CASE SENSITIVE!
|
||||
|
||||
@version 9.2
|
||||
@author Allan Bowe
|
||||
|
||||
**/
|
||||
|
||||
%macro mf_wordsInStr1ButNotStr2(
|
||||
Str1= /* string containing words to extract */
|
||||
,Str2= /* used to compare with the extract string */
|
||||
)/*/STORE SOURCE*/;
|
||||
|
||||
%local count_base count_extr i i2 extr_word base_word match outvar;
|
||||
%if %length(&str1)=0 or %length(&str2)=0 %then %do;
|
||||
%put WARNING: empty string provided!;
|
||||
%put base string (str1)= &str1;
|
||||
%put compare string (str2) = &str2;
|
||||
%return;
|
||||
%end;
|
||||
%let count_base=%sysfunc(countw(&Str2));
|
||||
%let count_extr=%sysfunc(countw(&Str1));
|
||||
|
||||
%do i=1 %to &count_extr;
|
||||
%let extr_word=%scan(&Str1,&i,%str( ));
|
||||
%let match=0;
|
||||
%do i2=1 %to &count_base;
|
||||
%let base_word=%scan(&Str2,&i2,%str( ));
|
||||
%if &extr_word=&base_word %then %let match=1;
|
||||
%end;
|
||||
%if &match=0 %then %let outvar=&outvar &extr_word;
|
||||
%end;
|
||||
|
||||
&outvar
|
||||
|
||||
%mend;
|
||||
|
||||
140
base/mp_abort.sas
Normal file
140
base/mp_abort.sas
Normal file
@@ -0,0 +1,140 @@
|
||||
/**
|
||||
@file
|
||||
@brief abort gracefully according to context
|
||||
@details Configures an abort mechanism according to site specific policies or
|
||||
the particulars of an environment. For instance, can stream custom
|
||||
results back to the client in an STP Web App context, or completely stop
|
||||
in the case of a batch run.
|
||||
|
||||
@param mac= to contain the name of the calling macro
|
||||
@param msg= message to be returned
|
||||
@param iftrue= supply a condition under which the macro should be executed.
|
||||
|
||||
@version 9.4M3
|
||||
@author Allan Bowe
|
||||
**/
|
||||
|
||||
%macro mp_abort(mac=mp_abort.sas, type=, msg=, iftrue=%str(1=1)
|
||||
)/*/STORE SOURCE*/;
|
||||
|
||||
%if not(%eval(%unquote(&iftrue))) %then %return;
|
||||
|
||||
%put NOTE: /// mp_abort macro executing //;
|
||||
%if %length(&mac)>0 %then %put NOTE- called by &mac;
|
||||
%put NOTE - &msg;
|
||||
|
||||
/* Stored Process Server web app context */
|
||||
%if %symexist(_metaperson)
|
||||
or (%symexist(SYSPROCESSNAME) and "&SYSPROCESSNAME"="Compute Server" )
|
||||
%then %do;
|
||||
options obs=max replace nosyntaxcheck mprint;
|
||||
/* extract log errs / warns, if exist */
|
||||
%local logloc logline;
|
||||
%global logmsg; /* capture global messages */
|
||||
%if %symexist(SYSPRINTTOLOG) %then %let logloc=&SYSPRINTTOLOG;
|
||||
%else %let logloc=%qsysfunc(getoption(LOG));
|
||||
proc printto log=log;run;
|
||||
%if %length(&logloc)>0 %then %do;
|
||||
%let logline=0;
|
||||
data _null_;
|
||||
infile &logloc lrecl=5000;
|
||||
input; putlog _infile_;
|
||||
i=1;
|
||||
retain logonce 0;
|
||||
if (_infile_=:"%str(WARN)ING" or _infile_=:"%str(ERR)OR") and logonce=0 then do;
|
||||
call symputx('logline',_n_);
|
||||
logonce+1;
|
||||
end;
|
||||
run;
|
||||
/* capture log including lines BEFORE the err */
|
||||
%if &logline>0 %then %do;
|
||||
data _null_;
|
||||
infile &logloc lrecl=5000;
|
||||
input;
|
||||
i=1;
|
||||
stoploop=0;
|
||||
if _n_ ge &logline-5 and stoploop=0 then do until (i>12);
|
||||
call symputx('logmsg',catx('\n',symget('logmsg'),_infile_));
|
||||
input;
|
||||
i+1;
|
||||
stoploop=1;
|
||||
end;
|
||||
if stoploop=1 then stop;
|
||||
run;
|
||||
%end;
|
||||
%end;
|
||||
|
||||
%if %symexist(SYS_JES_JOB_URI) %then %do;
|
||||
/* refer web service output to file service in one hit */
|
||||
filename _webout filesrvc parenturi="&SYS_JES_JOB_URI" name="_webout.json";
|
||||
%end;
|
||||
|
||||
/* send response in SASjs JSON format */
|
||||
data _null_;
|
||||
file _webout mod lrecl=32000;
|
||||
length msg $32767 debug $8;
|
||||
sasdatetime=datetime();
|
||||
msg=cats(symget('msg'),'\n\nLog Extract:\n',symget('logmsg'));
|
||||
/* escape the quotes */
|
||||
msg=tranwrd(msg,'"','\"');
|
||||
/* ditch the CRLFs as chrome complains */
|
||||
msg=compress(msg,,'kw');
|
||||
/* quote without quoting the quotes (which are escaped instead) */
|
||||
msg=cats('"',msg,'"');
|
||||
if symexist('_debug') then debug=quote(trim(symget('_debug')));
|
||||
else debug='""';
|
||||
if debug ge '"131"' then put '>>weboutBEGIN<<';
|
||||
put '{"START_DTTM" : "' "%sysfunc(datetime(),datetime20.3)" '"';
|
||||
put ',"sasjsAbort" : [{';
|
||||
put ' "MSG":' msg ;
|
||||
put ' ,"MAC": "' "&mac" '"}]';
|
||||
put ",""SYSUSERID"" : ""&sysuserid"" ";
|
||||
put ',"_DEBUG":' debug ;
|
||||
if symexist('_metauser') then do;
|
||||
_METAUSER=quote(trim(symget('_METAUSER')));
|
||||
put ",""_METAUSER"": " _METAUSER;
|
||||
_METAPERSON=quote(trim(symget('_METAPERSON')));
|
||||
put ',"_METAPERSON": ' _METAPERSON;
|
||||
end;
|
||||
if symexist('SYS_JES_JOB_URI') then do;
|
||||
SYS_JES_JOB_URI=quote(trim(symget('SYS_JES_JOB_URI')));
|
||||
put ',"SYS_JES_JOB_URI": ' SYS_JES_JOB_URI;
|
||||
end;
|
||||
_PROGRAM=quote(trim(resolve(symget('_PROGRAM'))));
|
||||
put ',"_PROGRAM" : ' _PROGRAM ;
|
||||
put ",""SYSCC"" : ""&syscc"" ";
|
||||
put ",""SYSERRORTEXT"" : ""&syserrortext"" ";
|
||||
put ",""SYSJOBID"" : ""&sysjobid"" ";
|
||||
put ",""SYSWARNINGTEXT"" : ""&syswarningtext"" ";
|
||||
put ',"END_DTTM" : "' "%sysfunc(datetime(),datetime20.3)" '" ';
|
||||
put "}" @;
|
||||
if debug ge '"131"' then put '>>weboutEND<<';
|
||||
run;
|
||||
|
||||
%let syscc=0;
|
||||
%if %symexist(_metaport) %then %do;
|
||||
data _null_;
|
||||
if symexist('sysprocessmode')
|
||||
then if symget("sysprocessmode")="SAS Stored Process Server"
|
||||
then rc=stpsrvset('program error', 0);
|
||||
run;
|
||||
%end;
|
||||
/**
|
||||
* endsas is reliable but kills some deployments.
|
||||
* Abort variants are ungraceful (non zero return code)
|
||||
* This approach lets SAS run silently until the end :-)
|
||||
*/
|
||||
%put _all_;
|
||||
filename skip temp;
|
||||
data _null_;
|
||||
file skip;
|
||||
put '%macro skip(); %macro skippy();';
|
||||
run;
|
||||
%inc skip;
|
||||
%end;
|
||||
%else %do;
|
||||
%put _all_;
|
||||
%abort cancel;
|
||||
%end;
|
||||
%mend;
|
||||
|
||||
55
base/mp_binarycopy.sas
Executable file
55
base/mp_binarycopy.sas
Executable file
@@ -0,0 +1,55 @@
|
||||
/**
|
||||
@file
|
||||
@brief Copy any file using binary input / output streams
|
||||
@details Reads in a file byte by byte and writes it back out. Is an
|
||||
os-independent method to copy files. In case of naming collision, the
|
||||
default filerefs can be modified.
|
||||
Based on http://stackoverflow.com/questions/13046116/using-sas-to-copy-a-text-file
|
||||
|
||||
%mp_binarycopy(inloc="/home/me/blah.txt", outref=_webout)
|
||||
|
||||
@param inloc full, quoted "path/and/filename.ext" of the object to be copied
|
||||
@param outloc full, quoted "path/and/filename.ext" of object to be created
|
||||
@param inref can override default input fileref to avoid naming clash
|
||||
@param outref an override default output fileref to avoid naming clash
|
||||
@returns nothing
|
||||
|
||||
@version 9.2
|
||||
|
||||
**/
|
||||
|
||||
%macro mp_binarycopy(
|
||||
inloc= /* full path and filename of the object to be copied */
|
||||
,outloc= /* full path and filename of object to be created */
|
||||
,inref=____in /* override default to use own filerefs */
|
||||
,outref=____out /* override default to use own filerefs */
|
||||
)/*/STORE SOURCE*/;
|
||||
/* these IN and OUT filerefs can point to anything */
|
||||
%if &inref = ____in %then %do;
|
||||
filename &inref &inloc lrecl=1048576 ;
|
||||
%end;
|
||||
%if &outref=____out %then %do;
|
||||
filename &outref &outloc lrecl=1048576 ;
|
||||
%end;
|
||||
|
||||
/* copy the file byte-for-byte */
|
||||
data _null_;
|
||||
length filein 8 fileid 8;
|
||||
filein = fopen("&inref",'I',1,'B');
|
||||
fileid = fopen("&outref",'O',1,'B');
|
||||
rec = '20'x;
|
||||
do while(fread(filein)=0);
|
||||
rc = fget(filein,rec,1);
|
||||
rc = fput(fileid, rec);
|
||||
rc =fwrite(fileid);
|
||||
end;
|
||||
rc = fclose(filein);
|
||||
rc = fclose(fileid);
|
||||
run;
|
||||
%if &inref = ____in %then %do;
|
||||
filename &inref clear;
|
||||
%end;
|
||||
%if &outref=____out %then %do;
|
||||
filename &outref clear;
|
||||
%end;
|
||||
%mend;
|
||||
68
base/mp_cleancsv.sas
Normal file
68
base/mp_cleancsv.sas
Normal file
@@ -0,0 +1,68 @@
|
||||
/**
|
||||
@file mp_cleancsv.sas
|
||||
@brief Fixes embedded cr / lf / crlf in CSV
|
||||
@details CSVs will sometimes contain lf or crlf within quotes (eg when
|
||||
saved by excel). When the termstr is ALSO lf or crlf that can be tricky
|
||||
to process using SAS defaults.
|
||||
This macro converts any csv to follow the convention of a windows excel file,
|
||||
applying CRLF line endings and converting embedded cr and crlf to lf.
|
||||
|
||||
usage:
|
||||
fileref mycsv "/path/your/csv";
|
||||
%mp_cleancsv(in=mycsv,out=/path/new.csv)
|
||||
|
||||
@param in= provide path or fileref to input csv
|
||||
@param out= output path or fileref to output csv
|
||||
@param qchar= quote char - hex code 22 is the double quote.
|
||||
|
||||
@version 9.2
|
||||
@author Allan Bowe
|
||||
**/
|
||||
|
||||
%macro mp_cleancsv(in=NOTPROVIDED,out=NOTPROVIDED,qchar='22'x);
|
||||
%if "&in"="NOTPROVIDED" or "&out"="NOTPROVIDED" %then %do;
|
||||
%put %str(ERR)OR: Please provide valid input (&in) and output (&out) locations;
|
||||
%return;
|
||||
%end;
|
||||
|
||||
/* presence of a period(.) indicates a physical location */
|
||||
%if %index(&in,.) %then %let in="&in";
|
||||
%if %index(&out,.) %then %let out="&out";
|
||||
|
||||
/**
|
||||
* convert all cr and crlf within quotes to lf
|
||||
* convert all other cr or lf to crlf
|
||||
*/
|
||||
data _null_;
|
||||
infile &in recfm=n ;
|
||||
file &out recfm=n;
|
||||
retain isq iscrlf 0 qchar &qchar;
|
||||
input inchar $char1. ;
|
||||
if inchar=qchar then isq = mod(isq+1,2);
|
||||
if isq then do;
|
||||
/* inside a quote change cr and crlf to lf */
|
||||
if inchar='0D'x then do;
|
||||
put '0A'x;
|
||||
input inchar $char1.;
|
||||
if inchar ne '0A'x then do;
|
||||
put inchar $char1.;
|
||||
if inchar=qchar then isq = mod(isq+1,2);
|
||||
end;
|
||||
end;
|
||||
else put inchar $char1.;
|
||||
end;
|
||||
else do;
|
||||
/* outside a quote, change cr and lf to crlf */
|
||||
if inchar='0D'x then do;
|
||||
put '0D0A'x;
|
||||
input inchar $char1.;
|
||||
if inchar ne '0A'x then do;
|
||||
put inchar $char1.;
|
||||
if inchar=qchar then isq = mod(isq+1,2);
|
||||
end;
|
||||
end;
|
||||
else if inchar='0A'x then put '0D0A'x;
|
||||
else put inchar $char1.;
|
||||
end;
|
||||
run;
|
||||
%mend;
|
||||
67
base/mp_createconstraints.sas
Normal file
67
base/mp_createconstraints.sas
Normal file
@@ -0,0 +1,67 @@
|
||||
/**
|
||||
@file mp_createconstraints.sas
|
||||
@brief Creates constraints
|
||||
@details Takes the output from mp_getconstraints.sas as input
|
||||
|
||||
proc sql;
|
||||
create table work.example(
|
||||
TX_FROM float format=datetime19.,
|
||||
DD_TYPE char(16),
|
||||
DD_SOURCE char(2048),
|
||||
DD_SHORTDESC char(256),
|
||||
constraint pk primary key(tx_from, dd_type,dd_source),
|
||||
constraint unq unique(tx_from, dd_type),
|
||||
constraint nnn not null(DD_SHORTDESC)
|
||||
);
|
||||
|
||||
%mp_getconstraints(lib=work,ds=example,outds=work.constraints)
|
||||
%mp_deleteconstraints(inds=work.constraints,outds=dropped,execute=YES)
|
||||
%mp_createconstraints(inds=work.constraints,outds=created,execute=YES)
|
||||
|
||||
@param inds= The input table containing the constraint info
|
||||
@param outds= a table containing the create statements (create_statement column)
|
||||
@param execute= `YES|NO` - default is NO. To actually create, use YES.
|
||||
|
||||
<h4> Dependencies </h4>
|
||||
|
||||
@version 9.2
|
||||
@author Allan Bowe
|
||||
|
||||
**/
|
||||
|
||||
%macro mp_createconstraints(inds=mp_getconstraints
|
||||
,outds=mp_createconstraints
|
||||
,execute=NO
|
||||
)/*/STORE SOURCE*/;
|
||||
|
||||
proc sort data=&inds out=&outds;
|
||||
by libref table_name constraint_name;
|
||||
run;
|
||||
|
||||
data &outds;
|
||||
set &outds;
|
||||
by libref table_name constraint_name;
|
||||
length create_statement $500;
|
||||
if _n_=1 and "&execute"="YES" then call execute('proc sql;');
|
||||
if first.constraint_name then do;
|
||||
if constraint_type='PRIMARY' then type='PRIMARY KEY';
|
||||
else type=constraint_type;
|
||||
create_statement=catx(" ","alter table",libref,".",table_name
|
||||
,"add constraint",constraint_name,type,"(");
|
||||
if last.constraint_name then
|
||||
create_statement=cats(create_statement,column_name,");");
|
||||
else create_statement=cats(create_statement,column_name,",");
|
||||
if "&execute"="YES" then call execute(create_statement);
|
||||
end;
|
||||
else if last.constraint_name then do;
|
||||
create_statement=cats(column_name,");");
|
||||
if "&execute"="YES" then call execute(create_statement);
|
||||
end;
|
||||
else do;
|
||||
create_statement=cats(column_name,",");
|
||||
if "&execute"="YES" then call execute(create_statement);
|
||||
end;
|
||||
output;
|
||||
run;
|
||||
|
||||
%mend;
|
||||
83
base/mp_createwebservice.sas
Normal file
83
base/mp_createwebservice.sas
Normal file
@@ -0,0 +1,83 @@
|
||||
/**
|
||||
@file mp_createwebservice.sas
|
||||
@brief Create a web service in SAS 9 or Viya
|
||||
@details Creates a SASJS ready Stored Process in SAS 9 or Job Execution
|
||||
Service in SAS Viya
|
||||
|
||||
Usage:
|
||||
|
||||
%* compile macros ;
|
||||
filename mc url "https://raw.githubusercontent.com/macropeople/macrocore/master/mc_all.sas";
|
||||
%inc mc;
|
||||
|
||||
%* write some code;
|
||||
filename ft15f001 temp;
|
||||
parmcards4;
|
||||
%* fetch any data from frontend ;
|
||||
%webout(FETCH)
|
||||
data example1 example2;
|
||||
set sashelp.class;
|
||||
run;
|
||||
%* send data back;
|
||||
%webout(OPEN)
|
||||
%webout(ARR,example1) * Array format, fast, suitable for large tables ;
|
||||
%webout(OBJ,example2) * Object format, easier to work with ;
|
||||
%webout(CLOSE)
|
||||
;;;;
|
||||
%mp_createwebservice(path=/Public/app/common,name=appInit,code=ft15f001,replace=YES)
|
||||
|
||||
<h4> Dependencies </h4>
|
||||
@li mf_getplatform.sas
|
||||
@li mm_createwebservice.sas
|
||||
@li mv_createwebservice.sas
|
||||
|
||||
@param path= The full folder path where the service will be created
|
||||
@param name= Service name. Avoid spaces.
|
||||
@param desc= The description of the service (optional)
|
||||
@param precode= Space separated list of filerefs, pointing to the code that
|
||||
needs to be attached to the beginning of the service (optional)
|
||||
@param code= Space seperated fileref(s) of the actual code to be added
|
||||
@param replace= select YES to replace any existing service in that location
|
||||
|
||||
|
||||
@version 9.2
|
||||
@author Allan Bowe
|
||||
|
||||
**/
|
||||
|
||||
%macro mp_createwebservice(path=HOME
|
||||
,name=initService
|
||||
,precode=
|
||||
,code=ft15f001
|
||||
,desc=This service was created by the mp_createwebservice macro
|
||||
,replace=YES
|
||||
)/*/STORE SOURCE*/;
|
||||
|
||||
%if &syscc ge 4 %then %do;
|
||||
%put syscc=&syscc - &sysmacroname will not execute in this state;
|
||||
%return;
|
||||
%end;
|
||||
|
||||
%local platform; %let platform=%mf_getplatform();
|
||||
%if &platform=SASVIYA %then %do;
|
||||
%if "&path"="HOME" %then %let path=/Users/&sysuserid/My Folder;
|
||||
%mv_createwebservice(path=&path
|
||||
,name=&name
|
||||
,code=&code
|
||||
,precode=&precode
|
||||
,desc=&desc
|
||||
,replace=&replace
|
||||
)
|
||||
%end;
|
||||
%else %do;
|
||||
%if "&path"="HOME" %then %let path=/User Folders/&sysuserid/My Folder;
|
||||
%mm_createwebservice(path=&path
|
||||
,name=&name
|
||||
,code=&code
|
||||
,precode=&precode
|
||||
,desc=&desc
|
||||
,replace=&replace
|
||||
)
|
||||
%end;
|
||||
|
||||
%mend;
|
||||
52
base/mp_deleteconstraints.sas
Normal file
52
base/mp_deleteconstraints.sas
Normal file
@@ -0,0 +1,52 @@
|
||||
/**
|
||||
@file mp_deleteconstraints.sas
|
||||
@brief Delete constraionts
|
||||
@details Takes the output from mp_getconstraints.sas as input
|
||||
|
||||
proc sql;
|
||||
create table work.example(
|
||||
TX_FROM float format=datetime19.,
|
||||
DD_TYPE char(16),
|
||||
DD_SOURCE char(2048),
|
||||
DD_SHORTDESC char(256),
|
||||
constraint pk primary key(tx_from, dd_type,dd_source),
|
||||
constraint unq unique(tx_from, dd_type),
|
||||
constraint nnn not null(DD_SHORTDESC)
|
||||
);
|
||||
|
||||
%mp_getconstraints(lib=work,ds=example,outds=work.constraints)
|
||||
%mp_deleteconstraints(inds=work.constraints,outds=dropped,execute=YES)
|
||||
|
||||
@param inds= The input table containing the constraint info
|
||||
@param outds= a table containing the drop statements (drop_statement column)
|
||||
@param execute= `YES|NO` - default is NO. To actually drop, use YES.
|
||||
|
||||
|
||||
@version 9.2
|
||||
@author Allan Bowe
|
||||
|
||||
**/
|
||||
|
||||
%macro mp_deleteconstraints(inds=mp_getconstraints
|
||||
,outds=mp_deleteconstraints
|
||||
,execute=NO
|
||||
)/*/STORE SOURCE*/;
|
||||
|
||||
proc sort data=&inds out=&outds;
|
||||
by libref table_name constraint_name;
|
||||
run;
|
||||
|
||||
data &outds;
|
||||
set &outds;
|
||||
by libref table_name constraint_name;
|
||||
length drop_statement $500;
|
||||
if _n_=1 and "&execute"="YES" then call execute('proc sql;');
|
||||
if first.constraint_name then do;
|
||||
drop_statement=catx(" ","alter table",libref,".",table_name
|
||||
,"drop constraint",constraint_name,";");
|
||||
output;
|
||||
if "&execute"="YES" then call execute(drop_statement);
|
||||
end;
|
||||
run;
|
||||
|
||||
%mend;
|
||||
155
base/mp_dirlist.sas
Normal file
155
base/mp_dirlist.sas
Normal file
@@ -0,0 +1,155 @@
|
||||
/**
|
||||
@file
|
||||
@brief Returns all files and subdirectories within a specified parent
|
||||
@details When used with getattrs=NO, is not OS specific (uses dopen / dread).
|
||||
|
||||
If getattrs=YES then the doptname / foptname functions are used to scan all
|
||||
properties - any characters that are not valid in a SAS name (v7) are simply
|
||||
stripped, and the table is transposed so theat each property is a column
|
||||
and there is one file per row. An attempt is made to get all properties
|
||||
whether a file or folder, but some files/folders cannot be accessed, and so
|
||||
not all properties can / will be populated.
|
||||
|
||||
Credit for the rename approach:
|
||||
https://communities.sas.com/t5/SAS-Programming/SAS-Function-to-convert-string-to-Legal-SAS-Name/m-p/27375/highlight/true#M5003
|
||||
|
||||
|
||||
usage:
|
||||
|
||||
%mp_dirlist(path=/some/location,outds=myTable)
|
||||
|
||||
%mp_dirlist(outds=cwdfileprops, getattrs=YES)
|
||||
|
||||
@warning In a Unix environment, the existence of a named pipe will cause this
|
||||
macro to hang. Therefore this tool should be used with caution in a SAS 9 web
|
||||
application, as it can use up all available multibridge sessions if requests
|
||||
are resubmitted.
|
||||
If anyone finds a way to positively identify a named pipe using SAS (without
|
||||
X CMD) do please raise an issue!
|
||||
|
||||
|
||||
@param path= for which to return contents
|
||||
@param outds= the output dataset to create
|
||||
@param getattrs= YES/NO (default=NO). Uses doptname and foptname to return
|
||||
all attributes for each file / folder.
|
||||
|
||||
|
||||
@returns outds contains the following variables:
|
||||
- file_or_folder (file / folder)
|
||||
- filepath (path/to/file.name)
|
||||
- filename (just the file name)
|
||||
- ext (.extension)
|
||||
- msg (system message if any issues)
|
||||
- OS SPECIFIC variables, if <code>getattrs=</code> is used.
|
||||
|
||||
@version 9.2
|
||||
@author Allan Bowe
|
||||
**/
|
||||
|
||||
%macro mp_dirlist(path=%sysfunc(pathname(work))
|
||||
, outds=work.mp_dirlist
|
||||
, getattrs=NO
|
||||
)/*/STORE SOURCE*/;
|
||||
%let getattrs=%upcase(&getattrs)XX;
|
||||
|
||||
data &outds (compress=no keep=file_or_folder filepath filename ext msg);
|
||||
length filepath $500 fref fref2 $8 file_or_folder $6 filename $80 ext $20 msg $200;
|
||||
rc = filename(fref, "&path");
|
||||
if rc = 0 then do;
|
||||
did = dopen(fref);
|
||||
if did=0 then do;
|
||||
putlog "NOTE: This directory is empty - &path";
|
||||
msg=sysmsg();
|
||||
put _all_;
|
||||
stop;
|
||||
end;
|
||||
rc = filename(fref);
|
||||
end;
|
||||
else do;
|
||||
msg=sysmsg();
|
||||
put _all_;
|
||||
stop;
|
||||
end;
|
||||
dnum = dnum(did);
|
||||
do i = 1 to dnum;
|
||||
filename = dread(did, i);
|
||||
rc = filename(fref2, "&path/"!!filename);
|
||||
midd=dopen(fref2);
|
||||
dmsg=sysmsg();
|
||||
if did > 0 then file_or_folder='folder';
|
||||
rc=dclose(midd);
|
||||
midf=fopen(fref2);
|
||||
fmsg=sysmsg();
|
||||
if midf > 0 then file_or_folder='file';
|
||||
rc=fclose(midf);
|
||||
|
||||
if index(fmsg,'File is in use') or index(dmsg,'is not a directory')
|
||||
then file_or_folder='file';
|
||||
else if index(fmsg, 'Insufficient authorization') then file_or_folder='file';
|
||||
else if file_or_folder='' then file_or_folder='locked';
|
||||
|
||||
if file_or_folder='file' then do;
|
||||
ext = prxchange('s/.*\.{1,1}(.*)/$1/', 1, filename);
|
||||
if filename = ext then ext = ' ';
|
||||
end;
|
||||
else do;
|
||||
ext='';
|
||||
file_or_folder='folder';
|
||||
end;
|
||||
filepath="&path/"!!filename;
|
||||
output;
|
||||
end;
|
||||
rc = dclose(did);
|
||||
stop;
|
||||
run;
|
||||
|
||||
%if %substr(&getattrs,1,1)=Y %then %do;
|
||||
data &outds;
|
||||
set &outds;
|
||||
length infoname infoval $60 fref $8;
|
||||
rc=filename(fref,filepath);
|
||||
drop rc infoname fid i close fref;
|
||||
if file_or_folder='file' then do;
|
||||
fid=fopen(fref);
|
||||
if fid le 0 then do;
|
||||
msg=sysmsg();
|
||||
putlog "Could not open file:" filepath fid= ;
|
||||
sasname='_MCNOTVALID_';
|
||||
output;
|
||||
end;
|
||||
else do i=1 to foptnum(fid);
|
||||
infoname=foptname(fid,i);
|
||||
infoval=finfo(fid,infoname);
|
||||
sasname=compress(infoname, '_', 'adik');
|
||||
if anydigit(sasname)=1 then sasname=substr(sasname,anyalpha(sasname));
|
||||
if upcase(sasname) ne 'FILENAME' then output;
|
||||
end;
|
||||
close=fclose(fid);
|
||||
end;
|
||||
else do;
|
||||
fid=dopen(fref);
|
||||
if fid le 0 then do;
|
||||
msg=sysmsg();
|
||||
putlog "Could not open folder:" filepath fid= ;
|
||||
sasname='_MCNOTVALID_';
|
||||
output;
|
||||
end;
|
||||
else do i=1 to doptnum(fid);
|
||||
infoname=doptname(fid,i);
|
||||
infoval=dinfo(fid,infoname);
|
||||
sasname=compress(infoname, '_', 'adik');
|
||||
if anydigit(sasname)=1 then sasname=substr(sasname,anyalpha(sasname));
|
||||
if upcase(sasname) ne 'FILENAME' then output;
|
||||
end;
|
||||
close=dclose(fid);
|
||||
end;
|
||||
run;
|
||||
proc sort;
|
||||
by filepath sasname;
|
||||
proc transpose data=&outds out=&outds(drop=_:);
|
||||
id sasname;
|
||||
var infoval;
|
||||
by filepath file_or_folder filename ext ;
|
||||
run;
|
||||
%end;
|
||||
%mend;
|
||||
50
base/mp_distinctfmtvalues.sas
Normal file
50
base/mp_distinctfmtvalues.sas
Normal file
@@ -0,0 +1,50 @@
|
||||
/**
|
||||
@file
|
||||
@brief Creates a dataset containing distinct _formatted_ values
|
||||
@details If no format is supplied, then the original value is used instead.
|
||||
There is also a dependency on other macros within the Macro Core library.
|
||||
Usage:
|
||||
|
||||
%mp_distinctfmtvalues(libds=sashelp.class,var=age,outvar=age,outds=test)
|
||||
|
||||
@param libds input dataset
|
||||
@param var variable to get distinct values for
|
||||
@param outvar variable to create. Default: `formatted_value`
|
||||
@param outds dataset to create. Default: work.mp_distinctfmtvalues
|
||||
@param varlen length of variable to create (default 200)
|
||||
|
||||
@version 9.2
|
||||
@author Allan Bowe
|
||||
|
||||
**/
|
||||
|
||||
%macro mp_distinctfmtvalues(
|
||||
libds=
|
||||
,var=
|
||||
,outvar=formatted_value
|
||||
,outds=work.mp_distinctfmtvalues
|
||||
,varlen=2000
|
||||
)/*/STORE SOURCE*/;
|
||||
|
||||
%local fmt vtype;
|
||||
%let fmt=%mf_getvarformat(&libds,&var);
|
||||
%let vtype=%mf_getvartype(&libds,&var);
|
||||
|
||||
proc sql;
|
||||
create table &outds as
|
||||
select distinct
|
||||
%if &vtype=C & %trim(&fmt)=%str() %then %do;
|
||||
&var
|
||||
%end;
|
||||
%else %if &vtype=C %then %do;
|
||||
put(&var,&fmt)
|
||||
%end;
|
||||
%else %if %trim(&fmt)=%str() %then %do;
|
||||
put(&var,32.)
|
||||
%end;
|
||||
%else %do;
|
||||
put(&var,&fmt)
|
||||
%end;
|
||||
as &outvar length=&varlen
|
||||
from &libds;
|
||||
%mend;
|
||||
38
base/mp_dropmembers.sas
Executable file
38
base/mp_dropmembers.sas
Executable file
@@ -0,0 +1,38 @@
|
||||
/**
|
||||
@file
|
||||
@brief Drops tables / views (if they exist) without warnings in the log
|
||||
@details
|
||||
Example usage:
|
||||
|
||||
proc sql;
|
||||
create table data1 as select * from sashelp.class;
|
||||
create view view2 as select * from sashelp.class;
|
||||
%mp_dropmembers(list=data1 view2)
|
||||
|
||||
<h4> Dependencies </h4>
|
||||
@li mf_isblank.sas
|
||||
|
||||
|
||||
@param list space separated list of datasets / views
|
||||
@param libref= can only drop from a single library at a time
|
||||
|
||||
@version 9.2
|
||||
@author Allan Bowe
|
||||
|
||||
**/
|
||||
|
||||
%macro mp_dropmembers(
|
||||
list /* space separated list of datasets / views */
|
||||
,libref=WORK /* can only drop from a single library at a time */
|
||||
)/*/STORE SOURCE*/;
|
||||
|
||||
%if %mf_isblank(&list) %then %do;
|
||||
%put NOTE: nothing to drop!;
|
||||
%return;
|
||||
%end;
|
||||
|
||||
proc datasets lib=&libref nolist;
|
||||
delete &list;
|
||||
delete &list /mtype=view;
|
||||
run;
|
||||
%mend;
|
||||
246
base/mp_ds2cards.sas
Normal file
246
base/mp_ds2cards.sas
Normal file
@@ -0,0 +1,246 @@
|
||||
/**
|
||||
@file
|
||||
@brief Create a CARDS file from a SAS dataset.
|
||||
@details Uses dataset attributes to convert all data into datalines.
|
||||
Running the generated file will rebuild the original dataset.
|
||||
usage:
|
||||
|
||||
%mp_ds2cards(base_ds=sashelp.class
|
||||
, cards_file= "C:\temp\class.sas"
|
||||
, maxobs=5)
|
||||
|
||||
stuff to add
|
||||
- labelling the dataset
|
||||
- explicity setting a unix LF
|
||||
- constraints / indexes etc
|
||||
|
||||
@param base_ds= Should be two level - eg work.blah. This is the table that
|
||||
is converted to a cards file.
|
||||
@param tgt_ds= Table that the generated cards file would create. Optional -
|
||||
if omitted, will be same as BASE_DS.
|
||||
@param cards_file= Location in which to write the (.sas) cards file
|
||||
@param maxobs= to limit output to the first <code>maxobs</code> observations
|
||||
@param showlog= whether to show generated cards file in the SAS log (YES/NO)
|
||||
@param outencoding= provide encoding value for file statement (eg utf-8)
|
||||
|
||||
|
||||
@version 9.2
|
||||
@author Allan Bowe
|
||||
**/
|
||||
|
||||
%macro mp_ds2cards(base_ds=, tgt_ds=
|
||||
,cards_file="%sysfunc(pathname(work))/cardgen.sas"
|
||||
,maxobs=max
|
||||
,random_sample=NO
|
||||
,showlog=YES
|
||||
,outencoding=
|
||||
)/*/STORE SOURCE*/;
|
||||
%local i setds nvars;
|
||||
|
||||
%if not %sysfunc(exist(&base_ds)) %then %do;
|
||||
%put WARNING: &base_ds does not exist;
|
||||
%return;
|
||||
%end;
|
||||
|
||||
%if %index(&base_ds,.)=0 %then %let base_ds=WORK.&base_ds;
|
||||
%if (&tgt_ds = ) %then %let tgt_ds=&base_ds;
|
||||
%if %index(&tgt_ds,.)=0 %then %let tgt_ds=WORK.%scan(&base_ds,2,.);
|
||||
%if ("&outencoding" ne "") %then %let outencoding=encoding="&outencoding";
|
||||
|
||||
/* get varcount */
|
||||
%let nvars=0;
|
||||
proc sql noprint;
|
||||
select count(*) into: nvars from dictionary.columns
|
||||
where libname="%scan(%upcase(&base_ds),1)"
|
||||
and memname="%scan(%upcase(&base_ds),2)";
|
||||
%if &nvars=0 %then %do;
|
||||
%put WARNING: Dataset &base_ds has no variables! It will not be converted.;
|
||||
%return;
|
||||
%end;
|
||||
|
||||
/* get indexes */
|
||||
proc sort data=sashelp.vindex
|
||||
(where=(upcase(libname)="%scan(%upcase(&base_ds),1)"
|
||||
and upcase(memname)="%scan(%upcase(&base_ds),2)"))
|
||||
out=_data_;
|
||||
by indxname indxpos;
|
||||
run;
|
||||
|
||||
%local indexes;
|
||||
data _null_;
|
||||
set &syslast end=last;
|
||||
if _n_=1 then call symputx('indexes','(index=(','l');
|
||||
by indxname indxpos;
|
||||
length vars $32767 nom uni $8;
|
||||
retain vars;
|
||||
if first.indxname then do;
|
||||
idxcnt+1;
|
||||
nom='';
|
||||
uni='';
|
||||
vars=name;
|
||||
end;
|
||||
else vars=catx(' ',vars,name);
|
||||
if last.indxname then do;
|
||||
if nomiss='yes' then nom='/nomiss';
|
||||
if unique='yes' then uni='/unique';
|
||||
call symputx('indexes'
|
||||
,catx(' ',symget('indexes'),indxname,'=(',vars,')',nom,uni)
|
||||
,'l');
|
||||
end;
|
||||
if last then call symputx('indexes',cats(symget('indexes'),'))'),'l');
|
||||
run;
|
||||
|
||||
|
||||
data;run;
|
||||
%let setds=&syslast;
|
||||
proc sql
|
||||
%if %datatyp(&maxobs)=NUMERIC %then %do;
|
||||
outobs=&maxobs;
|
||||
%end;
|
||||
;
|
||||
create table &setds as select * from &base_ds
|
||||
%if &random_sample=YES %then %do;
|
||||
order by ranuni(42)
|
||||
%end;
|
||||
;
|
||||
|
||||
|
||||
create table datalines1 as
|
||||
select name,type,length,varnum,format,label from dictionary.columns
|
||||
where libname="%upcase(%scan(&base_ds,1))"
|
||||
and memname="%upcase(%scan(&base_ds,2))";
|
||||
|
||||
/**
|
||||
Due to long decimals cannot use best. format
|
||||
So - use bestd. format and then use character functions to strip trailing
|
||||
zeros, if NOT an integer!!
|
||||
resolved code = ifc(int(VARIABLE)=VARIABLE
|
||||
,put(VARIABLE,best32.)
|
||||
,substrn(put(VARIABLE,bestd32.),1
|
||||
,findc(put(VARIABLE,bestd32.),'0','TBK')));
|
||||
**/
|
||||
|
||||
data datalines_2;
|
||||
format dataline $32000.;
|
||||
set datalines1 (where=(upcase(name) not in
|
||||
('PROCESSED_DTTM','VALID_FROM_DTTM','VALID_TO_DTTM')));
|
||||
if type='num' then dataline=
|
||||
cats('ifc(int(',name,')=',name,'
|
||||
,put(',name,',best32.-l)
|
||||
,substrn(put(',name,',bestd32.-l),1
|
||||
,findc(put(',name,',bestd32.-l),"0","TBK")))');
|
||||
else dataline=name;
|
||||
run;
|
||||
|
||||
proc sql noprint;
|
||||
select dataline into: datalines separated by ',' from datalines_2;
|
||||
|
||||
%local
|
||||
process_dttm_flg
|
||||
valid_from_dttm_flg
|
||||
valid_to_dttm_flg
|
||||
;
|
||||
%let process_dttm_flg = N;
|
||||
%let valid_from_dttm_flg = N;
|
||||
%let valid_to_dttm_flg = N;
|
||||
data _null_;
|
||||
set datalines1 ;
|
||||
/* build attrib statement */
|
||||
if type='char' then type2='$';
|
||||
if strip(format) ne '' then format2=cats('format=',format);
|
||||
if strip(label) ne '' then label2=cats('label=',quote(trim(label)));
|
||||
str1=catx(' ',(put(name,$33.)||'length=')
|
||||
,put(cats(type2,length),$7.)||format2,label2);
|
||||
|
||||
|
||||
/* Build input statement */
|
||||
if type='char' then type3=':$char.';
|
||||
str2=put(name,$33.)||type3;
|
||||
|
||||
|
||||
if(upcase(name) = "PROCESSED_DTTM") then
|
||||
call symputx("process_dttm_flg", "Y", "L");
|
||||
if(upcase(name) = "VALID_FROM_DTTM") then
|
||||
call symputx("valid_from_dttm_flg", "Y", "L");
|
||||
if(upcase(name) = "VALID_TO_DTTM") then
|
||||
call symputx("valid_to_dttm_flg", "Y", "L");
|
||||
|
||||
|
||||
call symputx(cats("attrib_stmt_", put(_N_, 8.)), str1, "L");
|
||||
call symputx(cats("input_stmt_", put(_N_, 8.))
|
||||
, ifc(upcase(name) not in
|
||||
('PROCESSED_DTTM','VALID_FROM_DTTM','VALID_TO_DTTM'), str2, ""), "L");
|
||||
run;
|
||||
|
||||
data _null_;
|
||||
file &cards_file. &outencoding lrecl=32767 termstr=nl;
|
||||
length __attrib $32767;
|
||||
if _n_=1 then do;
|
||||
put '/*******************************************************************';
|
||||
put " Datalines for %upcase(%scan(&base_ds,2)) dataset ";
|
||||
put " Generated by %nrstr(%%)mp_ds2cards()";
|
||||
put " Available on github.com/macropeople/macrocore";
|
||||
put '********************************************************************/';
|
||||
put "data &tgt_ds &indexes;";
|
||||
put "attrib ";
|
||||
%do i = 1 %to &nvars;
|
||||
__attrib=symget("attrib_stmt_&i");
|
||||
put __attrib;
|
||||
%end;
|
||||
put ";";
|
||||
|
||||
%if &process_dttm_flg. eq Y %then %do;
|
||||
put 'retain PROCESSED_DTTM %sysfunc(datetime());';
|
||||
%end;
|
||||
%if &valid_from_dttm_flg. eq Y %then %do;
|
||||
put 'retain VALID_FROM_DTTM &low_date;';
|
||||
%end;
|
||||
%if &valid_to_dttm_flg. eq Y %then %do;
|
||||
put 'retain VALID_TO_DTTM &high_date;';
|
||||
%end;
|
||||
if __nobs=0 then do;
|
||||
put 'call missing(of _all_);/* avoid uninitialised notes */';
|
||||
put 'stop;';
|
||||
put 'run;';
|
||||
end;
|
||||
else do;
|
||||
put "infile cards dsd delimiter=',';";
|
||||
put "input ";
|
||||
%do i = 1 %to &nvars.;
|
||||
%if(%length(&&input_stmt_&i..)) %then
|
||||
put " &&input_stmt_&i..";
|
||||
;
|
||||
%end;
|
||||
put ";";
|
||||
put "datalines4;";
|
||||
end;
|
||||
end;
|
||||
set &setds end=__lastobs nobs=__nobs;
|
||||
/* remove all formats for write purposes - some have long underlying decimals */
|
||||
format _numeric_ best30.29;
|
||||
length __dataline $32767;
|
||||
__dataline=catq('cqtmb',&datalines);
|
||||
put __dataline;
|
||||
if __lastobs then do;
|
||||
put ';;;;';
|
||||
put 'run;';
|
||||
stop;
|
||||
end;
|
||||
run;
|
||||
proc sql;
|
||||
drop table &setds;
|
||||
quit;
|
||||
|
||||
%if &showlog=YES %then %do;
|
||||
data _null_;
|
||||
infile &cards_file lrecl=32767;
|
||||
input;
|
||||
put _infile_;
|
||||
run;
|
||||
%end;
|
||||
|
||||
%put NOTE: CARDS FILE SAVED IN:;
|
||||
%put NOTE-;%put NOTE-;
|
||||
%put NOTE- %sysfunc(dequote(&cards_file.));
|
||||
%put NOTE-;%put NOTE-;
|
||||
%mend;
|
||||
60
base/mp_getconstraints.sas
Normal file
60
base/mp_getconstraints.sas
Normal file
@@ -0,0 +1,60 @@
|
||||
/**
|
||||
@file mp_getconstraints.sas
|
||||
@brief Get constraint details at column level
|
||||
@details Useful for capturing constraints before they are dropped / reapplied
|
||||
during an update.
|
||||
|
||||
proc sql;
|
||||
create table work.example(
|
||||
TX_FROM float format=datetime19.,
|
||||
DD_TYPE char(16),
|
||||
DD_SOURCE char(2048),
|
||||
DD_SHORTDESC char(256),
|
||||
constraint pk primary key(tx_from, dd_type,dd_source),
|
||||
constraint unq unique(tx_from, dd_type),
|
||||
constraint nnn not null(DD_SHORTDESC)
|
||||
);
|
||||
|
||||
%mp_getconstraints(lib=work,ds=example,outds=work.constraints)
|
||||
|
||||
@param lib= The target library (default=WORK)
|
||||
@param ds= The target dataset. Leave blank (default) for all datasets.
|
||||
@param outds the output dataset
|
||||
|
||||
<h4> Dependencies </h4>
|
||||
|
||||
@version 9.2
|
||||
@author Allan Bowe
|
||||
|
||||
**/
|
||||
|
||||
%macro mp_getconstraints(lib=WORK
|
||||
,ds=
|
||||
,outds=mp_getconstraints
|
||||
)/*/STORE SOURCE*/;
|
||||
|
||||
%let lib=%upcase(&lib);
|
||||
%let ds=%upcase(&ds);
|
||||
|
||||
/* must use SQL as proc datasets does not support length changes */
|
||||
proc sql noprint;
|
||||
create table &outds as
|
||||
select a.TABLE_CATALOG as libref
|
||||
,a.TABLE_NAME
|
||||
,a.constraint_type
|
||||
,a.constraint_name
|
||||
,b.column_name
|
||||
from dictionary.TABLE_CONSTRAINTS a
|
||||
left join dictionary.constraint_column_usage b
|
||||
on a.TABLE_CATALOG=b.TABLE_CATALOG
|
||||
and a.TABLE_NAME=b.TABLE_NAME
|
||||
and a.constraint_name=b.constraint_name
|
||||
where a.TABLE_CATALOG="&lib"
|
||||
and b.TABLE_CATALOG="&lib"
|
||||
%if "&ds" ne "" %then %do;
|
||||
and a.TABLE_NAME="&ds"
|
||||
and b.TABLE_NAME="&ds"
|
||||
%end;
|
||||
;
|
||||
|
||||
%mend;
|
||||
230
base/mp_getddl.sas
Normal file
230
base/mp_getddl.sas
Normal file
@@ -0,0 +1,230 @@
|
||||
/**
|
||||
@file mp_getddl.sas
|
||||
@brief Extract DDL in various formats, by table or library
|
||||
@details Data Definition Language relates to a set of SQL instructions used
|
||||
to create tables in SAS or a database. The macro can be used at table or
|
||||
library level. The default behaviour is to create DDL in SAS format.
|
||||
|
||||
Usage:
|
||||
|
||||
data test(index=(pk=(x y)/unique /nomiss));
|
||||
x=1;
|
||||
y='blah';
|
||||
label x='blah';
|
||||
run;
|
||||
proc sql; describe table &syslast;
|
||||
|
||||
%mp_getddl(work,test,flavour=tsql,showlog=YES)
|
||||
|
||||
@param lib libref of the library to create DDL for. Should be assigned.
|
||||
@param ds dataset to create ddl for
|
||||
@param fref= the fileref to which to write the DDL. If not preassigned, will
|
||||
be assigned to TEMP.
|
||||
@param flavour= The type of DDL to create (default=SAS). Supported=TSQL
|
||||
@param showlog= Set to YES to show the DDL in the log
|
||||
@param schema= Choose a preferred schema name (default is to use actual schema
|
||||
,else libref)
|
||||
@param applydttm= for non SAS DDL, choose if columns are created with native
|
||||
datetime2 format or regular decimal type
|
||||
|
||||
@version 9.3
|
||||
@author Allan Bowe
|
||||
@source https://github.com/macropeople/macrocore
|
||||
|
||||
**/
|
||||
|
||||
%macro mp_getddl(libref,ds,fref=getddl,flavour=SAS,showlog=NO,schema=
|
||||
,applydttm=NO
|
||||
)/*/STORE SOURCE*/;
|
||||
|
||||
/* check fileref is assigned */
|
||||
%if %sysfunc(fileref(&fref)) > 0 %then %do;
|
||||
filename &fref temp;
|
||||
%end;
|
||||
%if %length(&libref)=0 %then %let libref=WORK;
|
||||
%let flavour=%upcase(&flavour);
|
||||
|
||||
proc sql noprint;
|
||||
create table _data_ as
|
||||
select * from dictionary.tables
|
||||
where upcase(libname)="%upcase(&libref)"
|
||||
%if %length(&ds)>0 %then %do;
|
||||
and upcase(memname)="%upcase(&ds)"
|
||||
%end;
|
||||
;
|
||||
%local tabinfo; %let tabinfo=&syslast;
|
||||
|
||||
create table _data_ as
|
||||
select * from dictionary.indexes
|
||||
where upcase(libname)="%upcase(&libref)"
|
||||
%if %length(&ds)>0 %then %do;
|
||||
and upcase(memname)="%upcase(&ds)"
|
||||
%end;
|
||||
order by idxusage, indxname, indxpos
|
||||
;
|
||||
%local idxinfo; %let idxinfo=&syslast;
|
||||
|
||||
create table _data_ as
|
||||
select * from dictionary.columns
|
||||
where upcase(libname)="%upcase(&libref)"
|
||||
%if %length(&ds)>0 %then %do;
|
||||
and upcase(memname)="%upcase(&ds)"
|
||||
%end;
|
||||
;
|
||||
%local colinfo; %let colinfo=&syslast;
|
||||
%local dsnlist;
|
||||
select distinct upcase(memname) into: dsnlist
|
||||
separated by ' '
|
||||
from &syslast;
|
||||
data _null_;
|
||||
file &fref;
|
||||
put "/* DDL generated by &sysuserid on %sysfunc(datetime(),datetime19.) */";
|
||||
run;
|
||||
|
||||
%local x curds;
|
||||
%if &flavour=SAS %then %do;
|
||||
data _null_;
|
||||
file &fref;
|
||||
put "proc sql;";
|
||||
run;
|
||||
%do x=1 %to %sysfunc(countw(&dsnlist));
|
||||
%let curds=%scan(&dsnlist,&x);
|
||||
data _null_;
|
||||
file &fref mod;
|
||||
length nm lab $1024;
|
||||
set &colinfo (where=(upcase(memname)="&curds")) end=last;
|
||||
|
||||
if _n_=1 then do;
|
||||
if memtype='DATA' then do;
|
||||
put "create table &libref..&curds(";
|
||||
end;
|
||||
else do;
|
||||
put "create view &libref..&curds(";
|
||||
end;
|
||||
put " "@@;
|
||||
end;
|
||||
else put " ,"@@;
|
||||
if length(format)>1 then fmt=" format="!!cats(format);
|
||||
len=" length="!!cats(length);
|
||||
lab=" label="!!quote(trim(label));
|
||||
if notnull='yes' then notnul=' not null';
|
||||
put name type len fmt notnul lab;
|
||||
if last then put ');';
|
||||
run;
|
||||
|
||||
data _null_;
|
||||
length ds $128;
|
||||
set &idxinfo (where=(memname="&curds")) end=last;
|
||||
file &fref mod;
|
||||
by idxusage indxname;
|
||||
if unique='yes' then uniq=' unique';
|
||||
ds=cats(libname,'.',memname);
|
||||
if first.indxname then do;
|
||||
put 'create ' uniq ' index ' indxname;
|
||||
put ' on ' ds '(' name @@;
|
||||
end;
|
||||
else put ',' name @@;
|
||||
if last.indxname then put ');';
|
||||
run;
|
||||
/*
|
||||
ods output IntegrityConstraints=ic;
|
||||
proc contents data=testali out2=info;
|
||||
run;
|
||||
*/
|
||||
%end;
|
||||
%end;
|
||||
%else %if &flavour=TSQL %then %do;
|
||||
/* if schema does not exist, set to be same as libref */
|
||||
%local schemaactual;
|
||||
proc sql noprint;
|
||||
select sysvalue into: schemaactual
|
||||
from dictionary.libnames
|
||||
where libname="&libref" and engine='SQLSVR';
|
||||
%let schema=%sysfunc(coalescec(&schemaactual,&schema,&libref));
|
||||
|
||||
%do x=1 %to %sysfunc(countw(&dsnlist));
|
||||
%let curds=%scan(&dsnlist,&x);
|
||||
data _null_;
|
||||
file &fref mod;
|
||||
put "/* DDL for &schema..&curds */";
|
||||
data _null_;
|
||||
file &fref mod;
|
||||
set &colinfo (where=(upcase(memname)="&curds")) end=last;
|
||||
if _n_=1 then do;
|
||||
if memtype='DATA' then do;
|
||||
put "create table [&schema].[&curds](";
|
||||
end;
|
||||
else do;
|
||||
put "create view [&schema].[&curds](";
|
||||
end;
|
||||
put " "@@;
|
||||
end;
|
||||
else put " ,"@@;
|
||||
format=upcase(format);
|
||||
if 1=0 then; /* dummy if */
|
||||
%if &applydttm=YES %then %do;
|
||||
else if format=:'DATETIME' then fmt='[datetime2](7) ';
|
||||
%end;
|
||||
else if type='num' then fmt='[decimal](18,2)';
|
||||
else if length le 8000 then fmt='[varchar]('!!cats(length)!!')';
|
||||
else fmt=cats('[varchar](max)');
|
||||
if notnull='yes' then notnul=' NOT NULL';
|
||||
put name fmt notnul;
|
||||
run;
|
||||
data _null_;
|
||||
length ds $128;
|
||||
set &idxinfo (where=(memname="&curds"));
|
||||
file &fref mod;
|
||||
by idxusage indxname;
|
||||
if unique='yes' then uniq=' unique';
|
||||
ds=cats(libname,'.',memname);
|
||||
if first.indxname then do;
|
||||
if unique='yes' and nomiss='yes' then do;
|
||||
put ' ,constraint [' indxname '] PRIMARY KEY';
|
||||
end;
|
||||
else if unique='yes' then do;
|
||||
/* add nonclustered in case of multiple unique indexes */
|
||||
put ' ,index [' indxname '] UNIQUE NONCLUSTERED';
|
||||
end;
|
||||
put ' (';
|
||||
put ' [' name ']';
|
||||
end;
|
||||
else put ' ,[' name ']';
|
||||
if last.indxname then do;
|
||||
put ' )';
|
||||
end;
|
||||
run;
|
||||
data _null_;
|
||||
file &fref mod;
|
||||
put ')';
|
||||
put 'GO';
|
||||
run;
|
||||
|
||||
/* add extended properties for labels */
|
||||
data _null_;
|
||||
file &fref mod;
|
||||
length nm $64 lab $1024;
|
||||
set &colinfo (where=(upcase(memname)="&curds" and label ne '')) end=last;
|
||||
nm=cats("N'",tranwrd(name,"'","''"),"'");
|
||||
lab=cats("N'",tranwrd(label,"'","''"),"'");
|
||||
put ' ';
|
||||
put "EXEC sys.sp_addextendedproperty ";
|
||||
put " @name=N'MS_Description',@value=" lab ;
|
||||
put " ,@level0type=N'SCHEMA',@level0name=N'&schema' ";
|
||||
put " ,@level1type=N'TABLE',@level1name=N'&curds'";
|
||||
put " ,@level2type=N'COLUMN',@level2name=" nm ;
|
||||
if last then put 'GO';
|
||||
run;
|
||||
%end;
|
||||
%end;
|
||||
|
||||
%if &showlog=YES %then %do;
|
||||
options ps=max;
|
||||
data _null_;
|
||||
infile &fref;
|
||||
input;
|
||||
putlog _infile_;
|
||||
run;
|
||||
%end;
|
||||
|
||||
%mend;
|
||||
72
base/mp_getmaxvarlengths.sas
Executable file
72
base/mp_getmaxvarlengths.sas
Executable file
@@ -0,0 +1,72 @@
|
||||
/**
|
||||
@file mp_getmaxvarlengths.sas
|
||||
@brief Scans a dataset to find the max length of the variable values
|
||||
@details
|
||||
This macro will scan a base dataset and produce an output dataset with two
|
||||
columns:
|
||||
|
||||
- NAME Name of the base dataset column
|
||||
- MAXLEN Maximum length of the data contained therein.
|
||||
|
||||
Character fields may be allocated very large widths (eg 32000) of which the maximum
|
||||
value is likely to be much narrower. This macro was designed to enable a HTML
|
||||
table to be appropriately sized however this could be used as part of a data
|
||||
audit to ensure we aren't over-sizing our tables in relation to the data therein.
|
||||
|
||||
Numeric fields are converted using the relevant format to determine the width.
|
||||
Usage:
|
||||
|
||||
%mp_getmaxvarlengths(sashelp.class,outds=work.myds)
|
||||
|
||||
@param libds Two part dataset (or view) reference.
|
||||
@param outds= The output dataset to create
|
||||
|
||||
<h4> Dependencies </h4>
|
||||
@li mf_getvarlist.sas
|
||||
@li mf_getvartype.sas
|
||||
@li mf_getvarformat.sas
|
||||
|
||||
@version 9.2
|
||||
@author Allan Bowe
|
||||
|
||||
**/
|
||||
|
||||
%macro mp_getmaxvarlengths(
|
||||
libds /* libref.dataset to analyse */
|
||||
,outds=work.mp_getmaxvarlengths /* name of output dataset to create */
|
||||
)/*/STORE SOURCE*/;
|
||||
|
||||
%local vars x var fmt;
|
||||
%let vars=%mf_getvarlist(libds=&libds);
|
||||
|
||||
proc sql;
|
||||
create table &outds (rename=(
|
||||
%do x=1 %to %sysfunc(countw(&vars,%str( )));
|
||||
________&x=%scan(&vars,&x)
|
||||
%end;
|
||||
))
|
||||
as select
|
||||
%do x=1 %to %sysfunc(countw(&vars,%str( )));
|
||||
%let var=%scan(&vars,&x);
|
||||
%if &x>1 %then ,;
|
||||
%if %mf_getvartype(&libds,&var)=C %then %do;
|
||||
max(length(&var)) as ________&x
|
||||
%end;
|
||||
%else %do;
|
||||
%let fmt=%mf_getvarformat(&libds,&var);
|
||||
%put fmt=&fmt;
|
||||
%if %str(&fmt)=%str() %then %do;
|
||||
max(length(cats(&var))) as ________&x
|
||||
%end;
|
||||
%else %do;
|
||||
max(length(put(&var,&fmt))) as ________&x
|
||||
%end;
|
||||
%end;
|
||||
%end;
|
||||
from &libds;
|
||||
|
||||
proc transpose data=&outds
|
||||
out=&outds(rename=(_name_=NAME COL1=MAXLEN));
|
||||
run;
|
||||
|
||||
%mend;
|
||||
304
base/mp_guesspk.sas
Normal file
304
base/mp_guesspk.sas
Normal file
@@ -0,0 +1,304 @@
|
||||
/**
|
||||
@file mp_guesspk.sas
|
||||
@brief Guess the primary key of a table
|
||||
@details Tries to guess the primary key of a table based on the following logic:
|
||||
|
||||
* Columns with nulls are ignored
|
||||
* Return only column combinations that provide unique results
|
||||
* Start from one column, then move out to include composite keys of 2 to 6 columns
|
||||
|
||||
The library of the target should be assigned before using this macro.
|
||||
|
||||
Usage:
|
||||
|
||||
filename mc url "https://raw.githubusercontent.com/macropeople/macrocore/master/mc_all.sas";
|
||||
%inc mc;
|
||||
%mp_guesspk(sashelp.class,outds=classpks)
|
||||
|
||||
@param baseds The dataset to analyse
|
||||
@param outds= The output dataset to contain the possible PKs
|
||||
@param max_guesses= The total number of possible primary keys to generate. A
|
||||
table is likely to have multiple unlikely PKs, so no need to list them all. Default=3.
|
||||
@param min_rows= The minimum number of rows a table should have in order to try
|
||||
and guess the PK. Default=5.
|
||||
|
||||
<h4> Dependencies </h4>
|
||||
@li mf_getvarlist.sas
|
||||
@li mf_getuniquename.sas
|
||||
@li mf_nobs.sas
|
||||
|
||||
@version 9.3
|
||||
@author Allan Bowe
|
||||
|
||||
**/
|
||||
|
||||
%macro mp_guesspk(baseds
|
||||
,outds=mp_guesspk
|
||||
,max_guesses=3
|
||||
,min_rows=5
|
||||
)/*/STORE SOURCE*/;
|
||||
|
||||
/* declare local vars */
|
||||
%local var vars vcnt i j k l tmpvar tmpds rows posspks ppkcnt;
|
||||
%let vars=%mf_getvarlist(&baseds);
|
||||
%let vcnt=%sysfunc(countw(&vars));
|
||||
|
||||
%if &vcnt=0 %then %do;
|
||||
%put &sysmacroname: &baseds has no variables! Exiting.;
|
||||
%return;
|
||||
%end;
|
||||
|
||||
/* get null count and row count */
|
||||
%let tmpvar=%mf_getuniquename();
|
||||
proc sql noprint;
|
||||
create table _data_ as select
|
||||
count(*) as &tmpvar
|
||||
%do i=1 %to &vcnt;
|
||||
%let var=%scan(&vars,&i);
|
||||
,sum(case when &var is missing then 1 else 0 end) as &var
|
||||
%end;
|
||||
from &baseds;
|
||||
|
||||
/* transpose table and scan for not null cols */
|
||||
proc transpose;
|
||||
data _null_;
|
||||
set &syslast end=last;
|
||||
length vars $32767;
|
||||
retain vars ;
|
||||
if _name_="&tmpvar" then call symputx('rows',col1,'l');
|
||||
else if col1=0 then vars=catx(' ',vars,_name_);
|
||||
if last then call symputx('posspks',vars,'l');
|
||||
run;
|
||||
|
||||
%let ppkcnt=%sysfunc(countw(&posspks));
|
||||
%if &ppkcnt=0 %then %do;
|
||||
%put &sysmacroname: &baseds has no non-missing variables! Exiting.;
|
||||
%return;
|
||||
%end;
|
||||
|
||||
proc sort data=&baseds(keep=&posspks) out=_data_ noduprec;
|
||||
by _all_;
|
||||
run;
|
||||
%local pkds; %let pkds=&syslast;
|
||||
|
||||
%if &rows > %mf_nobs(&pkds) %then %do;
|
||||
%put &sysmacroname: &baseds has no combination of unique records! Exiting.;
|
||||
%return;
|
||||
%end;
|
||||
|
||||
/* now check cardinality */
|
||||
proc sql noprint;
|
||||
create table _data_ as select
|
||||
%do i=1 %to &ppkcnt;
|
||||
%let var=%scan(&posspks,&i);
|
||||
count(distinct &var) as &var
|
||||
%if &i<&ppkcnt %then ,;
|
||||
%end;
|
||||
from &pkds;
|
||||
|
||||
/* transpose and sort by cardinality */
|
||||
proc transpose;
|
||||
proc sort; by descending col1;
|
||||
run;
|
||||
|
||||
/* create initial PK list and re-order posspks list */
|
||||
data &outds(keep=pkguesses);
|
||||
length pkguesses $5000 vars $5000;
|
||||
set &syslast end=last;
|
||||
retain vars ;
|
||||
vars=catx(' ',vars,_name_);
|
||||
if col1=&rows then do;
|
||||
pkguesses=_name_;
|
||||
output;
|
||||
end;
|
||||
if last then call symputx('posspks',vars,'l');
|
||||
run;
|
||||
|
||||
%if %mf_nobs(&outds) ge &max_guesses %then %do;
|
||||
%put &sysmacroname: %mf_nobs(&outds) possible primary key values found;
|
||||
%return;
|
||||
%end;
|
||||
|
||||
%if &ppkcnt=1 %then %do;
|
||||
%put &sysmacroname: No more PK guess possible;
|
||||
%return;
|
||||
%end;
|
||||
|
||||
/* begin scanning for uniques on pairs of PKs */
|
||||
%let tmpds=%mf_getuniquename();
|
||||
%local lev1 lev2;
|
||||
%do i=1 %to &ppkcnt;
|
||||
%let lev1=%scan(&posspks,&i);
|
||||
%do j=2 %to &ppkcnt;
|
||||
%let lev2=%scan(&posspks,&j);
|
||||
%if &lev1 ne &lev2 %then %do;
|
||||
/* check for two level uniqueness */
|
||||
proc sort data=&pkds(keep=&lev1 &lev2) out=&tmpds noduprec;
|
||||
by _all_;
|
||||
run;
|
||||
%if %mf_nobs(&tmpds)=&rows %then %do;
|
||||
proc sql;
|
||||
insert into &outds values("&lev1 &lev2");
|
||||
%if %mf_nobs(&outds) ge &max_guesses %then %do;
|
||||
%put &sysmacroname: Max PKs reached at Level 2 for &baseds;
|
||||
%return;
|
||||
%end;
|
||||
%end;
|
||||
%end;
|
||||
%end;
|
||||
%end;
|
||||
|
||||
%if &ppkcnt=2 %then %do;
|
||||
%put &sysmacroname: No more PK guess possible;
|
||||
%return;
|
||||
%end;
|
||||
|
||||
/* begin scanning for uniques on PK triplets */
|
||||
%local lev3;
|
||||
%do i=1 %to &ppkcnt;
|
||||
%let lev1=%scan(&posspks,&i);
|
||||
%do j=2 %to &ppkcnt;
|
||||
%let lev2=%scan(&posspks,&j);
|
||||
%if &lev1 ne &lev2 %then %do k=3 %to &ppkcnt;
|
||||
%let lev3=%scan(&posspks,&k);
|
||||
%if &lev1 ne &lev3 and &lev2 ne &lev3 %then %do;
|
||||
/* check for three level uniqueness */
|
||||
proc sort data=&pkds(keep=&lev1 &lev2 &lev3) out=&tmpds noduprec;
|
||||
by _all_;
|
||||
run;
|
||||
%if %mf_nobs(&tmpds)=&rows %then %do;
|
||||
proc sql;
|
||||
insert into &outds values("&lev1 &lev2 &lev3");
|
||||
%if %mf_nobs(&outds) ge &max_guesses %then %do;
|
||||
%put &sysmacroname: Max PKs reached at Level 3 for &baseds;
|
||||
%return;
|
||||
%end;
|
||||
%end;
|
||||
%end;
|
||||
%end;
|
||||
%end;
|
||||
%end;
|
||||
|
||||
%if &ppkcnt=3 %then %do;
|
||||
%put &sysmacroname: No more PK guess possible;
|
||||
%return;
|
||||
%end;
|
||||
|
||||
/* scan for uniques on up to 4 PK fields */
|
||||
%local lev4;
|
||||
%do i=1 %to &ppkcnt;
|
||||
%let lev1=%scan(&posspks,&i);
|
||||
%do j=2 %to &ppkcnt;
|
||||
%let lev2=%scan(&posspks,&j);
|
||||
%if &lev1 ne &lev2 %then %do k=3 %to &ppkcnt;
|
||||
%let lev3=%scan(&posspks,&k);
|
||||
%if &lev1 ne &lev3 and &lev2 ne &lev3 %then %do l=4 %to &ppkcnt;
|
||||
%let lev4=%scan(&posspks,&l);
|
||||
%if &lev1 ne &lev4 and &lev2 ne &lev4 and &lev3 ne &lev4 %then %do;
|
||||
/* check for four level uniqueness */
|
||||
proc sort data=&pkds(keep=&lev1 &lev2 &lev3 &lev4) out=&tmpds noduprec;
|
||||
by _all_;
|
||||
run;
|
||||
%if %mf_nobs(&tmpds)=&rows %then %do;
|
||||
proc sql;
|
||||
insert into &outds values("&lev1 &lev2 &lev3 &lev4");
|
||||
%if %mf_nobs(&outds) ge &max_guesses %then %do;
|
||||
%put &sysmacroname: Max PKs reached at Level 4 for &baseds;
|
||||
%return;
|
||||
%end;
|
||||
%end;
|
||||
%end;
|
||||
%end;
|
||||
%end;
|
||||
%end;
|
||||
%end;
|
||||
|
||||
%if &ppkcnt=4 %then %do;
|
||||
%put &sysmacroname: No more PK guess possible;
|
||||
%return;
|
||||
%end;
|
||||
|
||||
/* scan for uniques on up to 4 PK fields */
|
||||
%local lev5 m;
|
||||
%do i=1 %to &ppkcnt;
|
||||
%let lev1=%scan(&posspks,&i);
|
||||
%do j=2 %to &ppkcnt;
|
||||
%let lev2=%scan(&posspks,&j);
|
||||
%if &lev1 ne &lev2 %then %do k=3 %to &ppkcnt;
|
||||
%let lev3=%scan(&posspks,&k);
|
||||
%if &lev1 ne &lev3 and &lev2 ne &lev3 %then %do l=4 %to &ppkcnt;
|
||||
%let lev4=%scan(&posspks,&l);
|
||||
%if &lev1 ne &lev4 and &lev2 ne &lev4 and &lev3 ne &lev4 %then
|
||||
%do m=5 %to &ppkcnt;
|
||||
%let lev5=%scan(&posspks,&m);
|
||||
%if &lev1 ne &lev5 & &lev2 ne &lev5 & &lev3 ne &lev5 & &lev4 ne &lev5 %then %do;
|
||||
/* check for four level uniqueness */
|
||||
proc sort data=&pkds(keep=&lev1 &lev2 &lev3 &lev4 &lev5) out=&tmpds noduprec;
|
||||
by _all_;
|
||||
run;
|
||||
%if %mf_nobs(&tmpds)=&rows %then %do;
|
||||
proc sql;
|
||||
insert into &outds values("&lev1 &lev2 &lev3 &lev4 &lev5");
|
||||
%if %mf_nobs(&outds) ge &max_guesses %then %do;
|
||||
%put &sysmacroname: Max PKs reached at Level 5 for &baseds;
|
||||
%return;
|
||||
%end;
|
||||
%end;
|
||||
%end;
|
||||
%end;
|
||||
%end;
|
||||
%end;
|
||||
%end;
|
||||
%end;
|
||||
|
||||
%if &ppkcnt=5 %then %do;
|
||||
%put &sysmacroname: No more PK guess possible;
|
||||
%return;
|
||||
%end;
|
||||
|
||||
/* scan for uniques on up to 4 PK fields */
|
||||
%local lev6 n;
|
||||
%do i=1 %to &ppkcnt;
|
||||
%let lev1=%scan(&posspks,&i);
|
||||
%do j=2 %to &ppkcnt;
|
||||
%let lev2=%scan(&posspks,&j);
|
||||
%if &lev1 ne &lev2 %then %do k=3 %to &ppkcnt;
|
||||
%let lev3=%scan(&posspks,&k);
|
||||
%if &lev1 ne &lev3 and &lev2 ne &lev3 %then %do l=4 %to &ppkcnt;
|
||||
%let lev4=%scan(&posspks,&l);
|
||||
%if &lev1 ne &lev4 and &lev2 ne &lev4 and &lev3 ne &lev4 %then
|
||||
%do m=5 %to &ppkcnt;
|
||||
%let lev5=%scan(&posspks,&m);
|
||||
%if &lev1 ne &lev5 & &lev2 ne &lev5 & &lev3 ne &lev5 & &lev4 ne &lev5 %then
|
||||
%do n=6 %to &ppkcnt;
|
||||
%let lev6=%scan(&posspks,&n);
|
||||
%if &lev1 ne &lev6 & &lev2 ne &lev6 & &lev3 ne &lev6
|
||||
& &lev4 ne &lev6 & &lev5 ne &lev6 %then
|
||||
%do;
|
||||
/* check for four level uniqueness */
|
||||
proc sort data=&pkds(keep=&lev1 &lev2 &lev3 &lev4 &lev5 &lev6)
|
||||
out=&tmpds noduprec;
|
||||
by _all_;
|
||||
run;
|
||||
%if %mf_nobs(&tmpds)=&rows %then %do;
|
||||
proc sql;
|
||||
insert into &outds values("&lev1 &lev2 &lev3 &lev4 &lev5 &lev6");
|
||||
%if %mf_nobs(&outds) ge &max_guesses %then %do;
|
||||
%put &sysmacroname: Max PKs reached at Level 6 for &baseds;
|
||||
%return;
|
||||
%end;
|
||||
%end;
|
||||
%end;
|
||||
%end;
|
||||
%end;
|
||||
%end;
|
||||
%end;
|
||||
%end;
|
||||
%end;
|
||||
|
||||
%if &ppkcnt=6 %then %do;
|
||||
%put &sysmacroname: No more PK guess possible;
|
||||
%return;
|
||||
%end;
|
||||
|
||||
%mend;
|
||||
163
base/mp_jsonout.sas
Normal file
163
base/mp_jsonout.sas
Normal file
@@ -0,0 +1,163 @@
|
||||
/**
|
||||
@file mp_jsonout.sas
|
||||
@brief Writes JSON in SASjs format to a fileref
|
||||
@details PROC JSON is faster but will produce errs like the ones below if
|
||||
special chars are encountered.
|
||||
|
||||
>An object or array close is not valid at this point in the JSON text.
|
||||
>Date value out of range
|
||||
|
||||
If this happens, try running with ENGINE=DATASTEP.
|
||||
|
||||
Usage:
|
||||
|
||||
filename tmp temp;
|
||||
data class; set sashelp.class;run;
|
||||
|
||||
%mp_jsonout(OBJ,class,jref=tmp)
|
||||
|
||||
data _null_;
|
||||
infile tmp;
|
||||
input;list;
|
||||
run;
|
||||
|
||||
If you are building web apps with SAS then you are strongly encouraged to use
|
||||
the mX_createwebservice macros in combination with [sasjs](https://github.com/macropeople/sasjs).
|
||||
For more information see https://sasjs.io
|
||||
|
||||
@param action Valid values:
|
||||
* OPEN - opens the JSON
|
||||
* OBJ - sends a table with each row as an object
|
||||
* ARR - sends a table with each row in an array
|
||||
* CLOSE - closes the JSON
|
||||
|
||||
@param ds the dataset to send. Must be a work table.
|
||||
@param jref= the fileref to which to send the JSON
|
||||
@param dslabel= the name to give the table in the exported JSON
|
||||
@param fmt= Whether to keep or strip formats from the table
|
||||
@param engine= Which engine to use to send the JSON, options are:
|
||||
* PROCJSON (default)
|
||||
* DATASTEP
|
||||
|
||||
@param dbg= Typically used with an _debug (numeric) option
|
||||
|
||||
@version 9.2
|
||||
@author Allan Bowe
|
||||
@source https://github.com/macropeople/macrocore
|
||||
|
||||
**/
|
||||
|
||||
%macro mp_jsonout(action,ds,jref=_webout,dslabel=,fmt=Y,engine=PROCJSON,dbg=0
|
||||
)/*/STORE SOURCE*/;
|
||||
%put output location=&jref;
|
||||
%if &action=OPEN %then %do;
|
||||
data _null_;file &jref encoding='utf-8';
|
||||
put '{"START_DTTM" : "' "%sysfunc(datetime(),datetime20.3)" '"';
|
||||
run;
|
||||
%end;
|
||||
%else %if (&action=ARR or &action=OBJ) %then %do;
|
||||
options validvarname=upcase;
|
||||
data _null_;file &jref mod encoding='utf-8';
|
||||
put ", ""%lowcase(%sysfunc(coalescec(&dslabel,&ds)))"":";
|
||||
|
||||
%if &engine=PROCJSON %then %do;
|
||||
data;run;%let tempds=&syslast;
|
||||
proc sql;drop table &tempds;
|
||||
data &tempds /view=&tempds;set &ds;
|
||||
%if &fmt=N %then format _numeric_ best32.;;
|
||||
proc json out=&jref
|
||||
%if &action=ARR %then nokeys ;
|
||||
%if &dbg ge 131 %then pretty ;
|
||||
;export &tempds / nosastags fmtnumeric;
|
||||
run;
|
||||
proc sql;drop view &tempds;
|
||||
%end;
|
||||
%else %if &engine=DATASTEP %then %do;
|
||||
%local cols i tempds;
|
||||
%let cols=0;
|
||||
%if %sysfunc(exist(&ds)) ne 1 & %sysfunc(exist(&ds,VIEW)) ne 1 %then %do;
|
||||
%put &sysmacroname: &ds NOT FOUND!!!;
|
||||
%return;
|
||||
%end;
|
||||
data _null_;file &jref mod ;
|
||||
put "["; call symputx('cols',0,'l');
|
||||
proc sort data=sashelp.vcolumn(where=(libname='WORK' & memname="%upcase(&ds)"))
|
||||
out=_data_;
|
||||
by varnum;
|
||||
|
||||
data _null_;
|
||||
set _last_ end=last;
|
||||
call symputx(cats('name',_n_),name,'l');
|
||||
call symputx(cats('type',_n_),type,'l');
|
||||
call symputx(cats('len',_n_),length,'l');
|
||||
if last then call symputx('cols',_n_,'l');
|
||||
run;
|
||||
|
||||
proc format; /* credit yabwon for special null removal */
|
||||
value bart ._ - .z = null
|
||||
other = [best.];
|
||||
|
||||
data;run; %let tempds=&syslast; /* temp table for spesh char management */
|
||||
proc sql; drop table &tempds;
|
||||
data &tempds/view=&tempds;
|
||||
attrib _all_ label='';
|
||||
%do i=1 %to &cols;
|
||||
%if &&type&i=char %then %do;
|
||||
length &&name&i $32767;
|
||||
format &&name&i $32767.;
|
||||
%end;
|
||||
%end;
|
||||
set &ds;
|
||||
format _numeric_ bart.;
|
||||
%do i=1 %to &cols;
|
||||
%if &&type&i=char %then %do;
|
||||
&&name&i='"'!!trim(prxchange('s/"/\"/',-1,
|
||||
prxchange('s/'!!'0A'x!!'/\n/',-1,
|
||||
prxchange('s/'!!'0D'x!!'/\r/',-1,
|
||||
prxchange('s/'!!'09'x!!'/\t/',-1,
|
||||
prxchange('s/\\/\\\\/',-1,&&name&i)
|
||||
)))))!!'"';
|
||||
%end;
|
||||
%end;
|
||||
run;
|
||||
/* write to temp loc to avoid _webout truncation - https://support.sas.com/kb/49/325.html */
|
||||
filename _sjs temp lrecl=131068 encoding='utf-8';
|
||||
data _null_; file _sjs lrecl=131068 encoding='utf-8' mod;
|
||||
set &tempds;
|
||||
if _n_>1 then put "," @; put
|
||||
%if &action=ARR %then "[" ; %else "{" ;
|
||||
%do i=1 %to &cols;
|
||||
%if &i>1 %then "," ;
|
||||
%if &action=OBJ %then """&&name&i"":" ;
|
||||
&&name&i
|
||||
%end;
|
||||
%if &action=ARR %then "]" ; %else "}" ; ;
|
||||
proc sql;
|
||||
drop view &tempds;
|
||||
/* now write the long strings to _webout 1 byte at a time */
|
||||
data _null_;
|
||||
length filein 8 fileid 8;
|
||||
filein = fopen("_sjs",'I',1,'B');
|
||||
fileid = fopen("&jref",'A',1,'B');
|
||||
rec = '20'x;
|
||||
do while(fread(filein)=0);
|
||||
rc = fget(filein,rec,1);
|
||||
rc = fput(fileid, rec);
|
||||
rc =fwrite(fileid);
|
||||
end;
|
||||
rc = fclose(filein);
|
||||
rc = fclose(fileid);
|
||||
run;
|
||||
filename _sjs clear;
|
||||
data _null_; file &jref mod encoding='utf-8';
|
||||
put "]";
|
||||
run;
|
||||
%end;
|
||||
%end;
|
||||
|
||||
%else %if &action=CLOSE %then %do;
|
||||
data _null_;file &jref encoding='utf-8';
|
||||
put "}";
|
||||
run;
|
||||
%end;
|
||||
%mend;
|
||||
51
base/mp_lib2cards.sas
Normal file
51
base/mp_lib2cards.sas
Normal file
@@ -0,0 +1,51 @@
|
||||
/**
|
||||
@file
|
||||
@brief Convert all library members to CARDS files
|
||||
@details Gets list of members then calls the <code>%mp_ds2cards()</code>
|
||||
macro
|
||||
usage:
|
||||
|
||||
%mp_lib2cards(lib=sashelp
|
||||
, outloc= C:\temp )
|
||||
|
||||
<h4> Dependencies </h4>
|
||||
@li mf_mkdir.sas
|
||||
@li mp_ds2cards.sas
|
||||
|
||||
@param lib= Library in which to convert all datasets
|
||||
@param outloc= Location in which to store output. Defaults to WORK library.
|
||||
Do not use a trailing slash (my/path not my/path/). No quotes.
|
||||
@param maxobs= limit output to the first <code>maxobs</code> observations
|
||||
|
||||
@version 9.2
|
||||
@author Allan Bowe
|
||||
**/
|
||||
|
||||
%macro mp_lib2cards(lib=
|
||||
,outloc=%sysfunc(pathname(work)) /* without trailing slash */
|
||||
,maxobs=max
|
||||
,random_sample=NO
|
||||
)/*/STORE SOURCE*/;
|
||||
|
||||
/* Find the tables */
|
||||
%local x ds memlist;
|
||||
proc sql noprint;
|
||||
select distinct lowcase(memname)
|
||||
into: memlist
|
||||
separated by ' '
|
||||
from dictionary.tables
|
||||
where upcase(libname)="%upcase(&lib)";
|
||||
|
||||
/* create the output directory */
|
||||
%mf_mkdir(&outloc)
|
||||
|
||||
/* create the cards files */
|
||||
%do x=1 %to %sysfunc(countw(&memlist));
|
||||
%let ds=%scan(&memlist,&x);
|
||||
%mp_ds2cards(base_ds=&lib..&ds
|
||||
,cards_file="&outloc/&ds..sas"
|
||||
,maxobs=&maxobs
|
||||
,random_sample=&random_sample)
|
||||
%end;
|
||||
|
||||
%mend;
|
||||
42
base/mp_perflog.sas
Normal file
42
base/mp_perflog.sas
Normal file
@@ -0,0 +1,42 @@
|
||||
/**
|
||||
@file
|
||||
@brief Logs the time the macro was executed in a control dataset.
|
||||
@details If the dataset does not exist, it is created. Usage:
|
||||
|
||||
%mp_perflog(started)
|
||||
%mp_perflog()
|
||||
%mp_perflog(startanew,libds=work.newdataset)
|
||||
%mp_perflog(finished,libds=work.newdataset)
|
||||
%mp_perflog(finished)
|
||||
|
||||
|
||||
@param label Provide label to go into the control dataset
|
||||
@param libds= Provide a dataset in which to store performance stats. Default
|
||||
name is <code>work.mp_perflog</code>;
|
||||
|
||||
@version 9.2
|
||||
@author Allan Bowe
|
||||
@source https://github.com/macropeople/macrocore
|
||||
|
||||
**/
|
||||
|
||||
%macro mp_perflog(label,libds=work.mp_perflog
|
||||
)/*/STORE SOURCE*/;
|
||||
|
||||
%if not (%mf_existds(&libds)) %then %do;
|
||||
data &libds;
|
||||
length sysjobid $10 label $256 dttm 8.;
|
||||
format dttm datetime19.3;
|
||||
call missing(of _all_);
|
||||
stop;
|
||||
run;
|
||||
%end;
|
||||
|
||||
proc sql;
|
||||
insert into &libds
|
||||
set sysjobid="&sysjobid"
|
||||
,label=symget('label')
|
||||
,dttm=%sysfunc(datetime());
|
||||
quit;
|
||||
|
||||
%mend;
|
||||
90
base/mp_recursivejoin.sas
Normal file
90
base/mp_recursivejoin.sas
Normal file
@@ -0,0 +1,90 @@
|
||||
/**
|
||||
@file
|
||||
@brief Returns all children from a hierarchy table for a specified parent
|
||||
@details Where data stores hierarchies in a simple parent / child mapping,
|
||||
it is not always straightforward to extract all the children for a
|
||||
particular parent. This problem is known as a recursive self join. This
|
||||
macro will extract all the descendents for a parent.
|
||||
Usage:
|
||||
|
||||
data have;
|
||||
p=1;c=2;output;
|
||||
p=2;c=3;output;
|
||||
p=2;c=4;output;
|
||||
p=3;c=5;output;
|
||||
p=6;c=7;output;
|
||||
p=8;c=9;output;
|
||||
run;
|
||||
|
||||
%mp_recursivejoin(base_ds=have
|
||||
,outds=want
|
||||
,matchval=1
|
||||
,parentvar=p
|
||||
,childvar=c
|
||||
)
|
||||
|
||||
@param base_ds= base table containing hierarchy (not modified)
|
||||
@param outds= the output dataset to create with the generated hierarchy
|
||||
@param matchval= the ultimate parent from which to filter
|
||||
@param parentvar= name of the parent variable
|
||||
@param childvar= name of the child variable (should be same type as parent)
|
||||
@param mdebug= set to 1 to prevent temp tables being dropped
|
||||
|
||||
|
||||
@returns outds contains the following variables:
|
||||
- level (0 = top level)
|
||||
- &parentvar
|
||||
- &childvar (null if none found)
|
||||
|
||||
@version 9.2
|
||||
@author Allan Bowe
|
||||
|
||||
**/
|
||||
|
||||
%macro mp_recursivejoin(base_ds=
|
||||
,outds=
|
||||
,matchval=
|
||||
,parentvar=
|
||||
,childvar=
|
||||
,iter= /* reserved for internal / recursive use by the macro itself */
|
||||
,maxiter=500 /* avoid infinite loop */
|
||||
,mDebug=0);
|
||||
|
||||
%if &iter= %then %do;
|
||||
proc sql;
|
||||
create table &outds as
|
||||
select 0 as level,&parentvar, &childvar
|
||||
from &base_ds
|
||||
where &parentvar=&matchval;
|
||||
%if &sqlobs.=0 %then %do;
|
||||
%put NOTE: &sysmacroname: No match for &parentvar=&matchval;
|
||||
%return;
|
||||
%end;
|
||||
%let iter=1;
|
||||
%end;
|
||||
%else %if &iter>&maxiter %then %return;
|
||||
|
||||
proc sql;
|
||||
create table _data_ as
|
||||
select &iter as level
|
||||
,curr.&childvar as &parentvar
|
||||
,base_ds.&childvar as &childvar
|
||||
from &outds curr
|
||||
left join &base_ds base_ds
|
||||
on curr.&childvar=base_ds.&parentvar
|
||||
where curr.level=%eval(&iter.-1)
|
||||
& curr.&childvar is not null;
|
||||
%local append_ds; %let append_ds=&syslast;
|
||||
%local obs; %let obs=&sqlobs;
|
||||
insert into &outds select distinct * from &append_ds;
|
||||
%if &mdebug=0 %then drop table &append_ds;;
|
||||
|
||||
%if &obs %then %do;
|
||||
%mp_recursivejoin(iter=%eval(&iter.+1)
|
||||
,outds=&outds,parentvar=&parentvar
|
||||
,childvar=&childvar
|
||||
,base_ds=&base_ds
|
||||
)
|
||||
%end;
|
||||
|
||||
%mend;
|
||||
32
base/mp_resetoption.sas
Normal file
32
base/mp_resetoption.sas
Normal file
@@ -0,0 +1,32 @@
|
||||
/**
|
||||
@file
|
||||
@brief Reset an option to original value
|
||||
@details Inspired by the SAS Jedi - https://blogs.sas.com/content/sastraining/2012/08/14/jedi-sas-tricks-reset-sas-system-options/
|
||||
Called as follows:
|
||||
|
||||
options obs=30;
|
||||
%mp_resetoption(OBS)
|
||||
|
||||
|
||||
@param option the option to reset
|
||||
|
||||
@version 9.2
|
||||
@author Allan Bowe
|
||||
|
||||
**/
|
||||
|
||||
%macro mp_resetoption(option /* the option to reset */
|
||||
)/*/STORE SOURCE*/;
|
||||
|
||||
data _null_;
|
||||
length code $1500;
|
||||
startup=getoption("&option",'startupvalue');
|
||||
current=getoption("&option");
|
||||
if startup ne current then do;
|
||||
code =cat('OPTIONS ',getoption("&option",'keyword','startupvalue'),';');
|
||||
putlog "NOTE: Resetting system option: " code ;
|
||||
call execute(code );
|
||||
end;
|
||||
run;
|
||||
|
||||
%mend;
|
||||
49
base/mp_runddl.sas
Normal file
49
base/mp_runddl.sas
Normal file
@@ -0,0 +1,49 @@
|
||||
/**
|
||||
@file mp_runddl.sas
|
||||
@brief An opinionated way to execute DDL files in SAS.
|
||||
@details When delivering projects there should be seperation between the DDL
|
||||
used to generate the tables and the sample data used to populate them.
|
||||
|
||||
This macro expects certain folder structure - eg:
|
||||
|
||||
rootlib
|
||||
|-- LIBREF1
|
||||
| |__ mytable.ddl
|
||||
| |__ someothertable.ddl
|
||||
|-- LIBREF2
|
||||
| |__ table1.ddl
|
||||
| |__ table2.ddl
|
||||
|-- LIBREF3
|
||||
|__ table3.ddl
|
||||
|__ table4.ddl
|
||||
|
||||
Only files with the .ddl suffix are executed. The parent folder name is used
|
||||
as the libref.
|
||||
Files should NOT contain the `proc sql` statement - this is to prevent
|
||||
statements being executed if there is an err condition.
|
||||
|
||||
Usage:
|
||||
|
||||
%mp_runddl(/some/rootlib) * execute all libs ;
|
||||
|
||||
%mp_runddl(/some/rootlib, inc=LIBREF1 LIBREF2) * include only these libs;
|
||||
|
||||
%mp_runddl(/some/rootlib, exc=LIBREF3) * same as above ;
|
||||
|
||||
|
||||
@param path location of the DDL folder structure
|
||||
@param inc= list of librefs to include
|
||||
@param exc= list of librefs to exclude (takes precedence over inc=)
|
||||
|
||||
@version 9.3
|
||||
@author Allan Bowe
|
||||
@source https://github.com/macropeople/macrocore
|
||||
|
||||
**/
|
||||
|
||||
%macro mp_runddl(path, inc=, exc=
|
||||
)/*/STORE SOURCE*/;
|
||||
|
||||
|
||||
|
||||
%mend;
|
||||
71
base/mp_searchcols.sas
Normal file
71
base/mp_searchcols.sas
Normal file
@@ -0,0 +1,71 @@
|
||||
/**
|
||||
@file mp_searchcols.sas
|
||||
@brief Searches all columns in a library
|
||||
@details
|
||||
Scans a set of libraries and creates a dataset containing all source tables
|
||||
containing one or more of a particular set of columns
|
||||
|
||||
Usage:
|
||||
|
||||
%mp_searchcols(libs=sashelp work, cols=name sex age)
|
||||
|
||||
@param libs=
|
||||
@version 9.2
|
||||
@author Allan Bowe
|
||||
**/
|
||||
|
||||
%macro mp_searchcols(libs=sashelp
|
||||
,cols=
|
||||
,outds=mp_searchcols
|
||||
)/*/STORE SOURCE*/;
|
||||
|
||||
%put &sysmacroname process began at %sysfunc(datetime(),datetime19.);
|
||||
|
||||
/* get the list of tables in the library */
|
||||
proc sql;
|
||||
create table _data_ as
|
||||
select distinct upcase(libname) as libname
|
||||
, upcase(memname) as memname
|
||||
, upcase(name) as name
|
||||
from dictionary.columns
|
||||
%if %sysevalf(%superq(libs)=,boolean)=0 %then %do;
|
||||
where upcase(libname) in ("IMPOSSIBLE",
|
||||
%local x;
|
||||
%do x=1 %to %sysfunc(countw(&libs));
|
||||
"%upcase(%scan(&libs,&x))"
|
||||
%end;
|
||||
)
|
||||
%end;
|
||||
order by 1,2,3;
|
||||
|
||||
data &outds;
|
||||
set &syslast;
|
||||
length cols matchcols $32767;
|
||||
cols=upcase(symget('cols'));
|
||||
colcount=countw(cols);
|
||||
by libname memname name;
|
||||
if _n_=1 then do;
|
||||
putlog "Searching libs: &libs";
|
||||
putlog "Searching cols: " cols;
|
||||
end;
|
||||
if first.memname then do;
|
||||
sumcols=0;
|
||||
retain matchcols;
|
||||
matchcols='';
|
||||
end;
|
||||
if findw(cols,name,,'spit') then do;
|
||||
sumcols+1;
|
||||
matchcols=cats(matchcols)!!' '!!cats(name);
|
||||
end;
|
||||
if last.memname then do;
|
||||
if sumcols>0 then output;
|
||||
if sumcols=colcount then putlog "Full Match: " libname memname;
|
||||
end;
|
||||
keep libname memname sumcols matchcols;
|
||||
run;
|
||||
|
||||
proc sort; by descending sumcols memname libname; run;
|
||||
|
||||
%put &sysmacroname process finished at %sysfunc(datetime(),datetime19.);
|
||||
|
||||
%mend;
|
||||
107
base/mp_searchdata.sas
Normal file
107
base/mp_searchdata.sas
Normal file
@@ -0,0 +1,107 @@
|
||||
/**
|
||||
@file
|
||||
@brief Searches all data in a library
|
||||
@details
|
||||
Scans an entire library and creates a copy of any table
|
||||
containing a specific string or numeric value. Only
|
||||
matching records are written out.
|
||||
If both a string and numval are provided, the string
|
||||
will take precedence.
|
||||
|
||||
Usage:
|
||||
|
||||
%mp_searchdata(lib=sashelp, string=Jan)
|
||||
%mp_searchdata(lib=sashelp, numval=1)
|
||||
|
||||
|
||||
Outputs zero or more tables to an MPSEARCH library with specific records.
|
||||
|
||||
@param lib= the libref to search (should be already assigned)
|
||||
@param ds= the dataset to search (leave blank to search entire library)
|
||||
@param string= the string value to search
|
||||
@param numval= the numeric value to search (must be exact)
|
||||
@param outloc= the directory in which to create the output datasets with matching
|
||||
rows. Will default to a subfolder in the WORK library.
|
||||
@param outobs= set to a positive integer to restrict the number of observations
|
||||
@param filter_text= add a (valid) filter clause to further filter the results
|
||||
|
||||
<h4> Dependencies </h4>
|
||||
@li mf_getvarlist.sas
|
||||
@li mf_getvartype.sas
|
||||
@li mf_mkdir.sas
|
||||
@li mf_nobs.sas
|
||||
|
||||
@version 9.2
|
||||
@author Allan Bowe
|
||||
**/
|
||||
|
||||
%macro mp_searchdata(lib=sashelp
|
||||
,ds=
|
||||
,string= /* the query will use a contains (?) operator */
|
||||
,numval= /* numeric must match exactly */
|
||||
,outloc=%sysfunc(pathname(work))/mpsearch
|
||||
,outobs=-1
|
||||
,filter_text=%str(1=1)
|
||||
)/*/STORE SOURCE*/;
|
||||
|
||||
%local table_list table table_num table colnum col start_tm vars type coltype;
|
||||
%put process began at %sysfunc(datetime(),datetime19.);
|
||||
|
||||
|
||||
%if &string = %then %let type=N;
|
||||
%else %let type=C;
|
||||
|
||||
%mf_mkdir(&outloc)
|
||||
libname mpsearch "&outloc";
|
||||
|
||||
/* get the list of tables in the library */
|
||||
proc sql noprint;
|
||||
select distinct memname into: table_list separated by ' '
|
||||
from dictionary.tables
|
||||
where upcase(libname)="%upcase(&lib)"
|
||||
%if &ds ne %then %do;
|
||||
and upcase(memname)=%upcase("&ds")
|
||||
%end;
|
||||
;
|
||||
/* check that we have something to check */
|
||||
proc sql
|
||||
%if &outobs>-1 %then %do;
|
||||
outobs=&outobs
|
||||
%end;
|
||||
;
|
||||
%if %length(&table_list)=0 %then %put library &lib contains no tables!;
|
||||
/* loop through each table */
|
||||
%else %do table_num=1 %to %sysfunc(countw(&table_list,%str( )));
|
||||
%let table=%scan(&table_list,&table_num,%str( ));
|
||||
%let vars=%mf_getvarlist(&lib..&table);
|
||||
%if %length(&vars)=0 %then %do;
|
||||
%put NO COLUMNS IN &lib..&table! This will be skipped.;
|
||||
%end;
|
||||
%else %do;
|
||||
/* build sql statement */
|
||||
create table mpsearch.&table as select * from &lib..&table
|
||||
where %unquote(&filter_text) and
|
||||
(0
|
||||
/* loop through columns */
|
||||
%do colnum=1 %to %sysfunc(countw(&vars,%str( )));
|
||||
%let col=%scan(&vars,&colnum,%str( ));
|
||||
%let coltype=%mf_getvartype(&lib..&table,&col);
|
||||
%if &type=C and &coltype=C %then %do;
|
||||
/* if a char column, see if it contains the string */
|
||||
or (&col ? "&string")
|
||||
%end;
|
||||
%else %if &type=N and &coltype=N %then %do;
|
||||
/* if numeric match exactly */
|
||||
or (&col = &numval)
|
||||
%end;
|
||||
%end;
|
||||
);
|
||||
%if %mf_nobs(mpsearch.&table)=0 %then %do;
|
||||
drop table mpsearch.&table;
|
||||
%end;
|
||||
%end;
|
||||
%end;
|
||||
|
||||
%put process finished at %sysfunc(datetime(),datetime19.);
|
||||
|
||||
%mend;
|
||||
52
base/mp_setkeyvalue.sas
Normal file
52
base/mp_setkeyvalue.sas
Normal file
@@ -0,0 +1,52 @@
|
||||
/**
|
||||
@file
|
||||
@brief Logs a key value pair a control dataset
|
||||
@details If the dataset does not exist, it is created. Usage:
|
||||
|
||||
%mp_setkeyvalue(someindex,22,type=N)
|
||||
%mp_setkeyvalue(somenewindex,somevalue)
|
||||
|
||||
<h4> Dependencies </h4>
|
||||
@li mf_existds.sas
|
||||
|
||||
@param key Provide a key on which to perform the lookup
|
||||
@param value Provide a value
|
||||
@param type= either C or N will populate valc and valn respectively. C is
|
||||
default.
|
||||
@param libds= define the target table to hold the parameters
|
||||
|
||||
@version 9.2
|
||||
@author Allan Bowe
|
||||
@source https://github.com/macropeople/macrocore
|
||||
|
||||
**/
|
||||
|
||||
%macro mp_setkeyvalue(key,value,type=C,libds=work.mp_setkeyvalue
|
||||
)/*/STORE SOURCE*/;
|
||||
|
||||
%if not (%mf_existds(&libds)) %then %do;
|
||||
data &libds (index=(key/unique));
|
||||
length key $32 valc $256 valn 8 type $1;
|
||||
call missing(of _all_);
|
||||
stop;
|
||||
run;
|
||||
%end;
|
||||
|
||||
proc sql;
|
||||
delete from &libds
|
||||
where key=symget('key');
|
||||
insert into &libds
|
||||
set key=symget('key')
|
||||
%if &type=C %then %do;
|
||||
,valc=symget('value')
|
||||
,type='C'
|
||||
%end;
|
||||
%else %do;
|
||||
,valn=symgetn('value')
|
||||
,type='N'
|
||||
%end;
|
||||
;
|
||||
|
||||
quit;
|
||||
|
||||
%mend;
|
||||
73
base/mp_stprequests.sas
Normal file
73
base/mp_stprequests.sas
Normal file
@@ -0,0 +1,73 @@
|
||||
/**
|
||||
@file
|
||||
@brief Capture session start / finish times and request details
|
||||
@details For details, see http://www.rawsas.com/2015/09/logging-of-stored-process-server.html.
|
||||
Requires a base table in the following structure (name can be changed):
|
||||
|
||||
proc sql;
|
||||
create table &libds(
|
||||
request_dttm num not null format=datetime.
|
||||
,status_cd char(4) not null
|
||||
,_metaperson varchar(100) not null
|
||||
,_program varchar(500)
|
||||
,sysuserid varchar(50)
|
||||
,sysjobid varchar(12)
|
||||
,_sessionid varchar(50)
|
||||
);
|
||||
|
||||
Called via STP init / term events (configurable in SMC) as follows:
|
||||
|
||||
%mp_stprequests(status_cd=INIT, libds=YOURLIB.DATASET )
|
||||
|
||||
|
||||
@param status_cd= Use INIT for INIT and TERM for TERM events
|
||||
@param libds= Location of base table (library.dataset). To minimise risk
|
||||
of table locks, we HIGHLY recommend using a database (NOT a SAS dataset).
|
||||
THE LIBRARY SHOULD BE ASSIGNED ALREADY - eg in autoexec or earlier in the
|
||||
init program proper.
|
||||
|
||||
@version 9.2
|
||||
@author Allan Bowe
|
||||
@source https://github.com/macropeople/macrocore
|
||||
|
||||
**/
|
||||
|
||||
%macro mp_stprequests(status_cd= /* $4 eg INIT or TERM */
|
||||
,libds=somelib.stp_requests /* base table location */
|
||||
)/*/STORE SOURCE*/;
|
||||
|
||||
/* set nosyntaxcheck so the code runs regardless */
|
||||
%local etls_syntaxcheck;
|
||||
%let etls_syntaxcheck=%sysfunc(getoption(syntaxcheck));
|
||||
options nosyntaxcheck;
|
||||
|
||||
data ;
|
||||
if 0 then set &libds;
|
||||
request_dttm=datetime();
|
||||
status_cd="&status_cd";
|
||||
_METAPERSON="&_metaperson";
|
||||
_PROGRAM="&_program";
|
||||
SYSUSERID="&sysuserid";
|
||||
SYSJOBID="&sysjobid";
|
||||
%if not %symexist(_SESSIONID) %then %do;
|
||||
/* session id is stored in the replay variable but needs to be extracted */
|
||||
_replay=symget('_replay');
|
||||
_replay=subpad(_replay,index(_replay,'_sessionid=')+11,length(_replay));
|
||||
index=index(_replay,'&')-1;
|
||||
if index=-1 then index=length(_replay);
|
||||
_replay=substr(_replay,1,index);
|
||||
_SESSIONID=_replay;
|
||||
drop _replay index;
|
||||
%end;
|
||||
%else %do;
|
||||
/* explicitly created sessions are automatically available */
|
||||
_SESSIONID=symget('_SESSIONID');
|
||||
%end;
|
||||
output;
|
||||
stop;
|
||||
run;
|
||||
|
||||
proc append base=&libds data=&syslast nowarn;run;
|
||||
|
||||
options &etls_syntaxcheck;
|
||||
%mend;
|
||||
101
base/mp_streamfile.sas
Normal file
101
base/mp_streamfile.sas
Normal file
@@ -0,0 +1,101 @@
|
||||
/**
|
||||
@file mp_streamfile.sas
|
||||
@brief Streams a file to _webout according to content type
|
||||
@details Will set headers using appropriate functions (SAS 9 vs Viya) and send
|
||||
content as a binary stream.
|
||||
|
||||
Usage:
|
||||
|
||||
filename mc url "https://raw.githubusercontent.com/macropeople/macrocore/master/mc_all.sas";
|
||||
%inc mc;
|
||||
|
||||
%mp_streamfile(contenttype=csv,inloc=/some/where.txt,outname=myfile.txt)
|
||||
|
||||
<h4> Dependencies </h4>
|
||||
@li mf_getplatform.sas
|
||||
@li mp_binarycopy.sas
|
||||
|
||||
@param contenttype= Either TEXT, ZIP, CSV, EXCEL (default TEXT)
|
||||
@param inloc= /path/to/file.ext to be sent
|
||||
@param outname= the name of the file, as downloaded by the browser
|
||||
|
||||
@author Allan Bowe
|
||||
@source https://github.com/macropeople/macrocore
|
||||
|
||||
**/
|
||||
|
||||
%macro mp_streamfile(
|
||||
contenttype=TEXT
|
||||
,inloc=
|
||||
,outname=
|
||||
)/*/STORE SOURCE*/;
|
||||
|
||||
%let contentype=%upcase(&contenttype);
|
||||
%local platform; %let platform=%mf_getplatform();
|
||||
|
||||
%if &contentype=ZIP %then %do;
|
||||
%if &platform=SASMETA %then %do;
|
||||
data _null_;
|
||||
rc=stpsrv_header('Content-type','application/zip');
|
||||
rc=stpsrv_header('Content-disposition',"attachment; filename=&outname");
|
||||
run;
|
||||
%end;
|
||||
%else %if &platform=SASVIYA %then %do;
|
||||
filename _webout filesrvc parenturi="&SYS_JES_JOB_URI" name='_webout.zip'
|
||||
contenttype='application/zip'
|
||||
contentdisp="attachment; filename=&outname";
|
||||
%end;
|
||||
%end;
|
||||
%else %if &contentype=EXCEL %then %do;
|
||||
%if &platform=SASMETA %then %do;
|
||||
data _null_;
|
||||
rc=stpsrv_header('Content-type','application/vnd.ms-excel');
|
||||
rc=stpsrv_header('Content-disposition',"attachment; filename=&outname");
|
||||
run;
|
||||
%end;
|
||||
%else %if &platform=SASVIYA %then %do;
|
||||
filename _webout filesrvc parenturi="&SYS_JES_JOB_URI" name='_webout.xls'
|
||||
contenttype='application/vnd.ms-excel'
|
||||
contentdisp="attachment; filename=&outname";
|
||||
%end;
|
||||
%end;
|
||||
%else %if &contentype=TEXT %then %do;
|
||||
%if &platform=SASMETA %then %do;
|
||||
data _null_;
|
||||
rc=stpsrv_header('Content-type','application/text');
|
||||
rc=stpsrv_header('Content-disposition',"attachment; filename=&outname");
|
||||
run;
|
||||
%end;
|
||||
%else %if &platform=SASVIYA %then %do;
|
||||
filename _webout filesrvc parenturi="&SYS_JES_JOB_URI" name='_webout.txt'
|
||||
contenttype='application/text'
|
||||
contentdisp="attachment; filename=&outname";
|
||||
%end;
|
||||
%end;
|
||||
%else %if &contentype=CSV %then %do;
|
||||
%if &platform=SASMETA %then %do;
|
||||
data _null_;
|
||||
rc=stpsrv_header('Content-type','application/csv');
|
||||
rc=stpsrv_header('Content-disposition',"attachment; filename=&outname");
|
||||
run;
|
||||
%end;
|
||||
%else %if &platform=SASVIYA %then %do;
|
||||
filename _webout filesrvc parenturi="&SYS_JES_JOB_URI" name='_webout.txt'
|
||||
contenttype='application/csv'
|
||||
contentdisp="attachment; filename=&outname";
|
||||
%end;
|
||||
%end;
|
||||
%else %if &contentype=HTML %then %do;
|
||||
%if &platform=SASVIYA %then %do;
|
||||
filename _webout filesrvc parenturi="&SYS_JES_JOB_URI" name="_webout.json"
|
||||
contenttype="text/html";
|
||||
%end;
|
||||
%end;
|
||||
%else %do;
|
||||
%put %str(ERR)OR: Content Type &contenttype NOT SUPPORTED by &sysmacroname!;
|
||||
%return;
|
||||
%end;
|
||||
|
||||
%mp_binarycopy(inloc="&inloc",outref=_webout)
|
||||
|
||||
%mend;
|
||||
66
base/mp_unzip.sas
Normal file
66
base/mp_unzip.sas
Normal file
@@ -0,0 +1,66 @@
|
||||
/**
|
||||
@file mp_unzip.sas
|
||||
@brief Unzips a zip file
|
||||
@details Opens the zip file and copies all the contents to another directory.
|
||||
It is not possible to retain permissions / timestamps, also the BOF marker
|
||||
is lost so it cannot extract binary files.
|
||||
|
||||
Usage:
|
||||
|
||||
filename mc url "https://raw.githubusercontent.com/macropeople/macrocore/master/mc_all.sas";
|
||||
%inc mc;
|
||||
|
||||
%mp_unzip(ziploc="/some/file.zip",outdir=/some/folder)
|
||||
|
||||
<h4> Dependencies </h4>
|
||||
@li mf_mkdir.sas
|
||||
@li mf_getuniquefileref.sas
|
||||
|
||||
@param ziploc= fileref or quoted full path to zip file ("/path/to/file.zip")
|
||||
@param outdir= directory in which to write the outputs (created if non existant)
|
||||
|
||||
@version 9.4
|
||||
@author Allan Bowe
|
||||
@source https://github.com/macropeople/macrocore
|
||||
|
||||
**/
|
||||
|
||||
%macro mp_unzip(
|
||||
ziploc=
|
||||
,outdir=%sysfunc(pathname(work))
|
||||
)/*/STORE SOURCE*/;
|
||||
|
||||
%local fname1 fname2 fname3;
|
||||
%let fname1=%mf_getuniquefileref();
|
||||
%let fname2=%mf_getuniquefileref();
|
||||
%let fname3=%mf_getuniquefileref();
|
||||
|
||||
filename &fname1 ZIP &ziploc; * Macro variable &datazip would be read from the file*;
|
||||
|
||||
/* Read the "members" (files) from the ZIP file */
|
||||
data _data_(keep=memname isFolder);
|
||||
length memname $200 isFolder 8;
|
||||
fid=dopen("&fname1");
|
||||
if fid=0 then stop;
|
||||
memcount=dnum(fid);
|
||||
do i=1 to memcount;
|
||||
memname=dread(fid,i);
|
||||
/* check for trailing / in folder name */
|
||||
isFolder = (first(reverse(trim(memname)))='/');
|
||||
output;
|
||||
end;
|
||||
rc=dclose(fid);
|
||||
run;
|
||||
filename &fname1 clear;
|
||||
|
||||
/* loop through each entry and either create the subfolder or extract member */
|
||||
data _null_;
|
||||
set &syslast;
|
||||
if isFolder then call execute('%mf_mkdir(&outdir/'!!memname!!')');
|
||||
else call execute('filename &fname2 zip &ziploc member='
|
||||
!!quote(trim(memname))!!';filename &fname3 "&outdir/'
|
||||
!!trim(memname)!!'" recfm=n;data _null_; rc=fcopy("&fname2","&fname3");run;'
|
||||
!!'filename &fname2 clear; filename &fname3 clear;');
|
||||
run;
|
||||
|
||||
%mend;
|
||||
93
base/mp_updatevarlength.sas
Normal file
93
base/mp_updatevarlength.sas
Normal file
@@ -0,0 +1,93 @@
|
||||
/**
|
||||
@file mp_updatevarlength.sas
|
||||
@brief Change the length of a variable
|
||||
@details The library is assumed to be assigned. Simple character updates
|
||||
currently supported, numerics are more complicated and will follow.
|
||||
|
||||
data example;
|
||||
a='1';
|
||||
b='12';
|
||||
c='123';
|
||||
run;
|
||||
%mp_updatevarlength(example,a,3)
|
||||
%mp_updatevarlength(example,c,1)
|
||||
proc sql;
|
||||
describe table example;
|
||||
|
||||
@param libds the library.dataset to be modified
|
||||
@param var The variable to modify
|
||||
@param len The new length to apply
|
||||
|
||||
<h4> Dependencies </h4>
|
||||
@li mf_existds.sas
|
||||
@li mp_abort.sas
|
||||
@li mf_existvar.sas
|
||||
@li mf_getvarlen.sas
|
||||
@li mf_getvartype.sas
|
||||
@li mf_getnobs.sas
|
||||
@li mp_createconstraints.sas
|
||||
@li mp_getconstraints.sas
|
||||
@li mp_deleteconstraints.sas
|
||||
|
||||
@version 9.2
|
||||
@author Allan Bowe
|
||||
|
||||
**/
|
||||
|
||||
%macro mp_updatevarlength(libds,var,len
|
||||
)/*/STORE SOURCE*/;
|
||||
|
||||
%if %index(&libds,.)=0 %then %let libds=WORK.&libds;
|
||||
|
||||
%mp_abort(iftrue=(%mf_existds(&libds)=0)
|
||||
,mac=&sysmacroname
|
||||
,msg=%str(Table &libds not found!)
|
||||
)
|
||||
|
||||
%mp_abort(iftrue=(%mf_existvar(&libds,&var)=0)
|
||||
,mac=&sysmacroname
|
||||
,msg=%str(Variable &var not found on &libds!)
|
||||
)
|
||||
|
||||
/* not possible to in-place modify a numeric length, to add later */
|
||||
%mp_abort(iftrue=(%mf_getvartype(&libds,&var)=0)
|
||||
,mac=&sysmacroname
|
||||
,msg=%str(Only character resizings are currently supported)
|
||||
)
|
||||
|
||||
%local oldlen;
|
||||
%let oldlen=%mf_getvarlen(&libds,&var);
|
||||
%if &oldlen=&len %then %do;
|
||||
%put &sysmacroname: Old and new lengths (&len) match!;
|
||||
%return;
|
||||
%end;
|
||||
|
||||
%let libds=%upcase(&libds);
|
||||
|
||||
|
||||
data;run;
|
||||
%local dsconst; %let dsconst=&syslast;
|
||||
%mp_getconstraints(lib=%scan(&libds,1,.),ds=%scan(&libds,2,.),outds=&dsconst)
|
||||
|
||||
%mp_abort(iftrue=(&syscc ne 0)
|
||||
,mac=&sysmacroname
|
||||
,msg=%str(syscc=&syscc)
|
||||
)
|
||||
|
||||
%if %mf_getnobs(&dscont)=0 %then %do;
|
||||
/* must use SQL as proc datasets does not support length changes */
|
||||
proc sql;
|
||||
alter table &libds modify &var char(&len);
|
||||
%return;
|
||||
%end;
|
||||
|
||||
/* we have constraints! */
|
||||
|
||||
%mp_deleteconstraints(inds=&dsconst,outds=&dsconst._dropd,execute=YES)
|
||||
|
||||
proc sql;
|
||||
alter table &libds modify &var char(&len);
|
||||
|
||||
%mp_createconstraints(inds=&dsconst,outds=&dsconst._addd,execute=YES)
|
||||
|
||||
%mend;
|
||||
79
base/mp_zip.sas
Normal file
79
base/mp_zip.sas
Normal file
@@ -0,0 +1,79 @@
|
||||
/**
|
||||
@file
|
||||
@brief Creates a zip file
|
||||
@details For DIRECTORY usage, will ignore subfolders. For DATASET usage,
|
||||
provide a column that contains the full file path to each file to be zipped.
|
||||
|
||||
%mp_zip(in=myzips,type=directory,outname=myDir)
|
||||
%mp_zip(in=/my/file/path.txt,type=FILE,outname=myFile)
|
||||
%mp_zip(in=SOMEDS,incol=FPATH,type=DATASET,outname=myFile)
|
||||
|
||||
If you are sending zipped output to the _webout destination as part of an STP
|
||||
be sure that _debug is not set (else the SPWA will send non zipped content
|
||||
as well).
|
||||
|
||||
<h4> Dependencies </h4>
|
||||
@li mp_dirlist.sas
|
||||
|
||||
@param in= unquoted filepath, dataset of files or directory to zip
|
||||
@param type= FILE, DATASET, DIRECTORY. (FILE / DATASET not ready yet)
|
||||
@param outname= output file to create, without .zip extension
|
||||
@param outpath= location for output zip file
|
||||
@param incol= if DATASET input, say which column contains the filepath
|
||||
|
||||
@version 9.2
|
||||
@author Allan Bowe
|
||||
@source https://github.com/macropeople/macrocore
|
||||
|
||||
**/
|
||||
|
||||
%macro mp_zip(
|
||||
in=
|
||||
,type=FILE
|
||||
,outname=FILE
|
||||
,outpath=%sysfunc(pathname(WORK))
|
||||
,incol=
|
||||
,debug=NO
|
||||
)/*/STORE SOURCE*/;
|
||||
|
||||
%let type=%upcase(&type);
|
||||
%local ds;
|
||||
|
||||
ods package open nopf;
|
||||
|
||||
%if &type=FILE %then %do;
|
||||
ods package add file="&in" mimetype="application/x-compress";
|
||||
%end;
|
||||
%else %if &type=DIRECTORY %then %do;
|
||||
%mp_dirlist(path=&in,outds=_data_)
|
||||
%let ds=&syslast;
|
||||
data _null_;
|
||||
set &ds;
|
||||
length __command $4000;
|
||||
if file_or_folder='file';
|
||||
command=cats('ods package add file="',filepath
|
||||
,'" mimetype="application/x-compress";');
|
||||
call execute(command);
|
||||
run;
|
||||
/* tidy up */
|
||||
%if &debug=NO %then %do;
|
||||
proc sql; drop table &ds;quit;
|
||||
%end;
|
||||
%end;
|
||||
%else %if &type=DATASET %then %do;
|
||||
data _null_;
|
||||
set ∈
|
||||
length __command $4000;
|
||||
command=cats('ods package add file="',&incol
|
||||
,'" mimetype="application/x-compress";');
|
||||
call execute(command);
|
||||
run;
|
||||
ods package add file="&in" mimetype="application/x-compress";
|
||||
%end;
|
||||
|
||||
|
||||
ods package publish archive properties
|
||||
(archive_name="&outname..zip" archive_path="&outpath");
|
||||
ods package close;
|
||||
|
||||
%mend;
|
||||
Reference in New Issue
Block a user