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

Compare commits

...

28 Commits

Author SHA1 Message Date
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
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
31 changed files with 1604 additions and 166 deletions

View File

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

794
all.sas
View File

@@ -43,6 +43,60 @@ options noquotelenmax;
%mend mf_abort;
/** @endcond *//**
@file
@brief de-duplicates a macro string
@details Removes all duplicates from a string of words. A delimeter can be
chosen. Is inspired heavily by this excellent [macro](
https://github.com/scottbass/SAS/blob/master/Macro/dedup_mstring.sas) from
[Scott Base](https://www.linkedin.com/in/scottbass). Case sensitive.
Usage:
%let str=One two one two and through and through;
%put %mf_dedup(&str);
%put %mf_dedup(&str,outdlm=%str(,));
Which returns:
> One two one and through
> One,two,one,and,through
@param [in] str String to be deduplicated
@param [in] indlm= ( ) Delimeter of the input string
@param [out] outdlm= ( ) Delimiter of the output string
<h4> Related Macros </h4>
@li mf_trimstr.sas
@li mf_wordsinstr1butnotstr2.sas
@version 9.2
@author Allan Bowe
**/
%macro mf_dedup(str
,indlm=%str( )
,outdlm=%str( )
)/*/STORE SOURCE*/;
%local num word i pos out;
%* loop over each token, searching the target for that token ;
%let num=%sysfunc(countc(%superq(str),%str(&indlm)));
%do i=1 %to %eval(&num+1);
%let word=%scan(%superq(str),&i,%str(&indlm));
%let pos=%sysfunc(indexw(&out,&word,%str(&outdlm)));
%if (&pos eq 0) %then %do;
%if (&i gt 1) %then %let out=&out%str(&outdlm);
%let out=&out&word;
%end;
%end;
%unquote(&out)
%mend mf_dedup;
/**
@file mf_existds.sas
@brief Checks whether a dataset OR a view exists.
@details Can be used in open code, eg as follows:
@@ -515,6 +569,109 @@ https://github.com/yabwon/SAS_PACKAGES/blob/main/packages/baseplus.md#functionex
%end;
%mend mf_getfilesize ;/**
@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;/**
@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;/**
@file
@brief retrieves a key value pair from a control dataset
@details By default, control dataset is work.mp_setkeyvalue. Usage:
@@ -627,8 +784,8 @@ https://github.com/yabwon/SAS_PACKAGES/blob/main/packages/baseplus.md#functionex
> "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.
@@ -639,7 +796,10 @@ https://github.com/yabwon/SAS_PACKAGES/blob/main/packages/baseplus.md#functionex
**/
%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));
@@ -1128,7 +1288,7 @@ https://github.com/yabwon/SAS_PACKAGES/blob/main/packages/baseplus.md#functionex
%put &sysmacroname: SYSMSG= %sysfunc(sysmsg());
%let rc=%sysfunc(close(&dsid));
%end;
&outvar
%do;%unquote(&outvar)%end;
%mend mf_getvarlist;/**
@file
@brief Returns the position of a variable in dataset (varnum attribute).
@@ -4192,6 +4352,103 @@ data _null_;
run;
%mend mp_ds2inserts;/**
@file
@brief Create a Markdown Table from a dataset
@details A markdown table is a simple table representation for use in
documents written in markdown format.
An online generator is available here:
https://www.tablesgenerator.com/markdown_tables
This structure is also used by the Macro Core library for documenting input/
output datasets, as well as the sasjs/cli tool for documenting inputs/outputs
for web services.
We take the standard definition one step further by embedding the informat
in the table header row, like so:
|var1:$32|var2:best.|var3:date9.|
|---|---|---|
|some text|42|01JAN1960|
|blah|1|31DEC1999|
Which resolves to:
|var1:$32|var2:best.|var3:date9.|
|---|---|---|
|some text|42|01JAN1960|
|blah|1|31DEC1999|
Usage:
%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
@version 9.3
@author Allan Bowe
**/
%macro mp_ds2md(
libds,
outref=mdtable,
showlog=YES
)/*/STORE SOURCE*/;
/* check fileref is assigned */
%if %sysfunc(fileref(&outref)) > 0 %then %do;
filename &outref temp;
%end;
%local vars;
%let vars=%upcase(%mf_getvarlist(&libds));
/* create the header row */
data _null_;
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=%lowcase(%mf_getvarformat(&libds,&var,force=1));
"&var:&fmt|"
%end;
;
put '|'
%do i=1 %to %sysfunc(countw(&vars));
"---|"
%end;
;
run;
/* write out the data */
data _null_;
file &outref mod dlm='|' lrecl=32767;
set &libds ;
length line $32767;
line='|`'!!cats(%mf_getvarlist(&libds,dlm=%str(%)!!' `|`'!!cats%()))!!' `|';
put line;
run;
%if %upcase(&showlog)=YES %then %do;
options ps=max;
data _null_;
infile &outref;
input;
putlog _infile_;
run;
%end;
%mend mp_ds2md;/**
@file
@brief Checks an input filter table for validity
@details Performs checks on the input table to ensure it arrives in the
@@ -5495,6 +5752,146 @@ run;
%end;
%mend mp_getddl;/**
@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_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;/**
@file mp_getmaxvarlengths.sas
@brief Scans a dataset to find the max length of the variable values
@details
@@ -6359,6 +6756,7 @@ run;
/* prepare the errds */
data &errds;
length msg mac $1000;
call missing(msg,mac);
iftrue='1=0';
run;
@@ -6404,23 +6802,25 @@ filename &tempref clear;
**/
%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 */
%str(err)orcheck=STRICT /* catch errs in libname/filename statements */
@@ -6430,6 +6830,7 @@ filename &tempref clear;
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 */
varinitchk=%str(ERR)OR /* avoid data mistakes from variable name typos */
@@ -7004,7 +7405,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;
@@ -7239,13 +7640,13 @@ data &ds1;
&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));
%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;
%end;
@@ -7260,103 +7661,6 @@ proc sql;
drop table &ds1;
%mend mp_makedata;/**
@file
@brief Create a Markdown Table from a dataset
@details A markdown table is a simple table representation for use in
documents written in markdown format.
An online generator is available here:
https://www.tablesgenerator.com/markdown_tables
This structure is also used by the Macro Core library for documenting input/
output datasets, as well as the sasjs/cli tool for documenting inputs/outputs
for web services.
We take the standard definition one step further by embedding the informat
in the table header row, like so:
|var1:$32|var2:best.|var3:date9.|
|---|---|---|
|some text|42|01JAN1960|
|blah|1|31DEC1999|
Which resolves to:
|var1:$32|var2:best.|var3:date9.|
|---|---|---|
|some text|42|01JAN1960|
|blah|1|31DEC1999|
Usage:
%mp_mdtablewrite(libds=sashelp.class,showlog=YES)
<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
)/*/STORE SOURCE*/;
/* check fileref is assigned */
%if %sysfunc(fileref(&fref)) > 0 %then %do;
filename &fref temp;
%end;
%local vars;
%let vars=%mf_getvarlist(&libds);
/* create the header row */
data _null_;
file &fref;
length line $32767;
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);
"&var:&fmt|"
%end;
;
put '|'
%do i=1 %to %sysfunc(countw(&vars));
"---|"
%end;
;
run;
/* write out the data */
data _null_;
file &fref mod dlm='|' lrecl=32767;
set &libds ;
length line $32767;
line=cats('|',%mf_getvarlist(&libds,dlm=%str(,'|',)),'|');
put line;
run;
%if %upcase(&showlog)=YES %then %do;
options ps=max;
data _null_;
infile &fref;
input;
putlog _infile_;
run;
%end;
%mend mp_mdtablewrite;/**
@file
@brief Logs the time the macro was executed in a control dataset.
@details If the dataset does not exist, it is created. Usage:
@@ -7974,13 +8278,15 @@ select distinct memname into: table_list separated by ' '
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
library, appending a sorted view into it, and finally - renaming it.
library, appending a sorted view into it, and finally - renaming it. By
default, COMPRESS=CHAR and REUSE=YES will be applied, this behaviour can
be adjusted using the `dsoptions=` parameter.
Example usage:
@@ -7998,6 +8304,7 @@ select distinct memname into: table_list separated by ' '
@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
@@ -8043,9 +8350,13 @@ select distinct memname into: table_list separated by ' '
%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;
@@ -8085,6 +8396,242 @@ run;
%mend mp_sortinplace;/**
@file
@brief Converts deletes/changes/appends into a single audit table.
@details When tracking changes to data over time, it can be helpful to have
a single base table to track ALL modifications - enabling audit trail,
data recovery, and change re-application. This macro is one of many
data management utilities used in [Data Controller for SAS](
https:datacontroller.io) - a comprehensive data ingestion solution, which
works on any SAS platform (Viya, SAS 9, Foundation) and is free for up to 5
users.
NOTE - this macro does not validate the inputs. It is assumed that the
datasets containing the new / changed / deleted rows are CORRECT, contain
no additional (or missing columns), and that the originals dataset contains
all relevant base records (and no additionals).
Usage:
data work.orig work.deleted work.changed work.appended;
set sashelp.class;
if _n_=1 then do;
output work.orig work.deleted;
end;
else if _n_=2 then do;
output work.orig;
age=99;
output work.changed;
end;
else do;
name='Newbie';
output work.appended;
stop;
end;
run;
%mp_storediffs(sashelp.class,work.orig,NAME
,delds=work.deleted
,modds=work.changed
,appds=work.appended
,outds=work.final
,mdebug=1
)
@param [in] libds Target table against which the changes were applied
@param [in] origds Dataset with original (unchanged) records. Can be empty if
only appending.
@param [in] key Space seperated list of key variables
@param [in] delds= (0) Dataset with deleted records
@param [in] appds= (0) Dataset with appended records
@param [in] modds= (0) Dataset with modified records
@param [out] outds= (work.mp_storediffs) Output table containing stored data.
Has the following format:
proc sql;
create table &outds(
load_ref char(36) label='unique load reference',
processed_dttm num format=E8601DT26.6 label='Processed at timestamp',
libref char(8) label='Library Reference (8 chars)',
dsn char(32) label='Dataset Name (32 chars)',
key_hash char(32) label=
'MD5 Hash of primary key values (pipe seperated)',
move_type char(1) label='Either (A)ppended, (D)eleted or (M)odified',
is_pk num label='Is Primary Key Field? (1/0)',
is_diff num label=
'Did value change? (1/0/-1). Always -1 for appends and deletes.',
tgtvar_type char(1) label='Either (C)haracter or (N)umeric',
tgtvar_nm char(32) label='Target variable name (32 chars)',
oldval_num num 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 *//**
@file
@brief Capture session start / finish times and request details
@details For details, see
@@ -9597,7 +10144,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 */
@@ -9743,11 +10290,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;
@@ -11995,7 +12542,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;
@@ -13468,14 +14015,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;
@@ -13620,7 +14166,7 @@ run;
data &outds;
length uri serveruri conn_uri domainuri libname ServerContext AuthDomain
path_schema usingpkguri type tableuri $256 id $17
libdesc $200 libref engine $8 IsDBMSLibname $1
libdesc $200 libref engine $8 IsDBMSLibname IsPreassigned $1
tablename $50 /* metadata table names can be longer than $32 */
;
keep libname libdesc libref engine ServerContext path_schema AuthDomain
@@ -17471,6 +18017,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'");
@@ -17790,6 +18338,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);
@@ -18946,6 +19496,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);

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;

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;

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;

