1
0
mirror of https://github.com/sasjs/core.git synced 2025-12-11 14:34:35 +00:00

Compare commits

...

76 Commits

Author SHA1 Message Date
Allan Bowe
c41918c0a8 Merge pull request #134 from sasjs/fmtfix
fix: preventing error when mp_applyformats has no formats to apply
2021-12-29 14:19:59 +02:00
munja
0361ca574d fix: preventing error when mp_applyformats has no formats to apply 2021-12-29 12:19:34 +00:00
Allan Bowe
c75c169b80 Merge pull request #133 from sasjs/fmtname
fix: adding fmtname to mp_getcols() macro
2021-12-28 15:39:07 +02:00
munja
eac47bd5db fix: adding fmtname to mp_getcols() macro 2021-12-28 13:25:53 +00:00
munja
d302ef266d chore: doc page formatting & content 2021-12-27 11:54:46 +00:00
munja
fdfe9b8250 fix: enabling further debugging in mp_filterstore.sas 2021-12-26 20:32:56 +00:00
munja
9b1f0d7bcb fix: avoiding append warning: syswarningtext=Variable processed_dttm has format DATETIME19. on the BASE data set and format E8601DT26. on the DATA data set. 2021-12-26 16:27:04 +00:00
Allan Bowe
98b1c44283 Merge pull request #131 from sasjs/mp_filterstore
feat: mp_filterstore macro
2021-12-26 17:28:45 +02:00
munja
ce026f19b5 feat: new mp_filterstore macro 2021-12-26 15:06:06 +00:00
munja
8e723d06b0 feat: mp_filterstore macro 2021-12-26 01:06:56 +00:00
Allan Bowe
a6d84cc65a Merge pull request #130 from sasjs/newvalidation
feat: new macro (mp_retainedkey) for adding retained key values to a …
2021-12-25 22:37:43 +02:00
munja
536ce8e95d feat: new macro (mp_retainedkey) for adding retained key values to a staging table 2021-12-25 20:21:32 +00:00
Allan Bowe
bc1d9e619b Merge pull request #129 from sasjs/newvalidation
feat: new validation (ISINT) in the mp_validatecol.sas macro
2021-12-25 00:19:50 +02:00
munja
1062a97cfe feat: new validation (ISINT) in the mp_validatecol.sas macro - https://core.sasjs.io/mp__validatecol_8sas_source.html 2021-12-24 22:02:23 +00:00
Allan Bowe
51db64c90a Merge pull request #128 from sasjs/writefile
Writefile
2021-12-23 22:13:20 +02:00
munja
7c4278c3f9 chore: updating all.sas 2021-12-23 19:29:54 +00:00
munja
6c6b55dcea chore: updating header, adding stop statement in mp_makedata(), writing test for mf_existvar() 2021-12-23 19:29:37 +00:00
munja
66b0c9e77e feat: new mf_writefile() macro 2021-12-23 19:29:01 +00:00
Allan Bowe
caf3b95269 Merge pull request #126 from sasjs/applyformats
Applyformats
2021-12-23 16:15:09 +02:00
munja
3866b97416 chore: updating doc header 2021-12-23 13:55:53 +00:00
munja
d687658687 chore: updating all.sas 2021-12-23 13:51:11 +00:00
munja
9f815c73e9 feat: new mp_applyformats macro (and test), plus new addition to mp_validatecol (is_format) 2021-12-23 13:50:58 +00:00
Allan Bowe
a13c782074 Merge pull request #125 from sasjs/makedata
`mp_makedata()` improvements
2021-12-23 13:11:36 +02:00
munja
f2991cfd63 fix: enabling makedata support for charvars > and tables without primary keys. Also added tests. 2021-12-23 10:56:01 +00:00
Allan Bowe
8eb4f0844c Merge pull request #124 from sasjs/updates
feat: mf_islibds() macro to test if a library.dataset reference is valid
2021-12-22 17:28:22 +02:00
munja
f90dc069dc feat: update to makedata to respect primary keys (and enable joins to other tables) 2021-12-22 15:12:10 +00:00
munja
436b430389 feat: mf_islibds() macro to test if a library.dataset reference is syntactically valid 2021-12-22 11:23:57 +00:00
Allan Bowe
6667b91ced Merge pull request #123 from sasjs/words
feat: new wordsinstr1andstr2() macro and associated tests
2021-12-22 00:07:39 +02:00
munja
bce56d8105 feat: new wordsinstr1andstr2() macro and associated tests 2021-12-21 21:54:48 +00:00
munja
2ec440b321 fix: removing termstr=lf as it breaks on SAS 9 deploys 2021-12-21 19:34:08 +00:00
munja
3d2ad531cf chore: docs 2021-12-18 14:37:54 +00:00
Allan Bowe
09136cfdbb Merge pull request #122 from sasjs/serverfix
fix: making ms_webout work with SAS on Windows Desktop
2021-12-18 14:12:35 +00:00
munja
0ca16f3d04 fix: quoting the server option in mm_assigndirectlib() to avoid assignment errors when the name contains dashes aetc 2021-12-18 14:11:38 +00:00
Allan Bowe
1e72f13f2d fix: making ms_webout work with SAS on Windows Desktop 2021-12-17 23:01:43 +00:00
Allan Bowe
5e8e8e02d3 Merge pull request #121 from sasjs/mp_ds2md
BREAKING CHANGE: renamed mp_mdtablewrite.sas to mp_ds2md.sas
2021-12-17 17:31:01 +00:00
munja
b2e2c7c798 chore(lint): fix indentation 2021-12-17 17:14:41 +00:00
munja
b29dd38188 fix: preventing merged cells in sas-generated markdown tables.
BREAKING CHANGE: renamed mp_mdtablewrite.sas to mp_ds2md.sas and modified the parameter from fref to outref.  This makes it more consistent with the other mp_ds2xx range of macros.
2021-12-17 17:14:03 +00:00
Allan Bowe
2e122c2ada Merge pull request #120 from sasjs/fix_makedata
fix: check charvars and numvars exist. Closes #119
2021-12-17 15:25:36 +00:00
Ivor Townsend
8b68c3bb27 fix: check charvars and numvars exist. Closes #119 2021-12-17 15:08:56 +00:00
Allan Bowe
8c7523deda Merge pull request #118 from sasjs/fmting
More Macros
2021-12-17 10:28:37 +00:00
munja
e8f656f48a chore: lint fixes 2021-12-17 00:57:35 +00:00
munja
1eb90202b9 fix: failing test for mp_getformats 2021-12-17 00:55:51 +00:00
munja
82108f4b97 fix: ensuring test for mp_sortinplace passes, fixing uninitialised var in mp_mdtablewrite, fix for non pk table in mp_getformats 2021-12-17 00:53:13 +00:00
munja
ab1030afb1 feat: finishing mp_formats and adding a test, including prefix in mp_init, allowing mp_sortinplace to work when there is no primary key, sand other small fixes 2021-12-17 00:32:49 +00:00
munja
26292740bb feat: mp_getformats() macro. Extracts bformat summary and detail 2021-12-16 00:02:52 +00:00
munja
96cc131305 chore(docs): updating header in mf_getquotedstr 2021-12-16 00:01:58 +00:00
munja
724cd72876 feat: adding mf_getfmtlist() and mf_getfmtname() macros and associated tests. Also added &sasjswork as a global macro variable in mp_init(). 2021-12-15 21:05:12 +00:00
munja
fa5d9ef744 feat: adding ls=max to mp_init.sas to reduce log ever so slightly and also to avoid word truncation 2021-12-15 19:20:14 +00:00
Allan Bowe
dc63b4adf5 Merge pull request #117 from sasjs/mf_dedup
`mf_dedup()` macro - removes duplicates from a macro string
2021-12-15 17:44:54 +00:00
munja
3f20ca03dd chore: updating all.sas 2021-12-15 17:04:27 +00:00
munja
3a826dccf1 feat: mf_dedup and associated test 2021-12-15 17:04:08 +00:00
munja
a1ce68ce56 fix: avoiding uninitialised variables in mm_getdetails and mm_gettables.sas 2021-12-15 15:10:17 +00:00
munja
a45384aacb chore: updating all.sas 2021-12-15 12:18:56 +00:00
Allan Bowe
032c4f318e Merge pull request #114 from sasjs/issue113
feat: mp_storediffs macro.  Closes #113
2021-12-15 12:18:10 +00:00
munja
5faaa4a4cd fix: test for mp_storediffs 2021-12-15 12:15:36 +00:00
Allan Bowe
4e41182521 Merge pull request #116 from sasjs/fix/mp_init_options_fix
fix: Aligns the autocorrect option with the intent. Closes #115.
2021-12-15 11:58:50 +00:00
Trevor Moody
7185032680 fix: Aligns the autocorrect option with the intent. 2021-12-15 11:25:41 +00:00
munja
c9d8df0a48 fix: updates from testing mp_storediffs (impact on some related macros) 2021-12-15 10:16:24 +00:00
munja
d93693ba55 feat: mp_storediffs macro. Closes #113 2021-12-14 17:45:37 +00:00
munja
d49b21f3f1 fix: initialising name/uri in mv_jobexecute 2021-12-14 08:42:39 +00:00
munja
a45d280a51 fix: avoiding uninitialised variables in mv_getjobcode and mv_getfoldermembers 2021-12-14 08:20:32 +00:00
Allan Bowe
2536e299ad Merge pull request #112 from sasjs/mf_isint
feat: new macro to determine if a macro variable value is an integer …
2021-12-13 18:55:41 +00:00
munja
8b5238230b feat: new macro to determine if a macro variable value is an integer - mf_isint (and associated test) 2021-12-13 18:36:22 +00:00
munja
0ce7efee3e fix: declaring msg variable prior to set statement in mp_copyfolder.sas 2021-12-13 11:26:17 +00:00
munja
357677e45c chore: switching pre-commit hook to bash from shell 2021-12-13 09:14:29 +00:00
Allan Bowe
a4a332926e Merge pull request #111 from sasjs/issue110
feat: adding varinitchk=ERROR to mp_init.  Closes #110.
2021-12-13 08:44:12 +00:00
munja
0a29006914 chore: running all.sas 2021-12-13 01:08:37 +00:00
munja
0885bad859 fix: updating the tests following varinitchk=error enablement. Removing the word 'error' from documenttion. 2021-12-13 01:07:54 +00:00
munja
42bd1750bd feat: adding varinitchk=ERROR to mp_init. Closes #110. Also updated the comments / documentation 2021-12-12 22:57:25 +00:00
Allan Bowe
58784b2f28 Merge pull request #109 from sasjs/issue108
fix: rebuilding mp_searchdata into data step to avoid SQL warning.
2021-12-11 14:34:08 +00:00
munja
e125aace9b fix: rebuilding mp_searchdata into data step to avoid SQL warning. Added test and refreshed docs. Closes #108 2021-12-10 15:56:39 +00:00
Allan Bowe
b02a9e3478 Merge pull request #107 from sasjs/hashdclogic
fix: adding iftrue switch to mp_hashdataset
2021-12-08 20:59:35 +00:00
munja
3d3c76c836 fix: adding iftrue switch to mp_hashdataset 2021-12-08 20:58:59 +00:00
Allan Bowe
e039f1cd83 Merge pull request #106 from sasjs/hook
chore: adding hook script to prevent accidental commits to master
2021-12-07 17:44:00 +00:00
munja
6c8165601d chore: adding hook script to prevent accidental commits to master 2021-12-07 16:58:23 +00:00
munja
596624c1bf chore: remove the word ERROR from the code 2021-12-06 15:14:46 +00:00
81 changed files with 4687 additions and 529 deletions

View File

@@ -1,2 +1,11 @@
#!/bin/sh
sasjs lint
#!/bin/bash
sasjs lint
# Avoid commits to the master branch
BRANCH=`git rev-parse --abbrev-ref HEAD`
if [[ "$BRANCH" =~ ^(master|main|develop)$ ]]; then
echo "You are on branch $BRANCH. Are you sure you want to commit to this branch?"
echo "If so, commit with -n to bypass the pre-commit hook."
exit 1
fi

View File

@@ -6,7 +6,7 @@
"hasMacroParentheses": true,
"noNestedMacros": false,
"noSpacesInFileNames": true,
"maxLineLength": 230,
"maxLineLength": 300,
"lowerCaseFileNames": true,
"noTabIndentation": true,
"indentationMultiple": 2

2111
all.sas

File diff suppressed because it is too large Load Diff

54
base/mf_dedup.sas Normal file
View 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;

View File

@@ -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);

View File

@@ -7,8 +7,12 @@
%put %mf_existvar(work.someds, somevar)
@param libds (positional) - 2 part dataset or view reference
@param var (positional) - variable name
@param [in] libds 2 part dataset or view reference
@param [in] var variable name
<h4> Related Macros </h4>
@li mf_existvar.test.sas
@version 9.2
@author Allan Bowe
**/

View File

@@ -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

View File

@@ -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

View File

@@ -12,9 +12,10 @@
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).
@param [in] libref Library reference (also accepts a 2 level libds ref).
@return output returns the library engine for the FIRST library encountered.
@return output returns the library engine (uppercase) for the FIRST library
encountered.
@warning will only return the FIRST library engine - for concatenated
libraries, with different engines, inconsistent results may be encountered.
@@ -46,7 +47,7 @@
%let rc= %sysfunc(close(&dsid));
%end;
&engine
%upcase(&engine)
%mend mf_getengine;

61
base/mf_getfmtlist.sas Normal file
View File

