@sasjs/utils 2.48.2 → 2.49.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/fs/generateCompileProgram.d.ts +1 -0
- package/fs/generateCompileProgram.js +154 -0
- package/fs/generateCompileProgram.js.map +1 -0
- package/fs/hash.d.ts +22 -0
- package/fs/hash.js +225 -0
- package/fs/hash.js.map +1 -0
- package/fs/index.d.ts +3 -0
- package/fs/index.js +13 -0
- package/fs/index.js.map +1 -0
- package/fs/internal/helper.d.ts +4 -0
- package/fs/internal/helper.js +125 -0
- package/fs/internal/helper.js.map +1 -0
- package/fs/sync.d.ts +6 -0
- package/fs/sync.js +208 -0
- package/fs/sync.js.map +1 -0
- package/index.d.ts +1 -0
- package/index.js +1 -0
- package/index.js.map +1 -1
- package/macros/mf_abort.sas +27 -0
- package/macros/mf_dedup.sas +53 -0
- package/macros/mf_deletefile.sas +31 -0
- package/macros/mf_existds.sas +30 -0
- package/macros/mf_existfeature.sas +59 -0
- package/macros/mf_existfileref.sas +33 -0
- package/macros/mf_existfunction.sas +37 -0
- package/macros/mf_existvar.sas +39 -0
- package/macros/mf_existvarlist.sas +56 -0
- package/macros/mf_fmtdttm.sas +42 -0
- package/macros/mf_getapploc.sas +78 -0
- package/macros/mf_getattrc.sas +34 -0
- package/macros/mf_getattrn.sas +34 -0
- package/macros/mf_getengine.sas +54 -0
- package/macros/mf_getfilesize.sas +64 -0
- package/macros/mf_getfmtlist.sas +61 -0
- package/macros/mf_getfmtname.sas +44 -0
- package/macros/mf_getkeyvalue.sas +32 -0
- package/macros/mf_getplatform.sas +75 -0
- package/macros/mf_getquotedstr.sas +57 -0
- package/macros/mf_getschema.sas +43 -0
- package/macros/mf_getuniquefileref.sas +60 -0
- package/macros/mf_getuniquelibref.sas +57 -0
- package/macros/mf_getuniquename.sas +22 -0
- package/macros/mf_getuser.sas +44 -0
- package/macros/mf_getvalue.sas +36 -0
- package/macros/mf_getvarcount.sas +49 -0
- package/macros/mf_getvarformat.sas +72 -0
- package/macros/mf_getvarlen.sas +56 -0
- package/macros/mf_getvarlist.sas +74 -0
- package/macros/mf_getvarnum.sas +58 -0
- package/macros/mf_getvartype.sas +52 -0
- package/macros/mf_getxengine.sas +43 -0
- package/macros/mf_increment.sas +29 -0
- package/macros/mf_isblank.sas +27 -0
- package/macros/mf_isdir.sas +34 -0
- package/macros/mf_isint.sas +36 -0
- package/macros/mf_islibds.sas +40 -0
- package/macros/mf_loc.sas +29 -0
- package/macros/mf_mkdir.sas +67 -0
- package/macros/mf_mval.sas +19 -0
- package/macros/mf_nobs.sas +26 -0
- package/macros/mf_trimstr.sas +50 -0
- package/macros/mf_uid.sas +21 -0
- package/macros/mf_verifymacvars.sas +72 -0
- package/macros/mf_wordsinstr1andstr2.sas +53 -0
- package/macros/mf_wordsinstr1butnotstr2.sas +54 -0
- package/macros/mf_writefile.sas +67 -0
- package/macros/mp_abort.sas +301 -0
- package/macros/mp_appendfile.sas +57 -0
- package/macros/mp_applyformats.sas +181 -0
- package/macros/mp_assert.sas +56 -0
- package/macros/mp_assertcols.sas +145 -0
- package/macros/mp_assertcolvals.sas +168 -0
- package/macros/mp_assertdsobs.sas +121 -0
- package/macros/mp_assertscope.sas +147 -0
- package/macros/mp_base64copy.sas +117 -0
- package/macros/mp_binarycopy.sas +79 -0
- package/macros/mp_chop.sas +194 -0
- package/macros/mp_cleancsv.sas +78 -0
- package/macros/mp_cntlout.sas +85 -0
- package/macros/mp_copyfolder.sas +84 -0
- package/macros/mp_coretable.sas +73 -0
- package/macros/mp_createconstraints.sas +70 -0
- package/macros/mp_createwebservice.sas +32 -0
- package/macros/mp_csv2ds.sas +140 -0
- package/macros/mp_deleteconstraints.sas +52 -0
- package/macros/mp_deletefolder.sas +70 -0
- package/macros/mp_dictionary.sas +52 -0
- package/macros/mp_dirlist.sas +263 -0
- package/macros/mp_distinctfmtvalues.sas +50 -0
- package/macros/mp_dropmembers.sas +46 -0
- package/macros/mp_ds2cards.sas +291 -0
- package/macros/mp_ds2csv.sas +212 -0
- package/macros/mp_ds2ddl.sas +30 -0
- package/macros/mp_ds2fmtds.sas +105 -0
- package/macros/mp_ds2inserts.sas +180 -0
- package/macros/mp_ds2md.sas +103 -0
- package/macros/mp_ds2squeeze.sas +120 -0
- package/macros/mp_dsmeta.sas +110 -0
- package/macros/mp_filtercheck.sas +251 -0
- package/macros/mp_filtergenerate.sas +105 -0
- package/macros/mp_filterstore.sas +254 -0
- package/macros/mp_filtervalidate.sas +105 -0
- package/macros/mp_getcols.sas +67 -0
- package/macros/mp_getconstraints.sas +115 -0
- package/macros/mp_getdbml.sas +335 -0
- package/macros/mp_getddl.sas +400 -0
- package/macros/mp_getformats.sas +124 -0
- package/macros/mp_getmaxvarlengths.sas +130 -0
- package/macros/mp_getpk.sas +281 -0
- package/macros/mp_gsubfile.sas +58 -0
- package/macros/mp_guesspk.sas +332 -0
- package/macros/mp_hashdataset.sas +91 -0
- package/macros/mp_hashdirectory.sas +162 -0
- package/macros/mp_include.sas +106 -0
- package/macros/mp_init.sas +76 -0
- package/macros/mp_jsonout.sas +406 -0
- package/macros/mp_lib2cards.sas +78 -0
- package/macros/mp_lib2inserts.sas +77 -0
- package/macros/mp_loadformat.sas +303 -0
- package/macros/mp_lockanytable.sas +243 -0
- package/macros/mp_lockfilecheck.sas +103 -0
- package/macros/mp_makedata.sas +107 -0
- package/macros/mp_md5.sas +58 -0
- package/macros/mp_perflog.sas +43 -0
- package/macros/mp_prevobs.sas +88 -0
- package/macros/mp_recursivejoin.sas +90 -0
- package/macros/mp_replace.sas +151 -0
- package/macros/mp_reseterror.sas +27 -0
- package/macros/mp_resetoption.sas +39 -0
- package/macros/mp_retainedkey.sas +247 -0
- package/macros/mp_runddl.sas +49 -0
- package/macros/mp_searchcols.sas +103 -0
- package/macros/mp_searchdata.sas +131 -0
- package/macros/mp_setkeyvalue.sas +55 -0
- package/macros/mp_sortinplace.sas +122 -0
- package/macros/mp_stackdiffs.sas +592 -0
- package/macros/mp_storediffs.sas +230 -0
- package/macros/mp_stprequests.sas +74 -0
- package/macros/mp_streamfile.sas +212 -0
- package/macros/mp_testjob.sas +92 -0
- package/macros/mp_testservice.sas +39 -0
- package/macros/mp_testwritespeedlibrary.sas +59 -0
- package/macros/mp_tree.sas +71 -0
- package/macros/mp_unzip.sas +90 -0
- package/macros/mp_updatevarlength.sas +93 -0
- package/macros/mp_validatecol.sas +97 -0
- package/macros/mp_wait4file.sas +35 -0
- package/macros/mp_webin.sas +59 -0
- package/macros/mp_zip.sas +85 -0
- package/package.json +5 -2
- package/scripts/copySASjsCore.ts +36 -0
- package/types/hash.d.ts +9 -0
- package/types/hash.js +3 -0
- package/types/hash.js.map +1 -0
- package/types/index.d.ts +1 -0
- package/types/index.js +1 -0
- package/types/index.js.map +1 -1
- package/utils/index.d.ts +1 -1
- package/utils/index.js +2 -1
- package/utils/index.js.map +1 -1
- package/utils/utils.d.ts +1 -0
- package/utils/utils.js +7 -1
- package/utils/utils.js.map +1 -1
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
/**
|
|
2
|
+
@file
|
|
3
|
+
@brief Checks a generated filter query for validity
|
|
4
|
+
@details Runs a generated filter in proc sql with the validate option.
|
|
5
|
+
Used in mp_filtercheck.sas in an fcmp container.
|
|
6
|
+
|
|
7
|
+
Built to support dynamic filtering in
|
|
8
|
+
[Data Controller for SAS®](https://datacontroller.io).
|
|
9
|
+
|
|
10
|
+
Usage:
|
|
11
|
+
|
|
12
|
+
data work.filtertable;
|
|
13
|
+
infile datalines4 dsd;
|
|
14
|
+
input GROUP_LOGIC:$3. SUBGROUP_LOGIC:$3. SUBGROUP_ID:8. VARIABLE_NM:$32.
|
|
15
|
+
OPERATOR_NM:$10. RAW_VALUE:$4000.;
|
|
16
|
+
datalines4;
|
|
17
|
+
AND,AND,1,AGE,=,12
|
|
18
|
+
AND,AND,1,SEX,<=,"'M'"
|
|
19
|
+
AND,OR,2,Name,NOT IN,"('Jane','Alfred')"
|
|
20
|
+
AND,OR,2,Weight,>=,7
|
|
21
|
+
;;;;
|
|
22
|
+
run;
|
|
23
|
+
|
|
24
|
+
%mp_filtergenerate(work.filtertable,outref=myfilter)
|
|
25
|
+
|
|
26
|
+
%mp_filtervalidate(myfilter,sashelp.class)
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
@returns The SYSCC value will be 1008 if there are validation issues.
|
|
30
|
+
|
|
31
|
+
@param [in] inref The input fileref to validate (generated by
|
|
32
|
+
mp_filtergenerate.sas)
|
|
33
|
+
@param [in] targetds The target dataset against which to verify the query
|
|
34
|
+
@param [out] abort= (YES) If YES will call mp_abort.sas on any exceptions
|
|
35
|
+
@param [out] outds= (work.mp_filtervalidate) Output dataset containing the
|
|
36
|
+
err / warning message, if one exists. If this table contains any rows,
|
|
37
|
+
there are problems!
|
|
38
|
+
|
|
39
|
+
<h4> SAS Macros </h4>
|
|
40
|
+
@li mf_getuniquefileref.sas
|
|
41
|
+
@li mf_nobs.sas
|
|
42
|
+
@li mp_abort.sas
|
|
43
|
+
|
|
44
|
+
<h4> Related Macros </h4>
|
|
45
|
+
@li mp_filtercheck.sas
|
|
46
|
+
@li mp_filtergenerate.sas
|
|
47
|
+
|
|
48
|
+
@version 9.3
|
|
49
|
+
@author Allan Bowe
|
|
50
|
+
|
|
51
|
+
**/
|
|
52
|
+
|
|
53
|
+
%macro mp_filtervalidate(inref,targetds,abort=YES,outds=work.mp_filtervalidate);
|
|
54
|
+
|
|
55
|
+
%mp_abort(iftrue= (&syscc ne 0 or &syserr ne 0)
|
|
56
|
+
,mac=&sysmacroname
|
|
57
|
+
,msg=%str(syscc=&syscc / syserr=&syserr - on macro entry)
|
|
58
|
+
)
|
|
59
|
+
|
|
60
|
+
%local fref1;
|
|
61
|
+
%let fref1=%mf_getuniquefileref();
|
|
62
|
+
|
|
63
|
+
data _null_;
|
|
64
|
+
file &fref1;
|
|
65
|
+
infile &inref end=eof;
|
|
66
|
+
if _n_=1 then do;
|
|
67
|
+
put "proc sql;";
|
|
68
|
+
put "validate select * from &targetds";
|
|
69
|
+
put "where " ;
|
|
70
|
+
end;
|
|
71
|
+
input;
|
|
72
|
+
put _infile_;
|
|
73
|
+
putlog _infile_;
|
|
74
|
+
if eof then put ";quit;";
|
|
75
|
+
run;
|
|
76
|
+
|
|
77
|
+
%inc &fref1;
|
|
78
|
+
|
|
79
|
+
data &outds;
|
|
80
|
+
if &sqlrc or &syscc or &syserr then do;
|
|
81
|
+
REASON_CD='VALIDATION_ERR'!!'OR: '!!
|
|
82
|
+
coalescec(symget('SYSERRORTEXT'),symget('SYSWARNINGTEXT'));
|
|
83
|
+
output;
|
|
84
|
+
end;
|
|
85
|
+
else stop;
|
|
86
|
+
run;
|
|
87
|
+
|
|
88
|
+
filename &fref1 clear;
|
|
89
|
+
|
|
90
|
+
%if %mf_nobs(&outds)>0 %then %do;
|
|
91
|
+
%if &abort=YES %then %do;
|
|
92
|
+
data _null_;
|
|
93
|
+
set &outds;
|
|
94
|
+
call symputx('REASON_CD',reason_cd,'l');
|
|
95
|
+
stop;
|
|
96
|
+
run;
|
|
97
|
+
%mp_abort(
|
|
98
|
+
mac=&sysmacroname,
|
|
99
|
+
msg=%str(Filter validation issues.)
|
|
100
|
+
)
|
|
101
|
+
%end;
|
|
102
|
+
%let syscc=1008;
|
|
103
|
+
%end;
|
|
104
|
+
|
|
105
|
+
%mend mp_filtervalidate;
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
/**
|
|
2
|
+
@file
|
|
3
|
+
@brief Creates a dataset with column metadata.
|
|
4
|
+
@details This macro takes the `proc contents` output and "tidies it up" in the
|
|
5
|
+
following ways:
|
|
6
|
+
|
|
7
|
+
@li Blank labels are filled in with column names
|
|
8
|
+
@li Formats are reconstructed with default values
|
|
9
|
+
@li Types such as DATE / TIME / DATETIME are inferred from the formats
|
|
10
|
+
|
|
11
|
+
Example usage:
|
|
12
|
+
|
|
13
|
+
%mp_getcols(sashelp.airline,outds=work.myds)
|
|
14
|
+
|
|
15
|
+
@param ds The dataset from which to obtain column metadata
|
|
16
|
+
@param outds= (work.cols) The output dataset to create. Sample data:
|
|
17
|
+
|NAME:$32.|LENGTH:best.|VARNUM:best.|LABEL:$256.|FMTNAME:$32.|FORMAT:$49.|TYPE:$1.|DDTYPE:$9.|
|
|
18
|
+
|---|---|---|---|---|---|---|---|
|
|
19
|
+
|`AIR `|`8 `|`2 `|`international airline travel (thousands) `|` `|`8. `|`N `|`NUMERIC `|
|
|
20
|
+
|`DATE `|`8 `|`1 `|`DATE `|`MONYY `|`MONYY. `|`N `|`DATE `|
|
|
21
|
+
|`REGION `|`3 `|`3 `|`REGION `|` `|`$3. `|`C `|`CHARACTER `|
|
|
22
|
+
|
|
23
|
+
<h4> Related Macros </h4>
|
|
24
|
+
@li mf_getvarlist.sas
|
|
25
|
+
@li mm_getcols.sas
|
|
26
|
+
|
|
27
|
+
@version 9.2
|
|
28
|
+
@author Allan Bowe
|
|
29
|
+
|
|
30
|
+
**/
|
|
31
|
+
|
|
32
|
+
%macro mp_getcols(ds, outds=work.cols);
|
|
33
|
+
%local dropds;
|
|
34
|
+
proc contents noprint data=&ds
|
|
35
|
+
out=_data_ (keep=name type length label varnum format:);
|
|
36
|
+
run;
|
|
37
|
+
%let dropds=&syslast;
|
|
38
|
+
data &outds(keep=name type length varnum format label ddtype fmtname);
|
|
39
|
+
set &dropds(rename=(format=fmtname type=type2));
|
|
40
|
+
name=upcase(name);
|
|
41
|
+
if type2=2 then do;
|
|
42
|
+
length format $49.;
|
|
43
|
+
if fmtname='' then format=cats('$',length,'.');
|
|
44
|
+
else if formatl=0 then format=cats(fmtname,'.');
|
|
45
|
+
else format=cats(fmtname,formatl,'.');
|
|
46
|
+
type='C';
|
|
47
|
+
ddtype='CHARACTER';
|
|
48
|
+
end;
|
|
49
|
+
else do;
|
|
50
|
+
if fmtname='' then format=cats(length,'.');
|
|
51
|
+
else if formatl=0 then format=cats(fmtname,'.');
|
|
52
|
+
else if formatd=0 then format=cats(fmtname,formatl,'.');
|
|
53
|
+
else format=cats(fmtname,formatl,'.',formatd);
|
|
54
|
+
type='N';
|
|
55
|
+
if format=:'DATETIME' or format=:'E8601DT' then ddtype='DATETIME';
|
|
56
|
+
else if format=:'DATE' or format=:'DDMMYY' or format=:'MMDDYY'
|
|
57
|
+
or format=:'YYMMDD' or format=:'E8601DA' or format=:'B8601DA'
|
|
58
|
+
or format=:'MONYY'
|
|
59
|
+
then ddtype='DATE';
|
|
60
|
+
else if format=:'TIME' then ddtype='TIME';
|
|
61
|
+
else ddtype='NUMERIC';
|
|
62
|
+
end;
|
|
63
|
+
if label='' then label=name;
|
|
64
|
+
run;
|
|
65
|
+
proc sql;
|
|
66
|
+
drop table &dropds;
|
|
67
|
+
%mend mp_getcols;
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
/**
|
|
2
|
+
@file mp_getconstraints.sas
|
|
3
|
+
@brief Get constraint details at column level
|
|
4
|
+
@details Useful for capturing constraints before they are dropped / reapplied
|
|
5
|
+
during an update.
|
|
6
|
+
|
|
7
|
+
proc sql;
|
|
8
|
+
create table work.example(
|
|
9
|
+
TX_FROM float format=datetime19.,
|
|
10
|
+
DD_TYPE char(16),
|
|
11
|
+
DD_SOURCE char(2048),
|
|
12
|
+
DD_SHORTDESC char(256),
|
|
13
|
+
constraint pk primary key(tx_from, dd_type,dd_source),
|
|
14
|
+
constraint unq unique(tx_from, dd_type),
|
|
15
|
+
constraint nnn not null(DD_SHORTDESC)
|
|
16
|
+
);
|
|
17
|
+
|
|
18
|
+
%mp_getconstraints(lib=work,ds=example,outds=work.constraints)
|
|
19
|
+
|
|
20
|
+
@param [in] lib= (WORK) The target library
|
|
21
|
+
@param [in] ds= The target dataset. Leave blank (default) for all datasets.
|
|
22
|
+
@param [in] mdebug= (0) Set to 1 to preserve temp tables, print var values etc
|
|
23
|
+
@param [out] outds= (mp_getconstraints) the output dataset
|
|
24
|
+
|
|
25
|
+
<h4> SAS Macros </h4>
|
|
26
|
+
@li mf_getuniquename.sas
|
|
27
|
+
@li mp_dropmembers.sas
|
|
28
|
+
|
|
29
|
+
@version 9.2
|
|
30
|
+
@author Allan Bowe
|
|
31
|
+
|
|
32
|
+
**/
|
|
33
|
+
|
|
34
|
+
%macro mp_getconstraints(lib=WORK
|
|
35
|
+
,ds=
|
|
36
|
+
,outds=mp_getconstraints
|
|
37
|
+
,mdebug=0
|
|
38
|
+
)/*/STORE SOURCE*/;
|
|
39
|
+
|
|
40
|
+
%let lib=%upcase(&lib);
|
|
41
|
+
%let ds=%upcase(&ds);
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Cater for environments where sashelp.vcncolu is not available
|
|
45
|
+
*/
|
|
46
|
+
%if %sysfunc(exist(sashelp.vcncolu,view))=0 %then %do;
|
|
47
|
+
proc sql;
|
|
48
|
+
create table &outds(
|
|
49
|
+
libref char(8)
|
|
50
|
+
,TABLE_NAME char(32)
|
|
51
|
+
,constraint_type char(8) label='Constraint Type'
|
|
52
|
+
,constraint_name char(32) label='Constraint Name'
|
|
53
|
+
,column_name char(32) label='Column'
|
|
54
|
+
,constraint_order num
|
|
55
|
+
);
|
|
56
|
+
%return;
|
|
57
|
+
%end;
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Neither dictionary tables nor sashelp provides a constraint order column,
|
|
61
|
+
* however they DO arrive in the correct order. So, create the col.
|
|
62
|
+
**/
|
|
63
|
+
%local vw;
|
|
64
|
+
%let vw=%mf_getuniquename(prefix=mp_getconstraints_vw_);
|
|
65
|
+
data &vw /view=&vw;
|
|
66
|
+
set sashelp.vcncolu;
|
|
67
|
+
where table_catalog="&lib";
|
|
68
|
+
|
|
69
|
+
/* use retain approach to reset the constraint order with each constraint */
|
|
70
|
+
length tmp $1000;
|
|
71
|
+
retain tmp;
|
|
72
|
+
drop tmp;
|
|
73
|
+
if tmp ne catx('|',table_catalog,table_name,constraint_name) then do;
|
|
74
|
+
constraint_order=1;
|
|
75
|
+
end;
|
|
76
|
+
else constraint_order+1;
|
|
77
|
+
tmp=catx('|',table_catalog, table_name,constraint_name);
|
|
78
|
+
run;
|
|
79
|
+
|
|
80
|
+
/* must use SQL as proc datasets does not support length changes */
|
|
81
|
+
proc sql noprint;
|
|
82
|
+
create table &outds as
|
|
83
|
+
select upcase(a.TABLE_CATALOG) as libref
|
|
84
|
+
,upcase(a.TABLE_NAME) as TABLE_NAME
|
|
85
|
+
,a.constraint_type
|
|
86
|
+
,a.constraint_name
|
|
87
|
+
,b.column_name
|
|
88
|
+
,b.constraint_order
|
|
89
|
+
from dictionary.TABLE_CONSTRAINTS a
|
|
90
|
+
left join &vw b
|
|
91
|
+
on upcase(a.TABLE_CATALOG)=upcase(b.TABLE_CATALOG)
|
|
92
|
+
and upcase(a.TABLE_NAME)=upcase(b.TABLE_NAME)
|
|
93
|
+
and a.constraint_name=b.constraint_name
|
|
94
|
+
/**
|
|
95
|
+
* We cannot apply this clause to the underlying dictionary table. See:
|
|
96
|
+
* https://communities.sas.com/t5/SAS-Programming/Unexpected-Where-Clause-behaviour-in-dictionary-TABLE/m-p/771554#M244867
|
|
97
|
+
* cannot use`where calculated libref="&lib"` either as it will STILL execute
|
|
98
|
+
* all the underlying constraint queries, causing exception errors in some
|
|
99
|
+
* cases: https://github.com/sasjs/core/issues/283
|
|
100
|
+
*/
|
|
101
|
+
where a.TABLE_CATALOG="&lib"
|
|
102
|
+
%if "&ds" ne "" %then %do;
|
|
103
|
+
and upcase(a.TABLE_NAME)="&ds"
|
|
104
|
+
and upcase(b.TABLE_NAME)="&ds"
|
|
105
|
+
%end;
|
|
106
|
+
order by libref, table_name, constraint_name, constraint_order
|
|
107
|
+
;
|
|
108
|
+
|
|
109
|
+
/* tidy up */
|
|
110
|
+
%mp_dropmembers(
|
|
111
|
+
&vw,
|
|
112
|
+
iftrue=(&mdebug=0)
|
|
113
|
+
)
|
|
114
|
+
|
|
115
|
+
%mend mp_getconstraints;
|
|
@@ -0,0 +1,335 @@
|
|
|
1
|
+
/**
|
|
2
|
+
@file
|
|
3
|
+
@brief Extract DBML from SAS Libraries
|
|
4
|
+
@details DBML is an open source markup format to represent databases.
|
|
5
|
+
More details: https://www.dbml.org/home/
|
|
6
|
+
|
|
7
|
+
Usage:
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
%mp_getdbml(liblist=SASHELP WORK,outref=mydbml,showlog=YES)
|
|
11
|
+
|
|
12
|
+
Take the log output and paste it into the renderer at https://dbdiagram.io
|
|
13
|
+
to view your data model diagram. The code takes a "best guess" at
|
|
14
|
+
the one to one and one to many relationships (based on constraints
|
|
15
|
+
and indexes, and assuming that the column names would match).
|
|
16
|
+
|
|
17
|
+
You may need to adjust the rendered DBML to suit your needs.
|
|
18
|
+
|
|
19
|
+

|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
<h4> SAS Macros </h4>
|
|
23
|
+
@li mf_getquotedstr.sas
|
|
24
|
+
@li mp_getconstraints.sas
|
|
25
|
+
|
|
26
|
+
@param liblist= Space seperated list of librefs to take as
|
|
27
|
+
input (Default=SASHELP)
|
|
28
|
+
@param outref= Fileref to contain the DBML (Default=getdbml)
|
|
29
|
+
@param showlog= set to YES to show the DBML in the log (Default is NO)
|
|
30
|
+
|
|
31
|
+
@version 9.3
|
|
32
|
+
@author Allan Bowe
|
|
33
|
+
**/
|
|
34
|
+
|
|
35
|
+
%macro mp_getdbml(liblist=SASHELP,outref=getdbml,showlog=NO
|
|
36
|
+
)/*/STORE SOURCE*/;
|
|
37
|
+
|
|
38
|
+
/* check fileref is assigned */
|
|
39
|
+
%if %sysfunc(fileref(&outref)) > 0 %then %do;
|
|
40
|
+
filename &outref temp;
|
|
41
|
+
%end;
|
|
42
|
+
|
|
43
|
+
%let liblist=%upcase(&liblist);
|
|
44
|
+
|
|
45
|
+
proc sql noprint;
|
|
46
|
+
create table _data_ as
|
|
47
|
+
select * from dictionary.tables
|
|
48
|
+
where upcase(libname) in (%mf_getquotedstr(&liblist))
|
|
49
|
+
order by libname,memname;
|
|
50
|
+
%local tabinfo; %let tabinfo=&syslast;
|
|
51
|
+
|
|
52
|
+
create table _data_ as
|
|
53
|
+
select * from dictionary.columns
|
|
54
|
+
where upcase(libname) in (%mf_getquotedstr(&liblist))
|
|
55
|
+
order by libname,memname,varnum;
|
|
56
|
+
%local colinfo; %let colinfo=&syslast;
|
|
57
|
+
|
|
58
|
+
%local dsnlist;
|
|
59
|
+
select distinct upcase(cats(libname,'.',memname)) into: dsnlist
|
|
60
|
+
separated by ' '
|
|
61
|
+
from &syslast
|
|
62
|
+
;
|
|
63
|
+
|
|
64
|
+
create table _data_ as
|
|
65
|
+
select * from dictionary.indexes
|
|
66
|
+
where upcase(libname) in (%mf_getquotedstr(&liblist))
|
|
67
|
+
order by idxusage, indxname, indxpos;
|
|
68
|
+
%local idxinfo; %let idxinfo=&syslast;
|
|
69
|
+
|
|
70
|
+
/* Extract all Primary Key and Unique data constraints */
|
|
71
|
+
%mp_getconstraints(lib=%scan(&liblist,1),outds=_data_)
|
|
72
|
+
%local colconst; %let colconst=&syslast;
|
|
73
|
+
|
|
74
|
+
%do x=2 %to %sysfunc(countw(&liblist));
|
|
75
|
+
%mp_getconstraints(lib=%scan(&liblist,&x),outds=_data_)
|
|
76
|
+
proc append base=&colconst data=&syslast;
|
|
77
|
+
run;
|
|
78
|
+
%end;
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
/* header info */
|
|
84
|
+
data _null_;
|
|
85
|
+
file &outref;
|
|
86
|
+
put "// DBML generated by &sysuserid on %sysfunc(datetime(),datetime19.) ";
|
|
87
|
+
put "Project sasdbml {";
|
|
88
|
+
put " database_type: 'SAS'";
|
|
89
|
+
put " Note: 'Generated by the mp_getdbml() macro'";
|
|
90
|
+
put "}";
|
|
91
|
+
run;
|
|
92
|
+
|
|
93
|
+
/* create table groups */
|
|
94
|
+
data _null_;
|
|
95
|
+
file &outref mod;
|
|
96
|
+
set &tabinfo;
|
|
97
|
+
by libname;
|
|
98
|
+
if first.libname then put "TableGroup " libname "{";
|
|
99
|
+
ds=quote(cats(libname,'.',memname));
|
|
100
|
+
put ' ' ds;
|
|
101
|
+
if last.libname then put "}";
|
|
102
|
+
run;
|
|
103
|
+
|
|
104
|
+
/* table for pks */
|
|
105
|
+
data _data_;
|
|
106
|
+
length curds const col $39;
|
|
107
|
+
call missing (of _all_);
|
|
108
|
+
stop;
|
|
109
|
+
run;
|
|
110
|
+
%let pkds=&syslast;
|
|
111
|
+
|
|
112
|
+
%local x curds constraints_used constcheck;
|
|
113
|
+
%do x=1 %to %sysfunc(countw(&dsnlist,%str( )));
|
|
114
|
+
%let curds=%scan(&dsnlist,&x,%str( ));
|
|
115
|
+
%let constraints_used=;
|
|
116
|
+
%let constcheck=0;
|
|
117
|
+
data _null_;
|
|
118
|
+
file &outref mod;
|
|
119
|
+
length lab $1024 typ $20;
|
|
120
|
+
set &colinfo (where=(
|
|
121
|
+
libname="%scan(&curds,1,.)" and upcase(memname)="%scan(&curds,2,.)"
|
|
122
|
+
)) end=last;
|
|
123
|
+
|
|
124
|
+
if _n_=1 then do;
|
|
125
|
+
table='Table "'!!"&curds"!!'"{';
|
|
126
|
+
put table;
|
|
127
|
+
end;
|
|
128
|
+
name=upcase(name);
|
|
129
|
+
lab=" note:"!!quote(trim(tranwrd(label,'"',"'")));
|
|
130
|
+
if upcase(format)=:'DATETIME' then typ='datetime';
|
|
131
|
+
else if type='char' then typ=cats('char(',length,')');
|
|
132
|
+
else typ='num';
|
|
133
|
+
|
|
134
|
+
if notnull='yes' then notnul=' not null';
|
|
135
|
+
if notnull='no' and missing(label) then put ' ' name typ;
|
|
136
|
+
else if notnull='yes' and missing(label) then do;
|
|
137
|
+
put ' ' name typ '[' notnul ']';
|
|
138
|
+
end;
|
|
139
|
+
else if notnull='no' then put ' ' name typ '[' lab ']';
|
|
140
|
+
else put ' ' name typ '[' notnul ',' lab ']';
|
|
141
|
+
|
|
142
|
+
run;
|
|
143
|
+
|
|
144
|
+
data _data_(keep=curds const col);
|
|
145
|
+
length ctype $11 cols constraints_used $5000;
|
|
146
|
+
set &colconst (where=(
|
|
147
|
+
upcase(libref)="%scan(&curds,1,.)"
|
|
148
|
+
and upcase(table_name)="%scan(&curds,2,.)"
|
|
149
|
+
and constraint_type in ('PRIMARY','UNIQUE')
|
|
150
|
+
)) end=last;
|
|
151
|
+
file &outref mod;
|
|
152
|
+
by constraint_type constraint_name;
|
|
153
|
+
retain cols;
|
|
154
|
+
column_name=upcase(column_name);
|
|
155
|
+
|
|
156
|
+
if _n_=1 then put / ' indexes {';
|
|
157
|
+
|
|
158
|
+
if upcase(strip(constraint_type)) = 'PRIMARY' then ctype='[pk]';
|
|
159
|
+
else ctype='[unique]';
|
|
160
|
+
|
|
161
|
+
if first.constraint_name then cols = cats('(',column_name);
|
|
162
|
+
else cols=cats(cols,',',column_name);
|
|
163
|
+
|
|
164
|
+
if last.constraint_name then do;
|
|
165
|
+
cols=cats(cols,')',ctype)!!' //'!!constraint_name;
|
|
166
|
+
put ' ' cols;
|
|
167
|
+
constraints_used=catx(' ',constraints_used, constraint_name);
|
|
168
|
+
call symputx('constcheck',1);
|
|
169
|
+
end;
|
|
170
|
+
|
|
171
|
+
if last then call symput('constraints_used',cats(upcase(constraints_used)));
|
|
172
|
+
|
|
173
|
+
length curds const col $39;
|
|
174
|
+
curds="&curds";
|
|
175
|
+
const=constraint_name;
|
|
176
|
+
col=column_name;
|
|
177
|
+
run;
|
|
178
|
+
|
|
179
|
+
proc append base=&pkds data=&syslast;run;
|
|
180
|
+
|
|
181
|
+
/* Create Unique Indexes, but only if they were not already defined within
|
|
182
|
+
the Constraints section. */
|
|
183
|
+
data _data_(keep=curds const col);
|
|
184
|
+
set &idxinfo (where=(
|
|
185
|
+
libname="%scan(&curds,1,.)"
|
|
186
|
+
and upcase(memname)="%scan(&curds,2,.)"
|
|
187
|
+
and unique='yes'
|
|
188
|
+
and upcase(indxname) not in (%mf_getquotedstr(&constraints_used))
|
|
189
|
+
));
|
|
190
|
+
file &outref mod;
|
|
191
|
+
by idxusage indxname;
|
|
192
|
+
name=upcase(name);
|
|
193
|
+
if &constcheck=1 then stop; /* we only care about PKs so stop if we have */
|
|
194
|
+
if _n_=1 and &constcheck=0 then put / ' indexes {';
|
|
195
|
+
|
|
196
|
+
length cols $5000;
|
|
197
|
+
retain cols;
|
|
198
|
+
if first.indxname then cols = cats('(',name);
|
|
199
|
+
else cols=cats(cols,',',name);
|
|
200
|
+
|
|
201
|
+
if last.indxname then do;
|
|
202
|
+
cols=cats(cols,')[unique]')!!' //'!!indxname;
|
|
203
|
+
put ' ' cols;
|
|
204
|
+
call symputx('constcheck',1);
|
|
205
|
+
end;
|
|
206
|
+
|
|
207
|
+
length curds const col $39;
|
|
208
|
+
curds="&curds";
|
|
209
|
+
const=indxname;
|
|
210
|
+
col=name;
|
|
211
|
+
run;
|
|
212
|
+
proc append base=&pkds data=&syslast;run;
|
|
213
|
+
|
|
214
|
+
data _null_;
|
|
215
|
+
file &outref mod;
|
|
216
|
+
if &constcheck =1 then put ' }';
|
|
217
|
+
put '}';
|
|
218
|
+
run;
|
|
219
|
+
|
|
220
|
+
%end;
|
|
221
|
+
|
|
222
|
+
/**
|
|
223
|
+
* now we need to figure out the relationships
|
|
224
|
+
*/
|
|
225
|
+
|
|
226
|
+
/* sort alphabetically so we can have one set of unique cols per table */
|
|
227
|
+
proc sort data=&pkds nodupkey;
|
|
228
|
+
by curds const col;
|
|
229
|
+
run;
|
|
230
|
+
|
|
231
|
+
data &pkds.1 (keep=curds col)
|
|
232
|
+
&pkds.2 (keep=curds cols);
|
|
233
|
+
set &pkds;
|
|
234
|
+
by curds const;
|
|
235
|
+
length retconst $39 cols $5000;
|
|
236
|
+
retain retconst cols;
|
|
237
|
+
if first.curds then do;
|
|
238
|
+
retconst=const;
|
|
239
|
+
cols=upcase(col);
|
|
240
|
+
end;
|
|
241
|
+
else cols=catx(' ',cols,upcase(col));
|
|
242
|
+
if retconst=const then do;
|
|
243
|
+
output &pkds.1;
|
|
244
|
+
if last.const then output &pkds.2;
|
|
245
|
+
end;
|
|
246
|
+
run;
|
|
247
|
+
|
|
248
|
+
%let curdslist="0";
|
|
249
|
+
%do x=1 %to %sysfunc(countw(&dsnlist,%str( )));
|
|
250
|
+
%let curds=%scan(&dsnlist,&x,%str( ));
|
|
251
|
+
|
|
252
|
+
%let pkcols=0;
|
|
253
|
+
data _null_;
|
|
254
|
+
set &pkds.2(where=(curds="&curds"));
|
|
255
|
+
call symputx('pkcols',cols);
|
|
256
|
+
run;
|
|
257
|
+
%if &pkcols ne 0 %then %do;
|
|
258
|
+
%let curdslist=&curdslist,"&curds";
|
|
259
|
+
|
|
260
|
+
/* start with one2one */
|
|
261
|
+
data &pkds.4;
|
|
262
|
+
file &outref mod;
|
|
263
|
+
set &pkds.2(where=(cols="&pkcols" and curds not in (&curdslist)));
|
|
264
|
+
line='Ref: "'!!"&curds"
|
|
265
|
+
!!cats('".(',"%mf_getquotedstr(&pkcols,dlm=%str(,),quote=%str( ))",')')
|
|
266
|
+
!!' - '
|
|
267
|
+
!!cats(quote(trim(curds))
|
|
268
|
+
,'.('
|
|
269
|
+
,"%mf_getquotedstr(&pkcols,dlm=%str(,),quote=%str( ))"
|
|
270
|
+
,')'
|
|
271
|
+
);
|
|
272
|
+
put line;
|
|
273
|
+
run;
|
|
274
|
+
|
|
275
|
+
/* now many2one */
|
|
276
|
+
/* get table with one row per col */
|
|
277
|
+
data &pkds.5;
|
|
278
|
+
set &pkds.1(where=(curds="&curds"));
|
|
279
|
+
run;
|
|
280
|
+
/* get tables which contain the PK columns */
|
|
281
|
+
proc sql;
|
|
282
|
+
create table &pkds.5a as
|
|
283
|
+
select upcase(cats(b.libname,'.',b.memname)) as curds
|
|
284
|
+
,b.name
|
|
285
|
+
from &pkds.5 a
|
|
286
|
+
inner join &colinfo b
|
|
287
|
+
on a.col=upcase(b.name);
|
|
288
|
+
/* count to make sure those tables contain ALL the columns */
|
|
289
|
+
create table &pkds.5b as
|
|
290
|
+
select curds,count(*) as cnt
|
|
291
|
+
from &pkds.5a
|
|
292
|
+
where curds not in (
|
|
293
|
+
select curds from &pkds.2 where cols="&pkcols"
|
|
294
|
+
) /* not a one to one match */
|
|
295
|
+
and curds ne "&curds" /* exclude self */
|
|
296
|
+
group by 1;
|
|
297
|
+
create table &pkds.6 as
|
|
298
|
+
select a.*
|
|
299
|
+
,b.cols
|
|
300
|
+
from &pkds.5b a
|
|
301
|
+
left join &pkds.4 b
|
|
302
|
+
on a.curds=b.curds;
|
|
303
|
+
|
|
304
|
+
data _null_;
|
|
305
|
+
set &pkds.6;
|
|
306
|
+
file &outref mod;
|
|
307
|
+
colcnt=%sysfunc(countw(&pkcols));
|
|
308
|
+
if cnt=colcnt then do;
|
|
309
|
+
/* table contains all the PK cols, and was not a direct / 121 match */
|
|
310
|
+
line='Ref: "'!!"&curds"
|
|
311
|
+
!!'".('
|
|
312
|
+
!!"%mf_getquotedstr(&pkcols,dlm=%str(,),quote=%str( ))"
|
|
313
|
+
!!') > '
|
|
314
|
+
!!cats(quote(trim(curds))
|
|
315
|
+
,'.('
|
|
316
|
+
,"%mf_getquotedstr(&pkcols,dlm=%str(,),quote=%str( ))"
|
|
317
|
+
,')'
|
|
318
|
+
);
|
|
319
|
+
put line;
|
|
320
|
+
end;
|
|
321
|
+
run;
|
|
322
|
+
%end;
|
|
323
|
+
%end;
|
|
324
|
+
|
|
325
|
+
|
|
326
|
+
%if %upcase(&showlog)=YES %then %do;
|
|
327
|
+
options ps=max;
|
|
328
|
+
data _null_;
|
|
329
|
+
infile &outref;
|
|
330
|
+
input;
|
|
331
|
+
putlog _infile_;
|
|
332
|
+
run;
|
|
333
|
+
%end;
|
|
334
|
+
|
|
335
|
+
%mend mp_getdbml;
|