141
base/mp_getformats.sas Normal file
View File

@@ -0,0 +1,141 @@
/**
@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_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

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

View File

@@ -30,23 +30,25 @@
**/
%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 */
%str(err)orcheck=STRICT /* catch errs in libname/filename statements */
@@ -56,6 +58,7 @@
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 */
varinitchk=%str(ERR)OR /* avoid data mistakes from variable name typos */

View File

@@ -180,7 +180,7 @@ run;
%else %do;
data _null_;
putlog 'NOTE-' / 'NOTE-';
putlog "NOTE- &sysmacroname: Table &lib..&ds locked at "@
putlog "NOTE- &sysmacroname: Table &lib..&ds locked at "@;
putlog " %sysfunc(datetime(),datetime19.) (iteration &x)"@;
putlog 'NOTE-' / 'NOTE-';
run;

View File

@@ -64,13 +64,13 @@ data &ds1;
&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));
%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;
%end;

View File

@@ -5,13 +5,15 @@
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
library, appending a sorted view into it, and finally - renaming it.
library, appending a sorted view into it, and finally - renaming it. By
default, COMPRESS=CHAR and REUSE=YES will be applied, this behaviour can
be adjusted using the `dsoptions=` parameter.
Example usage:
@@ -29,6 +31,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
@@ -74,9 +77,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

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

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

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

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

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

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

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