@@ -0,0 +1,61 @@
/**
@file
@brief Returns a distinct list of formats from a table
@details Reads the dataset header and returns a distinct list of formats
applied.
%put NOTE- %mf_getfmtlist(sashelp.prdsale);
%put NOTE- %mf_getfmtlist(sashelp.shoes);
%put NOTE- %mf_getfmtlist(sashelp.demographics);
returns:
> DOLLAR $CHAR W MONNAME
> $CHAR BEST DOLLAR
> BEST Z $CHAR COMMA PERCENTN
@param [in] libds Two part library.dataset reference.
<h4> SAS Macros </h4>
@li mf_getfmtname.sas
@version 9.2
@author Allan Bowe
**/
%macro mf_getfmtlist(libds
)/*/STORE SOURCE*/;
/* declare local vars */
%local out dsid nvars x rc fmt;
/* open dataset in macro */
%let dsid=%sysfunc(open(&libds));
/* continue if dataset exists */
%if &dsid %then %do;
/* loop each variable in the dataset */
%let nvars=%sysfunc(attrn(&dsid,NVARS));
%do x=1 %to &nvars;
/* grab format and check it exists */
%let fmt=%sysfunc(varfmt(&dsid,&x));
%if %quote(&fmt) ne %quote() %then %let fmt=%mf_getfmtname(&fmt);
%else %do;
/* assign default format depending on variable type */
%if %sysfunc(vartype(&dsid, &x))=C %then %let fmt=$CHAR;
%else %let fmt=BEST;
%end;
/* concatenate unique list of formats */
%if %sysfunc(indexw(&out,&fmt,%str( )))=0 %then %let out=&out &fmt;
%end;
%let rc=%sysfunc(close(&dsid));
%end;
%else %do;
%put &sysmacroname: Unable to open &libds (rc=&dsid);
%put &sysmacroname: SYSMSG= %sysfunc(sysmsg());
%let rc=%sysfunc(close(&dsid));
%end;
/* send them out without spaces or quote markers */
%do;%unquote(&out)%end;
%mend mf_getfmtlist;

44
base/mf_getfmtname.sas Normal file
View File

@@ -0,0 +1,44 @@
/**
@file
@brief Extracts a format name from a fully defined format
@details Converts formats in like $thi3. and th13.2 $THI and TH.
Usage:
%put %mf_getfmtname(8.);
%put %mf_getfmtname($4.);
%put %mf_getfmtname(comma14.10);
Returns:
> W
> $CHAR
> COMMA
Note that system defaults are inferred from the values provided.
@param [in] fmt The fully defined format. If left blank, nothing is returned.
@returns The name (without width or decimal) of the format.
@version 9.2
@author Allan Bowe
**/
%macro mf_getfmtname(fmt
)/*/STORE SOURCE*/ /minoperator mindelimiter=' ';
%local out dsid nvars x rc fmt;
/* extract actual format name from the format definition */
%let fmt=%scan(&fmt,1,.);
%do %while(%substr(&fmt,%length(&fmt),1) in 1 2 3 4 5 6 7 8 9 0);
%if %length(&fmt)=1 %then %let fmt=W;
%else %let fmt=%substr(&fmt,1,%length(&fmt)-1);
%end;
%if &fmt=$ %then %let fmt=$CHAR;
/* send them out without spaces or quote markers */
%do;%unquote(%upcase(&fmt))%end;
%mend mf_getfmtname;

View File

@@ -16,8 +16,8 @@
> "these","words","are","double","quoted"
@param [in] in_str The unquoted, spaced delimited string to transform
@param [in] dlm= The delimeter to be applied to the output (default comma)
@param [in] indlm= (,) The delimeter used for the input (default is space)
@param [in] dlm= (,) The delimeter to be applied to the output (default comma)
@param [in] indlm= ( ) The delimeter used for the input (default is space)
@param [in] quote= (S) The quote mark to apply (S=Single, D=Double, N=None).
If any other value than uppercase S or D is supplied, then that value will
be used as the quoting character.
@@ -28,7 +28,10 @@
**/
%macro mf_getquotedstr(IN_STR,DLM=%str(,),QUOTE=S,indlm=%str( )
%macro mf_getquotedstr(IN_STR
,DLM=%str(,)
,QUOTE=S
,indlm=%str( )
)/*/STORE SOURCE*/;
/* credit Rowland Hale - byte34 is double quote, 39 is single quote */
%if &quote=S %then %let quote=%qsysfunc(byte(39));

View File

@@ -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
View 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;

40
base/mf_islibds.sas Normal file
View File

@@ -0,0 +1,40 @@
/**
@file
@brief Checks whether a string follows correct library.dataset format
@details Many macros in the core library accept a library.dataset parameter
referred to as 'libds'. This macro validates the structure of that parameter,
eg:
@li 8 character libref?
@li 32 character dataset?
@li contains a period?
It does NOT check whether the dataset exists, or if the library is assigned.
Usage:
%put %mf_islibds(work.something)=1;
%put %mf_islibds(nolib)=0;
%put %mf_islibds(badlibref.ds)=0;
%put %mf_islibds(w.t.f)=0;
@param [in] libds The string to be checked
@return output Returns 1 if libds is valid, 0 if it is not
<h4> Related Macros </h4>
@li mf_islibds.test.sas
@li mp_validatecol.sas
@version 9.2
**/
%macro mf_islibds(libds
)/*/STORE SOURCE*/;
%local regex;
%let regex=%sysfunc(prxparse(%str(/^[_a-z]\w{0,7}\.[_a-z]\w{0,31}$/i)));
%sysfunc(prxmatch(&regex,&libds))
%mend mf_islibds;

View File

@@ -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));

View File

@@ -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

View File

@@ -0,0 +1,54 @@
/**
@file
@brief Returns words that are in both string 1 and string 2
@details Compares two space separated strings and returns the words that are
in both.
Usage:
%put %mf_wordsInStr1andStr2(
Str1=blah sss blaaah brah bram boo
,Str2= blah blaaah brah ssss
);
returns:
> blah blaaah brah
@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_wordsInStr1andStr2(
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 %str(WARN)ING: 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=1 %then %let outvar=&outvar &extr_word;
%end;
&outvar
%mend mf_wordsInStr1andStr2;

67
base/mf_writefile.sas Normal file
View File

@@ -0,0 +1,67 @@
/**
@file
@brief Creates a text file using pure macro
@details Creates a text file of up to 10 lines. If further lines are
desired, feel free to [create an issue](
https://github.com/sasjs/core/issues/new), or make a pull request!
The use of PARMBUFF was considered for this macro, but it would have made
things problematic for writing lines containing commas.
Usage:
%mf_writefile(&sasjswork/myfile.txt,l1=some content,l2=more content)
data _null_;
infile "&sasjswork/myfile.txt";
input;
list;
run;
@param [in] fpath Full path to file to be created or appended to
@param [in] mode= (O) Available options are A or O as follows:
@li A APPEND mode, writes new records after the current end of the file.
@li O OUTPUT mode, writes new records from the beginning of the file.
@param [in] l1= First line
@param [in] l2= Second line (etc through to l10)
<h4> Related Macros </h4>
@li mf_writefile.test.sas
@version 9.2
@author Allan Bowe
**/
/** @cond */
%macro mf_writefile(fpath,mode=O,l1=,l2=,l3=,l4=,l5=,l6=,l7=,l8=,l9=,l10=
)/*/STORE SOURCE*/;
%local fref rc fid i total_lines;
/* find number of lines by reference to first non-blank param */
%do i=10 %to 1 %by -1;
%if %str(&&l&i) ne %str() %then %goto continue;
%end;
%continue:
%let total_lines=&i;
%if %sysfunc(filename(fref,&fpath)) ne 0 %then %do;
%put &=fref &=fpath;
%put %str(ERR)OR: %sysfunc(sysmsg());
%return;
%end;
%let fid=%sysfunc(fopen(&fref,&mode));
%if &fid=0 %then %do;
%put %str(ERR)OR: %sysfunc(sysmsg());
%return;
%end;
%do i=1 %to &total_lines;
%let rc=%sysfunc(fput(&fid, &&l&i));
%let rc=%sysfunc(fwrite(&fid));
%end;
%let rc=%sysfunc(fclose(&fid));
%let rc=%sysfunc(filename(&fref));
%mend mf_writefile;
/** @endcond */

181
base/mp_applyformats.sas Normal file
View File

@@ -0,0 +1,181 @@
/**
@file
@brief Apply a set of formats to a table
@details Applies a set of formats to the metadata of one or more SAS datasets.
Can be used to migrate formats from one table to another. The input table
must contain the following columns:
@li lib - the libref of the table to be updated
@li ds - the dataset to be updated
@li var - the variable to be updated
@li fmt - the format to apply. Missing or default ($CHAR, 8.) formats are
ignored.
The macro will abort in the following scenarios:
@li Libref not assigned
@li Dataset does not exist
@li Input table contains null or invalid values
Example usage:
data work.example;
set sashelp.prdsale;
format _all_ clear;
run;
%mp_getcols(sashelp.prdsale,outds=work.cols)
data work.cols2;
set work.cols;
lib='WORK';
ds='EXAMPLE';
var=name;
fmt=format;
keep lib ds var fmt;
run;
%mp_applyformats(work.cols2)
@param [in] inds The input dataset containing the formats to apply (and where
to apply them). Example structure:
|LIB:$8.|DS:$32.|VAR:$32.|FMT:$49.|
|---|---|---|---|
|`WORK `|`EXAMPLE `|`ACTUAL `|`DOLLAR12.2 `|
|`WORK `|`EXAMPLE `|`COUNTRY `|`$CHAR10. `|
|`WORK `|`EXAMPLE `|`DIVISION `|`$CHAR10. `|
|`WORK `|`EXAMPLE `|`MONTH `|`MONNAME3. `|
|`WORK `|`EXAMPLE `|`PREDICT `|`DOLLAR12.2 `|
|`WORK `|`EXAMPLE `|`PRODTYPE `|`$CHAR10. `|
|`WORK `|`EXAMPLE `|`PRODUCT `|`$CHAR10. `|
|`WORK `|`EXAMPLE `|`QUARTER `|`8. `|
|`WORK `|`EXAMPLE `|`REGION `|`$CHAR10. `|
|`WORK `|`EXAMPLE `|`YEAR `|`8. `|
@param [out] errds= (0) Provide a libds reference here to export the
error messages to a table. In this case, they will not be printed to the
log.
<h4> SAS Macros </h4>
@li mf_getengine.sas
@li mf_getuniquefileref.sas
@li mf_getuniquename.sas
@li mf_nobs.sas
@li mp_validatecol.sas
<h4> Related Macros </h4>
@li mp_getformats.sas
@version 9.2
@author Allan Bowe
**/
%macro mp_applyformats(inds,errds=0
)/*/STORE SOURCE*/;
%local outds liblist i engine lib msg ;
/**
* Validations
*/
proc sort data=&inds;
by lib ds var fmt;
run;
%if &errds=0 %then %let outds=%mf_getuniquename(prefix=mp_applyformats);
%else %let outds=&errds;
data &outds;
set &inds;
where fmt not in ('','.', '$', '$CHAR.','8.');
length msg $128;
by lib ds var fmt;
if libref(lib) ne 0 then do;
msg=catx(' ','libref',lib,'is not assigned!');
%if &errds=0 %then %do;
putlog "%str(ERR)OR: " msg;
%end;
output;
return;
end;
if exist(cats(lib,'.',ds)) ne 1 then do;
msg=catx(' ','libds',lib,'.',ds,'does not exist!');
%if &errds=0 %then %do;
putlog "%str(ERR)OR: " msg;
%end;
output;
return;
end;
%mp_validatecol(fmt,FORMAT,is_fmt)
if is_fmt=0 then do;
msg=catx(' ','format',fmt,'on libds',lib,'.',ds,'.',var,'is not valid!');
%if &errds=0 %then %do;
putlog "%str(ERR)OR: " msg;
%end;
output;
return;
end;
if first.ds then do;
retain dsid;
dsid=open(cats(lib,'.',ds));
if dsid=0 then do;
msg=catx(' ','libds',lib,'.',ds,' could not be opened!');
%if &errds=0 %then %do;
putlog "%str(ERR)OR: " msg;
%end;
output;
return;
end;
if varnum(dsid,var)<1 then do;
msg=catx(' ','Variable',lib,'.',ds,'.',var,' was not found!');
%if &errds=0 %then %do;
putlog "%str(ERR)OR: " msg;
%end;
output;
end;
end;
if last.ds then rc=close(dsid);
run;
proc sql noprint;
select distinct lib into: liblist separated by ' ' from &inds;
%put &=liblist;
%if %length(&liblist)>0 %then %do i=1 %to %sysfunc(countw(&liblist));
%let lib=%scan(&liblist,1);
%let engine=%mf_getengine(&lib);
%if &engine ne V9 and &engine ne BASE %then %do;
%let msg=&lib has &engine engine - formats cannot be applied;
insert into &outds set lib="&lib",ds="_all_",var="_all", msg="&msg" ;
%if &errds=0 %then %put %str(ERR)OR: &msg;
%end;
%end;
quit;
%if %mf_nobs(&outds)>0 %then %return;
/**
* Validations complete - now apply the actual formats!
*/
%let fref=%mf_getuniquefileref();
data _null_;
set &inds;
by lib ds var fmt;
where fmt not in ('','.', '$', '$CHAR.','8.');
file &fref;
if first.lib then put 'proc datasets nolist lib=' lib ';';
if first.ds then put ' modify ' ds ';';
put ' format ' var fmt ';';
if last.ds then put ' run;';
if last.lib then put 'quit;';
run;
%inc &fref/source2;
%if &errds=0 %then %do;
proc sql;
drop table &outds;
%end;
%mend mp_applyformats;

View File

@@ -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);

92
base/mp_coretable.sas Normal file
View File

