mirror of
https://github.com/sasjs/core.git
synced 2025-12-14 16:04:34 +00:00
Compare commits
27 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9b1f0d7bcb | ||
|
|
98b1c44283 | ||
|
|
ce026f19b5 | ||
|
|
8e723d06b0 | ||
|
|
a6d84cc65a | ||
|
|
536ce8e95d | ||
|
|
bc1d9e619b | ||
|
|
1062a97cfe | ||
|
|
51db64c90a | ||
|
|
7c4278c3f9 | ||
|
|
6c6b55dcea | ||
|
|
66b0c9e77e | ||
|
|
caf3b95269 | ||
|
|
3866b97416 | ||
|
|
d687658687 | ||
|
|
9f815c73e9 | ||
|
|
a13c782074 | ||
|
|
f2991cfd63 | ||
|
|
8eb4f0844c | ||
|
|
f90dc069dc | ||
|
|
436b430389 | ||
|
|
6667b91ced | ||
|
|
bce56d8105 | ||
|
|
2ec440b321 | ||
|
|
3d2ad531cf | ||
|
|
09136cfdbb | ||
|
|
1e72f13f2d |
@@ -7,8 +7,12 @@
|
|||||||
|
|
||||||
%put %mf_existvar(work.someds, somevar)
|
%put %mf_existvar(work.someds, somevar)
|
||||||
|
|
||||||
@param libds (positional) - 2 part dataset or view reference
|
@param [in] libds 2 part dataset or view reference
|
||||||
@param var (positional) - variable name
|
@param [in] var variable name
|
||||||
|
|
||||||
|
<h4> Related Macros </h4>
|
||||||
|
@li mf_existvar.test.sas
|
||||||
|
|
||||||
@version 9.2
|
@version 9.2
|
||||||
@author Allan Bowe
|
@author Allan Bowe
|
||||||
**/
|
**/
|
||||||
|
|||||||
@@ -12,9 +12,10 @@
|
|||||||
contributors of Chris Hemedingers blog [post](
|
contributors of Chris Hemedingers blog [post](
|
||||||
http://blogs.sas.com/content/sasdummy/2013/06/04/find-a-sas-library-engine/)
|
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
|
@warning will only return the FIRST library engine - for concatenated
|
||||||
libraries, with different engines, inconsistent results may be encountered.
|
libraries, with different engines, inconsistent results may be encountered.
|
||||||
@@ -46,7 +47,7 @@
|
|||||||
%let rc= %sysfunc(close(&dsid));
|
%let rc= %sysfunc(close(&dsid));
|
||||||
%end;
|
%end;
|
||||||
|
|
||||||
&engine
|
%upcase(&engine)
|
||||||
|
|
||||||
%mend mf_getengine;
|
%mend mf_getengine;
|
||||||
|
|
||||||
|
|||||||
40
base/mf_islibds.sas
Normal file
40
base/mf_islibds.sas
Normal 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(®ex,&libds))
|
||||||
|
|
||||||
|
%mend mf_islibds;
|
||||||
54
base/mf_wordsinstr1andstr2.sas
Normal file
54
base/mf_wordsinstr1andstr2.sas
Normal 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
67
base/mf_writefile.sas
Normal 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
181
base/mp_applyformats.sas
Normal 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;
|
||||||
|
%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;
|
||||||
|
proc sql;
|
||||||
|
insert into &outds set lib="&lib",ds="_all_",var="_all", msg="&msg" ;
|
||||||
|
%if &errds=0 %then %put %str(ERR)OR: &msg;
|
||||||
|
%end;
|
||||||
|
%end;
|
||||||
|
|
||||||
|
%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;
|
||||||
92
base/mp_coretable.sas
Normal file
92
base/mp_coretable.sas
Normal 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;
|
||||||
213
base/mp_filterstore.sas
Normal file
213
base/mp_filterstore.sas
Normal file
@@ -0,0 +1,213 @@
|
|||||||
|
/**
|
||||||
|
@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;
|
||||||
|
|
||||||
|
/* update detail table first */
|
||||||
|
%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 update - &filter_hash
|
||||||
|
,ctl_ds=&lock_table
|
||||||
|
)
|
||||||
|
|
||||||
|
/* now update summary table */
|
||||||
|
%let ds3=%mf_getuniquename(prefix=filtersum);
|
||||||
|
data &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 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 update - &filter_hash
|
||||||
|
,ctl_ds=&lock_table
|
||||||
|
)
|
||||||
|
|
||||||
|
data &outresult;
|
||||||
|
set &filter_summary;
|
||||||
|
where filter_hash="&filter_hash";
|
||||||
|
run;
|
||||||
|
|
||||||
|
%end;
|
||||||
|
|
||||||
|
proc sort data=&filter_detail(where=(filter_hash="&filter_hash")) out=&outquery;
|
||||||
|
by filter_line;
|
||||||
|
run;
|
||||||
|
|
||||||
|
%mp_abort(iftrue= (&syscc ne 0)
|
||||||
|
,mac=mp_filterstore
|
||||||
|
,msg=%str(syscc=&syscc on macro exit)
|
||||||
|
)
|
||||||
|
|
||||||
|
%mend mp_filterstore;
|
||||||
@@ -48,6 +48,7 @@ https://support.sas.com/documentation/cdl/en/proc/61895/HTML/default/viewer.htm#
|
|||||||
|
|
||||||
|
|
||||||
<h4> Related Macros </h4>
|
<h4> Related Macros </h4>
|
||||||
|
@li mp_applyformats.sas
|
||||||
@li mp_getformats.test.sas
|
@li mp_getformats.test.sas
|
||||||
|
|
||||||
@version 9.2
|
@version 9.2
|
||||||
|
|||||||
@@ -62,13 +62,13 @@
|
|||||||
%put output location=&jref;
|
%put output location=&jref;
|
||||||
%if &action=OPEN %then %do;
|
%if &action=OPEN %then %do;
|
||||||
options nobomfile;
|
options nobomfile;
|
||||||
data _null_;file &jref encoding='utf-8';
|
data _null_;file &jref encoding='utf-8' ;
|
||||||
put '{"PROCESSED_DTTM" : "' "%sysfunc(datetime(),E8601DT26.6)" '"';
|
put '{"PROCESSED_DTTM" : "' "%sysfunc(datetime(),E8601DT26.6)" '"';
|
||||||
run;
|
run;
|
||||||
%end;
|
%end;
|
||||||
%else %if (&action=ARR or &action=OBJ) %then %do;
|
%else %if (&action=ARR or &action=OBJ) %then %do;
|
||||||
options validvarname=upcase;
|
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)))"":";
|
put ", ""%lowcase(%sysfunc(coalescec(&dslabel,&ds)))"":";
|
||||||
|
|
||||||
%if &engine=PROCJSON %then %do;
|
%if &engine=PROCJSON %then %do;
|
||||||
@@ -147,7 +147,7 @@
|
|||||||
run;
|
run;
|
||||||
%let ds=&fmtds;
|
%let ds=&fmtds;
|
||||||
%end; /* &fmt=Y */
|
%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');
|
put "["; call symputx('cols',0,'l');
|
||||||
proc sort
|
proc sort
|
||||||
data=sashelp.vcolumn(where=(libname='WORK' & memname="%upcase(&ds)"))
|
data=sashelp.vcolumn(where=(libname='WORK' & memname="%upcase(&ds)"))
|
||||||
@@ -192,7 +192,7 @@
|
|||||||
/* write to temp loc to avoid _webout truncation
|
/* write to temp loc to avoid _webout truncation
|
||||||
- https://support.sas.com/kb/49/325.html */
|
- https://support.sas.com/kb/49/325.html */
|
||||||
filename _sjs temp lrecl=131068 encoding='utf-8';
|
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;
|
set &tempds;
|
||||||
if _n_>1 then put "," @; put
|
if _n_>1 then put "," @; put
|
||||||
%if &action=ARR %then "[" ; %else "{" ;
|
%if &action=ARR %then "[" ; %else "{" ;
|
||||||
@@ -219,14 +219,14 @@
|
|||||||
rc = fclose(fileid);
|
rc = fclose(fileid);
|
||||||
run;
|
run;
|
||||||
filename _sjs clear;
|
filename _sjs clear;
|
||||||
data _null_; file &jref mod encoding='utf-8';
|
data _null_; file &jref mod encoding='utf-8' ;
|
||||||
put "]";
|
put "]";
|
||||||
run;
|
run;
|
||||||
%end;
|
%end;
|
||||||
%end;
|
%end;
|
||||||
|
|
||||||
%else %if &action=CLOSE %then %do;
|
%else %if &action=CLOSE %then %do;
|
||||||
data _null_;file &jref encoding='utf-8' mod;
|
data _null_;file &jref encoding='utf-8' mod ;
|
||||||
put "}";
|
put "}";
|
||||||
run;
|
run;
|
||||||
%end;
|
%end;
|
||||||
|
|||||||
@@ -5,19 +5,18 @@
|
|||||||
Only useful if every update uses the macro! Used heavily within
|
Only useful if every update uses the macro! Used heavily within
|
||||||
[Data Controller for SAS](https://datacontroller.io).
|
[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:
|
@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 LOCK - Sets the lock flag, also confirms if a SAS lock is available
|
||||||
@li UNLOCK - Unlocks the table
|
@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
|
@param [in] lib= (WORK) The libref of the table to lock. Should already be
|
||||||
assigned.
|
assigned.
|
||||||
@param [in] ds= The dataset to lock
|
@param [in] ds= The dataset to lock
|
||||||
@param [in] ref= A meaningful reference to enable the lock to be traced. Max
|
@param [in] ref= A meaningful reference to enable the lock to be traced. Max
|
||||||
length is 200 characters.
|
length is 200 characters.
|
||||||
@param [out] ctl_ds= (0) The control table which controls the actual locking.
|
@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] loops= (25) Number of times to check for a lock.
|
||||||
@param [in] loop_secs= (1) Seconds to wait between each lock attempt
|
@param [in] loop_secs= (1) Seconds to wait between each lock attempt
|
||||||
|
|
||||||
@@ -221,19 +220,6 @@ run;
|
|||||||
%let abortme=1;
|
%let abortme=1;
|
||||||
%end;
|
%end;
|
||||||
%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;
|
%else %do;
|
||||||
%let msg=lock_anytable given unsupported action (&action);
|
%let msg=lock_anytable given unsupported action (&action);
|
||||||
%let abortme=1;
|
%let abortme=1;
|
||||||
|
|||||||
@@ -10,8 +10,6 @@
|
|||||||
according to the variable types and formats.
|
according to the variable types and formats.
|
||||||
|
|
||||||
TODO:
|
TODO:
|
||||||
@li Respect PKs
|
|
||||||
@li Respect NOT NULLs
|
|
||||||
@li Consider dates, datetimes, times, integers etc
|
@li Consider dates, datetimes, times, integers etc
|
||||||
|
|
||||||
Usage:
|
Usage:
|
||||||
@@ -27,16 +25,23 @@
|
|||||||
);
|
);
|
||||||
%mp_makedata(work.example)
|
%mp_makedata(work.example)
|
||||||
|
|
||||||
@param [in] libds The empty table in which to create data
|
@param [in] libds The empty table (libref.dataset) in which to create data
|
||||||
@param [out] obs= (500) The number of records to create.
|
@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>
|
<h4> SAS Macros </h4>
|
||||||
@li mf_getuniquename.sas
|
@li mf_getuniquename.sas
|
||||||
@li mf_getvarlen.sas
|
@li mf_getvarlen.sas
|
||||||
|
@li mf_getvarlist.sas
|
||||||
|
@li mf_islibds.sas
|
||||||
@li mf_nobs.sas
|
@li mf_nobs.sas
|
||||||
@li mp_getcols.sas
|
@li mp_getcols.sas
|
||||||
@li mp_getpk.sas
|
@li mp_getpk.sas
|
||||||
|
|
||||||
|
<h4> Related Macros </h4>
|
||||||
|
@li mp_makedata.test.sas
|
||||||
|
|
||||||
@version 9.2
|
@version 9.2
|
||||||
@author Allan Bowe
|
@author Allan Bowe
|
||||||
|
|
||||||
@@ -44,44 +49,59 @@
|
|||||||
|
|
||||||
%macro mp_makedata(libds
|
%macro mp_makedata(libds
|
||||||
,obs=500
|
,obs=500
|
||||||
|
,seed=1
|
||||||
)/*/STORE SOURCE*/;
|
)/*/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;
|
%put &sysmacroname: &libds has data, it will not be recreated;
|
||||||
%return;
|
%return;
|
||||||
%end;
|
%end;
|
||||||
|
|
||||||
%local ds1 c1 n1;
|
/* set up temporary vars */
|
||||||
%let ds1=%mf_getuniquename(prefix=mp_makedata);
|
%let ds1=%mf_getuniquename(prefix=mp_makedatads1);
|
||||||
%let c1=%mf_getuniquename(prefix=mp_makedatacol);
|
%let ds2=%mf_getuniquename(prefix=mp_makedatads2);
|
||||||
%let n1=%mf_getuniquename(prefix=mp_makedatacol);
|
%let lib=%scan(&libds,1,.);
|
||||||
data &ds1;
|
%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;
|
if 0 then set &libds;
|
||||||
do _n_=1 to &obs;
|
do _n_=1 to &obs;
|
||||||
&c1=repeat(uuidgen(),10);
|
|
||||||
&n1=ranuni(1)*5000000;
|
|
||||||
drop &c1 &n1;
|
|
||||||
%let charvars=%mf_getvarlist(&libds,typefilter=C);
|
%let charvars=%mf_getvarlist(&libds,typefilter=C);
|
||||||
%if &charvars ^= %then %do i=1 %to %sysfunc(countw(&charvars));
|
%if &charvars ^= %then %do i=1 %to %sysfunc(countw(&charvars));
|
||||||
%let col=%scan(&charvars,&i);
|
%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;
|
%end;
|
||||||
|
|
||||||
%let numvars=%mf_getvarlist(&libds,typefilter=N);
|
%let numvars=%mf_getvarlist(&libds,typefilter=N);
|
||||||
%if &numvars ^= %then %do i=1 %to %sysfunc(countw(&numvars));
|
%if &numvars ^= %then %do i=1 %to %sysfunc(countw(&numvars));
|
||||||
%let col=%scan(&numvars,&i);
|
%let col=%scan(&numvars,&i);
|
||||||
&col=&n1;
|
&col=_n_;
|
||||||
%end;
|
%end;
|
||||||
output;
|
output;
|
||||||
end;
|
end;
|
||||||
|
stop;
|
||||||
|
run;
|
||||||
|
proc sort data=&ds2 nodupkey;
|
||||||
|
by &pk_fields;
|
||||||
run;
|
run;
|
||||||
|
|
||||||
proc append base=&libds data=&ds1;
|
proc append base=&libds data=&ds2;
|
||||||
run;
|
run;
|
||||||
|
|
||||||
proc sql;
|
proc sql;
|
||||||
drop table &ds1;
|
drop table &ds1, &ds2;
|
||||||
|
|
||||||
%mend mp_makedata;
|
%mend mp_makedata;
|
||||||
@@ -3,13 +3,15 @@
|
|||||||
@brief Reset an option to original value
|
@brief Reset an option to original value
|
||||||
@details Inspired by the SAS Jedi -
|
@details Inspired by the SAS Jedi -
|
||||||
https://blogs.sas.com/content/sastraining/2012/08/14/jedi-sas-tricks-reset-sas-system-options
|
https://blogs.sas.com/content/sastraining/2012/08/14/jedi-sas-tricks-reset-sas-system-options
|
||||||
|
|
||||||
Called as follows:
|
Called as follows:
|
||||||
|
|
||||||
options obs=30;
|
options obs=30 ps=max;
|
||||||
%mp_resetoption(OBS)
|
%mp_resetoption(OBS)
|
||||||
|
%mp_resetoption(PS)
|
||||||
|
|
||||||
|
|
||||||
@param option the option to reset
|
@param [in] option the option to reset
|
||||||
|
|
||||||
@version 9.2
|
@version 9.2
|
||||||
@author Allan Bowe
|
@author Allan Bowe
|
||||||
|
|||||||
246
base/mp_retainedkey.sas
Normal file
246
base/mp_retainedkey.sas
Normal 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;
|
||||||
|
|
||||||
@@ -11,9 +11,7 @@
|
|||||||
|
|
||||||
This macro will only work for BASE (V9) engine libraries. It works by
|
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
|
creating a copy of the dataset (without data, WITH constraints) in the same
|
||||||
library, appending a sorted view into it, and finally - renaming it. By
|
library, appending a sorted view into it, and finally - renaming it.
|
||||||
default, COMPRESS=CHAR and REUSE=YES will be applied, this behaviour can
|
|
||||||
be adjusted using the `dsoptions=` parameter.
|
|
||||||
|
|
||||||
Example usage:
|
Example usage:
|
||||||
|
|
||||||
@@ -41,7 +39,6 @@
|
|||||||
|
|
||||||
@version 9.2
|
@version 9.2
|
||||||
@author Allan Bowe
|
@author Allan Bowe
|
||||||
@source https://github.com/sasjs/core
|
|
||||||
|
|
||||||
**/
|
**/
|
||||||
|
|
||||||
|
|||||||
@@ -20,15 +20,24 @@
|
|||||||
;;;;
|
;;;;
|
||||||
run;
|
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] incol The column to be validated
|
||||||
@param [in] rule The rule to apply. Current rules:
|
@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 ISNUM - checks if the variable is numeric
|
||||||
@li LIBDS - matches LIBREF.DATASET format
|
@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
|
@param [out] outcol The variable to create, with the results of the match
|
||||||
|
|
||||||
<h4> SAS Macros </h4>
|
<h4> SAS Macros </h4>
|
||||||
@li mf_getuniquename.sas
|
@li mf_getuniquename.sas
|
||||||
|
|
||||||
|
<h4> Related Macros </h4>
|
||||||
|
@li mp_validatecol.test.sas
|
||||||
|
|
||||||
@version 9.3
|
@version 9.3
|
||||||
**/
|
**/
|
||||||
|
|
||||||
@@ -38,7 +47,13 @@
|
|||||||
%local tempcol;
|
%local tempcol;
|
||||||
%let tempcol=%mf_getuniquename();
|
%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
|
credit SØREN LASSEN
|
||||||
https://sasmacro.blogspot.com/2009/06/welcome-isnum-macro.html
|
https://sasmacro.blogspot.com/2009/06/welcome-isnum-macro.html
|
||||||
@@ -62,5 +77,19 @@
|
|||||||
if prxmatch(&tempcol, trim(&incol)) then &outcol=1;
|
if prxmatch(&tempcol, trim(&incol)) then &outcol=1;
|
||||||
else &outcol=0;
|
else &outcol=0;
|
||||||
%end;
|
%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;
|
%mend mp_validatecol;
|
||||||
|
|||||||
@@ -94,13 +94,13 @@ data _null_;
|
|||||||
put '%put output location=&jref; ';
|
put '%put output location=&jref; ';
|
||||||
put '%if &action=OPEN %then %do; ';
|
put '%if &action=OPEN %then %do; ';
|
||||||
put ' options nobomfile; ';
|
put ' options nobomfile; ';
|
||||||
put ' data _null_;file &jref encoding=''utf-8''; ';
|
put ' data _null_;file &jref encoding=''utf-8'' ; ';
|
||||||
put ' put ''{"PROCESSED_DTTM" : "'' "%sysfunc(datetime(),E8601DT26.6)" ''"''; ';
|
put ' put ''{"PROCESSED_DTTM" : "'' "%sysfunc(datetime(),E8601DT26.6)" ''"''; ';
|
||||||
put ' run; ';
|
put ' run; ';
|
||||||
put '%end; ';
|
put '%end; ';
|
||||||
put '%else %if (&action=ARR or &action=OBJ) %then %do; ';
|
put '%else %if (&action=ARR or &action=OBJ) %then %do; ';
|
||||||
put ' options validvarname=upcase; ';
|
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 ", ""%lowcase(%sysfunc(coalescec(&dslabel,&ds)))"":"; ';
|
||||||
put ' ';
|
put ' ';
|
||||||
put ' %if &engine=PROCJSON %then %do; ';
|
put ' %if &engine=PROCJSON %then %do; ';
|
||||||
@@ -179,7 +179,7 @@ data _null_;
|
|||||||
put ' run; ';
|
put ' run; ';
|
||||||
put ' %let ds=&fmtds; ';
|
put ' %let ds=&fmtds; ';
|
||||||
put ' %end; /* &fmt=Y */ ';
|
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 ' put "["; call symputx(''cols'',0,''l''); ';
|
||||||
put ' proc sort ';
|
put ' proc sort ';
|
||||||
put ' data=sashelp.vcolumn(where=(libname=''WORK'' & memname="%upcase(&ds)")) ';
|
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 ' /* write to temp loc to avoid _webout truncation ';
|
||||||
put ' - https://support.sas.com/kb/49/325.html */ ';
|
put ' - https://support.sas.com/kb/49/325.html */ ';
|
||||||
put ' filename _sjs temp lrecl=131068 encoding=''utf-8''; ';
|
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 ' set &tempds; ';
|
||||||
put ' if _n_>1 then put "," @; put ';
|
put ' if _n_>1 then put "," @; put ';
|
||||||
put ' %if &action=ARR %then "[" ; %else "{" ; ';
|
put ' %if &action=ARR %then "[" ; %else "{" ; ';
|
||||||
@@ -251,14 +251,14 @@ data _null_;
|
|||||||
put ' rc = fclose(fileid); ';
|
put ' rc = fclose(fileid); ';
|
||||||
put ' run; ';
|
put ' run; ';
|
||||||
put ' filename _sjs clear; ';
|
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 ' put "]"; ';
|
||||||
put ' run; ';
|
put ' run; ';
|
||||||
put ' %end; ';
|
put ' %end; ';
|
||||||
put '%end; ';
|
put '%end; ';
|
||||||
put ' ';
|
put ' ';
|
||||||
put '%else %if &action=CLOSE %then %do; ';
|
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 ' put "}"; ';
|
||||||
put ' run; ';
|
put ' run; ';
|
||||||
put '%end; ';
|
put '%end; ';
|
||||||
|
|||||||
@@ -79,7 +79,7 @@
|
|||||||
OPTIONS NOBOMFILE;
|
OPTIONS NOBOMFILE;
|
||||||
|
|
||||||
/* setup json */
|
/* 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;
|
%if %str(&_debug) ge 131 %then %do;
|
||||||
put '>>weboutBEGIN<<';
|
put '>>weboutBEGIN<<';
|
||||||
%end;
|
%end;
|
||||||
@@ -108,14 +108,14 @@
|
|||||||
i+1;
|
i+1;
|
||||||
call symputx('wt'!!left(i),name,'l');
|
call symputx('wt'!!left(i),name,'l');
|
||||||
call symputx('wtcnt',i,'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"":{";
|
put ",""WORK"":{";
|
||||||
%do i=1 %to &wtcnt;
|
%do i=1 %to &wtcnt;
|
||||||
%let wt=&&wt&i;
|
%let wt=&&wt&i;
|
||||||
proc contents noprint data=&wt
|
proc contents noprint data=&wt
|
||||||
out=_data_ (keep=name type length format:);
|
out=_data_ (keep=name type length format:);
|
||||||
run;%let tempds=%scan(&syslast,2,.);
|
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');
|
dsid=open("WORK.&wt",'is');
|
||||||
nlobs=attrn(dsid,'NLOBS');
|
nlobs=attrn(dsid,'NLOBS');
|
||||||
nvars=attrn(dsid,'NVARS');
|
nvars=attrn(dsid,'NVARS');
|
||||||
@@ -126,15 +126,15 @@
|
|||||||
put ',"nvars":' nvars;
|
put ',"nvars":' nvars;
|
||||||
%mp_jsonout(OBJ,&tempds,jref=&fref,dslabel=colattrs,engine=DATASTEP)
|
%mp_jsonout(OBJ,&tempds,jref=&fref,dslabel=colattrs,engine=DATASTEP)
|
||||||
%mp_jsonout(OBJ,&wt,jref=&fref,dslabel=first10rows,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 "}";
|
put "}";
|
||||||
%end;
|
%end;
|
||||||
data _null_; file &fref mod encoding='utf-8';
|
data _null_; file &fref mod encoding='utf-8' termstr=lf termstr=lf;
|
||||||
put "}";
|
put "}";
|
||||||
run;
|
run;
|
||||||
%end;
|
%end;
|
||||||
/* close off json */
|
/* 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'))));
|
_PROGRAM=quote(trim(resolve(symget('_PROGRAM'))));
|
||||||
put ",""SYSUSERID"" : ""&sysuserid"" ";
|
put ",""SYSUSERID"" : ""&sysuserid"" ";
|
||||||
put ",""MF_GETUSER"" : ""%mf_getuser()"" ";
|
put ",""MF_GETUSER"" : ""%mf_getuser()"" ";
|
||||||
@@ -147,7 +147,9 @@
|
|||||||
put ",""SYSHOSTNAME"" : ""&syshostname"" ";
|
put ",""SYSHOSTNAME"" : ""&syshostname"" ";
|
||||||
put ",""SYSPROCESSID"" : ""&SYSPROCESSID"" ";
|
put ",""SYSPROCESSID"" : ""&SYSPROCESSID"" ";
|
||||||
put ",""SYSPROCESSMODE"" : ""&SYSPROCESSMODE"" ";
|
put ",""SYSPROCESSMODE"" : ""&SYSPROCESSMODE"" ";
|
||||||
put ",""SYSPROCESSNAME"" : ""&SYSPROCESSNAME"" ";
|
length SYSPROCESSNAME $512;
|
||||||
|
SYSPROCESSNAME=quote(urlencode(cats(SYSPROCESSNAME)));
|
||||||
|
put ",""SYSPROCESSNAME"" : " SYSPROCESSNAME;
|
||||||
put ",""SYSJOBID"" : ""&sysjobid"" ";
|
put ",""SYSJOBID"" : ""&sysjobid"" ";
|
||||||
put ",""SYSSCPL"" : ""&sysscpl"" ";
|
put ",""SYSSCPL"" : ""&sysscpl"" ";
|
||||||
put ",""SYSSITE"" : ""&syssite"" ";
|
put ",""SYSSITE"" : ""&syssite"" ";
|
||||||
@@ -156,7 +158,8 @@
|
|||||||
put ',"SYSVLONG" : ' sysvlong;
|
put ',"SYSVLONG" : ' sysvlong;
|
||||||
put ",""SYSWARNINGTEXT"" : ""&syswarningtext"" ";
|
put ",""SYSWARNINGTEXT"" : ""&syswarningtext"" ";
|
||||||
put ',"END_DTTM" : "' "%sysfunc(datetime(),datetime20.3)" '" ';
|
put ',"END_DTTM" : "' "%sysfunc(datetime(),datetime20.3)" '" ';
|
||||||
autoexec=quote(trim(getoption('autoexec')));
|
length autoexec $512;
|
||||||
|
autoexec=quote(urlencode(trim(getoption('autoexec'))));
|
||||||
put ',"AUTOEXEC" : ' autoexec;
|
put ',"AUTOEXEC" : ' autoexec;
|
||||||
memsize="%sysfunc(INPUTN(%sysfunc(getoption(memsize)), best.),sizekmg.)";
|
memsize="%sysfunc(INPUTN(%sysfunc(getoption(memsize)), best.),sizekmg.)";
|
||||||
memsize=quote(cats(memsize));
|
memsize=quote(cats(memsize));
|
||||||
|
|||||||
22
tests/crossplatform/mf_existvar.test.sas
Normal file
22
tests/crossplatform/mf_existvar.test.sas
Normal 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
|
||||||
|
)
|
||||||
46
tests/crossplatform/mf_islibds.test.sas
Normal file
46
tests/crossplatform/mf_islibds.test.sas
Normal 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
|
||||||
|
)
|
||||||
20
tests/crossplatform/mf_wordsinstr1andstr2.test.sas
Normal file
20
tests/crossplatform/mf_wordsinstr1andstr2.test.sas
Normal 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
|
||||||
|
)
|
||||||
20
tests/crossplatform/mf_wordsinstr1butnotstr2.test.sas
Normal file
20
tests/crossplatform/mf_wordsinstr1butnotstr2.test.sas
Normal 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
|
||||||
|
)
|
||||||
74
tests/crossplatform/mf_writefile.test.sas
Normal file
74
tests/crossplatform/mf_writefile.test.sas
Normal 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
|
||||||
|
)
|
||||||
45
tests/crossplatform/mp_applyformats.test.sas
Normal file
45
tests/crossplatform/mp_applyformats.test.sas
Normal 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
|
||||||
|
)
|
||||||
31
tests/crossplatform/mp_coretable.test.sas
Normal file
31
tests/crossplatform/mp_coretable.test.sas
Normal 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
|
||||||
|
)
|
||||||
85
tests/crossplatform/mp_filterstore.test.sas
Normal file
85
tests/crossplatform/mp_filterstore.test.sas
Normal 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
|
||||||
|
)
|
||||||
59
tests/crossplatform/mp_makedata.test.sas
Normal file
59
tests/crossplatform/mp_makedata.test.sas
Normal 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
|
||||||
|
)
|
||||||
116
tests/crossplatform/mp_retainedkey.test.sas
Normal file
116
tests/crossplatform/mp_retainedkey.test.sas
Normal 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
|
||||||
|
)
|
||||||
@@ -60,3 +60,70 @@ run;
|
|||||||
test=EQUALS 4,
|
test=EQUALS 4,
|
||||||
outds=work.test_results
|
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
|
||||||
|
)
|
||||||
@@ -242,13 +242,13 @@ data _null_;
|
|||||||
put '%put output location=&jref; ';
|
put '%put output location=&jref; ';
|
||||||
put '%if &action=OPEN %then %do; ';
|
put '%if &action=OPEN %then %do; ';
|
||||||
put ' options nobomfile; ';
|
put ' options nobomfile; ';
|
||||||
put ' data _null_;file &jref encoding=''utf-8''; ';
|
put ' data _null_;file &jref encoding=''utf-8'' ; ';
|
||||||
put ' put ''{"PROCESSED_DTTM" : "'' "%sysfunc(datetime(),E8601DT26.6)" ''"''; ';
|
put ' put ''{"PROCESSED_DTTM" : "'' "%sysfunc(datetime(),E8601DT26.6)" ''"''; ';
|
||||||
put ' run; ';
|
put ' run; ';
|
||||||
put '%end; ';
|
put '%end; ';
|
||||||
put '%else %if (&action=ARR or &action=OBJ) %then %do; ';
|
put '%else %if (&action=ARR or &action=OBJ) %then %do; ';
|
||||||
put ' options validvarname=upcase; ';
|
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 ", ""%lowcase(%sysfunc(coalescec(&dslabel,&ds)))"":"; ';
|
||||||
put ' ';
|
put ' ';
|
||||||
put ' %if &engine=PROCJSON %then %do; ';
|
put ' %if &engine=PROCJSON %then %do; ';
|
||||||
@@ -327,7 +327,7 @@ data _null_;
|
|||||||
put ' run; ';
|
put ' run; ';
|
||||||
put ' %let ds=&fmtds; ';
|
put ' %let ds=&fmtds; ';
|
||||||
put ' %end; /* &fmt=Y */ ';
|
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 ' put "["; call symputx(''cols'',0,''l''); ';
|
||||||
put ' proc sort ';
|
put ' proc sort ';
|
||||||
put ' data=sashelp.vcolumn(where=(libname=''WORK'' & memname="%upcase(&ds)")) ';
|
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 ' /* write to temp loc to avoid _webout truncation ';
|
||||||
put ' - https://support.sas.com/kb/49/325.html */ ';
|
put ' - https://support.sas.com/kb/49/325.html */ ';
|
||||||
put ' filename _sjs temp lrecl=131068 encoding=''utf-8''; ';
|
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 ' set &tempds; ';
|
||||||
put ' if _n_>1 then put "," @; put ';
|
put ' if _n_>1 then put "," @; put ';
|
||||||
put ' %if &action=ARR %then "[" ; %else "{" ; ';
|
put ' %if &action=ARR %then "[" ; %else "{" ; ';
|
||||||
@@ -399,14 +399,14 @@ data _null_;
|
|||||||
put ' rc = fclose(fileid); ';
|
put ' rc = fclose(fileid); ';
|
||||||
put ' run; ';
|
put ' run; ';
|
||||||
put ' filename _sjs clear; ';
|
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 ' put "]"; ';
|
||||||
put ' run; ';
|
put ' run; ';
|
||||||
put ' %end; ';
|
put ' %end; ';
|
||||||
put '%end; ';
|
put '%end; ';
|
||||||
put ' ';
|
put ' ';
|
||||||
put '%else %if &action=CLOSE %then %do; ';
|
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 ' put "}"; ';
|
||||||
put ' run; ';
|
put ' run; ';
|
||||||
put '%end; ';
|
put '%end; ';
|
||||||
|
|||||||
Reference in New Issue
Block a user