mirror of
https://github.com/sasjs/core.git
synced 2025-12-25 20:20:05 +00:00
Compare commits
23 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
dc63b4adf5 | ||
|
|
3f20ca03dd | ||
|
|
3a826dccf1 | ||
|
|
a1ce68ce56 | ||
|
|
a45384aacb | ||
|
|
032c4f318e | ||
|
|
5faaa4a4cd | ||
|
|
4e41182521 | ||
|
|
7185032680 | ||
|
|
c9d8df0a48 | ||
|
|
d93693ba55 | ||
|
|
d49b21f3f1 | ||
|
|
a45d280a51 | ||
|
|
2536e299ad | ||
|
|
8b5238230b | ||
|
|
0ce7efee3e | ||
|
|
357677e45c | ||
|
|
a4a332926e | ||
|
|
0a29006914 | ||
|
|
0885bad859 | ||
|
|
42bd1750bd | ||
|
|
58784b2f28 | ||
|
|
e125aace9b |
@@ -1,4 +1,4 @@
|
||||
#!/bin/sh
|
||||
#!/bin/bash
|
||||
sasjs lint
|
||||
|
||||
# Avoid commits to the master branch
|
||||
|
||||
489
all.sas
489
all.sas
@@ -43,6 +43,60 @@ options noquotelenmax;
|
||||
%mend mf_abort;
|
||||
|
||||
/** @endcond *//**
|
||||
@file
|
||||
@brief de-duplicates a macro string
|
||||
@details Removes all duplicates from a string of words. A delimeter can be
|
||||
chosen. Is inspired heavily by this excellent [macro](
|
||||
https://github.com/scottbass/SAS/blob/master/Macro/dedup_mstring.sas) from
|
||||
[Scott Base](https://www.linkedin.com/in/scottbass). Case sensitive.
|
||||
|
||||
Usage:
|
||||
|
||||
%let str=One two one two and through and through;
|
||||
%put %mf_dedup(&str);
|
||||
%put %mf_dedup(&str,outdlm=%str(,));
|
||||
|
||||
Which returns:
|
||||
|
||||
> One two one and through
|
||||
> One,two,one,and,through
|
||||
|
||||
@param [in] str String to be deduplicated
|
||||
@param [in] indlm= ( ) Delimeter of the input string
|
||||
@param [out] outdlm= ( ) Delimiter of the output string
|
||||
|
||||
<h4> Related Macros </h4>
|
||||
@li mf_trimstr.sas
|
||||
@li mf_wordsinstr1butnotstr2.sas
|
||||
|
||||
@version 9.2
|
||||
@author Allan Bowe
|
||||
**/
|
||||
|
||||
%macro mf_dedup(str
|
||||
,indlm=%str( )
|
||||
,outdlm=%str( )
|
||||
)/*/STORE SOURCE*/;
|
||||
|
||||
%local num word i pos out;
|
||||
|
||||
%* loop over each token, searching the target for that token ;
|
||||
%let num=%sysfunc(countc(%superq(str),%str(&indlm)));
|
||||
%do i=1 %to %eval(&num+1);
|
||||
%let word=%scan(%superq(str),&i,%str(&indlm));
|
||||
%let pos=%sysfunc(indexw(&out,&word,%str(&outdlm)));
|
||||
%if (&pos eq 0) %then %do;
|
||||
%if (&i gt 1) %then %let out=&out%str(&outdlm);
|
||||
%let out=&out&word;
|
||||
%end;
|
||||
%end;
|
||||
|
||||
%unquote(&out)
|
||||
|
||||
%mend mf_dedup;
|
||||
|
||||
|
||||
/**
|
||||
@file mf_existds.sas
|
||||
@brief Checks whether a dataset OR a view exists.
|
||||
@details Can be used in open code, eg as follows:
|
||||
@@ -79,7 +133,7 @@ options noquotelenmax;
|
||||
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.
|
||||
without generating errs in most cases.
|
||||
|
||||
%put %mf_existfeature(PROCLUA);
|
||||
|
||||
@@ -362,7 +416,7 @@ https://github.com/yabwon/SAS_PACKAGES/blob/main/packages/baseplus.md#functionex
|
||||
@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.
|
||||
message if err.
|
||||
|
||||
@version 9.2
|
||||
@author Allan Bowe
|
||||
@@ -395,7 +449,7 @@ https://github.com/yabwon/SAS_PACKAGES/blob/main/packages/baseplus.md#functionex
|
||||
@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.
|
||||
message if err.
|
||||
|
||||
@version 9.2
|
||||
@author Allan Bowe
|
||||
@@ -1128,7 +1182,7 @@ https://github.com/yabwon/SAS_PACKAGES/blob/main/packages/baseplus.md#functionex
|
||||
%put &sysmacroname: SYSMSG= %sysfunc(sysmsg());
|
||||
%let rc=%sysfunc(close(&dsid));
|
||||
%end;
|
||||
&outvar
|
||||
%do;%unquote(&outvar)%end;
|
||||
%mend mf_getvarlist;/**
|
||||
@file
|
||||
@brief Returns the position of a variable in dataset (varnum attribute).
|
||||
@@ -1340,6 +1394,38 @@ Usage:
|
||||
&is_directory
|
||||
|
||||
%mend mf_isdir;/**
|
||||
@file
|
||||
@brief Returns 1 if the variable contains only digits 0-9, else 0
|
||||
@details Note that numerics containing any punctuation (including decimals
|
||||
or exponents) will be flagged zero.
|
||||
|
||||
If you'd like support for this, then do raise an issue (or even better, a
|
||||
pull request!)
|
||||
|
||||
Usage:
|
||||
|
||||
%put %mf_isint(1) returns 1;
|
||||
%put %mf_isint(1.1) returns 0;
|
||||
%put %mf_isint(%str(1,1)) returns 0;
|
||||
|
||||
@param [in] arg input value to check
|
||||
|
||||
@version 9.2
|
||||
**/
|
||||
|
||||
%macro mf_isint(arg
|
||||
)/*/STORE SOURCE*/;
|
||||
/* remove minus sign if exists */
|
||||
|
||||
%local val;
|
||||
%if "%substr(%str(&arg),1,1)"="-" %then %let val=%substr(%str(&arg),2);
|
||||
%else %let val=&arg;
|
||||
|
||||
/* check remaining chars */
|
||||
%if %sysfunc(findc(%str(&val),,kd)) %then %do;0%end;
|
||||
%else %do;1%end;
|
||||
|
||||
%mend mf_isint;/**
|
||||
@file
|
||||
@brief Returns physical location of various SAS items
|
||||
@details Returns location of the PlatformObjectFramework tools
|
||||
@@ -1421,7 +1507,7 @@ Usage:
|
||||
%end;
|
||||
|
||||
/*
|
||||
Now create the directory. Complain loudly of any errors.
|
||||
Now create the directory. Complain loudly of any errs.
|
||||
*/
|
||||
|
||||
%let dname = %sysfunc(dcreate(&child, &parent));
|
||||
@@ -1468,7 +1554,7 @@ Usage:
|
||||
@param libds library.dataset
|
||||
|
||||
@return output returns result of the attrn value supplied, or log message
|
||||
if error.
|
||||
if err.
|
||||
|
||||
|
||||
@version 9.2
|
||||
@@ -2790,6 +2876,8 @@ run;
|
||||
|
||||
/* create folders and copy content */
|
||||
data _null_;
|
||||
length msg $200;
|
||||
call missing(msg);
|
||||
set work.&tempds;
|
||||
if _n_ = 1 then dpos+sum(length(directory),2);
|
||||
filepath2="&target/"!!substr(filepath,dpos);
|
||||
@@ -2799,9 +2887,9 @@ run;
|
||||
rc1=filename(fref1,filepath,'disk','recfm=n');
|
||||
rc2=filename(fref2,filepath2,'disk','recfm=n');
|
||||
if fcopy(fref1,fref2) ne 0 then do;
|
||||
sysmsg=sysmsg();
|
||||
msg=sysmsg();
|
||||
putlog "%str(ERR)OR: Unable to copy " filepath " to " filepath2;
|
||||
putlog sysmg=;
|
||||
putlog msg=;
|
||||
end;
|
||||
end;
|
||||
rc=filename(fref1);
|
||||
@@ -3876,17 +3964,23 @@ run;
|
||||
|
||||
%mend mp_ds2csv;/**
|
||||
@file
|
||||
@brief Converts every value in a dataset to it's formatted value
|
||||
@brief Converts every value in a dataset to formatted value
|
||||
@details Converts every value to it's formatted value. All variables will
|
||||
become character, and will be in the same order.
|
||||
become character, and will be in the same order as the original dataset.
|
||||
|
||||
Lengths will be adjusted according to the format lengths, where applicable.
|
||||
|
||||
Usage:
|
||||
|
||||
%mp_ds2fmtds(sashelp.cars,work.cars)
|
||||
%mp_ds2fmtds(sashelp.vallopt,vw_vallopt)
|
||||
|
||||
@param [in] libds The library.dataset to be converted
|
||||
@param [out] outds The dataset to create.
|
||||
|
||||
<h4> SAS Macros </h4>
|
||||
@li mf_existds.sas
|
||||
|
||||
<h4> Related Macros <h4>
|
||||
@li mp_jsonout.sas
|
||||
|
||||
@@ -3898,8 +3992,9 @@ run;
|
||||
)/*/STORE SOURCE*/;
|
||||
|
||||
/* validations */
|
||||
%if not %sysfunc(exist(&libds)) %then %do;
|
||||
%put %str(WARN)ING: &libds does not exist;
|
||||
|
||||
%if not %mf_existds(libds=&libds) %then %do;
|
||||
%put %str(WARN)ING: &libds does not exist as either a VIEW or DATASET;
|
||||
%return;
|
||||
%end;
|
||||
%if %index(&libds,.)=0 %then %let libds=WORK.&libds;
|
||||
@@ -4089,6 +4184,7 @@ data _null_;
|
||||
if _n_>&maxobs then stop;
|
||||
%end;
|
||||
length _____str $32767;
|
||||
call missing(_____str);
|
||||
format _numeric_ best.;
|
||||
format _character_ ;
|
||||
%local i comma var vtype vfmt;
|
||||
@@ -4489,8 +4585,8 @@ filename &outref temp;
|
||||
@param [in] targetds The target dataset against which to verify the query
|
||||
@param [out] abort= (YES) If YES will call mp_abort.sas on any exceptions
|
||||
@param [out] outds= (work.mp_filtervalidate) Output dataset containing the
|
||||
error / warning message, if one exists. If this table contains any rows,
|
||||
there are problems!
|
||||
err / warning message, if one exists. If this table contains any rows,
|
||||
there are problems!
|
||||
|
||||
<h4> SAS Macros </h4>
|
||||
@li mf_getuniquefileref.sas
|
||||
@@ -4674,17 +4770,17 @@ run;
|
||||
%let vw=%mf_getuniquename(prefix=mp_getconstraints_vw_);
|
||||
data &vw /view=&vw;
|
||||
set sashelp.vcncolu;
|
||||
where TABLE_CATALOG="&lib";
|
||||
where table_catalog="&lib";
|
||||
|
||||
/* use retain approach to reset the constraint order with each constraint */
|
||||
length tmp $1000;
|
||||
retain tmp;
|
||||
drop tmp;
|
||||
if tmp ne catx('|',libref,table_name,constraint_type,constraint_name) then do;
|
||||
if tmp ne catx('|',table_catalog,table_name,constraint_name) then do;
|
||||
constraint_order=1;
|
||||
end;
|
||||
else constraint_order+1;
|
||||
tmp=catx('|',libref, table_name, constraint_type,constraint_name);
|
||||
tmp=catx('|',table_catalog, table_name,constraint_name);
|
||||
run;
|
||||
|
||||
/* must use SQL as proc datasets does not support length changes */
|
||||
@@ -5194,7 +5290,7 @@ run;
|
||||
%let curds=%scan(&dsnlist,&x);
|
||||
data _null_;
|
||||
file &fref mod;
|
||||
length nm lab $1024 typ $20;
|
||||
length lab $1024 typ $20;
|
||||
set &colinfo (where=(upcase(memname)="&curds")) end=last;
|
||||
|
||||
if _n_=1 then do;
|
||||
@@ -6317,6 +6413,7 @@ run;
|
||||
/* prepare the errds */
|
||||
data &errds;
|
||||
length msg mac $1000;
|
||||
call missing(msg,mac);
|
||||
iftrue='1=0';
|
||||
run;
|
||||
|
||||
@@ -6333,15 +6430,25 @@ filename &tempref clear;
|
||||
%mend mp_include;/**
|
||||
@file
|
||||
@brief Initialise session with useful settings and variables
|
||||
@details Implements a set of recommended options for general SAS use. This
|
||||
macro is NOT used elsewhere within the core library (other than in tests),
|
||||
but it is used by the SASjs team when building web services for
|
||||
SAS-Powered applications elsewhere.
|
||||
@details Implements a "strict" set of SAS options for use in defensive
|
||||
programming. Highly recommended, if you want your code to run on some
|
||||
other machine.
|
||||
|
||||
If you have a good idea for an option, setting, or useful global variable -
|
||||
feel free to [raise an issue](https://github.com/sasjs/core/issues/new)!
|
||||
This macro is recommended to be compiled and invoked in the `initProgram`
|
||||
for SASjs [Jobs](https://cli.sasjs.io/sasjsconfig.html#jobConfig_initProgram
|
||||
), [Services](
|
||||
https://cli.sasjs.io/sasjsconfig.html#serviceConfig_initProgram) and [Tests]
|
||||
(https://cli.sasjs.io/sasjsconfig.html#testConfig_initProgram).
|
||||
|
||||
All global variables are prefixed with "SASJS_" (unless modfied with the
|
||||
For non SASjs projects, you could invoke in the autoexec, or in your own
|
||||
solution initialisation macro.
|
||||
|
||||
|
||||
If you have a good idea for another useful option, setting, or global
|
||||
variable - feel free to [raise an issue](
|
||||
https://github.com/sasjs/core/issues/new)!
|
||||
|
||||
All global variables are prefixed with "SASJS" (unless modified with the
|
||||
prefix parameter).
|
||||
|
||||
@param [in] prefix= (SASJS) The prefix to apply to the global macro variables
|
||||
@@ -6368,18 +6475,19 @@ filename &tempref clear;
|
||||
run;
|
||||
|
||||
options
|
||||
autocorrect /* disallow mis-spelled procedure names */
|
||||
noautocorrect /* disallow misspelled procedure names */
|
||||
compress=CHAR /* default is none so ensure we have something! */
|
||||
datastmtchk=ALLKEYWORDS /* protection from overwriting input datasets */
|
||||
errorcheck=STRICT /* catch errors in libname/filename statements */
|
||||
fmterr /* ensure err when a format cannot be found */
|
||||
mergenoby=%str(ERR)OR /* Throw err when a merge has no BY variables */
|
||||
missing=. /* some sites change this which causes hard to detect errors */
|
||||
%str(err)orcheck=STRICT /* catch errs in libname/filename statements */
|
||||
fmterr /* ensure err when a format cannot be found */
|
||||
mergenoby=%str(ERR)OR /* throw err when a merge has no BY variables */
|
||||
missing=. /* changing this can cause hard to detect errs */
|
||||
noquotelenmax /* avoid warnings for long strings */
|
||||
noreplace /* avoid overwriting permanent datasets */
|
||||
ps=max /* reduce log size slightly */
|
||||
validmemname=COMPATIBLE /* avoid special characters etc in table names */
|
||||
validvarname=V7 /* avoid special characters etc in variable names */
|
||||
varinitchk=%str(ERR)OR /* avoid data mistakes from variable name typos */
|
||||
varlenchk=%str(ERR)OR /* fail hard if truncation (data loss) can result */
|
||||
;
|
||||
|
||||
@@ -6448,7 +6556,7 @@ filename &tempref clear;
|
||||
%if &action=OPEN %then %do;
|
||||
options nobomfile;
|
||||
data _null_;file &jref encoding='utf-8';
|
||||
put '{"START_DTTM" : "' "%sysfunc(datetime(),datetime20.3)" '"';
|
||||
put '{"PROCESSED_DTTM" : "' "%sysfunc(datetime(),E8601DT26.6)" '"';
|
||||
run;
|
||||
%end;
|
||||
%else %if (&action=ARR or &action=OBJ) %then %do;
|
||||
@@ -6951,7 +7059,7 @@ run;
|
||||
%else %do;
|
||||
data _null_;
|
||||
putlog 'NOTE-' / 'NOTE-';
|
||||
putlog "NOTE- &sysmacroname: Table &lib..&ds locked at "@
|
||||
putlog "NOTE- &sysmacroname: Table &lib..&ds locked at "@;
|
||||
putlog " %sysfunc(datetime(),datetime19.) (iteration &x)"@;
|
||||
putlog 'NOTE-' / 'NOTE-';
|
||||
run;
|
||||
@@ -7010,7 +7118,7 @@ run;
|
||||
%let abortme=1;
|
||||
%end;
|
||||
|
||||
/* catch errors - mp_abort must be called outside of a logic block */
|
||||
/* catch errs - mp_abort must be called outside of a logic block */
|
||||
%mp_abort(iftrue=(&abortme=1),
|
||||
msg=%superq(msg),
|
||||
mac=&sysmacroname
|
||||
@@ -7742,22 +7850,28 @@ drop table &tempds;
|
||||
Usage:
|
||||
|
||||
%mp_searchdata(lib=sashelp, string=Jan)
|
||||
%mp_searchdata(lib=sashelp, numval=1)
|
||||
%mp_searchdata(lib=sashelp, ds=bird, numval=1)
|
||||
%mp_searchdata(lib=sashelp, ds=class, string=l,outobs=5)
|
||||
|
||||
|
||||
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
|
||||
@param [in] lib= The libref to search (should be already assigned)
|
||||
@param [in] ds= The dataset to search (leave blank to search entire library)
|
||||
@param [in] string= String value to search (case sensitive, can be partial)
|
||||
@param [in] numval= Numeric value to search (must be exact)
|
||||
@param [out] outloc= (0) Optionally specify the directory in which to
|
||||
create the the output datasets with matching rows. By default it will
|
||||
write them to a temporary subdirectory within the WORK folder.
|
||||
@param [out] outlib= (MPSEARCH) Assign a different libref to the output
|
||||
library containing the matching datasets / records
|
||||
@param [in] 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
|
||||
@param [in] filter_text= (1=1) Add a (valid) filter clause to further filter
|
||||
the results.
|
||||
|
||||
<h4> SAS Macros </h4>
|
||||
@li mf_getuniquename.sas
|
||||
@li mf_getvarlist.sas
|
||||
@li mf_getvartype.sas
|
||||
@li mf_mkdir.sas
|
||||
@@ -7767,11 +7881,12 @@ drop table &tempds;
|
||||
@author Allan Bowe
|
||||
**/
|
||||
|
||||
%macro mp_searchdata(lib=sashelp
|
||||
%macro mp_searchdata(lib=
|
||||
,ds=
|
||||
,string= /* the query will use a contains (?) operator */
|
||||
,numval= /* numeric must match exactly */
|
||||
,outloc=%sysfunc(pathname(work))/mpsearch
|
||||
,outloc=0
|
||||
,outlib=MPSEARCH
|
||||
,outobs=-1
|
||||
,filter_text=%str(1=1)
|
||||
)/*/STORE SOURCE*/;
|
||||
@@ -7788,8 +7903,12 @@ drop table &tempds;
|
||||
%if &string = %then %let type=N;
|
||||
%else %let type=C;
|
||||
|
||||
%if "&outloc"="0" %then %do;
|
||||
%let outloc=%sysfunc(pathname(work))/%mf_getuniquename();
|
||||
%end;
|
||||
|
||||
%mf_mkdir(&outloc)
|
||||
libname mpsearch "&outloc";
|
||||
libname &outlib "&outloc";
|
||||
|
||||
/* get the list of tables in the library */
|
||||
proc sql noprint;
|
||||
@@ -7801,11 +7920,6 @@ select distinct memname into: table_list separated by ' '
|
||||
%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( )));
|
||||
@@ -7816,10 +7930,10 @@ proc sql
|
||||
%end;
|
||||
%else %do;
|
||||
%let check_tm=%sysfunc(datetime());
|
||||
/* build sql statement */
|
||||
create table mpsearch.&table as select * from &lib..&table
|
||||
where %unquote(&filter_text) and
|
||||
(0
|
||||
/* prep input */
|
||||
data &outlib..&table;
|
||||
set &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( ));
|
||||
@@ -7833,15 +7947,20 @@ proc sql
|
||||
or ("&col"n = &numval)
|
||||
%end;
|
||||
%end;
|
||||
);
|
||||
);
|
||||
%if &outobs>-1 %then %do;
|
||||
if _n_ > &outobs then stop;
|
||||
%end;
|
||||
run;
|
||||
%put Search query for &table took
|
||||
%sysevalf(%sysfunc(datetime())-&check_tm) seconds;
|
||||
%if &sqlrc ne 0 %then %do;
|
||||
%put %str(WAR)NING: SQLRC=&sqlrc when processing &table;
|
||||
%if &syscc ne 0 %then %do;
|
||||
%put %str(ERR)ROR: SYSCC=&syscc when processing &lib..&table;
|
||||
%return;
|
||||
%end;
|
||||
%if %mf_nobs(mpsearch.&table)=0 %then %do;
|
||||
drop table mpsearch.&table;
|
||||
%if %mf_nobs(&outlib..&table)=0 %then %do;
|
||||
proc sql;
|
||||
drop table &outlib..&table;
|
||||
%end;
|
||||
%end;
|
||||
%end;
|
||||
@@ -8021,6 +8140,242 @@ run;
|
||||
|
||||
|
||||
%mend mp_sortinplace;/**
|
||||
@file
|
||||
@brief Converts deletes/changes/appends into a single audit table.
|
||||
@details When tracking changes to data over time, it can be helpful to have
|
||||
a single base table to track ALL modifications - enabling audit trail,
|
||||
data recovery, and change re-application. This macro is one of many
|
||||
data management utilities used in [Data Controller for SAS](
|
||||
https:datacontroller.io) - a comprehensive data ingestion solution, which
|
||||
works on any SAS platform (Viya, SAS 9, Foundation) and is free for up to 5
|
||||
users.
|
||||
|
||||
NOTE - this macro does not validate the inputs. It is assumed that the
|
||||
datasets containing the new / changed / deleted rows are CORRECT, contain
|
||||
no additional (or missing columns), and that the originals dataset contains
|
||||
all relevant base records (and no additionals).
|
||||
|
||||
Usage:
|
||||
|
||||
data work.orig work.deleted work.changed work.appended;
|
||||
set sashelp.class;
|
||||
if _n_=1 then do;
|
||||
output work.orig work.deleted;
|
||||
end;
|
||||
else if _n_=2 then do;
|
||||
output work.orig;
|
||||
age=99;
|
||||
output work.changed;
|
||||
end;
|
||||
else do;
|
||||
name='Newbie';
|
||||
output work.appended;
|
||||
stop;
|
||||
end;
|
||||
run;
|
||||
|
||||
%mp_storediffs(sashelp.class,work.orig,NAME
|
||||
,delds=work.deleted
|
||||
,modds=work.changed
|
||||
,appds=work.appended
|
||||
,outds=work.final
|
||||
,mdebug=1
|
||||
)
|
||||
|
||||
@param [in] libds Target table against which the changes were applied
|
||||
@param [in] origds Dataset with original (unchanged) records. Can be empty if
|
||||
only appending.
|
||||
@param [in] key Space seperated list of key variables
|
||||
@param [in] delds= (0) Dataset with deleted records
|
||||
@param [in] appds= (0) Dataset with appended records
|
||||
@param [in] modds= (0) Dataset with modified records
|
||||
@param [out] outds= (work.mp_storediffs) Output table containing stored data.
|
||||
Has the following format:
|
||||
|
||||
proc sql;
|
||||
create table &outds(
|
||||
load_ref char(36) label='unique load reference',
|
||||
processed_dttm num format=E8601DT26.6 label='Processed at timestamp',
|
||||
libref char(8) label='Library Reference (8 chars)',
|
||||
dsn char(32) label='Dataset Name (32 chars)',
|
||||
key_hash char(32) label=
|
||||
'MD5 Hash of primary key values (pipe seperated)',
|
||||
move_type char(1) label='Either (A)ppended, (D)eleted or (M)odified',
|
||||
is_pk num label='Is Primary Key Field? (1/0)',
|
||||
is_diff num label=
|
||||
'Did value change? (1/0/-1). Always -1 for appends and deletes.',
|
||||
tgtvar_type char(1) label='Either (C)haracter or (N)umeric',
|
||||
tgtvar_nm char(32) label='Target variable name (32 chars)',
|
||||
oldval_num num label='Old (numeric) value',
|
||||
newval_num num label='New (numeric) value',
|
||||
oldval_char char(32767) label='Old (character) value',
|
||||
newval_char char(32767) label='New (character) value',
|
||||
constraint pk_mpe_audit
|
||||
primary key(load_ref,libref,dsn,key_hash,tgtvar_nm)
|
||||
);
|
||||
|
||||
@param [in] processed_dttm= (0) Provide a datetime constant in relation to
|
||||
the actual load time. If not provided, current timestamp is used.
|
||||
@param [in] mdebug= set to 1 to enable DEBUG messages and preserve outputs
|
||||
@param [out] loadref= (0) Provide a unique key to reference the load,
|
||||
otherwise a UUID will be generated.
|
||||
|
||||
<h4> SAS Macros </h4>
|
||||
@li mf_getquotedstr.sas
|
||||
@li mf_getuniquename.sas
|
||||
@li mf_getvarlist.sas
|
||||
|
||||
@version 9.2
|
||||
@author Allan Bowe
|
||||
**/
|
||||
/** @cond */
|
||||
|
||||
%macro mp_storediffs(libds
|
||||
,origds
|
||||
,key
|
||||
,delds=0
|
||||
,appds=0
|
||||
,modds=0
|
||||
,outds=work.mp_storediffs
|
||||
,loadref=0
|
||||
,processed_dttm=0
|
||||
,mdebug=0
|
||||
)/*/STORE SOURCE*/;
|
||||
%local dbg;
|
||||
%if &mdebug=1 %then %do;
|
||||
%put &sysmacroname entry vars:;
|
||||
%put _local_;
|
||||
%end;
|
||||
%else %let dbg=*;
|
||||
|
||||
/* set up unique and temporary vars */
|
||||
%local ds1 ds2 ds3 ds4 hashkey inds_auto inds_keep dslist;
|
||||
%let ds1=%upcase(work.%mf_getuniquename(prefix=mpsd_ds1));
|
||||
%let ds2=%upcase(work.%mf_getuniquename(prefix=mpsd_ds2));
|
||||
%let ds3=%upcase(work.%mf_getuniquename(prefix=mpsd_ds3));
|
||||
%let ds4=%upcase(work.%mf_getuniquename(prefix=mpsd_ds4));
|
||||
%let hashkey=%upcase(%mf_getuniquename(prefix=mpsd_hashkey));
|
||||
%let inds_auto=%upcase(%mf_getuniquename(prefix=mpsd_inds_auto));
|
||||
%let inds_keep=%upcase(%mf_getuniquename(prefix=mpsd_inds_keep));
|
||||
|
||||
%let dslist=&origds;
|
||||
%if &delds ne 0 %then %do;
|
||||
%let delds=%upcase(&delds);
|
||||
%if %scan(&delds,-1,.)=&delds %then %let delds=WORK.&delds;
|
||||
%let dslist=&dslist &delds;
|
||||
%end;
|
||||
%if &appds ne 0 %then %do;
|
||||
%let appds=%upcase(&appds);
|
||||
%if %scan(&appds,-1,.)=&appds %then %let appds=WORK.&appds;
|
||||
%let dslist=&dslist &appds;
|
||||
%end;
|
||||
%if &modds ne 0 %then %do;
|
||||
%let modds=%upcase(&modds);
|
||||
%if %scan(&modds,-1,.)=&modds %then %let modds=WORK.&modds;
|
||||
%let dslist=&dslist &modds;
|
||||
%end;
|
||||
|
||||
%let origds=%upcase(&origds);
|
||||
%if %scan(&origds,-1,.)=&origds %then %let origds=WORK.&origds;
|
||||
|
||||
%let key=%upcase(&key);
|
||||
|
||||
/* hash the key and append all the tables (marking the source) */
|
||||
data &ds1;
|
||||
set &dslist indsname=&inds_auto;
|
||||
&hashkey=put(md5(catx('|',%mf_getquotedstr(&key,quote=N))),$hex32.);
|
||||
&inds_keep=&inds_auto;
|
||||
proc sort;
|
||||
by &inds_keep &hashkey;
|
||||
run;
|
||||
|
||||
/* transpose numeric & char vars */
|
||||
proc transpose data=&ds1
|
||||
out=&ds2(rename=(&hashkey=key_hash _name_=tgtvar_nm col1=newval_num));
|
||||
by &inds_keep &hashkey;
|
||||
var _numeric_;
|
||||
run;
|
||||
proc transpose data=&ds1
|
||||
out=&ds3(
|
||||
rename=(&hashkey=key_hash _name_=tgtvar_nm col1=newval_char)
|
||||
where=(tgtvar_nm not in ("&hashkey","&inds_keep"))
|
||||
);
|
||||
by &inds_keep &hashkey;
|
||||
var _character_;
|
||||
run;
|
||||
data &ds4;
|
||||
length &inds_keep $41 tgtvar_nm $32;
|
||||
set &ds2 &ds3 indsname=&inds_auto;
|
||||
|
||||
tgtvar_nm=upcase(tgtvar_nm);
|
||||
if tgtvar_nm in (%upcase(%mf_getvarlist(&libds,dlm=%str(,),quote=DOUBLE)));
|
||||
|
||||
if &inds_auto="&ds2" then tgtvar_type='N';
|
||||
else if &inds_auto="&ds3" then tgtvar_type='C';
|
||||
else do;
|
||||
putlog "%str(ERR)OR: unidentified vartype input!" &inds_auto;
|
||||
call symputx('syscc',98);
|
||||
end;
|
||||
|
||||
if &inds_keep="&appds" then move_type='A';
|
||||
else if &inds_keep="&delds" then move_type='D';
|
||||
else if &inds_keep="&modds" then move_type='M';
|
||||
else if &inds_keep="&origds" then move_type='O';
|
||||
else do;
|
||||
putlog "%str(ERR)OR: unidentified movetype input!" &inds_keep;
|
||||
call symputx('syscc',99);
|
||||
end;
|
||||
tgtvar_nm=upcase(tgtvar_nm);
|
||||
if tgtvar_nm in (%mf_getquotedstr(&key)) then is_pk=1;
|
||||
else is_pk=0;
|
||||
drop &inds_keep;
|
||||
run;
|
||||
|
||||
%if "&loadref"="0" %then %let loadref=%sysfunc(uuidgen());
|
||||
%if &processed_dttm=0 %then %let processed_dttm=%sysfunc(datetime());
|
||||
%let libds=%upcase(&libds);
|
||||
|
||||
/* join orig vals for modified & deleted */
|
||||
proc sql;
|
||||
create table &outds as
|
||||
select "&loadref" as load_ref length=36
|
||||
,&processed_dttm as processed_dttm format=E8601DT26.6
|
||||
,"%scan(&libds,1,.)" as libref length=8
|
||||
,"%scan(&libds,2,.)" as dsn length=32
|
||||
,b.key_hash length=32
|
||||
,b.move_type length=1
|
||||
,b.tgtvar_nm length=32
|
||||
,b.is_pk
|
||||
,case when b.move_type ne 'M' then -1
|
||||
when a.newval_num=b.newval_num and a.newval_char=b.newval_char then 0
|
||||
else 1
|
||||
end as is_diff
|
||||
,b.tgtvar_type length=1
|
||||
,case when b.move_type='D' then b.newval_num
|
||||
else a.newval_num
|
||||
end as oldval_num
|
||||
,case when b.move_type='D' then .
|
||||
else b.newval_num
|
||||
end as newval_num
|
||||
,case when b.move_type='D' then b.newval_char
|
||||
else a.newval_char
|
||||
end as oldval_char length=32767
|
||||
,case when b.move_type='D' then ''
|
||||
else b.newval_char
|
||||
end as newval_char length=32767
|
||||
from &ds4(where=(move_type='O')) as a
|
||||
full join &ds4(where=(move_type ne 'O')) as b
|
||||
on a.tgtvar_nm=b.tgtvar_nm
|
||||
and a.key_hash=b.key_hash
|
||||
order by move_type, key_hash,is_pk desc, tgtvar_nm;
|
||||
|
||||
%if &mdebug=0 %then %do;
|
||||
proc sql;
|
||||
drop table &ds1, &ds2, &ds3, &ds4;
|
||||
%end;
|
||||
|
||||
%mend mp_storediffs;
|
||||
/** @endcond *//**
|
||||
@file
|
||||
@brief Capture session start / finish times and request details
|
||||
@details For details, see
|
||||
@@ -11101,7 +11456,7 @@ data _null_;
|
||||
put '%if &action=OPEN %then %do; ';
|
||||
put ' options nobomfile; ';
|
||||
put ' data _null_;file &jref encoding=''utf-8''; ';
|
||||
put ' put ''{"START_DTTM" : "'' "%sysfunc(datetime(),datetime20.3)" ''"''; ';
|
||||
put ' put ''{"PROCESSED_DTTM" : "'' "%sysfunc(datetime(),E8601DT26.6)" ''"''; ';
|
||||
put ' run; ';
|
||||
put '%end; ';
|
||||
put '%else %if (&action=ARR or &action=OBJ) %then %do; ';
|
||||
@@ -11931,7 +12286,7 @@ run;
|
||||
data &outattrs;
|
||||
keep type name value;
|
||||
length type $4 name $256 value $32767;
|
||||
rc1=1;n1=1;type='Prop';
|
||||
rc1=1;n1=1;type='Prop';name='';value='';
|
||||
do while(rc1>0);
|
||||
rc1=metadata_getnprp("&uri",n1,name,value);
|
||||
if rc1>0 then output;
|
||||
@@ -13556,7 +13911,7 @@ run;
|
||||
data &outds;
|
||||
length uri serveruri conn_uri domainuri libname ServerContext AuthDomain
|
||||
path_schema usingpkguri type tableuri $256 id $17
|
||||
libdesc $200 libref engine $8 IsDBMSLibname $1
|
||||
libdesc $200 libref engine $8 IsDBMSLibname IsPreassigned $1
|
||||
tablename $50 /* metadata table names can be longer than $32 */
|
||||
;
|
||||
keep libname libdesc libref engine ServerContext path_schema AuthDomain
|
||||
@@ -16183,7 +16538,7 @@ data _null_;
|
||||
put '%if &action=OPEN %then %do; ';
|
||||
put ' options nobomfile; ';
|
||||
put ' data _null_;file &jref encoding=''utf-8''; ';
|
||||
put ' put ''{"START_DTTM" : "'' "%sysfunc(datetime(),datetime20.3)" ''"''; ';
|
||||
put ' put ''{"PROCESSED_DTTM" : "'' "%sysfunc(datetime(),E8601DT26.6)" ''"''; ';
|
||||
put ' run; ';
|
||||
put '%end; ';
|
||||
put '%else %if (&action=ARR or &action=OBJ) %then %do; ';
|
||||
@@ -17407,6 +17762,8 @@ options noquotelenmax;
|
||||
%local href cnt;
|
||||
%let cnt=0;
|
||||
data _null_;
|
||||
length rel href $512;
|
||||
call missing(rel,href);
|
||||
set &libref1..links;
|
||||
if rel='members' then do;
|
||||
url=cats("'","&base_uri",href,"?limit=10000'");
|
||||
@@ -17430,6 +17787,7 @@ options noquotelenmax;
|
||||
libname &libref2 JSON fileref=&fname2;
|
||||
data &outds;
|
||||
length id $36 name $128 uri $64 type $32 description $256;
|
||||
if _n_=1 then call missing (of _all_);
|
||||
set &libref2..items;
|
||||
run;
|
||||
filename &fname2 clear;
|
||||
@@ -17725,6 +18083,8 @@ data;run;
|
||||
%local joburi;
|
||||
%let joburi=0;
|
||||
data _null_;
|
||||
length name uri $512;
|
||||
call missing(name,uri);
|
||||
set &foldermembers;
|
||||
if name="&name" and uri=:'/jobDefinitions/definitions'
|
||||
then call symputx('joburi',uri);
|
||||
@@ -18881,6 +19241,8 @@ data;run;
|
||||
%local joburi;
|
||||
%let joburi=0;
|
||||
data _null_;
|
||||
length name uri $512;
|
||||
call missing(name,uri);
|
||||
set &foldermembers;
|
||||
if name="&name" and uri=:'/jobDefinitions/definitions'
|
||||
then call symputx('joburi',uri);
|
||||
@@ -19514,6 +19876,7 @@ run;
|
||||
data &outds;
|
||||
format _program uri $128. state $32. stateDetails $32. timestamp datetime19.
|
||||
jobparams $32767.;
|
||||
call missing (of _all_);
|
||||
stop;
|
||||
run;
|
||||
|
||||
|
||||
54
base/mf_dedup.sas
Normal file
54
base/mf_dedup.sas
Normal file
@@ -0,0 +1,54 @@
|
||||
/**
|
||||
@file
|
||||
@brief de-duplicates a macro string
|
||||
@details Removes all duplicates from a string of words. A delimeter can be
|
||||
chosen. Is inspired heavily by this excellent [macro](
|
||||
https://github.com/scottbass/SAS/blob/master/Macro/dedup_mstring.sas) from
|
||||
[Scott Base](https://www.linkedin.com/in/scottbass). Case sensitive.
|
||||
|
||||
Usage:
|
||||
|
||||
%let str=One two one two and through and through;
|
||||
%put %mf_dedup(&str);
|
||||
%put %mf_dedup(&str,outdlm=%str(,));
|
||||
|
||||
Which returns:
|
||||
|
||||
> One two one and through
|
||||
> One,two,one,and,through
|
||||
|
||||
@param [in] str String to be deduplicated
|
||||
@param [in] indlm= ( ) Delimeter of the input string
|
||||
@param [out] outdlm= ( ) Delimiter of the output string
|
||||
|
||||
<h4> Related Macros </h4>
|
||||
@li mf_trimstr.sas
|
||||
@li mf_wordsinstr1butnotstr2.sas
|
||||
|
||||
@version 9.2
|
||||
@author Allan Bowe
|
||||
**/
|
||||
|
||||
%macro mf_dedup(str
|
||||
,indlm=%str( )
|
||||
,outdlm=%str( )
|
||||
)/*/STORE SOURCE*/;
|
||||
|
||||
%local num word i pos out;
|
||||
|
||||
%* loop over each token, searching the target for that token ;
|
||||
%let num=%sysfunc(countc(%superq(str),%str(&indlm)));
|
||||
%do i=1 %to %eval(&num+1);
|
||||
%let word=%scan(%superq(str),&i,%str(&indlm));
|
||||
%let pos=%sysfunc(indexw(&out,&word,%str(&outdlm)));
|
||||
%if (&pos eq 0) %then %do;
|
||||
%if (&i gt 1) %then %let out=&out%str(&outdlm);
|
||||
%let out=&out&word;
|
||||
%end;
|
||||
%end;
|
||||
|
||||
%unquote(&out)
|
||||
|
||||
%mend mf_dedup;
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
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.
|
||||
without generating errs in most cases.
|
||||
|
||||
%put %mf_existfeature(PROCLUA);
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
@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.
|
||||
message if err.
|
||||
|
||||
@version 9.2
|
||||
@author Allan Bowe
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
@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.
|
||||
message if err.
|
||||
|
||||
@version 9.2
|
||||
@author Allan Bowe
|
||||
|
||||
@@ -70,5 +70,5 @@
|
||||
%put &sysmacroname: SYSMSG= %sysfunc(sysmsg());
|
||||
%let rc=%sysfunc(close(&dsid));
|
||||
%end;
|
||||
&outvar
|
||||
%do;%unquote(&outvar)%end;
|
||||
%mend mf_getvarlist;
|
||||
33
base/mf_isint.sas
Normal file
33
base/mf_isint.sas
Normal file
@@ -0,0 +1,33 @@
|
||||
/**
|
||||
@file
|
||||
@brief Returns 1 if the variable contains only digits 0-9, else 0
|
||||
@details Note that numerics containing any punctuation (including decimals
|
||||
or exponents) will be flagged zero.
|
||||
|
||||
If you'd like support for this, then do raise an issue (or even better, a
|
||||
pull request!)
|
||||
|
||||
Usage:
|
||||
|
||||
%put %mf_isint(1) returns 1;
|
||||
%put %mf_isint(1.1) returns 0;
|
||||
%put %mf_isint(%str(1,1)) returns 0;
|
||||
|
||||
@param [in] arg input value to check
|
||||
|
||||
@version 9.2
|
||||
**/
|
||||
|
||||
%macro mf_isint(arg
|
||||
)/*/STORE SOURCE*/;
|
||||
/* remove minus sign if exists */
|
||||
|
||||
%local val;
|
||||
%if "%substr(%str(&arg),1,1)"="-" %then %let val=%substr(%str(&arg),2);
|
||||
%else %let val=&arg;
|
||||
|
||||
/* check remaining chars */
|
||||
%if %sysfunc(findc(%str(&val),,kd)) %then %do;0%end;
|
||||
%else %do;1%end;
|
||||
|
||||
%mend mf_isint;
|
||||
@@ -51,7 +51,7 @@ Usage:
|
||||
%end;
|
||||
|
||||
/*
|
||||
Now create the directory. Complain loudly of any errors.
|
||||
Now create the directory. Complain loudly of any errs.
|
||||
*/
|
||||
|
||||
%let dname = %sysfunc(dcreate(&child, &parent));
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
@param libds library.dataset
|
||||
|
||||
@return output returns result of the attrn value supplied, or log message
|
||||
if error.
|
||||
if err.
|
||||
|
||||
|
||||
@version 9.2
|
||||
|
||||
@@ -54,6 +54,8 @@
|
||||
|
||||
/* create folders and copy content */
|
||||
data _null_;
|
||||
length msg $200;
|
||||
call missing(msg);
|
||||
set work.&tempds;
|
||||
if _n_ = 1 then dpos+sum(length(directory),2);
|
||||
filepath2="&target/"!!substr(filepath,dpos);
|
||||
@@ -63,9 +65,9 @@
|
||||
rc1=filename(fref1,filepath,'disk','recfm=n');
|
||||
rc2=filename(fref2,filepath2,'disk','recfm=n');
|
||||
if fcopy(fref1,fref2) ne 0 then do;
|
||||
sysmsg=sysmsg();
|
||||
msg=sysmsg();
|
||||
putlog "%str(ERR)OR: Unable to copy " filepath " to " filepath2;
|
||||
putlog sysmg=;
|
||||
putlog msg=;
|
||||
end;
|
||||
end;
|
||||
rc=filename(fref1);
|
||||
|
||||
@@ -1,16 +1,22 @@
|
||||
/**
|
||||
@file
|
||||
@brief Converts every value in a dataset to it's formatted value
|
||||
@brief Converts every value in a dataset to formatted value
|
||||
@details Converts every value to it's formatted value. All variables will
|
||||
become character, and will be in the same order.
|
||||
become character, and will be in the same order as the original dataset.
|
||||
|
||||
Lengths will be adjusted according to the format lengths, where applicable.
|
||||
|
||||
Usage:
|
||||
|
||||
%mp_ds2fmtds(sashelp.cars,work.cars)
|
||||
%mp_ds2fmtds(sashelp.vallopt,vw_vallopt)
|
||||
|
||||
@param [in] libds The library.dataset to be converted
|
||||
@param [out] outds The dataset to create.
|
||||
|
||||
<h4> SAS Macros </h4>
|
||||
@li mf_existds.sas
|
||||
|
||||
<h4> Related Macros <h4>
|
||||
@li mp_jsonout.sas
|
||||
|
||||
@@ -22,8 +28,9 @@
|
||||
)/*/STORE SOURCE*/;
|
||||
|
||||
/* validations */
|
||||
%if not %sysfunc(exist(&libds)) %then %do;
|
||||
%put %str(WARN)ING: &libds does not exist;
|
||||
|
||||
%if not %mf_existds(libds=&libds) %then %do;
|
||||
%put %str(WARN)ING: &libds does not exist as either a VIEW or DATASET;
|
||||
%return;
|
||||
%end;
|
||||
%if %index(&libds,.)=0 %then %let libds=WORK.&libds;
|
||||
|
||||
@@ -116,6 +116,7 @@ data _null_;
|
||||
if _n_>&maxobs then stop;
|
||||
%end;
|
||||
length _____str $32767;
|
||||
call missing(_____str);
|
||||
format _numeric_ best.;
|
||||
format _character_ ;
|
||||
%local i comma var vtype vfmt;
|
||||
|
||||
@@ -33,8 +33,8 @@
|
||||
@param [in] targetds The target dataset against which to verify the query
|
||||
@param [out] abort= (YES) If YES will call mp_abort.sas on any exceptions
|
||||
@param [out] outds= (work.mp_filtervalidate) Output dataset containing the
|
||||
error / warning message, if one exists. If this table contains any rows,
|
||||
there are problems!
|
||||
err / warning message, if one exists. If this table contains any rows,
|
||||
there are problems!
|
||||
|
||||
<h4> SAS Macros </h4>
|
||||
@li mf_getuniquefileref.sas
|
||||
|
||||
@@ -48,17 +48,17 @@
|
||||
%let vw=%mf_getuniquename(prefix=mp_getconstraints_vw_);
|
||||
data &vw /view=&vw;
|
||||
set sashelp.vcncolu;
|
||||
where TABLE_CATALOG="&lib";
|
||||
where table_catalog="&lib";
|
||||
|
||||
/* use retain approach to reset the constraint order with each constraint */
|
||||
length tmp $1000;
|
||||
retain tmp;
|
||||
drop tmp;
|
||||
if tmp ne catx('|',libref,table_name,constraint_type,constraint_name) then do;
|
||||
if tmp ne catx('|',table_catalog,table_name,constraint_name) then do;
|
||||
constraint_order=1;
|
||||
end;
|
||||
else constraint_order+1;
|
||||
tmp=catx('|',libref, table_name, constraint_type,constraint_name);
|
||||
tmp=catx('|',table_catalog, table_name,constraint_name);
|
||||
run;
|
||||
|
||||
/* must use SQL as proc datasets does not support length changes */
|
||||
|
||||
@@ -139,7 +139,7 @@ run;
|
||||
%let curds=%scan(&dsnlist,&x);
|
||||
data _null_;
|
||||
file &fref mod;
|
||||
length nm lab $1024 typ $20;
|
||||
length lab $1024 typ $20;
|
||||
set &colinfo (where=(upcase(memname)="&curds")) end=last;
|
||||
|
||||
if _n_=1 then do;
|
||||
|
||||
@@ -88,6 +88,7 @@ run;
|
||||
/* prepare the errds */
|
||||
data &errds;
|
||||
length msg mac $1000;
|
||||
call missing(msg,mac);
|
||||
iftrue='1=0';
|
||||
run;
|
||||
|
||||
|
||||
@@ -1,15 +1,25 @@
|
||||
/**
|
||||
@file
|
||||
@brief Initialise session with useful settings and variables
|
||||
@details Implements a set of recommended options for general SAS use. This
|
||||
macro is NOT used elsewhere within the core library (other than in tests),
|
||||
but it is used by the SASjs team when building web services for
|
||||
SAS-Powered applications elsewhere.
|
||||
@details Implements a "strict" set of SAS options for use in defensive
|
||||
programming. Highly recommended, if you want your code to run on some
|
||||
other machine.
|
||||
|
||||
If you have a good idea for an option, setting, or useful global variable -
|
||||
feel free to [raise an issue](https://github.com/sasjs/core/issues/new)!
|
||||
This macro is recommended to be compiled and invoked in the `initProgram`
|
||||
for SASjs [Jobs](https://cli.sasjs.io/sasjsconfig.html#jobConfig_initProgram
|
||||
), [Services](
|
||||
https://cli.sasjs.io/sasjsconfig.html#serviceConfig_initProgram) and [Tests]
|
||||
(https://cli.sasjs.io/sasjsconfig.html#testConfig_initProgram).
|
||||
|
||||
All global variables are prefixed with "SASJS_" (unless modfied with the
|
||||
For non SASjs projects, you could invoke in the autoexec, or in your own
|
||||
solution initialisation macro.
|
||||
|
||||
|
||||
If you have a good idea for another useful option, setting, or global
|
||||
variable - feel free to [raise an issue](
|
||||
https://github.com/sasjs/core/issues/new)!
|
||||
|
||||
All global variables are prefixed with "SASJS" (unless modified with the
|
||||
prefix parameter).
|
||||
|
||||
@param [in] prefix= (SASJS) The prefix to apply to the global macro variables
|
||||
@@ -36,18 +46,19 @@
|
||||
run;
|
||||
|
||||
options
|
||||
autocorrect /* disallow mis-spelled procedure names */
|
||||
noautocorrect /* disallow misspelled procedure names */
|
||||
compress=CHAR /* default is none so ensure we have something! */
|
||||
datastmtchk=ALLKEYWORDS /* protection from overwriting input datasets */
|
||||
errorcheck=STRICT /* catch errors in libname/filename statements */
|
||||
fmterr /* ensure err when a format cannot be found */
|
||||
mergenoby=%str(ERR)OR /* Throw err when a merge has no BY variables */
|
||||
missing=. /* some sites change this which causes hard to detect errors */
|
||||
%str(err)orcheck=STRICT /* catch errs in libname/filename statements */
|
||||
fmterr /* ensure err when a format cannot be found */
|
||||
mergenoby=%str(ERR)OR /* throw err when a merge has no BY variables */
|
||||
missing=. /* changing this can cause hard to detect errs */
|
||||
noquotelenmax /* avoid warnings for long strings */
|
||||
noreplace /* avoid overwriting permanent datasets */
|
||||
ps=max /* reduce log size slightly */
|
||||
validmemname=COMPATIBLE /* avoid special characters etc in table names */
|
||||
validvarname=V7 /* avoid special characters etc in variable names */
|
||||
varinitchk=%str(ERR)OR /* avoid data mistakes from variable name typos */
|
||||
varlenchk=%str(ERR)OR /* fail hard if truncation (data loss) can result */
|
||||
;
|
||||
|
||||
|
||||
@@ -63,7 +63,7 @@
|
||||
%if &action=OPEN %then %do;
|
||||
options nobomfile;
|
||||
data _null_;file &jref encoding='utf-8';
|
||||
put '{"START_DTTM" : "' "%sysfunc(datetime(),datetime20.3)" '"';
|
||||
put '{"PROCESSED_DTTM" : "' "%sysfunc(datetime(),E8601DT26.6)" '"';
|
||||
run;
|
||||
%end;
|
||||
%else %if (&action=ARR or &action=OBJ) %then %do;
|
||||
|
||||
@@ -180,7 +180,7 @@ run;
|
||||
%else %do;
|
||||
data _null_;
|
||||
putlog 'NOTE-' / 'NOTE-';
|
||||
putlog "NOTE- &sysmacroname: Table &lib..&ds locked at "@
|
||||
putlog "NOTE- &sysmacroname: Table &lib..&ds locked at "@;
|
||||
putlog " %sysfunc(datetime(),datetime19.) (iteration &x)"@;
|
||||
putlog 'NOTE-' / 'NOTE-';
|
||||
run;
|
||||
@@ -239,7 +239,7 @@ run;
|
||||
%let abortme=1;
|
||||
%end;
|
||||
|
||||
/* catch errors - mp_abort must be called outside of a logic block */
|
||||
/* catch errs - mp_abort must be called outside of a logic block */
|
||||
%mp_abort(iftrue=(&abortme=1),
|
||||
msg=%superq(msg),
|
||||
mac=&sysmacroname
|
||||
|
||||
@@ -11,22 +11,28 @@
|
||||
Usage:
|
||||
|
||||
%mp_searchdata(lib=sashelp, string=Jan)
|
||||
%mp_searchdata(lib=sashelp, numval=1)
|
||||
%mp_searchdata(lib=sashelp, ds=bird, numval=1)
|
||||
%mp_searchdata(lib=sashelp, ds=class, string=l,outobs=5)
|
||||
|
||||
|
||||
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
|
||||
@param [in] lib= The libref to search (should be already assigned)
|
||||
@param [in] ds= The dataset to search (leave blank to search entire library)
|
||||
@param [in] string= String value to search (case sensitive, can be partial)
|
||||
@param [in] numval= Numeric value to search (must be exact)
|
||||
@param [out] outloc= (0) Optionally specify the directory in which to
|
||||
create the the output datasets with matching rows. By default it will
|
||||
write them to a temporary subdirectory within the WORK folder.
|
||||
@param [out] outlib= (MPSEARCH) Assign a different libref to the output
|
||||
library containing the matching datasets / records
|
||||
@param [in] 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
|
||||
@param [in] filter_text= (1=1) Add a (valid) filter clause to further filter
|
||||
the results.
|
||||
|
||||
<h4> SAS Macros </h4>
|
||||
@li mf_getuniquename.sas
|
||||
@li mf_getvarlist.sas
|
||||
@li mf_getvartype.sas
|
||||
@li mf_mkdir.sas
|
||||
@@ -36,11 +42,12 @@
|
||||
@author Allan Bowe
|
||||
**/
|
||||
|
||||
%macro mp_searchdata(lib=sashelp
|
||||
%macro mp_searchdata(lib=
|
||||
,ds=
|
||||
,string= /* the query will use a contains (?) operator */
|
||||
,numval= /* numeric must match exactly */
|
||||
,outloc=%sysfunc(pathname(work))/mpsearch
|
||||
,outloc=0
|
||||
,outlib=MPSEARCH
|
||||
,outobs=-1
|
||||
,filter_text=%str(1=1)
|
||||
)/*/STORE SOURCE*/;
|
||||
@@ -57,8 +64,12 @@
|
||||
%if &string = %then %let type=N;
|
||||
%else %let type=C;
|
||||
|
||||
%if "&outloc"="0" %then %do;
|
||||
%let outloc=%sysfunc(pathname(work))/%mf_getuniquename();
|
||||
%end;
|
||||
|
||||
%mf_mkdir(&outloc)
|
||||
libname mpsearch "&outloc";
|
||||
libname &outlib "&outloc";
|
||||
|
||||
/* get the list of tables in the library */
|
||||
proc sql noprint;
|
||||
@@ -70,11 +81,6 @@ select distinct memname into: table_list separated by ' '
|
||||
%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( )));
|
||||
@@ -85,10 +91,10 @@ proc sql
|
||||
%end;
|
||||
%else %do;
|
||||
%let check_tm=%sysfunc(datetime());
|
||||
/* build sql statement */
|
||||
create table mpsearch.&table as select * from &lib..&table
|
||||
where %unquote(&filter_text) and
|
||||
(0
|
||||
/* prep input */
|
||||
data &outlib..&table;
|
||||
set &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( ));
|
||||
@@ -102,15 +108,20 @@ proc sql
|
||||
or ("&col"n = &numval)
|
||||
%end;
|
||||
%end;
|
||||
);
|
||||
);
|
||||
%if &outobs>-1 %then %do;
|
||||
if _n_ > &outobs then stop;
|
||||
%end;
|
||||
run;
|
||||
%put Search query for &table took
|
||||
%sysevalf(%sysfunc(datetime())-&check_tm) seconds;
|
||||
%if &sqlrc ne 0 %then %do;
|
||||
%put %str(WAR)NING: SQLRC=&sqlrc when processing &table;
|
||||
%if &syscc ne 0 %then %do;
|
||||
%put %str(ERR)ROR: SYSCC=&syscc when processing &lib..&table;
|
||||
%return;
|
||||
%end;
|
||||
%if %mf_nobs(mpsearch.&table)=0 %then %do;
|
||||
drop table mpsearch.&table;
|
||||
%if %mf_nobs(&outlib..&table)=0 %then %do;
|
||||
proc sql;
|
||||
drop table &outlib..&table;
|
||||
%end;
|
||||
%end;
|
||||
%end;
|
||||
|
||||
237
base/mp_storediffs.sas
Normal file
237
base/mp_storediffs.sas
Normal file
@@ -0,0 +1,237 @@
|
||||
/**
|
||||
@file
|
||||
@brief Converts deletes/changes/appends into a single audit table.
|
||||
@details When tracking changes to data over time, it can be helpful to have
|
||||
a single base table to track ALL modifications - enabling audit trail,
|
||||
data recovery, and change re-application. This macro is one of many
|
||||
data management utilities used in [Data Controller for SAS](
|
||||
https:datacontroller.io) - a comprehensive data ingestion solution, which
|
||||
works on any SAS platform (Viya, SAS 9, Foundation) and is free for up to 5
|
||||
users.
|
||||
|
||||
NOTE - this macro does not validate the inputs. It is assumed that the
|
||||
datasets containing the new / changed / deleted rows are CORRECT, contain
|
||||
no additional (or missing columns), and that the originals dataset contains
|
||||
all relevant base records (and no additionals).
|
||||
|
||||
Usage:
|
||||
|
||||
data work.orig work.deleted work.changed work.appended;
|
||||
set sashelp.class;
|
||||
if _n_=1 then do;
|
||||
output work.orig work.deleted;
|
||||
end;
|
||||
else if _n_=2 then do;
|
||||
output work.orig;
|
||||
age=99;
|
||||
output work.changed;
|
||||
end;
|
||||
else do;
|
||||
name='Newbie';
|
||||
output work.appended;
|
||||
stop;
|
||||
end;
|
||||
run;
|
||||
|
||||
%mp_storediffs(sashelp.class,work.orig,NAME
|
||||
,delds=work.deleted
|
||||
,modds=work.changed
|
||||
,appds=work.appended
|
||||
,outds=work.final
|
||||
,mdebug=1
|
||||
)
|
||||
|
||||
@param [in] libds Target table against which the changes were applied
|
||||
@param [in] origds Dataset with original (unchanged) records. Can be empty if
|
||||
only appending.
|
||||
@param [in] key Space seperated list of key variables
|
||||
@param [in] delds= (0) Dataset with deleted records
|
||||
@param [in] appds= (0) Dataset with appended records
|
||||
@param [in] modds= (0) Dataset with modified records
|
||||
@param [out] outds= (work.mp_storediffs) Output table containing stored data.
|
||||
Has the following format:
|
||||
|
||||
proc sql;
|
||||
create table &outds(
|
||||
load_ref char(36) label='unique load reference',
|
||||
processed_dttm num format=E8601DT26.6 label='Processed at timestamp',
|
||||
libref char(8) label='Library Reference (8 chars)',
|
||||
dsn char(32) label='Dataset Name (32 chars)',
|
||||
key_hash char(32) label=
|
||||
'MD5 Hash of primary key values (pipe seperated)',
|
||||
move_type char(1) label='Either (A)ppended, (D)eleted or (M)odified',
|
||||
is_pk num label='Is Primary Key Field? (1/0)',
|
||||
is_diff num label=
|
||||
'Did value change? (1/0/-1). Always -1 for appends and deletes.',
|
||||
tgtvar_type char(1) label='Either (C)haracter or (N)umeric',
|
||||
tgtvar_nm char(32) label='Target variable name (32 chars)',
|
||||
oldval_num num label='Old (numeric) value',
|
||||
newval_num num label='New (numeric) value',
|
||||
oldval_char char(32767) label='Old (character) value',
|
||||
newval_char char(32767) label='New (character) value',
|
||||
constraint pk_mpe_audit
|
||||
primary key(load_ref,libref,dsn,key_hash,tgtvar_nm)
|
||||
);
|
||||
|
||||
@param [in] processed_dttm= (0) Provide a datetime constant in relation to
|
||||
the actual load time. If not provided, current timestamp is used.
|
||||
@param [in] mdebug= set to 1 to enable DEBUG messages and preserve outputs
|
||||
@param [out] loadref= (0) Provide a unique key to reference the load,
|
||||
otherwise a UUID will be generated.
|
||||
|
||||
<h4> SAS Macros </h4>
|
||||
@li mf_getquotedstr.sas
|
||||
@li mf_getuniquename.sas
|
||||
@li mf_getvarlist.sas
|
||||
|
||||
@version 9.2
|
||||
@author Allan Bowe
|
||||
**/
|
||||
/** @cond */
|
||||
|
||||
%macro mp_storediffs(libds
|
||||
,origds
|
||||
,key
|
||||
,delds=0
|
||||
,appds=0
|
||||
,modds=0
|
||||
,outds=work.mp_storediffs
|
||||
,loadref=0
|
||||
,processed_dttm=0
|
||||
,mdebug=0
|
||||
)/*/STORE SOURCE*/;
|
||||
%local dbg;
|
||||
%if &mdebug=1 %then %do;
|
||||
%put &sysmacroname entry vars:;
|
||||
%put _local_;
|
||||
%end;
|
||||
%else %let dbg=*;
|
||||
|
||||
/* set up unique and temporary vars */
|
||||
%local ds1 ds2 ds3 ds4 hashkey inds_auto inds_keep dslist;
|
||||
%let ds1=%upcase(work.%mf_getuniquename(prefix=mpsd_ds1));
|
||||
%let ds2=%upcase(work.%mf_getuniquename(prefix=mpsd_ds2));
|
||||
%let ds3=%upcase(work.%mf_getuniquename(prefix=mpsd_ds3));
|
||||
%let ds4=%upcase(work.%mf_getuniquename(prefix=mpsd_ds4));
|
||||
%let hashkey=%upcase(%mf_getuniquename(prefix=mpsd_hashkey));
|
||||
%let inds_auto=%upcase(%mf_getuniquename(prefix=mpsd_inds_auto));
|
||||
%let inds_keep=%upcase(%mf_getuniquename(prefix=mpsd_inds_keep));
|
||||
|
||||
%let dslist=&origds;
|
||||
%if &delds ne 0 %then %do;
|
||||
%let delds=%upcase(&delds);
|
||||
%if %scan(&delds,-1,.)=&delds %then %let delds=WORK.&delds;
|
||||
%let dslist=&dslist &delds;
|
||||
%end;
|
||||
%if &appds ne 0 %then %do;
|
||||
%let appds=%upcase(&appds);
|
||||
%if %scan(&appds,-1,.)=&appds %then %let appds=WORK.&appds;
|
||||
%let dslist=&dslist &appds;
|
||||
%end;
|
||||
%if &modds ne 0 %then %do;
|
||||
%let modds=%upcase(&modds);
|
||||
%if %scan(&modds,-1,.)=&modds %then %let modds=WORK.&modds;
|
||||
%let dslist=&dslist &modds;
|
||||
%end;
|
||||
|
||||
%let origds=%upcase(&origds);
|
||||
%if %scan(&origds,-1,.)=&origds %then %let origds=WORK.&origds;
|
||||
|
||||
%let key=%upcase(&key);
|
||||
|
||||
/* hash the key and append all the tables (marking the source) */
|
||||
data &ds1;
|
||||
set &dslist indsname=&inds_auto;
|
||||
&hashkey=put(md5(catx('|',%mf_getquotedstr(&key,quote=N))),$hex32.);
|
||||
&inds_keep=&inds_auto;
|
||||
proc sort;
|
||||
by &inds_keep &hashkey;
|
||||
run;
|
||||
|
||||
/* transpose numeric & char vars */
|
||||
proc transpose data=&ds1
|
||||
out=&ds2(rename=(&hashkey=key_hash _name_=tgtvar_nm col1=newval_num));
|
||||
by &inds_keep &hashkey;
|
||||
var _numeric_;
|
||||
run;
|
||||
proc transpose data=&ds1
|
||||
out=&ds3(
|
||||
rename=(&hashkey=key_hash _name_=tgtvar_nm col1=newval_char)
|
||||
where=(tgtvar_nm not in ("&hashkey","&inds_keep"))
|
||||
);
|
||||
by &inds_keep &hashkey;
|
||||
var _character_;
|
||||
run;
|
||||
data &ds4;
|
||||
length &inds_keep $41 tgtvar_nm $32;
|
||||
set &ds2 &ds3 indsname=&inds_auto;
|
||||
|
||||
tgtvar_nm=upcase(tgtvar_nm);
|
||||
if tgtvar_nm in (%upcase(%mf_getvarlist(&libds,dlm=%str(,),quote=DOUBLE)));
|
||||
|
||||
if &inds_auto="&ds2" then tgtvar_type='N';
|
||||
else if &inds_auto="&ds3" then tgtvar_type='C';
|
||||
else do;
|
||||
putlog "%str(ERR)OR: unidentified vartype input!" &inds_auto;
|
||||
call symputx('syscc',98);
|
||||
end;
|
||||
|
||||
if &inds_keep="&appds" then move_type='A';
|
||||
else if &inds_keep="&delds" then move_type='D';
|
||||
else if &inds_keep="&modds" then move_type='M';
|
||||
else if &inds_keep="&origds" then move_type='O';
|
||||
else do;
|
||||
putlog "%str(ERR)OR: unidentified movetype input!" &inds_keep;
|
||||
call symputx('syscc',99);
|
||||
end;
|
||||
tgtvar_nm=upcase(tgtvar_nm);
|
||||
if tgtvar_nm in (%mf_getquotedstr(&key)) then is_pk=1;
|
||||
else is_pk=0;
|
||||
drop &inds_keep;
|
||||
run;
|
||||
|
||||
%if "&loadref"="0" %then %let loadref=%sysfunc(uuidgen());
|
||||
%if &processed_dttm=0 %then %let processed_dttm=%sysfunc(datetime());
|
||||
%let libds=%upcase(&libds);
|
||||
|
||||
/* join orig vals for modified & deleted */
|
||||
proc sql;
|
||||
create table &outds as
|
||||
select "&loadref" as load_ref length=36
|
||||
,&processed_dttm as processed_dttm format=E8601DT26.6
|
||||
,"%scan(&libds,1,.)" as libref length=8
|
||||
,"%scan(&libds,2,.)" as dsn length=32
|
||||
,b.key_hash length=32
|
||||
,b.move_type length=1
|
||||
,b.tgtvar_nm length=32
|
||||
,b.is_pk
|
||||
,case when b.move_type ne 'M' then -1
|
||||
when a.newval_num=b.newval_num and a.newval_char=b.newval_char then 0
|
||||
else 1
|
||||
end as is_diff
|
||||
,b.tgtvar_type length=1
|
||||
,case when b.move_type='D' then b.newval_num
|
||||
else a.newval_num
|
||||
end as oldval_num
|
||||
,case when b.move_type='D' then .
|
||||
else b.newval_num
|
||||
end as newval_num
|
||||
,case when b.move_type='D' then b.newval_char
|
||||
else a.newval_char
|
||||
end as oldval_char length=32767
|
||||
,case when b.move_type='D' then ''
|
||||
else b.newval_char
|
||||
end as newval_char length=32767
|
||||
from &ds4(where=(move_type='O')) as a
|
||||
full join &ds4(where=(move_type ne 'O')) as b
|
||||
on a.tgtvar_nm=b.tgtvar_nm
|
||||
and a.key_hash=b.key_hash
|
||||
order by move_type, key_hash,is_pk desc, tgtvar_nm;
|
||||
|
||||
%if &mdebug=0 %then %do;
|
||||
proc sql;
|
||||
drop table &ds1, &ds2, &ds3, &ds4;
|
||||
%end;
|
||||
|
||||
%mend mp_storediffs;
|
||||
/** @endcond */
|
||||
@@ -95,7 +95,7 @@ data _null_;
|
||||
put '%if &action=OPEN %then %do; ';
|
||||
put ' options nobomfile; ';
|
||||
put ' data _null_;file &jref encoding=''utf-8''; ';
|
||||
put ' put ''{"START_DTTM" : "'' "%sysfunc(datetime(),datetime20.3)" ''"''; ';
|
||||
put ' put ''{"PROCESSED_DTTM" : "'' "%sysfunc(datetime(),E8601DT26.6)" ''"''; ';
|
||||
put ' run; ';
|
||||
put '%end; ';
|
||||
put '%else %if (&action=ARR or &action=OBJ) %then %do; ';
|
||||
|
||||
@@ -45,7 +45,7 @@ run;
|
||||
data &outattrs;
|
||||
keep type name value;
|
||||
length type $4 name $256 value $32767;
|
||||
rc1=1;n1=1;type='Prop';
|
||||
rc1=1;n1=1;type='Prop';name='';value='';
|
||||
do while(rc1>0);
|
||||
rc1=metadata_getnprp("&uri",n1,name,value);
|
||||
if rc1>0 then output;
|
||||
|
||||
@@ -39,7 +39,7 @@
|
||||
data &outds;
|
||||
length uri serveruri conn_uri domainuri libname ServerContext AuthDomain
|
||||
path_schema usingpkguri type tableuri $256 id $17
|
||||
libdesc $200 libref engine $8 IsDBMSLibname $1
|
||||
libdesc $200 libref engine $8 IsDBMSLibname IsPreassigned $1
|
||||
tablename $50 /* metadata table names can be longer than $32 */
|
||||
;
|
||||
keep libname libdesc libref engine ServerContext path_schema AuthDomain
|
||||
|
||||
23
tests/crossplatform/mf_dedup.test.sas
Normal file
23
tests/crossplatform/mf_dedup.test.sas
Normal file
@@ -0,0 +1,23 @@
|
||||
/**
|
||||
@file
|
||||
@brief Testing mf_dedup macro
|
||||
|
||||
<h4> SAS Macros </h4>
|
||||
@li mf_dedup.sas
|
||||
@li mp_assert.sas
|
||||
|
||||
**/
|
||||
|
||||
%let str=One two one two and through and through;
|
||||
|
||||
%mp_assert(
|
||||
iftrue=("%mf_dedup(&str)"="One two one and through"),
|
||||
desc=Basic test,
|
||||
outds=work.test_results
|
||||
)
|
||||
|
||||
%mp_assert(
|
||||
iftrue=("%mf_dedup(&str,outdlm=%str(,))"="One,two,one,and,through"),
|
||||
desc=Outdlm test,
|
||||
outds=work.test_results
|
||||
)
|
||||
33
tests/crossplatform/mf_isint.test.sas
Normal file
33
tests/crossplatform/mf_isint.test.sas
Normal file
@@ -0,0 +1,33 @@
|
||||
/**
|
||||
@file
|
||||
@brief Testing mf_isint macro
|
||||
|
||||
<h4> SAS Macros </h4>
|
||||
@li mf_isint.sas
|
||||
@li mp_assert.sas
|
||||
|
||||
**/
|
||||
|
||||
%mp_assert(
|
||||
iftrue=(
|
||||
"%mf_isint(1)"="1"
|
||||
),
|
||||
desc=Checking basic mf_isint(1),
|
||||
outds=work.test_results
|
||||
)
|
||||
|
||||
%mp_assert(
|
||||
iftrue=(
|
||||
"%mf_isint(1.1)"="0"
|
||||
),
|
||||
desc=Checking basic mf_isint(1.1),
|
||||
outds=work.test_results
|
||||
)
|
||||
|
||||
%mp_assert(
|
||||
iftrue=(
|
||||
"%mf_isint(-1)"="1"
|
||||
),
|
||||
desc=Checking mf_isint(-1),
|
||||
outds=work.test_results
|
||||
)
|
||||
@@ -15,10 +15,12 @@ filename inc temp;
|
||||
data _null_;
|
||||
set work.test;
|
||||
file inc;
|
||||
line=cats('%mp_ds2fmtds(sashelp.',memname,',',memname,')');
|
||||
libds=cats('sashelp.',memname);
|
||||
if exist(libds) then line=cats('%mp_ds2fmtds(',libds,',',memname,')');
|
||||
put line;
|
||||
run;
|
||||
|
||||
options obs=50;
|
||||
%inc inc;
|
||||
|
||||
%mp_assert(
|
||||
|
||||
29
tests/crossplatform/mp_searchdata.test.sas
Normal file
29
tests/crossplatform/mp_searchdata.test.sas
Normal file
@@ -0,0 +1,29 @@
|
||||
/**
|
||||
@file
|
||||
@brief Testing mp_searchdata.sas
|
||||
|
||||
<h4> SAS Macros </h4>
|
||||
@li mp_searchdata.sas
|
||||
@li mp_assert.sas
|
||||
|
||||
|
||||
**/
|
||||
|
||||
/** Test 1 - generic useage */
|
||||
|
||||
%mp_searchdata(lib=sashelp, ds=class, string=a)
|
||||
%mp_assert(
|
||||
iftrue=(&syscc=0),
|
||||
desc=No errors in regular usage,
|
||||
outds=work.test_results
|
||||
)
|
||||
|
||||
/** Test 2 - with obs issue */
|
||||
|
||||
%mp_searchdata(lib=sashelp, ds=class, string=l,outobs=5)
|
||||
|
||||
%mp_assert(
|
||||
iftrue=("&SYSWARNINGTEXT" = ""),
|
||||
desc=Ensuring WARN status is clean,
|
||||
outds=work.test_results
|
||||
)
|
||||
64
tests/crossplatform/mp_storediffs.test.sas
Normal file
64
tests/crossplatform/mp_storediffs.test.sas
Normal file
@@ -0,0 +1,64 @@
|
||||
/**
|
||||
@file
|
||||
@brief Testing mp_storediffs macro
|
||||
|
||||
<h4> SAS Macros </h4>
|
||||
@li mp_storediffs.sas
|
||||
@li mp_assert.sas
|
||||
@li mp_assertcolvals.sas
|
||||
@li mp_assertdsobs.sas
|
||||
|
||||
**/
|
||||
|
||||
/* make some data */
|
||||
|
||||
data work.orig work.deleted work.changed work.appended;
|
||||
set sashelp.class;
|
||||
if _n_=1 then do;
|
||||
output work.orig work.deleted;
|
||||
end;
|
||||
else if _n_=2 then do;
|
||||
output work.orig;
|
||||
age=99;
|
||||
output work.changed;
|
||||
end;
|
||||
else do;
|
||||
name='Newbie';
|
||||
output work.appended;
|
||||
stop;
|
||||
end;
|
||||
run;
|
||||
|
||||
%mp_storediffs(sashelp.class,work.orig,NAME
|
||||
,delds=work.deleted
|
||||
,modds=work.changed
|
||||
,appds=work.appended
|
||||
,outds=work.final
|
||||
,mdebug=1
|
||||
)
|
||||
|
||||
%mp_assert(
|
||||
iftrue=(
|
||||
%str(&syscc)=%str(0)
|
||||
),
|
||||
desc=ensure no errors,
|
||||
outds=work.test_results
|
||||
)
|
||||
|
||||
%mp_assertdsobs(work.final,
|
||||
desc=Has 15 records,
|
||||
test=EQUALS 15,
|
||||
outds=work.test_results
|
||||
)
|
||||
|
||||
data work.check;
|
||||
length val $10;
|
||||
do val='C','N';
|
||||
output;
|
||||
end;
|
||||
run;
|
||||
%mp_assertcolvals(work.final.tgtvar_type,
|
||||
checkvals=work.check.val,
|
||||
desc=All values have a match,
|
||||
test=ALLVALS
|
||||
)
|
||||
@@ -33,6 +33,7 @@ run;
|
||||
%put TEST1: checking web service code;
|
||||
data work.test_results;
|
||||
length test_description $256 test_result $4 test_comments $256;
|
||||
if _n_=1 then call missing (of _all_);
|
||||
infile compare end=eof;
|
||||
input;
|
||||
if eof then do;
|
||||
|
||||
@@ -243,7 +243,7 @@ data _null_;
|
||||
put '%if &action=OPEN %then %do; ';
|
||||
put ' options nobomfile; ';
|
||||
put ' data _null_;file &jref encoding=''utf-8''; ';
|
||||
put ' put ''{"START_DTTM" : "'' "%sysfunc(datetime(),datetime20.3)" ''"''; ';
|
||||
put ' put ''{"PROCESSED_DTTM" : "'' "%sysfunc(datetime(),E8601DT26.6)" ''"''; ';
|
||||
put ' run; ';
|
||||
put '%end; ';
|
||||
put '%else %if (&action=ARR or &action=OBJ) %then %do; ';
|
||||
|
||||
@@ -100,6 +100,8 @@ options noquotelenmax;
|
||||
%local href cnt;
|
||||
%let cnt=0;
|
||||
data _null_;
|
||||
length rel href $512;
|
||||
call missing(rel,href);
|
||||
set &libref1..links;
|
||||
if rel='members' then do;
|
||||
url=cats("'","&base_uri",href,"?limit=10000'");
|
||||
@@ -123,6 +125,7 @@ options noquotelenmax;
|
||||
libname &libref2 JSON fileref=&fname2;
|
||||
data &outds;
|
||||
length id $36 name $128 uri $64 type $32 description $256;
|
||||
if _n_=1 then call missing (of _all_);
|
||||
set &libref2..items;
|
||||
run;
|
||||
filename &fname2 clear;
|
||||
|
||||
@@ -92,6 +92,8 @@ data;run;
|
||||
%local joburi;
|
||||
%let joburi=0;
|
||||
data _null_;
|
||||
length name uri $512;
|
||||
call missing(name,uri);
|
||||
set &foldermembers;
|
||||
if name="&name" and uri=:'/jobDefinitions/definitions'
|
||||
then call symputx('joburi',uri);
|
||||
|
||||
@@ -115,6 +115,8 @@ data;run;
|
||||
%local joburi;
|
||||
%let joburi=0;
|
||||
data _null_;
|
||||
length name uri $512;
|
||||
call missing(name,uri);
|
||||
set &foldermembers;
|
||||
if name="&name" and uri=:'/jobDefinitions/definitions'
|
||||
then call symputx('joburi',uri);
|
||||
|
||||
@@ -168,6 +168,7 @@ run;
|
||||
data &outds;
|
||||
format _program uri $128. state $32. stateDetails $32. timestamp datetime19.
|
||||
jobparams $32767.;
|
||||
call missing (of _all_);
|
||||
stop;
|
||||
run;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user