@@ -0,0 +1,92 @@
/**
@file
@brief Create the permanent Core tables
@details Several macros in the [core](https://github.com/sasjs/core) library
make use of permanent tables. To avoid duplication in definitions, this
macro provides a central location for managing the corresponding DDL.
Example usage:
%mp_coretable(LOCKTABLE,libds=work.locktable)
@param [in] table_ref The type of table to create. Example values:
@li FILTER_DETAIL - For storing detailed filter values. Used by
mp_filterstore.sas.
@li FILTER_SUMMARY - For storing summary filter values. Used by
mp_filterstore.sas.
@li LOCKANYTABLE - For "locking" tables prior to multipass loads. Used by
mp_lockanytable.sas
@li MAXKEYTABLE - For storing the maximum retained key information. Used
by mp_retainedkey.sas
@param [in] libds= (0) The library.dataset reference used to create the table.
If not provided, then the DDL is simply printed to the log.
<h4> Related Macros </h4>
@li mp_filterstore.sas
@li mp_lockanytable.sas
@li mp_retainedkey.sas
@version 9.2
@author Allan Bowe
**/
%macro mp_coretable(table_ref,libds=0
)/*/STORE SOURCE*/;
%local outds ;
%let outds=%sysfunc(ifc(&libds=0,_data_,&libds));
proc sql;
%if &table_ref=LOCKTABLE %then %do;
create table &outds(
lock_lib char(8),
lock_ds char(32),
lock_status_cd char(10) not null,
lock_user_nm char(100) not null ,
lock_ref char(200),
lock_pid char(10),
lock_start_dttm num format=E8601DT26.6,
lock_end_dttm num format=E8601DT26.6,
constraint pk_mp_lockanytable primary key(lock_lib,lock_ds));
%end;
%else %if &table_ref=FILTER_SUMMARY %then %do;
create table &outds(
filter_rk num not null,
filter_hash char(32) not null,
filter_table char(41) not null,
processed_dttm num not null format=E8601DT26.6,
constraint pk_mpe_filteranytable
primary key(filter_rk));
%end;
%else %if &table_ref=FILTER_DETAIL %then %do;
create table &outds(
filter_hash char(32) not null,
filter_line num not null,
group_logic char(3) not null,
subgroup_logic char(3) not null,
subgroup_id num not null,
variable_nm varchar(32) not null,
operator_nm varchar(12) not null,
raw_value varchar(4000) not null,
processed_dttm num not null format=E8601DT26.6,
constraint pk_mpe_filteranytable
primary key(filter_hash,filter_line));
%end;
%else %if &table_ref=MAXKEYTABLE %then %do;
create table &outds(
keytable varchar(41) label='Base table in libref.dataset format',
keycolumn char(32) format=$32.
label='The Retained key field containing the key values.',
max_key num label=
'Integer representing current max RK or SK value in the KEYTABLE',
processed_dttm num format=E8601DT26.6
label='Datetime this value was last updated',
constraint pk_mpe_maxkeyvalues
primary key(keytable));
%end;
%if &libds=0 %then %do;
describe table &syslast;
drop table &syslast;
%end;
%mend mp_coretable;

View File

@@ -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;

View File

@@ -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;

View File

@@ -29,44 +29,44 @@
Usage:
%mp_mdtablewrite(libds=sashelp.class,showlog=YES)
%mp_ds2md(sashelp.class)
@param [in] libds the library / dataset to create or read from.
@param [out] outref= (mdtable) Fileref to contain the markdown
@param [out] showlog= (YES) Set to NO to avoid printing markdown to the log
<h4> SAS Macros </h4>
@li mf_getvarlist.sas
@li mf_getvarformat.sas
@param [in] libds= the library / dataset to create or read from.
@param [out] fref= Fileref to contain the markdown. Default=mdtable.
@param [out] showlog= set to YES to show the markdown in the log. Default=NO.
@version 9.3
@author Allan Bowe
**/
%macro mp_mdtablewrite(
libds=,
fref=mdtable,
showlog=NO
%macro mp_ds2md(
libds,
outref=mdtable,
showlog=YES
)/*/STORE SOURCE*/;
/* check fileref is assigned */
%if %sysfunc(fileref(&fref)) > 0 %then %do;
filename &fref temp;
%if %sysfunc(fileref(&outref)) > 0 %then %do;
filename &outref temp;
%end;
%local vars;
%let vars=%mf_getvarlist(&libds);
%let vars=%upcase(%mf_getvarlist(&libds));
/* create the header row */
data _null_;
file &fref;
file &outref;
length line $32767;
call missing(line);
put '|'
%local i var fmt;
%do i=1 %to %sysfunc(countw(&vars));
%let var=%scan(&vars,&i);
%let fmt=%mf_getvarformat(&libds,&var,force=1);
%let fmt=%lowcase(%mf_getvarformat(&libds,&var,force=1));
"&var:&fmt|"
%end;
;
@@ -79,20 +79,20 @@ run;
/* write out the data */
data _null_;
file &fref mod dlm='|' lrecl=32767;
file &outref mod dlm='|' lrecl=32767;
set &libds ;
length line $32767;
line=cats('|',%mf_getvarlist(&libds,dlm=%str(,'|',)),'|');
line='|`'!!cats(%mf_getvarlist(&libds,dlm=%str(%)!!' `|`'!!cats%()))!!' `|';
put line;
run;
%if %upcase(&showlog)=YES %then %do;
options ps=max;
data _null_;
infile &fref;
infile &outref;
input;
putlog _infile_;
run;
%end;
%mend mp_mdtablewrite;
%mend mp_ds2md;

230
base/mp_filterstore.sas Normal file
View File

@@ -0,0 +1,230 @@
/**
@file
@brief Checks & Stores an input filter table and returns the Filter Key
@details Used to generate a FILTER_RK from an input query dataset. This
process requires several permanent tables (names are configurable). The
benefit of storing query values at backend is to enable stored 'views' of
filtered tables at frontend (ie, when building [SAS-Powered Apps](
https://sasapps.io)). This macro is also used in [Data Controller for SAS](
https://datacontroller.io).
@param [in] libds= The target dataset to be filtered (lib should be assigned)
@param [in] queryds= (WORK.FILTERQUERY) The temporary input query dataset to
be validated. Has the following format:
|GROUP_LOGIC:$3|SUBGROUP_LOGIC:$3|SUBGROUP_ID:8.|VARIABLE_NM:$32|OPERATOR_NM:$10|RAW_VALUE:$32767|
|---|---|---|---|---|---|
|AND|AND|1|SOME_BESTNUM|>|1|
|AND|AND|1|SOME_TIME|=|77333|
@param [in] filter_summary= (PERM.FILTER_SUMMARY) Permanent table containing
summary filter values. The definition is available by running
mp_coretable.sas as follows: `mp_coretable(FILTER_SUMMARY)`. Example
values:
|FILTER_RK:best.|FILTER_HASH:$32.|FILTER_TABLE:$41.|PROCESSED_DTTM:datetime19.|
|---|---|---|---|
|`1 `|`540E96F566D194AB58DD4C413C99C9DB `|`VIYA6014.MPE_TABLES `|`1956084246 `|
|`2 `|`87737DB9EEE2650F5C89956CEAD0A14F `|`VIYA6014.MPE_X_TEST `|`1956084452.1`|
|`3 `|`8048BD908DBBD83D013560734E90D394 `|`VIYA6014.MPE_TABLES `|`1956093620.6`|
@param [in] filter_detail= (PERM.FILTER_DETAIL) Permanent table containing
detailed (raw) filter values. The definition is available by running
mp_coretable.sas as follows: `mp_coretable(FILTER_DETAIL)`. Example
values:
|FILTER_HASH:$32.|FILTER_LINE:best.|GROUP_LOGIC:$3.|SUBGROUP_LOGIC:$3.|SUBGROUP_ID:best.|VARIABLE_NM:$32.|OPERATOR_NM:$12.|RAW_VALUE:$4000.|PROCESSED_DTTM:datetime19.|
|---|---|---|---|---|---|---|---|---|
|`540E96F566D194AB58DD4C413C99C9DB `|`1 `|`AND `|`AND `|`1 `|`LIBREF `|`CONTAINS `|`DC`|`1956084245.8 `|
|`540E96F566D194AB58DD4C413C99C9DB `|`2 `|`AND `|`OR `|`2 `|`DSN `|`= `|` MPE_LOCK_ANYTABLE `|`1956084245.8 `|
|`87737DB9EEE2650F5C89956CEAD0A14F `|`1 `|`AND `|`AND `|`1 `|`PRIMARY_KEY_FIELD `|`IN `|`(1,2,3) `|`1956084451.9 `|
@param [in] lock_table= (PERM.LOCK_TABLE) Permanent locking table. Used to
manage concurrent access. The definition is available by running
mp_coretable.sas as follows: `mp_coretable(LOCKTABLE)`.
@param [in] maxkeytable= (0) Optional permanent reference table used for
retained key tracking. Described in mp_retainedkey.sas.
@param [in] mdebug= set to 1 to enable DEBUG messages
@param [out] outresult= The result table with the FILTER_RK
@param [out] outquery= The original query, taken as extract after table load
<h4> SAS Macros </h4>
@li mf_getuniquename.sas
@li mf_getvalue.sas
@li mf_islibds.sas
@li mf_nobs.sas
@li mp_abort.sas
@li mp_filtercheck.sas
@li mp_hashdataset.sas
@li mp_retainedkey.sas
<h4> Related Macros </h4>
@li mp_filtercheck.sas
@li mp_filtergenerate.sas
@li mp_filtervalidate.sas
@li mp_filterstore.test.sas
@version 9.2
@author [Allan Bowe](https://www.linkedin.com/in/allanbowe)
**/
%macro mp_filterstore(libds=,
queryds=work.filterquery,
filter_summary=PERM.FILTER_SUMMARY,
filter_detail=PERM.FILTER_DETAIL,
lock_table=PERM.LOCK_TABLE,
maxkeytable=PERM.MAXKEYTABLE,
outresult=work.result,
outquery=work.query,
mdebug=1
);
%put &sysmacroname entry vars:;
%put _local_;
%local ds1 ds2 ds3 ds4 filter_hash;
%mp_abort(iftrue= (&syscc ne 0)
,mac=mp_filterstore
,msg=%str(syscc=&syscc on macro entry)
)
%mp_abort(iftrue= (%mf_islibds(&filter_summary)=0)
,mac=mp_filterstore
,msg=%str(Invalid filter_summary value: &filter_summary)
)
%mp_abort(iftrue= (%mf_islibds(&filter_detail)=0)
,mac=mp_filterstore
,msg=%str(Invalid filter_detail value: &filter_detail)
)
%mp_abort(iftrue= (%mf_islibds(&lock_table)=0)
,mac=mp_filterstore
,msg=%str(Invalid lock_table value: &lock_table)
)
/* validate query */
%mp_filtercheck(&queryds,targetds=&libds,abort=YES)
/* hash the result */
%let ds1=%mf_getuniquename(prefix=hashds);
%mp_hashdataset(&queryds,outds=&ds1,salt=&libds)
%let filter_hash=%upcase(%mf_getvalue(&ds1,hashkey));
%if &mdebug=1 %then %do;
data _null_;
putlog "filter_hash=&filter_hash";
set &ds1;
putlog (_all_)(=);
run;
%end;
/* check if data already exists for this hash */
data &outresult;
set &filter_summary;
where filter_hash="&filter_hash";
run;
%mp_abort(iftrue= (&syscc ne 0)
,mac=mp_filterstore
,msg=%str(syscc=&syscc after hash check)
)
%mp_abort(iftrue= ("&filter_hash "=" ")
,mac=mp_filterstore
,msg=%str(problem with filter_hash generation)
)
%if %mf_nobs(&outresult)=0 %then %do;
/* first update summary table */
%let ds3=%mf_getuniquename(prefix=filtersum);
data work.&ds3;
if 0 then set &filter_summary;
filter_table=symget('libds');
filter_hash="&filter_hash";
PROCESSED_DTTM=%sysfunc(datetime());
output;
stop;
run;
%mp_lockanytable(LOCK,
lib=%scan(&filter_summary,1,.)
,ds=%scan(&filter_summary,2,.)
,ref=MP_FILTERSTORE summary update - &filter_hash
,ctl_ds=&lock_table
)
%let ds4=%mf_getuniquename(prefix=filtersumappend);
%mp_retainedkey(
base_lib=%scan(&filter_summary,1,.)
,base_dsn=%scan(&filter_summary,2,.)
,append_lib=work
,append_dsn=&ds3
,retained_key=filter_rk
,business_key=filter_hash
,maxkeytable=&maxkeytable
,locktable=&lock_table
,outds=work.&ds4
)
proc append base=&filter_summary data=&ds4;
run;
%mp_lockanytable(UNLOCK,
lib=%scan(&filter_summary,1,.)
,ds=%scan(&filter_summary,2,.)
,ref=MP_FILTERSTORE summary update - &filter_hash
,ctl_ds=&lock_table
)
%if &syscc ne 0 %then %do;
data _null_;
set &ds4;
putlog (_all_)(=);
run;
%goto err;
%end;
data &outresult;
set &filter_summary;
where filter_hash="&filter_hash";
run;
/* Next, update detail table */
%let ds2=%mf_getuniquename(prefix=filterdetail);
data &ds2;
if 0 then set &filter_detail;
set &queryds;
format filter_hash $hex32. filter_line 8.;
filter_hash="&filter_hash";
filter_line=_n_;
PROCESSED_DTTM=%sysfunc(datetime());
run;
%mp_lockanytable(LOCK,
lib=%scan(&filter_detail,1,.)
,ds=%scan(&filter_detail,2,.)
,ref=MP_FILTERSTORE update - &filter_hash
,ctl_ds=&lock_table
)
proc append base=&filter_detail data=&ds2;
run;
%mp_lockanytable(UNLOCK,
lib=%scan(&filter_detail,1,.)
,ds=%scan(&filter_detail,2,.)
,ref=MP_FILTERSTORE detail update &filter_hash
,ctl_ds=&lock_table
)
%if &syscc ne 0 %then %do;
data _null_;
set &ds2;
putlog (_all_)(=);
run;
%goto err;
%end;
%end;
proc sort data=&filter_detail(where=(filter_hash="&filter_hash")) out=&outquery;
by filter_line;
run;
%err:
%mp_abort(iftrue= (&syscc ne 0)
,mac=mp_filterstore
,msg=%str(syscc=&syscc on macro exit)
)
%mend mp_filterstore;

View File

@@ -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

View File

@@ -14,11 +14,11 @@
@param ds The dataset from which to obtain column metadata
@param outds= (work.cols) The output dataset to create. Sample data:
|NAME $|LENGTH 8|VARNUM 8|LABEL $|FORMAT $49|TYPE $1 |DDTYPE $|
|---|---|---|---|---|---|---|
|AIR|8|2|international airline travel (thousands)|8.|N|NUMERIC|
|DATE|8|1|DATE|MONYY.|N|DATE|
|REGION|3|3|REGION|$3.|C|CHARACTER|
|NAME:$32.|LENGTH:best.|VARNUM:best.|LABEL:$256.|FMTNAME:$32.|FORMAT:$49.|TYPE:$1.|DDTYPE:$9.|
|---|---|---|---|---|---|---|---|
|`AIR `|`8 `|`2 `|`international airline travel (thousands) `|` `|`8. `|`N `|`NUMERIC `|
|`DATE `|`8 `|`1 `|`DATE `|`MONYY `|`MONYY. `|`N `|`DATE `|
|`REGION `|`3 `|`3 `|`REGION `|` `|`$3. `|`C `|`CHARACTER `|
<h4> Related Macros </h4>
@li mf_getvarlist.sas
@@ -30,26 +30,27 @@
**/
%macro mp_getcols(ds, outds=work.cols);
%local dropds;
proc contents noprint data=&ds
out=_data_ (keep=name type length label varnum format:);
run;
data &outds(keep=name type length varnum format label ddtype);
set &syslast(rename=(format=format2 type=type2));
%let dropds=&syslast;
data &outds(keep=name type length varnum format label ddtype fmtname);
set &dropds(rename=(format=fmtname type=type2));
name=upcase(name);
if type2=2 then do;
length format $49.;
if format2='' then format=cats('$',length,'.');
else if formatl=0 then format=cats(format2,'.');
else format=cats(format2,formatl,'.');
if fmtname='' then format=cats('$',length,'.');
else if formatl=0 then format=cats(fmtname,'.');
else format=cats(fmtname,formatl,'.');
type='C';
ddtype='CHARACTER';
end;
else do;
if format2='' then format=cats(length,'.');
else if formatl=0 then format=cats(format2,'.');
else if formatd=0 then format=cats(format2,formatl,'.');
else format=cats(format2,formatl,'.',formatd);
if fmtname='' then format=cats(length,'.');
else if formatl=0 then format=cats(fmtname,'.');
else if formatd=0 then format=cats(fmtname,formatl,'.');
else format=cats(fmtname,formatl,'.',formatd);
type='N';
if format=:'DATETIME' or format=:'E8601DT' then ddtype='DATETIME';
else if format=:'DATE' or format=:'DDMMYY' or format=:'MMDDYY'
@@ -61,5 +62,6 @@ data &outds(keep=name type length varnum format label ddtype);
end;
if label='' then label=name;
run;
proc sql;
drop table &dropds;
%mend mp_getcols;

View File

@@ -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 */

View File

@@ -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;

142
base/mp_getformats.sas Normal file
View File

@@ -0,0 +1,142 @@
/**
@file
@brief Export format definitions
@details Formats are exported from the first (if any) catalog entry in the
FMTSEARCH path.
Formats are taken from the library / dataset reference and / or a static
format list.
Example usage:
%mp_getformats(lib=sashelp,ds=prdsale,outsummary=work.dictable)
@param [in] lib= (0) The libref for which to return formats.
@todo Enable exporting of formats for an entire library
@param [in] ds= (0) The dataset from which to obtain format definitions
@param [in] fmtlist= (0) A list of additional format names
@param [out] outsummary= (work.mp_getformats_summary) Output dataset
containing summary definitions - structure taken from dictionary.formats as
follows:
|libname:$8.|memname:$32.|path:$1024.|objname:$32.|fmtname:$32.|fmttype:$1.|source:$1.|minw:best.|mind:best.|maxw:best.|maxd:best.|defw:best.|defd:best.|
|---|---|---|---|---|---|---|---|---|---|---|---|---|
| | | | |$|F|B|1|0|32767|0|1|0|
| | | | |$|I|B|1|0|32767|0|1|0|
|` `|` `|/opt/sas/sas9/SASHome/SASFoundation/9.4/sasexe|UWIANYDT|$ANYDTIF|I|U|1|0|60|0|19|0|
| | |/opt/sas/sas9/SASHome/SASFoundation/9.4/sasexe|UWFASCII|$ASCII|F|U|1|0|32767|0|1|0|
| | |/opt/sas/sas9/SASHome/SASFoundation/9.4/sasexe|UWIASCII|$ASCII|I|U|1|0|32767|0|1|0|
| | |/opt/sas/sas9/SASHome/SASFoundation/9.4/sasexe|UWFBASE6|$BASE64X|F|U|1|0|32767|0|1|0|
@param [out] outdetail= (0) Provide an output dataset in which to export all
the custom format definitions (from proc format CNTLOUT). Definitions:
https://support.sas.com/documentation/cdl/en/proc/61895/HTML/default/viewer.htm#a002473477.htm
Sample data:
|FMTNAME:$32.|START:$16.|END:$16.|LABEL:$256.|MIN:best.|MAX:best.|DEFAULT:best.|LENGTH:best.|FUZZ:best.|PREFIX:$2.|MULT:best.|FILL:$1.|NOEDIT:best.|TYPE:$1.|SEXCL:$1.|EEXCL:$1.|HLO:$13.|DECSEP:$1.|DIG3SEP:$1.|DATATYPE:$8.|LANGUAGE:$8.|
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|`WHICHPATH `|`0 `|`0 `|`path1 `|`1 `|`40 `|`28 `|`28 `|`1E-12 `|` `|`0 `|` `|`0 `|`N `|`N `|`N `|` `|` `|` `|` `|` `|
|`WHICHPATH `|`**OTHER** `|`**OTHER** `|`big fat problem if not path1 `|`1 `|`40 `|`28 `|`28 `|`1E-12 `|` `|`0 `|` `|`0 `|`N `|`N `|`N `|`O `|` `|` `|` `|` `|
<h4> SAS Macros </h4>
@li mf_dedup.sas
@li mf_getfmtlist.sas
@li mf_getfmtname.sas
@li mf_getquotedstr.sas
@li mf_getuniquename.sas
<h4> Related Macros </h4>
@li mp_applyformats.sas
@li mp_getformats.test.sas
@version 9.2
@author Allan Bowe
**/
%macro mp_getformats(lib=0
,ds=0
,fmtlist=0
,outsummary=work.mp_getformats_summary
,outdetail=0
);
%local i fmt allfmts tempds fmtcnt;
%if "&fmtlist" ne "0" %then %do i=1 %to %sysfunc(countw(&fmtlist,,%str( )));
/* ensure format list contains format _name_ only */
%let fmt=%scan(&fmtlist,&i,%str( ));
%let fmt=%mf_getfmtname(&fmt);
%let allfmts=&allfmts &fmt;
%end;
%if &ds=0 and &lib ne 0 %then %do;
/* grab formats from library */
/* to do */
%end;
%else %if &ds ne 0 and &lib ne 0 %then %do;
/* grab formats from dataset */
%let allfmts=%mf_getfmtlist(&lib..&ds) &allfmts;
%end;
/* ensure list is unique */
%let allfmts=%mf_dedup(%upcase(&allfmts));
/* create summary table */
%if %index(&outsummary,.)=0 %then %let outsummary=WORK.&outsummary;
proc sql;
create table &outsummary as
select * from dictionary.formats
where fmtname in (%mf_getquotedstr(&allfmts,quote=D))
and fmttype='F';
%if "&outdetail" ne "0" %then %do;
/* ensure base table always exists */
proc sql;
create table &outdetail(
FMTNAME char(32) label='Format name'
,START char(16) label='Starting value for format'
,END char(16) label='Ending value for format'
,LABEL char(256) label='Format value label'
,MIN num length=3 label='Minimum length'
,MAX num length=3 label='Maximum length'
,DEFAULT num length=3 label='Default length'
,LENGTH num length=3 label='Format length'
,FUZZ num label='Fuzz value'
,PREFIX char(2) label='Prefix characters'
,MULT num label='Multiplier'
,FILL char(1) label='Fill character'
,NOEDIT num length=3 label='Is picture string noedit?'
,TYPE char(1) label='Type of format'
,SEXCL char(1) label='Start exclusion'
,EEXCL char(1) label='End exclusion'
,HLO char(13) label='Additional information'
,DECSEP char(1) label='Decimal separator'
,DIG3SEP char(1) label='Three-digit separator'
,DATATYPE char(8) label='Date/time/datetime?'
,LANGUAGE char(8) label='Language for date strings'
);
/* grab the location of each format */
%let fmtcnt=0;
data _null_;
set &outsummary;
if not missing(libname);
x+1;
call symputx(cats('fmtloc',x),cats(libname,'.',memname),'l');
call symputx(cats('fmtname',x),fmtname,'l');
call symputx('fmtcnt',x,'l');
run;
/* export each format and append to the output table */
%let tempds=%mf_getuniquename(prefix=mp_getformats);
%do i=1 %to &fmtcnt;
proc format library=&&fmtloc&i CNTLOUT=&tempds;
select &&fmtname&i;
run;
proc append base=&outdetail data=&tempds;
run;
%end;
%end;
%mend mp_getformats;

View File

@@ -21,6 +21,7 @@
@param [in] libds dataset to hash
@param [in] salt= Provide a salt (could be, for instance, the dataset name)
@param [in] iftrue= A condition under which the macro should be executed.
@param [out] outds= (work.mf_hashdataset) The output dataset to create. This
will contain one column (hashkey) with one observation (a hex32.
representation of the input hash)
@@ -35,10 +36,14 @@
%macro mp_hashdataset(
libds,
outds=,
salt=
salt=,
iftrue=%str(1=1)
)/*/STORE SOURCE*/;
%if not(%eval(%unquote(&iftrue))) %then %return;
%if %mf_getattrn(&libds,NLOBS)=0 %then %do;
%put %str(WARN)ING: Dataset &libds is empty;, or is not a dataset;
%put %str(WARN)ING: Dataset &libds is empty, or is not a dataset;
%end;
%else %if %mf_getattrn(&libds,NLOBS)<0 %then %do;
%put %str(ERR)OR: Dataset &libds is not a dataset;

View File

@@ -88,6 +88,7 @@ run;
/* prepare the errds */
data &errds;
length msg mac $1000;
call missing(msg,mac);
iftrue='1=0';
run;

View File

@@ -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
@@ -20,35 +30,39 @@
**/
%macro mp_init(prefix=
%macro mp_init(prefix=SASJS
)/*/STORE SOURCE*/;
%global
&prefix._INIT_NUM /* initialisation time as numeric */
&prefix._INIT_DTTM /* initialisation time in E8601DT26.6 format */
&prefix._INIT_NUM /* initialisation time as numeric */
&prefix._INIT_DTTM /* initialisation time in E8601DT26.6 format */
&prefix.WORK /* avoid typing %sysfunc(pathname(work)) every time */
;
%if %eval(&&&prefix._INIT_NUM>0) %then %return; /* only run once */
data _null_;
dttm=datetime();
call symputx("&prefix._init_num",dttm);
call symputx("&prefix._init_dttm",put(dttm,E8601DT26.6));
call symputx("&prefix._init_num",dttm,'g');
call symputx("&prefix._init_dttm",put(dttm,E8601DT26.6),'g');
call symputx("&prefix.work",pathname('WORK'),'g');
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 error when a format cannot be found */
mergenoby=ERROR /* Throw error 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 */
ls=max /* reduce log even more and avoid word truncation */
validmemname=COMPATIBLE /* avoid special characters etc in table names */
validvarname=V7 /* avoid special characters etc in variable names */
varlenchk=ERROR /* fail hard if truncation (data loss) can result */
varinitchk=%str(ERR)OR /* avoid data mistakes from variable name typos */
varlenchk=%str(ERR)OR /* fail hard if truncation (data loss) can result */
;
%mend mp_init;

View File

@@ -62,13 +62,13 @@
%put output location=&jref;
%if &action=OPEN %then %do;
options nobomfile;
data _null_;file &jref encoding='utf-8';
put '{"START_DTTM" : "' "%sysfunc(datetime(),datetime20.3)" '"';
data _null_;file &jref encoding='utf-8' ;
put '{"PROCESSED_DTTM" : "' "%sysfunc(datetime(),E8601DT26.6)" '"';
run;
%end;
%else %if (&action=ARR or &action=OBJ) %then %do;
options validvarname=upcase;
data _null_;file &jref mod encoding='utf-8';
data _null_;file &jref mod encoding='utf-8' ;
put ", ""%lowcase(%sysfunc(coalescec(&dslabel,&ds)))"":";
%if &engine=PROCJSON %then %do;
@@ -147,7 +147,7 @@
run;
%let ds=&fmtds;
%end; /* &fmt=Y */
data _null_;file &jref mod encoding='utf-8';
data _null_;file &jref mod encoding='utf-8' ;
put "["; call symputx('cols',0,'l');
proc sort
data=sashelp.vcolumn(where=(libname='WORK' & memname="%upcase(&ds)"))
@@ -192,7 +192,7 @@
/* 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;
data _null_; file _sjs lrecl=131068 encoding='utf-8' mod ;
set &tempds;
if _n_>1 then put "," @; put
%if &action=ARR %then "[" ; %else "{" ;
@@ -219,14 +219,14 @@
rc = fclose(fileid);
run;
filename _sjs clear;
data _null_; file &jref mod encoding='utf-8';
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' mod;
data _null_;file &jref encoding='utf-8' mod ;
put "}";
run;
%end;

View File

@@ -5,19 +5,18 @@
Only useful if every update uses the macro! Used heavily within
[Data Controller for SAS](https://datacontroller.io).
The underlying table is structured as per the MAKETABLE action.
@param [in] action The action to be performed. Valid values:
@li LOCK - Sets the lock flag, also confirms if a SAS lock is available
@li UNLOCK - Unlocks the table
@li MAKETABLE - creates the control table (ctl_ds)
@param [in] lib= (WORK) The libref of the table to lock. Should already be
assigned.
@param [in] ds= The dataset to lock
@param [in] ref= A meaningful reference to enable the lock to be traced. Max
length is 200 characters.
@param [out] ctl_ds= (0) The control table which controls the actual locking.
Should already be assigned and available.
Should already be assigned and available. The definition is available by
running mp_coretable.sas as follows: `mp_coretable(LOCKTABLE)`.
@param [in] loops= (25) Number of times to check for a lock.
@param [in] loop_secs= (1) Seconds to wait between each lock attempt
@@ -180,7 +179,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;
@@ -221,25 +220,12 @@ run;
%let abortme=1;
%end;
%end;
%else %if &action=MAKETABLE %then %do;
proc sql;
create table &ctl_ds(
lock_lib char(8),
lock_ds char(32),
lock_status_cd char(10) not null,
lock_user_nm char(100) not null ,
lock_ref char(200),
lock_pid char(10),
lock_start_dttm num format=E8601DT26.6,
lock_end_dttm num format=E8601DT26.6,
constraint pk_mp_lockanytable primary key(lock_lib,lock_ds));
%end;
%else %do;
%let msg=lock_anytable given unsupported action (&action);
%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

View File

@@ -10,8 +10,6 @@
according to the variable types and formats.
TODO:
@li Respect PKs
@li Respect NOT NULLs
@li Consider dates, datetimes, times, integers etc
Usage:
@@ -27,16 +25,23 @@
);
%mp_makedata(work.example)
@param [in] libds The empty table in which to create data
@param [out] obs= (500) The number of records to create.
@param [in] libds The empty table (libref.dataset) in which to create data
@param [out] obs= (500) The maximum number of records to create. The table
is sorted with nodup on the primary key, so the actual number of records may
be lower than this.
<h4> SAS Macros </h4>
@li mf_getuniquename.sas
@li mf_getvarlen.sas
@li mf_getvarlist.sas
@li mf_islibds.sas
@li mf_nobs.sas
@li mp_getcols.sas
@li mp_getpk.sas
<h4> Related Macros </h4>
@li mp_makedata.test.sas
@version 9.2
@author Allan Bowe
@@ -44,44 +49,59 @@
%macro mp_makedata(libds
,obs=500
,seed=1
)/*/STORE SOURCE*/;
%local ds1 c1 n1 i col charvars numvars;
%local ds1 ds2 lib ds pk_fields i col charvars numvars ispk;
%if %mf_nobs(&libds)>0 %then %do;
%if %mf_islibds(&libds)=0 %then %do;
%put &sysmacroname: Invalid libds (&libds) - should be library.dataset format;
%return;
%end;
%else %if %mf_nobs(&libds)>0 %then %do;
%put &sysmacroname: &libds has data, it will not be recreated;
%return;
%end;
%local ds1 c1 n1;
%let ds1=%mf_getuniquename(prefix=mp_makedata);
%let c1=%mf_getuniquename(prefix=mp_makedatacol);
%let n1=%mf_getuniquename(prefix=mp_makedatacol);
data &ds1;
/* set up temporary vars */
%let ds1=%mf_getuniquename(prefix=mp_makedatads1);
%let ds2=%mf_getuniquename(prefix=mp_makedatads2);
%let lib=%scan(&libds,1,.);
%let ds=%scan(&libds,2,.);
/* grab the primary key vars */
%mp_getpk(&lib,ds=&ds,outds=&ds1)
proc sql noprint;
select coalescec(pk_fields,'_all_') into: pk_fields from &ds1;
data &ds2;
if 0 then set &libds;
do _n_=1 to &obs;
&c1=repeat(uuidgen(),10);
&n1=ranuni(1)*5000000;
drop &c1 &n1;
%let charvars=%mf_getvarlist(&libds,typefilter=C);
%do i=1 %to %sysfunc(countw(&charvars));
%if &charvars ^= %then %do i=1 %to %sysfunc(countw(&charvars));
%let col=%scan(&charvars,&i);
&col=subpad(&c1,1,%mf_getvarlen(&libds,&col));
/* create random value based on observation number and colum length */
&col=repeat(put(md5(cats(_n_)),$hex32.),%mf_getvarlen(&libds,&col)/32);
%end;
%let numvars=%mf_getvarlist(&libds,typefilter=N);
%do i=1 %to %sysfunc(countw(&numvars));
%if &numvars ^= %then %do i=1 %to %sysfunc(countw(&numvars));
%let col=%scan(&numvars,&i);
&col=&n1;
&col=_n_;
%end;
output;
end;
stop;
run;
proc sort data=&ds2 nodupkey;
by &pk_fields;
run;
proc append base=&libds data=&ds1;
proc append base=&libds data=&ds2;
run;
proc sql;
drop table &ds1;
drop table &ds1, &ds2;
%mend mp_makedata;

View File

@@ -3,13 +3,15 @@
@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)
Called as follows:
options obs=30 ps=max;
%mp_resetoption(OBS)
%mp_resetoption(PS)
@param option the option to reset
@param [in] option the option to reset
@version 9.2
@author Allan Bowe

246
base/mp_retainedkey.sas Normal file
View File

@@ -0,0 +1,246 @@
/**
@file
@brief Generate and apply retained key values to a staging table
@details This macro will populate a staging table with a Retained Key based on
a business key and a base (target) table.
Definition of retained key ([source](
http://bukhantsov.org/2012/04/what-is-data-vault/)):
> The retained key is a key which is mapped to business key one-to-one. In
> comparison, the surrogate key includes time and there can be many surrogate
> keys corresponding to one business key. This explains the name of the key,
> it is retained with insertion of a new version of a row while surrogate key
> is increasing.
This macro is designed to be used as part of a wider load / ETL process (such
as the one in [Data Controller for SAS](https://datacontroller.io)).
Specifically, the macro assumes that the base table has already been 'locked'
(eg with the mp_lockanytable.sas macro) prior to invocation. Also, several
tables are assumed to exist (names are configurable):
@li work.staging_table - the staged data, minus the retained key element
@li permlib.base_table - the target table to be loaded (**not** loaded by this
macro)
@li permlib.maxkeytable - optional, used to store load metaadata.
The definition is available by running mp_coretable.sas as follows:
`mp_coretable(MAXKEYTABLE)`.
@li permlib.locktable - Necessary if maxkeytable is being populated. The
definition is available by running mp_coretable.sas as follows:
`mp_coretable(LOCKTABLE)`.
@param [in] base_lib= (WORK) Libref of the base (target) table.
@param [in] base_dsn= (BASETABLE) Name of the base (target) table.
@param [in] append_lib= (WORK) Libref of the staging table
@param [in] append_dsn= (APPENDTABLE) Name of the staging table
@param [in] retained_key= (DEFAULT_RK) Name of RK to generate (should exist on
base table)
@param [in] business_key= (PK1 PK2) Business key against which to generate
RK values. Should be unique and not null on the staging table.
@param [in] check_uniqueness=(NO) Set to yes to perform a uniqueness check.
Recommended if there is a chance that the staging data is not unique on the
business key.
@param [in] maxkeytable= (0) Provide a maxkeytable libds reference here, to
store load metadata (maxkey val, load time). Set to zero if metadata is not
required, eg, when preparing a 'dummy' load. Structure is described above.
See below for sample data.
|KEYTABLE:$32.|KEYCOLUMN:$32.|MAX_KEY:best.|PROCESSED_DTTM:E8601DT26.6|
|---|---|---|---|
|`DC487173.MPE_SELECTBOX `|`SELECTBOX_RK `|`55 `|`1950427787.8 `|
|`DC487173.MPE_FILTERANYTABLE `|`filter_rk `|`14 `|`1951053886.8 `|
@param [in] locktable= (0) If updating the maxkeytable, provide the libds
reference to the lock table (per mp_lockanytable.sas macro)
@param [in] filter_str= Apply a filter - useful for SCD2 or BITEMPORAL loads.
Example: `filter_str=%str( (where=( &now < &tech_to)) )`
@param [out] outds= (WORK.APPEND) Output table (staging table + retained key)
<h4> SAS Macros </h4>
@li mf_existvar.sas
@li mf_getquotedstr.sas
@li mf_getuniquename.sas
@li mf_nobs.sas
@li mp_abort.sas
@li mp_lockanytable.sas
<h4> Related Macros </h4>
@li mp_filterstore.sas
@li mp_retainedkey.test.sas
@version 9.2
**/
%macro mp_retainedkey(
base_lib=WORK
,base_dsn=BASETABLE
,append_lib=WORK
,append_dsn=APPENDTABLE
,retained_key=DEFAULT_RK
,business_key= PK1 PK2
,check_uniqueness=NO
,maxkeytable=0
,locktable=0
,outds=WORK.APPEND
,filter_str=
);
%put &sysmacroname entry vars:;
%put _local_;
%local base_libds app_libds key_field check maxkey idx_pk newkey_cnt iserr
msg x tempds1 tempds2 comma_pk appnobs checknobs dropvar tempvar idx_val;
%let base_libds=%upcase(&base_lib..&base_dsn);
%let app_libds=%upcase(&append_lib..&append_dsn);
%let tempds1=%mf_getuniquename();
%let tempds2=%mf_getuniquename();
%let comma_pk=%mf_getquotedstr(in_str=%str(&business_key),dlm=%str(,),quote=);
%let outds=%sysfunc(ifc(%index(&outds,.)=0,work.&outds,&outds));
/* validation checks */
%let iserr=0;
%if &syscc>0 %then %do;
%let iserr=1;
%let msg=%str(SYSCC=&syscc on macro entry);
%end;
%else %if %sysfunc(exist(&base_libds))=0 %then %do;
%let iserr=1;
%let msg=%str(Base LIBDS (&base_libds) expected but NOT FOUND);
%end;
%else %if %sysfunc(exist(&app_libds))=0 %then %do;
%let iserr=1;
%let msg=%str(Append LIBDS (&app_libds) expected but NOT FOUND);
%end;
%else %if &maxkeytable ne 0 and %sysfunc(exist(&maxkeytable))=0 %then %do;
%let iserr=1;
%let msg=%str(Maxkeytable (&maxkeytable) expected but NOT FOUND);
%end;
%else %if &maxkeytable ne 0 and %sysfunc(exist(&locktable))=0 %then %do;
%let iserr=1;
%let msg=%str(Locktable (&locktable) expected but NOT FOUND);
%end;
%else %if %length(&business_key)=0 %then %do;
%let iserr=1;
%let msg=%str(Business key (&business_key) expected but NOT FOUND);
%end;
%do x=1 %to %sysfunc(countw(&business_key));
/* check business key values exist */
%let key_field=%scan(&business_key,&x,%str( ));
%if not %mf_existvar(&app_libds,&key_field) %then %do;
%let iserr=1;
%let msg=Business key (&key_field) not found on &app_libds!;
%goto err;
%end;
%else %if not %mf_existvar(&base_libds,&key_field) %then %do;
%let iserr=1;
%let msg=Business key (&key_field) not found on &base_libds!;
%goto err;
%end;
%end;
%err:
%if &iserr=1 %then %do;
/* err case so first perform an unlock of the base table before exiting */
%mp_lockanytable(
UNLOCK,lib=&base_lib,ds=&base_dsn,ref=%superq(msg),ctl_ds=&locktable
)
%end;
%mp_abort(iftrue=(&iserr=1),mac=mp_retainedkey,msg=%superq(msg))
proc sql noprint;
select sum(max(&retained_key),0) into: maxkey from &base_libds;
/**
* get base table RK and bus field values for lookup
*/
proc sql noprint;
create table &tempds1 as
select distinct &comma_pk,&retained_key
from &base_libds &filter_str
order by &comma_pk,&retained_key;
%if &check_uniqueness=YES %then %do;
select count(*) into:checknobs
from (select distinct &comma_pk from &app_libds);
select count(*) into: appnobs from &app_libds; /* might be view */
%if &checknobs ne &appnobs %then %do;
%let msg=Source table &app_libds is not unique on (&business_key);
%let iserr=1;
%end;
%end;
%if &iserr=1 %then %do;
/* err case so first perform an unlock of the base table before exiting */
%mp_lockanytable(
UNLOCK,lib=&base_lib,ds=&base_dsn,ref=%superq(msg),ctl_ds=&locktable
)
%end;
%mp_abort(iftrue= (&iserr=1),mac=mp_retainedkey,msg=%superq(msg))
%if %mf_existvar(&app_libds,&retained_key)
%then %let dropvar=(drop=&retained_key);
/* prepare interim table with retained key populated for matching keys */
proc sql noprint;
create table &tempds2 as
select b.&retained_key, a.*
from &app_libds &dropvar a
left join &tempds1 b
on 1
%do idx_pk=1 %to %sysfunc(countw(&business_key));
%let idx_val=%scan(&business_key,&idx_pk);
and a.&idx_val=b.&idx_val
%end;
order by &retained_key;
/* identify the number of entries without retained keys (new records) */
select count(*) into: newkey_cnt
from &tempds2
where missing(&retained_key);
quit;
/**
* Update maxkey table if link provided
*/
%if &maxkeytable ne 0 %then %do;
proc sql noprint;
select count(*) into: check from &maxkeytable
where upcase(keytable)="&base_libds";
%mp_lockanytable(LOCK
,lib=%scan(&maxkeytable,1,.)
,ds=%scan(&maxkeytable,2,.)
,ref=Updating maxkeyvalues with mp_retainedkey
,ctl_ds=&locktable
)
proc sql;
%if &check=0 %then %do;
insert into &maxkeytable
set keytable="&base_libds"
,keycolumn="&retained_key"
,max_key=%eval(&maxkey+&newkey_cnt)
,processed_dttm="%sysfunc(datetime(),E8601DT26.6)"dt;
%end;
%else %do;
update &maxkeytable
set max_key=%eval(&maxkey+&newkey_cnt)
,processed_dttm="%sysfunc(datetime(),E8601DT26.6)"dt
where keytable="&base_libds";
%end;
%mp_lockanytable(UNLOCK
,lib=%scan(&maxkeytable,1,.)
,ds=%scan(&maxkeytable,2,.)
,ref=Updating maxkeyvalues with maxkey=%eval(&maxkey+&newkey_cnt)
,ctl_ds=&locktable
)
%end;
/* fill in the missing retained key values */
%let tempvar=%mf_getuniquename();
data &outds(drop=&tempvar);
retain &tempvar %eval(&maxkey+1);
set &tempds2;
if &retained_key =. then &retained_key=&tempvar;
&tempvar=&tempvar+1;
run;
%mend mp_retainedkey;

View File

@@ -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;

View File

@@ -5,9 +5,9 @@
sort it before performing operations such as merges / joins etc.
That said, there are a few edge cases where it can be desirable:
@li To improve performance for particular scenarios
@li To allow adjacent records to be viewed directly in the dataset
@li To reduce dataset size (eg when there are deleted records)
@li To apply compression, or to remove deleted records
@li To improve performance for specific queries
This macro will only work for BASE (V9) engine libraries. It works by
creating a copy of the dataset (without data, WITH constraints) in the same
@@ -29,6 +29,7 @@
@li mf_getengine.sas
@li mf_getquotedstr.sas
@li mf_getuniquename.sas
@li mf_getvarlist.sas
@li mf_nobs.sas
@li mp_abort.sas
@li mp_getpk.sas
@@ -38,7 +39,6 @@
@version 9.2
@author Allan Bowe
@source https://github.com/sasjs/core
**/
@@ -74,9 +74,13 @@
%return;
%end;
/* fallback sortkey is all fields */
%let sortkey=%mf_getvarlist(&libds);
/* overlay actual sort key if it exists */
data _null_;
set work.&tempds1;
call symputx('sortkey',pk_fields);
call symputx('sortkey',coalescec(pk_fields,symget('sortkey')));
run;

237
base/mp_storediffs.sas Normal file
View 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 format=best32. label='Old (numeric) value',
newval_num num format=best32. label='New (numeric) value',
oldval_char char(32765) label='Old (character) value',
newval_char char(32765) 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 format=best32.
,case when b.move_type='D' then .
else b.newval_num
end as newval_num format=best32.
,case when b.move_type='D' then b.newval_char
else a.newval_char
end as oldval_char length=32765
,case when b.move_type='D' then ''
else b.newval_char
end as newval_char length=32765
from &ds4(where=(move_type='O')) as a
right 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 */

View File

@@ -20,15 +20,24 @@
;;;;
run;
For more examples, see mp_validatecol.test.sas
Tip - when contributing, use https://regex101.com to test the regex validity!
@param [in] incol The column to be validated
@param [in] rule The rule to apply. Current rules:
@li ISINT - checks if the variable is an integer
@li ISNUM - checks if the variable is numeric
@li LIBDS - matches LIBREF.DATASET format
@li FORMAT - checks if the provided format is syntactically valid
@param [out] outcol The variable to create, with the results of the match
<h4> SAS Macros </h4>
@li mf_getuniquename.sas
<h4> Related Macros </h4>
@li mp_validatecol.test.sas
@version 9.3
**/
@@ -38,7 +47,13 @@
%local tempcol;
%let tempcol=%mf_getuniquename();
%if &rule=ISNUM %then %do;
%if &rule=ISINT %then %do;
&tempcol=input(&incol,?? best32.);
&outcol=0;
if not missing(&tempcol) then if mod(&incol,1)=0 then &outcol=1;
drop &tempcol;
%end;
%else %if &rule=ISNUM %then %do;
/*
credit SØREN LASSEN
https://sasmacro.blogspot.com/2009/06/welcome-isnum-macro.html
@@ -62,5 +77,19 @@
if prxmatch(&tempcol, trim(&incol)) then &outcol=1;
else &outcol=0;
%end;
%else %if &rule=FORMAT %then %do;
/* match valid format - regex could probably be improved */
if _n_=1 then do;
retain &tempcol;
&tempcol=prxparse('/^[_a-z\$]\w{0,31}\.[0-9]*$/i');
if missing(&tempcol) then do;
putlog "%str(ERR)OR: Invalid expression for FORMAT";
stop;
end;
drop &tempcol;
end;
if prxmatch(&tempcol, trim(&incol)) then &outcol=1;
else &outcol=0;
%end;
%mend mp_validatecol;

View File

@@ -295,7 +295,7 @@ run;
prop='Connection.DBMS.Property.SERVER.Name.xmlKey.txt';
rc=metadata_getprop(uri,prop,server,"");
end;
if server^='' then server='server='!!server;
if server^='' then server='server='!!quote(cats(server));
call symputx('server',server,'l');
/* get SCHEMA value */
@@ -441,11 +441,11 @@ run;
run;
%put NOTE: Executing the following:/; %put NOTE-;
%put NOTE- libname &libref TERADATA server=&path schema=&schema ;
%put NOTE- libname &libref TERADATA server="&path" schema=&schema ;
%put NOTe- authdomain=&authdomain;
%put NOTE-;
libname &libref TERADATA server=&path schema=&schema authdomain=&authdomain;
libname &libref TERADATA server="&path" schema=&schema authdomain=&authdomain;
%end;
%else %if &engine= %then %do;
%put NOTE: Libref &libref is not registered in metadata;

View File

@@ -94,13 +94,13 @@ data _null_;
put '%put output location=&jref; ';
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 ' data _null_;file &jref encoding=''utf-8'' ; ';
put ' put ''{"PROCESSED_DTTM" : "'' "%sysfunc(datetime(),E8601DT26.6)" ''"''; ';
put ' run; ';
put '%end; ';
put '%else %if (&action=ARR or &action=OBJ) %then %do; ';
put ' options validvarname=upcase; ';
put ' data _null_;file &jref mod encoding=''utf-8''; ';
put ' data _null_;file &jref mod encoding=''utf-8'' ; ';
put ' put ", ""%lowcase(%sysfunc(coalescec(&dslabel,&ds)))"":"; ';
put ' ';
put ' %if &engine=PROCJSON %then %do; ';
@@ -179,7 +179,7 @@ data _null_;
put ' run; ';
put ' %let ds=&fmtds; ';
put ' %end; /* &fmt=Y */ ';
put ' data _null_;file &jref mod encoding=''utf-8''; ';
put ' data _null_;file &jref mod encoding=''utf-8'' ; ';
put ' put "["; call symputx(''cols'',0,''l''); ';
put ' proc sort ';
put ' data=sashelp.vcolumn(where=(libname=''WORK'' & memname="%upcase(&ds)")) ';
@@ -224,7 +224,7 @@ data _null_;
put ' /* write to temp loc to avoid _webout truncation ';
put ' - https://support.sas.com/kb/49/325.html */ ';
put ' filename _sjs temp lrecl=131068 encoding=''utf-8''; ';
put ' data _null_; file _sjs lrecl=131068 encoding=''utf-8'' mod; ';
put ' data _null_; file _sjs lrecl=131068 encoding=''utf-8'' mod ; ';
put ' set &tempds; ';
put ' if _n_>1 then put "," @; put ';
put ' %if &action=ARR %then "[" ; %else "{" ; ';
@@ -251,14 +251,14 @@ data _null_;
put ' rc = fclose(fileid); ';
put ' run; ';
put ' filename _sjs clear; ';
put ' data _null_; file &jref mod encoding=''utf-8''; ';
put ' data _null_; file &jref mod encoding=''utf-8'' ; ';
put ' put "]"; ';
put ' run; ';
put ' %end; ';
put '%end; ';
put ' ';
put '%else %if &action=CLOSE %then %do; ';
put ' data _null_;file &jref encoding=''utf-8'' mod; ';
put ' data _null_;file &jref encoding=''utf-8'' mod ; ';
put ' put "}"; ';
put ' run; ';
put '%end; ';

View File

@@ -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;

View File

@@ -63,14 +63,13 @@ run;
%if %length(&tree)>0 %then %do;
/* get tree info */
%mm_gettree(tree=&tree,inds=&outds, outds=&outds, mDebug=&mDebug)
%if %mf_nobs(&outds)=0 %then %do;
%if %mf_nobs(&outds)=0 %then %do;
%put NOTE: Tree &tree did not exist!!;
%return;
%end;
%end;
data &outds ;
set &outds(rename=(treeuri=treeuri_compare));
length treeuri query stpuri $256;

View File

@@ -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

View File

@@ -1,6 +1,6 @@
{
"name": "@sasjs/core",
"description": "Production Ready Macros for SAS Application Developers",
"description": "Macros for SAS Application Developers",
"license": "MIT",
"keywords": [
"SAS",
@@ -38,4 +38,4 @@
"dependencies": {
"ts-loader": "^9.2.6"
}
}
}

View File

@@ -1,67 +1,73 @@
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "https://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<!DOCTYPE html
PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "https://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<!-- HTML header for doxygen 1.8.17-->
<html xmlns="https://www.w3.org/1999/xhtml" lang="en">
<head>
<meta http-equiv="Content-Type" content="text/xhtml;charset=UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=9" />
<meta property="og:type" content="website">
<meta name="author" content="Allan Bowe">
<meta name="generator" content="Doxygen $doxygenversion" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<!--BEGIN PROJECT_NAME-->
<meta name="description" content="$projectbrief" />
<!--END PROJECT_NAME-->
<!--BEGIN !PROJECT_NAME-->
<title>$title</title>
<!--END !PROJECT_NAME-->
<link href="$relpath^tabs.css" rel="stylesheet" type="text/css" />
<script type="text/javascript" src="$relpath^jquery.js"></script>
<script type="text/javascript" src="$relpath^dynsections.js"></script>
$treeview $search $mathjax
<link href="$relpath^$stylesheet" rel="stylesheet" type="text/css" />
<link rel="shortcut icon" href="$relpath^favicon.ico" type="image/x-icon" />
$extrastylesheet
</head>
<body>
<div id="top">
<!-- do not remove this div, it is closed by doxygen! -->
<!--BEGIN TITLEAREA-->
<div id="titlearea">
<table cellspacing="0" cellpadding="0">
<tbody>
<tr style="height: 56px">
<!--BEGIN PROJECT_LOGO-->
<td id="projectlogo">
<a href="$relpath^"
><img alt="Logo" src="$relpath^$projectlogo"
/></a>
</td>
<!--END PROJECT_LOGO-->
<td id="projectalign" style="padding-left: 0.5em">
<div id="projectbrief">
Production Ready Macros for SAS Application Developers<br />
<a href="https://github.com/sasjs/core">
https://github.com/sasjs/core
</a>
</div>
</td>
</tr>
</table>
</td>
<head>
<meta http-equiv="Content-Type" content="text/xhtml;charset=UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=9" />
<meta property="og:type" content="website">
<meta property="og:title" content="MacroCore" />
<meta property="og:url" content="https://core.sasjs.io" />
<meta property="og:image" content="https://core.sasjs.io/Macro_core_website_1.png" />
<meta name="author" content="Allan Bowe">
<meta name="generator" content="Doxygen $doxygenversion" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<!--BEGIN PROJECT_NAME-->
<meta name="description" content="$projectbrief" />
<meta name="og:description" content="$projectbrief" />
<!--END PROJECT_NAME-->
<!--BEGIN !PROJECT_NAME-->
<title>$title</title>
<!--END !PROJECT_NAME-->
<link href="$relpath^tabs.css" rel="stylesheet" type="text/css" />
<script type="text/javascript" src="$relpath^jquery.js"></script>
<script type="text/javascript" src="$relpath^dynsections.js"></script>
$treeview $search $mathjax
<link href="$relpath^$stylesheet" rel="stylesheet" type="text/css" />
<link rel="shortcut icon" href="$relpath^favicon.ico" type="image/x-icon" />
$extrastylesheet
</head>
<body>
<div id="top">
<!-- do not remove this div, it is closed by doxygen! -->
<!--BEGIN TITLEAREA-->
<div id="titlearea">
<table cellspacing="0" cellpadding="0">
<tbody>
<tr style="height: 56px">
<!--BEGIN PROJECT_LOGO-->
<td id="projectlogo">
<a href="$relpath^"><img alt="Logo" src="$relpath^$projectlogo" /></a>
</td>
<!--END PROJECT_LOGO-->
<td id="projectalign" style="padding-left: 0.5em">
<div id="projectbrief">
Macros for SAS Application Developers<br />
<a href="https://github.com/sasjs/core">
https://github.com/sasjs/core
</a>
</div>
</td>
</tr>
</table>
</td>
<!--BEGIN DISABLE_INDEX-->
<!--BEGIN SEARCHENGINE-->
<td>$searchbox</td>
<!--END SEARCHENGINE-->
<!--END DISABLE_INDEX-->
</tr>
</tbody>
</table>
</div>
<!--END TITLEAREA-->
<!-- end header part -->
<!--BEGIN DISABLE_INDEX-->
<!--BEGIN SEARCHENGINE-->
<td>$searchbox</td>
<!--END SEARCHENGINE-->
<!--END DISABLE_INDEX-->
</tr>
</tbody>
</table>
</div>
</body>
</html>
<!--END TITLEAREA-->
<!-- end header part -->
</div>
</body>
</html>

View File

@@ -1,5 +1,5 @@
{
"$schema": "https://cli.sasjs.io/sasjsconfig-schema.json",
"$schema": "https://raw.githubusercontent.com/sasjs/utils/main/src/types/sasjsconfig-schema.json",
"macroFolders": [
"base",
"fcmp",
@@ -32,7 +32,9 @@
"name": "viya",
"serverUrl": "https://sas.analytium.co.uk",
"serverType": "SASVIYA",
"allowInsecureRequests": false,
"httpsAgentOptions": {
"allowInsecureRequests": false
},
"appLoc": "/Public/temp/macrocore",
"macroFolders": [
"tests/viyaonly"

View File

@@ -79,7 +79,7 @@
OPTIONS NOBOMFILE;
/* setup json */
data _null_;file &fref encoding='utf-8';
data _null_;file &fref encoding='utf-8' termstr=lf;
%if %str(&_debug) ge 131 %then %do;
put '>>weboutBEGIN<<';
%end;
@@ -108,14 +108,14 @@
i+1;
call symputx('wt'!!left(i),name,'l');
call symputx('wtcnt',i,'l');
data _null_; file &fref mod encoding='utf-8';
data _null_; file &fref mod encoding='utf-8' termstr=lf;
put ",""WORK"":{";
%do i=1 %to &wtcnt;
%let wt=&&wt&i;
proc contents noprint data=&wt
out=_data_ (keep=name type length format:);
run;%let tempds=%scan(&syslast,2,.);
data _null_; file &fref mod encoding='utf-8';
data _null_; file &fref mod encoding='utf-8' termstr=lf;
dsid=open("WORK.&wt",'is');
nlobs=attrn(dsid,'NLOBS');
nvars=attrn(dsid,'NVARS');
@@ -126,15 +126,15 @@
put ',"nvars":' nvars;
%mp_jsonout(OBJ,&tempds,jref=&fref,dslabel=colattrs,engine=DATASTEP)
%mp_jsonout(OBJ,&wt,jref=&fref,dslabel=first10rows,engine=DATASTEP)
data _null_; file &fref mod encoding='utf-8';
data _null_; file &fref mod encoding='utf-8' termstr=lf;
put "}";
%end;
data _null_; file &fref mod encoding='utf-8';
data _null_; file &fref mod encoding='utf-8' termstr=lf termstr=lf;
put "}";
run;
%end;
/* close off json */
data _null_;file &fref mod encoding='utf-8';
data _null_;file &fref mod encoding='utf-8' termstr=lf;
_PROGRAM=quote(trim(resolve(symget('_PROGRAM'))));
put ",""SYSUSERID"" : ""&sysuserid"" ";
put ",""MF_GETUSER"" : ""%mf_getuser()"" ";
@@ -147,7 +147,9 @@
put ",""SYSHOSTNAME"" : ""&syshostname"" ";
put ",""SYSPROCESSID"" : ""&SYSPROCESSID"" ";
put ",""SYSPROCESSMODE"" : ""&SYSPROCESSMODE"" ";
put ",""SYSPROCESSNAME"" : ""&SYSPROCESSNAME"" ";
length SYSPROCESSNAME $512;
SYSPROCESSNAME=quote(urlencode(cats(SYSPROCESSNAME)));
put ",""SYSPROCESSNAME"" : " SYSPROCESSNAME;
put ",""SYSJOBID"" : ""&sysjobid"" ";
put ",""SYSSCPL"" : ""&sysscpl"" ";
put ",""SYSSITE"" : ""&syssite"" ";
@@ -156,7 +158,8 @@
put ',"SYSVLONG" : ' sysvlong;
put ",""SYSWARNINGTEXT"" : ""&syswarningtext"" ";
put ',"END_DTTM" : "' "%sysfunc(datetime(),datetime20.3)" '" ';
autoexec=quote(trim(getoption('autoexec')));
length autoexec $512;
autoexec=quote(urlencode(trim(getoption('autoexec'))));
put ',"AUTOEXEC" : ' autoexec;
memsize="%sysfunc(INPUTN(%sysfunc(getoption(memsize)), best.),sizekmg.)";
memsize=quote(cats(memsize));

View 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
)

View File

@@ -0,0 +1,22 @@
/**
@file
@brief Testing mf_existvar macro
<h4> SAS Macros </h4>
@li mf_existvar.sas
@li mp_assert.sas
**/
%mp_assert(
iftrue=(%mf_existvar(sashelp.class,age)=1),
desc=Checking existing var exists,
outds=work.test_results
)
%mp_assert(
iftrue=(%mf_existvar(sashelp.class,isjustanumber)=0),
desc=Checking non existing var does not exist,
outds=work.test_results
)

View File

@@ -0,0 +1,33 @@
/**
@file
@brief Testing mf_getfmtlist macro
<h4> SAS Macros </h4>
@li mf_getfmtlist.sas
@li mp_assert.sas
**/
%mp_assert(
iftrue=(
"%mf_getfmtlist(sashelp.prdsale)"="DOLLAR $CHAR W MONNAME"
),
desc=Checking basic numeric,
outds=work.test_results
)
%mp_assert(
iftrue=(
"%mf_getfmtlist(sashelp.shoes)"="$CHAR BEST DOLLAR"
),
desc=Checking basic char,
outds=work.test_results
)
%mp_assert(
iftrue=(
"%mf_getfmtlist(sashelp.demographics)"="BEST Z $CHAR COMMA PERCENTN"
),
desc=Checking longer numeric,
outds=work.test_results
)

View File

@@ -0,0 +1,33 @@
/**
@file
@brief Testing mf_getfmtname macro
<h4> SAS Macros </h4>
@li mf_getfmtname.sas
@li mp_assert.sas
**/
%mp_assert(
iftrue=(
"%mf_getfmtname(8.)"="W"
),
desc=Checking basic numeric,
outds=work.test_results
)
%mp_assert(
iftrue=(
"%mf_getfmtname($4.)"="$CHAR"
),
desc=Checking basic char,
outds=work.test_results
)
%mp_assert(
iftrue=(
"%mf_getfmtname(comma14.10)"="COMMA"
),
desc=Checking longer numeric,
outds=work.test_results
)

View 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
)

View File

@@ -0,0 +1,46 @@
/**
@file
@brief Testing mf_islibds macro
%put %mf_islibds(work.something)=1;
%put %mf_islibds(nolib)=0;
%put %mf_islibds(badlibref.ds)=0;
%put %mf_islibds(w.t.f)=0;
<h4> SAS Macros </h4>
@li mf_islibds.sas
@li mp_assert.sas
**/
%mp_assert(
iftrue=(
%mf_islibds(work.something)=1
),
desc=%str(Checking mf_islibds(work.something)=1),
outds=work.test_results
)
%mp_assert(
iftrue=(
%mf_islibds(nolib)=0
),
desc=%str(Checking mf_islibds(nolib)=0),
outds=work.test_results
)
%mp_assert(
iftrue=(
%mf_islibds(badlibref.ds)=0
),
desc=%str(Checking mf_islibds(badlibref.ds)=0),
outds=work.test_results
)
%mp_assert(
iftrue=(
%mf_islibds(w.t.f)=0
),
desc=%str(Checking mf_islibds(w.t.f)=0),
outds=work.test_results
)

View File

@@ -0,0 +1,20 @@
/**
@file
@brief Testing mf_wordsinstr1andstr2 macro
<h4> SAS Macros </h4>
@li mf_wordsinstr1andstr2.sas
@li mp_assert.sas
**/
%let x=%mf_wordsinstr1andstr2(str1=xx DOLLAR x $CHAR xxx W MONNAME
,str2=DOLLAR $CHAR W MONNAME xxxxxx
);
%mp_assert(
iftrue=(
"&x"="DOLLAR $CHAR W MONNAME"
),
desc=Checking basic string,
outds=work.test_results
)

View File

@@ -0,0 +1,20 @@
/**
@file
@brief Testing mf_wordsinstr1butnotstr2 macro
<h4> SAS Macros </h4>
@li mf_wordsinstr1butnotstr2.sas
@li mp_assert.sas
**/
%let x=%mf_wordsinstr1butnotstr2(str1=xx DOLLAR x $CHAR xxx W MONNAME
,str2=ff xx x xxx xxxxxx
);
%mp_assert(
iftrue=(
"&x"="DOLLAR $CHAR W MONNAME"
),
desc=Checking basic string,
outds=work.test_results
)

View File

@@ -0,0 +1,74 @@
/**
@file
@brief Testing mf_writefile.sas macro
<h4> SAS Macros </h4>
@li mf_writefile.sas
@li mp_assert.sas
**/
%mf_writefile(&sasjswork/myfile.txt,l1=some content,l2=more content)
data _null_;
infile "&sasjswork/myfile.txt";
input;
if _n_=2 then call symputx('test1',_infile_);
run;
%mp_assert(
iftrue=(&syscc=0),
desc=Check code ran without errors,
outds=work.test_results
)
%mp_assert(
iftrue=(&test1=more content),
desc=Checking line was created,
outds=work.test_results
)
%mf_writefile(&sasjswork/myfile.txt,l1=some content,l2=different content)
data _null_;
infile "&sasjswork/myfile.txt";
input;
if _n_=2 then call symputx('test2',_infile_);
run;
%mp_assert(
iftrue=(&syscc=0),
desc=Check code ran without errors for test2,
outds=work.test_results
)
%mp_assert(
iftrue=(&test2=different content),
desc=Checking second line was overwritten,
outds=work.test_results
)
%global test3 test4;
%mf_writefile(&sasjswork/myfile.txt
,mode=a
,l1=%str(aah, content)
,l2=append content
)
data _null_;
infile "&sasjswork/myfile.txt";
input;
if _n_=2 then call symputx('test3',_infile_);
if _n_=4 then call symputx('test4',_infile_);
run;
%mp_assert(
iftrue=(&syscc=0),
desc=Check code ran without errors for test2,
outds=work.test_results
)
%mp_assert(
iftrue=(&test3=different content),
desc=Checking second line was not overwritten,
outds=work.test_results
)
%mp_assert(
iftrue=(&test4=append content),
desc=Checking fourth line was appended,
outds=work.test_results
)

View File

@@ -0,0 +1,45 @@
/**
@file
@brief Testing mp_applyformats.sas macro
<h4> SAS Macros </h4>
@li mf_getvarformat.sas
@li mp_applyformats.sas
@li mp_assert.sas
@li mp_getcols.sas
**/
/**
* Test 1 Base case
*/
data work.example;
set sashelp.prdsale;
format _all_;
run;
%let origfmt=%mf_getvarformat(work.example,month);
%mp_getcols(sashelp.prdsale,outds=work.cols)
data work.cols2;
set work.cols;
lib='WORK';
ds='EXAMPLE';
var=name;
fmt=format;
keep lib ds var fmt;
run;
%mp_applyformats(work.cols2)
%mp_assert(
iftrue=("&orig_fmt"=""),
desc=Check that formats were cleared,
outds=work.test_results
)
%mp_assert(
iftrue=("%mf_getvarformat(work.example,month)"="MONNAME3."),
desc=Check that formats were applied,
outds=work.test_results
)

View File

@@ -0,0 +1,31 @@
/**
@file
@brief Testing mp_coretable.sas macro
<h4> SAS Macros </h4>
@li mf_existds.sas
@li mp_coretable.sas
@li mp_assert.sas
**/
%mp_coretable(LOCKTABLE,libds=work.lock)
%mp_assert(
iftrue=(%mf_existds(work.lock)=1),
desc=Lock table created,
outds=work.test_results
)
%mp_coretable(LOCKTABLE)
%mp_assert(
iftrue=("&syscc"="0"),
desc=DDL export ran without errors,
outds=work.test_results
)
%mp_coretable(FILTER_SUMMARY,libds=work.sum)
%mp_assert(
iftrue=(%mf_existds(work.sum)=1),
desc=Filter summary table created,
outds=work.test_results
)

View File

@@ -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(

View File

@@ -0,0 +1,34 @@
/**
@file
@brief Testing mp_ds2md.sas macro
<h4> SAS Macros </h4>
@li mp_ds2md.sas
@li mp_assert.sas
**/
%mp_ds2md(sashelp.class,outref=md)
data _null_;
infile md;
input;
call symputx(cats('test',_n_),_infile_);
if _n_=4 then stop;
run;
%mp_assert(
iftrue=("&test1"="|NAME:$8.|SEX:$1.|AGE:best.|HEIGHT:best.|WEIGHT:best.|"),
desc=Checking header row,
outds=work.test_results
)
%mp_assert(
iftrue=("&test2"="|---|---|---|---|---|"),
desc=Checking divider row,
outds=work.test_results
)
%mp_assert(
iftrue=("&test3"="|`Alfred `|`M `|`14 `|`69 `|`112.5 `|"),
desc=Checking data row,
outds=work.test_results
)

View File

@@ -0,0 +1,85 @@
/**
@file
@brief Testing mp_filterstore macro
<h4> SAS Macros </h4>
@li mp_coretable.sas
@li mp_filterstore.sas
@li mp_assertdsobs.sas
@li mp_assert.sas
**/
libname permlib (work);
%mp_coretable(LOCKTABLE,libds=permlib.locktable)
%mp_coretable(FILTER_SUMMARY,libds=permlib.filtsum)
%mp_coretable(FILTER_DETAIL,libds=permlib.filtdet)
%mp_coretable(MAXKEYTABLE,libds=permlib.maxkey)
/* valid filter */
data work.inds;
infile datalines4 dsd;
input GROUP_LOGIC:$3. SUBGROUP_LOGIC:$3. SUBGROUP_ID:8. VARIABLE_NM:$32.
OPERATOR_NM:$12. RAW_VALUE:$4000.;
datalines4;
AND,AND,1,AGE,=,12
AND,AND,1,SEX,<=,"'M'"
AND,OR,2,Name,NOT IN,"('Jane','Alfred')"
AND,OR,2,Weight,>=,77.7
AND,OR,2,Weight,NE,77.7
;;;;
run;
%mp_filterstore(libds=sashelp.class,
queryds=work.inds,
filter_summary=permlib.filtsum,
filter_detail=permlib.filtdet,
lock_table=permlib.locktable,
maxkeytable=permlib.maxkey,
outresult=work.result,
outquery=work.query,
mdebug=1
)
%mp_assert(iftrue=(&syscc>0),
desc=Ensure macro runs without errors,
outds=work.test_results
)
/* ensure only one record created */
%mp_assertdsobs(permlib.filtsum,
desc=Initial query,
test=ATMOST 1,
outds=work.test_results
)
/* check RK is correct */
proc sql noprint;
select max(filter_rk) into: test1 from work.result;
%mp_assert(iftrue=(&test1=1),
desc=Ensure filter rk is correct,
outds=work.test_results
)
/* Test 2 - load same table again and ensure we get the same RK */
%mp_filterstore(libds=sashelp.class,
queryds=work.inds,
filter_summary=permlib.filtsum,
filter_detail=permlib.filtdet,
lock_table=permlib.locktable,
maxkeytable=permlib.maxkey,
outresult=work.result,
outquery=work.query,
mdebug=1
)
/* ensure only one record created */
%mp_assertdsobs(permlib.filtsum,
desc=Initial query - same obs,
test=ATMOST 1,
outds=work.test_results
)
/* check RK is correct */
proc sql noprint;
select max(filter_rk) into: test2 from work.result;
%mp_assert(iftrue=(&test2=1),
desc=Ensure filter rk is correct for second run,
outds=work.test_results
)

View File

@@ -4,6 +4,7 @@
<h4> SAS Macros </h4>
@li mp_getcols.sas
@li mp_assertcols.sas
@li mp_assertcolvals.sas
@li mp_assertdsobs.sas
@@ -30,4 +31,10 @@ run;
checkvals=work.check.val,
desc=All values have a match,
test=ALLVALS
)
%mp_assertcols(work.info,
cols=name type length varnum format label ddtype fmtname,
test=ALL,
desc=check all columns exist
)

View File

@@ -0,0 +1,76 @@
/**
@file
@brief Testing mp_getformats.sas macro
<h4> SAS Macros </h4>
@li mf_mkdir.sas
@li mp_getformats.sas
@li mp_assert.sas
**/
/**
* Test - setup
*/
%mf_mkdir(&sasjswork/path1)
%mf_mkdir(&sasjswork/path2)
libname path1 "&sasjswork/path1";
libname path2 "&sasjswork/path2";
PROC FORMAT library=path1;
value whichpath 0 = 'path1' other='big fat problem if not path1';
PROC FORMAT library=path2;
value whichpath 0 = 'path2' other='big fat problem if not path2';
RUN;
/** run with path1 path2 FMTSEARCH */
options insert=(fmtsearch=(path1 path2));
data _null_;
test=0;
call symputx('test1',put(test,whichpath.));
run;
%mp_assert(
iftrue=("&test1"="path1"),
desc=Check correct format is applied,
outds=work.test_results
)
%mp_getformats(fmtlist=WHICHPATH,outsummary=sum,outdetail=detail1)
%let tst1=0;
data _null_;
set detail1;
if fmtname='WHICHPATH' and start='**OTHER**' then call symputx('tst1',label);
putlog (_all_)(=);
run;
%mp_assert(
iftrue=("&tst1"="big fat problem if not path1"),
desc=Check correct detail results are applied,
outds=work.test_results
)
/** run with path2 path1 FMTSEARCH */
options insert=(fmtsearch=(path2 path1));
data _null_;
test=0;
call symputx('test2',put(test,whichpath.));
run;
%mp_assert(
iftrue=("&test2"="path2"),
desc=Check correct format is applied,
outds=work.test_results
)
%mp_getformats(fmtlist=WHICHPATH,outsummary=sum,outdetail=detail2)
%let tst2=0;
data _null_;
set detail2;
if fmtname='WHICHPATH' and start='**OTHER**' then call symputx('tst2',label);
putlog (_all_)(=);
run;
%mp_assert(
iftrue=("&tst2"="big fat problem if not path2"),
desc=Check correct detail results are applied,
outds=work.test_results
)

View File

@@ -15,7 +15,7 @@
%let initial_value=&sasjs_init_num;
%mp_init();
%mp_init()
%mp_assert(
iftrue=("&initial_value"="&sasjs_init_num"),

View File

@@ -0,0 +1,59 @@
/**
@file
@brief Testing mp_makedata.sas macro
<h4> SAS Macros </h4>
@li mf_nobs.sas
@li mp_makedata.sas
@li mp_assert.sas
**/
/**
* Test 1 - Regular makedata call
*/
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 nnn not null(DD_SHORTDESC)
);
%mp_makedata(work.example,obs=500)
%mp_assert(
iftrue=("%mf_nobs(work.example)"="500"),
desc=Check that 500 rows were created,
outds=work.test_results
)
data _null_;
set work.example;
call symputx('lenvar',length(dd_source));
stop;
run;
%mp_assert(
iftrue=("&lenvar"="2048"),
desc=Check that entire length of variable is populated,
outds=work.test_results
)
proc sql;
create table work.example2(
TX_FROM float format=datetime19.,
DD_TYPE char(16),
DD_SOURCE char(2048),
DD_SHORTDESC char(256),
some_num num
);
%mp_makedata(work.example2)
%mp_assert(
iftrue=(&syscc=0),
desc=Ensure tables without keys still generate,
outds=work.test_results
)

View File

@@ -0,0 +1,116 @@
/**
@file
@brief Testing mp_retainedkey macro
<h4> SAS Macros </h4>
@li mp_assert.sas
@li mp_assertcolvals.sas
@li mp_retainedkey.sas
**/
/**
* Setup base tables
*/
proc sql;
create table work.maxkeytable(
keytable varchar(41) label='Base table in libref.dataset format',
keycolumn char(32) format=$32.
label='The Retained key field containing the key values.',
max_key num label=
'Integer representing current max RK or SK value in the KEYTABLE',
processed_dttm num format=E8601DT26.6
label='Datetime this value was last updated',
constraint pk_mpe_maxkeyvalues
primary key(keytable));
create table work.locktable(
lock_lib char(8),
lock_ds char(32),
lock_status_cd char(10) not null,
lock_user_nm char(100) not null ,
lock_ref char(200),
lock_pid char(10),
lock_start_dttm num format=E8601DT26.6,
lock_end_dttm num format=E8601DT26.6,
constraint pk_mp_lockanytable primary key(lock_lib,lock_ds));
data work.targetds;
rk_col=_n_;
set sashelp.class;
run;
data work.appendtable;
set sashelp.class;
if mod(_n_,2)=0 then name=cats('New',_n_);
if _n_<7;
run;
libname x (work);
/** Test 1 - base case **/
%mp_retainedkey(
base_lib=X
,base_dsn=targetds
,append_lib=X
,append_dsn=APPENDTABLE
,retained_key=rk_col
,business_key= name
,check_uniqueness=NO
,maxkeytable=0
,locktable=0
,outds=work.APPEND
,filter_str=
)
%mp_assert(
iftrue=(&syscc=0),
desc=Checking errors in test 1,
outds=work.test_results
)
data work.check;
do val=1,3,5,20,21,22;
output;
end;
run;
%mp_assertcolvals(work.append.rk_col,
checkvals=work.check.val,
desc=All values have a match,
test=ALLVALS
)
/** Test 2 - all new records, with metadata logging and unique check **/
data work.targetds2;
rk_col=_n_;
set sashelp.class;
run;
data work.appendtable2;
set sashelp.class;
do x=1 to 21;
name=cats('New',x);
output;
end;
stop;
run;
%mp_retainedkey(base_dsn=targetds2
,append_dsn=APPENDTABLE2
,retained_key=rk_col
,business_key= name
,check_uniqueness=YES
,maxkeytable=x.maxkeytable
,locktable=work.locktable
,outds=WORK.APPEND2
,filter_str=
)
%mp_assert(
iftrue=(&syscc=0),
desc=Checking errors in test 2,
outds=work.test_results
)
%mp_assert(
iftrue=(%mf_nobs(work.append2)=21),
desc=Checking append records created,
outds=work.test_results
)

View 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
)

View File

@@ -39,4 +39,31 @@ run;
),
desc=Check if sort was appplied,
outds=work.test_results
)
/** Test 2 - table without PK */
proc sql;
create table work.example2 as
select * from sashelp.classfit;
%mp_sortinplace(work.example2)
%mp_assert(
iftrue=(
%str(&syscc)=%str(0)
),
desc=Ensure no errors when no key exists,
outds=work.test_results
)
%let test2=0;
data _null_;
set work.example2;
call symputx('test2',name);
stop;
run;
%mp_assert(
iftrue=(
%str(&test2)=%str(Alfred)
),
desc=Check if sort was appplied when no index exists,
outds=work.test_results
)

View File

@@ -0,0 +1,102 @@
/**
@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
)
/* Test for when there are no actual changes */
data work.orig work.deleted work.changed work.appended;
set sashelp.class;
output work.orig;
run;
%mp_storediffs(sashelp.class,work.orig,NAME
,delds=work.deleted
,modds=work.changed
,appds=work.appended
,outds=work.final2
,mdebug=1
)
%mp_assertdsobs(work.final2,
desc=No changes produces 0 records,
test=EQUALS 0,
outds=work.test_results
)
/* Test for deletes only */
data work.orig work.deleted work.changed work.appended;
set sashelp.class;
output work.orig;
if _n_>5 then output work.deleted;
run;
%mp_storediffs(sashelp.class,work.orig,NAME
,delds=work.deleted
,modds=work.changed
,appds=work.appended
,outds=work.final3
,mdebug=1
)
%mp_assertdsobs(work.final3,
desc=Delete has 70 records,
test=EQUALS 70,
outds=work.test_results
)

View File

@@ -59,4 +59,71 @@ run;
desc=Test2 - ISNUM,
test=EQUALS 4,
outds=work.test_results
)
/**
* Test 3 - FORMAT
*/
data test3;
infile datalines4 dsd;
input;
infile=_infile_;
%mp_validatecol(infile,FORMAT,is_format)
if is_format=1;
datalines4;
$.
$format.
$format12.2
somenum.
somenum12.4
above are good
the rest are bad
%abort
1&somethingverybad.
&
+-1
.
a.A
$format12.1b
$format12.1b1
;;;;
run;
%mp_assertdsobs(work.test3,
desc=Test3 - ISFORMAT,
test=EQUALS 5,
outds=work.test_results
)
/**
* Test 4 - ISINT
*/
data test4;
infile datalines4 dsd;
input;
infile=_infile_;
%mp_validatecol(infile,ISINT,is_integer)
if is_integer=1;
datalines4;
1
1234
-134
-1.0
1.0
0
above are good
the rest are bad
%abort
1&somethingverybad.
&
+-1
.
a.A
$format12.1b
$format12.1b1
;;;;
run;
%mp_assertdsobs(work.test4,
desc=Test4 - ISFORMAT,
test=EQUALS 6,
outds=work.test_results
)

View File

@@ -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;

View File

@@ -242,13 +242,13 @@ data _null_;
put '%put output location=&jref; ';
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 ' data _null_;file &jref encoding=''utf-8'' ; ';
put ' put ''{"PROCESSED_DTTM" : "'' "%sysfunc(datetime(),E8601DT26.6)" ''"''; ';
put ' run; ';
put '%end; ';
put '%else %if (&action=ARR or &action=OBJ) %then %do; ';
put ' options validvarname=upcase; ';
put ' data _null_;file &jref mod encoding=''utf-8''; ';
put ' data _null_;file &jref mod encoding=''utf-8'' ; ';
put ' put ", ""%lowcase(%sysfunc(coalescec(&dslabel,&ds)))"":"; ';
put ' ';
put ' %if &engine=PROCJSON %then %do; ';
@@ -327,7 +327,7 @@ data _null_;
put ' run; ';
put ' %let ds=&fmtds; ';
put ' %end; /* &fmt=Y */ ';
put ' data _null_;file &jref mod encoding=''utf-8''; ';
put ' data _null_;file &jref mod encoding=''utf-8'' ; ';
put ' put "["; call symputx(''cols'',0,''l''); ';
put ' proc sort ';
put ' data=sashelp.vcolumn(where=(libname=''WORK'' & memname="%upcase(&ds)")) ';
@@ -372,7 +372,7 @@ data _null_;
put ' /* write to temp loc to avoid _webout truncation ';
put ' - https://support.sas.com/kb/49/325.html */ ';
put ' filename _sjs temp lrecl=131068 encoding=''utf-8''; ';
put ' data _null_; file _sjs lrecl=131068 encoding=''utf-8'' mod; ';
put ' data _null_; file _sjs lrecl=131068 encoding=''utf-8'' mod ; ';
put ' set &tempds; ';
put ' if _n_>1 then put "," @; put ';
put ' %if &action=ARR %then "[" ; %else "{" ; ';
@@ -399,14 +399,14 @@ data _null_;
put ' rc = fclose(fileid); ';
put ' run; ';
put ' filename _sjs clear; ';
put ' data _null_; file &jref mod encoding=''utf-8''; ';
put ' data _null_; file &jref mod encoding=''utf-8'' ; ';
put ' put "]"; ';
put ' run; ';
put ' %end; ';
put '%end; ';
put ' ';
put '%else %if &action=CLOSE %then %do; ';
put ' data _null_;file &jref encoding=''utf-8'' mod; ';
put ' data _null_;file &jref encoding=''utf-8'' mod ; ';
put ' put "}"; ';
put ' run; ';
put '%end; ';

View File

@@ -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;

View File

@@ -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);

View File

@@ -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);

View File

@@ -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;