@sasjs/utils 2.49.0 → 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/internal/helper.js +19 -24
- package/fs/internal/helper.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 +4 -1
- package/scripts/copySASjsCore.ts +36 -0
- package/utils/index.d.ts +1 -2
- package/utils/index.js +2 -3
- 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
- package/utils/getNodeModulePath.d.ts +0 -2
- package/utils/getNodeModulePath.js +0 -83
- package/utils/getNodeModulePath.js.map +0 -1
|
@@ -0,0 +1,247 @@
|
|
|
1
|
+
/**
|
|
2
|
+
@file
|
|
3
|
+
@brief Generate and apply retained key values to a staging table
|
|
4
|
+
@details This macro will populate a staging table with a Retained Key based on
|
|
5
|
+
a business key and a base (target) table.
|
|
6
|
+
|
|
7
|
+
Definition of retained key ([source](
|
|
8
|
+
http://bukhantsov.org/2012/04/what-is-data-vault/)):
|
|
9
|
+
|
|
10
|
+
> The retained key is a key which is mapped to business key one-to-one. In
|
|
11
|
+
> comparison, the surrogate key includes time and there can be many surrogate
|
|
12
|
+
> keys corresponding to one business key. This explains the name of the key,
|
|
13
|
+
> it is retained with insertion of a new version of a row while surrogate key
|
|
14
|
+
> is increasing.
|
|
15
|
+
|
|
16
|
+
This macro is designed to be used as part of a wider load / ETL process (such
|
|
17
|
+
as the one in [Data Controller for SAS](https://datacontroller.io)).
|
|
18
|
+
|
|
19
|
+
Specifically, the macro assumes that the base table has already been 'locked'
|
|
20
|
+
(eg with the mp_lockanytable.sas macro) prior to invocation. Also, several
|
|
21
|
+
tables are assumed to exist (names are configurable):
|
|
22
|
+
|
|
23
|
+
@li work.staging_table - the staged data, minus the retained key element
|
|
24
|
+
@li permlib.base_table - the target table to be loaded (**not** loaded by this
|
|
25
|
+
macro)
|
|
26
|
+
@li permlib.maxkeytable - optional, used to store load metaadata.
|
|
27
|
+
The definition is available by running mp_coretable.sas as follows:
|
|
28
|
+
`mp_coretable(MAXKEYTABLE)`.
|
|
29
|
+
@li permlib.locktable - Necessary if maxkeytable is being populated. The
|
|
30
|
+
definition is available by running mp_coretable.sas as follows:
|
|
31
|
+
`mp_coretable(LOCKTABLE)`.
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
@param [in] base_lib= (WORK) Libref of the base (target) table.
|
|
35
|
+
@param [in] base_dsn= (BASETABLE) Name of the base (target) table.
|
|
36
|
+
@param [in] append_lib= (WORK) Libref of the staging table
|
|
37
|
+
@param [in] append_dsn= (APPENDTABLE) Name of the staging table
|
|
38
|
+
@param [in] retained_key= (DEFAULT_RK) Name of RK to generate (should exist on
|
|
39
|
+
base table)
|
|
40
|
+
@param [in] business_key= (PK1 PK2) Business key against which to generate
|
|
41
|
+
RK values. Should be unique and not null on the staging table.
|
|
42
|
+
@param [in] check_uniqueness=(NO) Set to yes to perform a uniqueness check.
|
|
43
|
+
Recommended if there is a chance that the staging data is not unique on the
|
|
44
|
+
business key.
|
|
45
|
+
@param [in] maxkeytable= (0) Provide a maxkeytable libds reference here, to
|
|
46
|
+
store load metadata (maxkey val, load time). Set to zero if metadata is not
|
|
47
|
+
required, eg, when preparing a 'dummy' load. Structure is described above.
|
|
48
|
+
See below for sample data.
|
|
49
|
+
|KEYTABLE:$32.|KEYCOLUMN:$32.|MAX_KEY:best.|PROCESSED_DTTM:E8601DT26.6|
|
|
50
|
+
|---|---|---|---|
|
|
51
|
+
|`DC487173.MPE_SELECTBOX `|`SELECTBOX_RK `|`55 `|`1950427787.8 `|
|
|
52
|
+
|`DC487173.MPE_FILTERANYTABLE `|`filter_rk `|`14 `|`1951053886.8 `|
|
|
53
|
+
@param [in] locktable= (0) If updating the maxkeytable, provide the libds
|
|
54
|
+
reference to the lock table (per mp_lockanytable.sas macro)
|
|
55
|
+
@param [in] filter_str= Apply a filter - useful for SCD2 or BITEMPORAL loads.
|
|
56
|
+
Example: `filter_str=%str( (where=( &now < &tech_to)) )`
|
|
57
|
+
@param [out] outds= (WORK.APPEND) Output table (staging table + retained key)
|
|
58
|
+
|
|
59
|
+
<h4> SAS Macros </h4>
|
|
60
|
+
@li mf_existvar.sas
|
|
61
|
+
@li mf_fmtdttm.sas
|
|
62
|
+
@li mf_getquotedstr.sas
|
|
63
|
+
@li mf_getuniquename.sas
|
|
64
|
+
@li mf_nobs.sas
|
|
65
|
+
@li mp_abort.sas
|
|
66
|
+
@li mp_lockanytable.sas
|
|
67
|
+
|
|
68
|
+
<h4> Related Macros </h4>
|
|
69
|
+
@li mp_filterstore.sas
|
|
70
|
+
@li mp_retainedkey.test.sas
|
|
71
|
+
|
|
72
|
+
@version 9.2
|
|
73
|
+
|
|
74
|
+
**/
|
|
75
|
+
|
|
76
|
+
%macro mp_retainedkey(
|
|
77
|
+
base_lib=WORK
|
|
78
|
+
,base_dsn=BASETABLE
|
|
79
|
+
,append_lib=WORK
|
|
80
|
+
,append_dsn=APPENDTABLE
|
|
81
|
+
,retained_key=DEFAULT_RK
|
|
82
|
+
,business_key= PK1 PK2
|
|
83
|
+
,check_uniqueness=NO
|
|
84
|
+
,maxkeytable=0
|
|
85
|
+
,locktable=0
|
|
86
|
+
,outds=WORK.APPEND
|
|
87
|
+
,filter_str=
|
|
88
|
+
);
|
|
89
|
+
%put &sysmacroname entry vars:;
|
|
90
|
+
%put _local_;
|
|
91
|
+
|
|
92
|
+
%local base_libds app_libds key_field check maxkey idx_pk newkey_cnt iserr
|
|
93
|
+
msg x tempds1 tempds2 comma_pk appnobs checknobs dropvar tempvar idx_val;
|
|
94
|
+
%let base_libds=%upcase(&base_lib..&base_dsn);
|
|
95
|
+
%let app_libds=%upcase(&append_lib..&append_dsn);
|
|
96
|
+
%let tempds1=%mf_getuniquename();
|
|
97
|
+
%let tempds2=%mf_getuniquename();
|
|
98
|
+
%let comma_pk=%mf_getquotedstr(in_str=%str(&business_key),dlm=%str(,),quote=);
|
|
99
|
+
%let outds=%sysfunc(ifc(%index(&outds,.)=0,work.&outds,&outds));
|
|
100
|
+
/* validation checks */
|
|
101
|
+
%let iserr=0;
|
|
102
|
+
%if &syscc>0 %then %do;
|
|
103
|
+
%let iserr=1;
|
|
104
|
+
%let msg=%str(SYSCC=&syscc on macro entry);
|
|
105
|
+
%end;
|
|
106
|
+
%else %if %sysfunc(exist(&base_libds))=0 %then %do;
|
|
107
|
+
%let iserr=1;
|
|
108
|
+
%let msg=%str(Base LIBDS (&base_libds) expected but NOT FOUND);
|
|
109
|
+
%end;
|
|
110
|
+
%else %if %sysfunc(exist(&app_libds))=0 %then %do;
|
|
111
|
+
%let iserr=1;
|
|
112
|
+
%let msg=%str(Append LIBDS (&app_libds) expected but NOT FOUND);
|
|
113
|
+
%end;
|
|
114
|
+
%else %if &maxkeytable ne 0 and %sysfunc(exist(&maxkeytable))=0 %then %do;
|
|
115
|
+
%let iserr=1;
|
|
116
|
+
%let msg=%str(Maxkeytable (&maxkeytable) expected but NOT FOUND);
|
|
117
|
+
%end;
|
|
118
|
+
%else %if &maxkeytable ne 0 and %sysfunc(exist(&locktable))=0 %then %do;
|
|
119
|
+
%let iserr=1;
|
|
120
|
+
%let msg=%str(Locktable (&locktable) expected but NOT FOUND);
|
|
121
|
+
%end;
|
|
122
|
+
%else %if %length(&business_key)=0 %then %do;
|
|
123
|
+
%let iserr=1;
|
|
124
|
+
%let msg=%str(Business key (&business_key) expected but NOT FOUND);
|
|
125
|
+
%end;
|
|
126
|
+
|
|
127
|
+
%do x=1 %to %sysfunc(countw(&business_key));
|
|
128
|
+
/* check business key values exist */
|
|
129
|
+
%let key_field=%scan(&business_key,&x,%str( ));
|
|
130
|
+
%if not %mf_existvar(&app_libds,&key_field) %then %do;
|
|
131
|
+
%let iserr=1;
|
|
132
|
+
%let msg=Business key (&key_field) not found on &app_libds!;
|
|
133
|
+
%goto err;
|
|
134
|
+
%end;
|
|
135
|
+
%else %if not %mf_existvar(&base_libds,&key_field) %then %do;
|
|
136
|
+
%let iserr=1;
|
|
137
|
+
%let msg=Business key (&key_field) not found on &base_libds!;
|
|
138
|
+
%goto err;
|
|
139
|
+
%end;
|
|
140
|
+
%end;
|
|
141
|
+
%err:
|
|
142
|
+
%if &iserr=1 %then %do;
|
|
143
|
+
/* err case so first perform an unlock of the base table before exiting */
|
|
144
|
+
%mp_lockanytable(
|
|
145
|
+
UNLOCK,lib=&base_lib,ds=&base_dsn,ref=%superq(msg),ctl_ds=&locktable
|
|
146
|
+
)
|
|
147
|
+
%end;
|
|
148
|
+
%mp_abort(iftrue=(&iserr=1),mac=mp_retainedkey,msg=%superq(msg))
|
|
149
|
+
|
|
150
|
+
proc sql noprint;
|
|
151
|
+
select sum(max(&retained_key),0) into: maxkey from &base_libds;
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* get base table RK and bus field values for lookup
|
|
155
|
+
*/
|
|
156
|
+
proc sql noprint;
|
|
157
|
+
create table &tempds1 as
|
|
158
|
+
select distinct &comma_pk,&retained_key
|
|
159
|
+
from &base_libds &filter_str
|
|
160
|
+
order by &comma_pk,&retained_key;
|
|
161
|
+
|
|
162
|
+
%if &check_uniqueness=YES %then %do;
|
|
163
|
+
select count(*) into:checknobs
|
|
164
|
+
from (select distinct &comma_pk from &app_libds);
|
|
165
|
+
select count(*) into: appnobs from &app_libds; /* might be view */
|
|
166
|
+
%if &checknobs ne &appnobs %then %do;
|
|
167
|
+
%let msg=Source table &app_libds is not unique on (&business_key);
|
|
168
|
+
%let iserr=1;
|
|
169
|
+
%end;
|
|
170
|
+
%end;
|
|
171
|
+
%if &iserr=1 %then %do;
|
|
172
|
+
/* err case so first perform an unlock of the base table before exiting */
|
|
173
|
+
%mp_lockanytable(
|
|
174
|
+
UNLOCK,lib=&base_lib,ds=&base_dsn,ref=%superq(msg),ctl_ds=&locktable
|
|
175
|
+
)
|
|
176
|
+
%end;
|
|
177
|
+
%mp_abort(iftrue= (&iserr=1),mac=mp_retainedkey,msg=%superq(msg))
|
|
178
|
+
|
|
179
|
+
%if %mf_existvar(&app_libds,&retained_key)
|
|
180
|
+
%then %let dropvar=(drop=&retained_key);
|
|
181
|
+
|
|
182
|
+
/* prepare interim table with retained key populated for matching keys */
|
|
183
|
+
proc sql noprint;
|
|
184
|
+
create table &tempds2 as
|
|
185
|
+
select b.&retained_key, a.*
|
|
186
|
+
from &app_libds &dropvar a
|
|
187
|
+
left join &tempds1 b
|
|
188
|
+
on 1
|
|
189
|
+
%do idx_pk=1 %to %sysfunc(countw(&business_key));
|
|
190
|
+
%let idx_val=%scan(&business_key,&idx_pk);
|
|
191
|
+
and a.&idx_val=b.&idx_val
|
|
192
|
+
%end;
|
|
193
|
+
order by &retained_key;
|
|
194
|
+
|
|
195
|
+
/* identify the number of entries without retained keys (new records) */
|
|
196
|
+
select count(*) into: newkey_cnt
|
|
197
|
+
from &tempds2
|
|
198
|
+
where missing(&retained_key);
|
|
199
|
+
quit;
|
|
200
|
+
|
|
201
|
+
/**
|
|
202
|
+
* Update maxkey table if link provided
|
|
203
|
+
*/
|
|
204
|
+
%if &maxkeytable ne 0 %then %do;
|
|
205
|
+
proc sql noprint;
|
|
206
|
+
select count(*) into: check from &maxkeytable
|
|
207
|
+
where upcase(keytable)="&base_libds";
|
|
208
|
+
|
|
209
|
+
%mp_lockanytable(LOCK
|
|
210
|
+
,lib=%scan(&maxkeytable,1,.)
|
|
211
|
+
,ds=%scan(&maxkeytable,2,.)
|
|
212
|
+
,ref=Updating maxkeyvalues with mp_retainedkey
|
|
213
|
+
,ctl_ds=&locktable
|
|
214
|
+
)
|
|
215
|
+
proc sql;
|
|
216
|
+
%if &check=0 %then %do;
|
|
217
|
+
insert into &maxkeytable
|
|
218
|
+
set keytable="&base_libds"
|
|
219
|
+
,keycolumn="&retained_key"
|
|
220
|
+
,max_key=%eval(&maxkey+&newkey_cnt)
|
|
221
|
+
,processed_dttm="%sysfunc(datetime(),%mf_fmtdttm())"dt;
|
|
222
|
+
%end;
|
|
223
|
+
%else %do;
|
|
224
|
+
update &maxkeytable
|
|
225
|
+
set max_key=%eval(&maxkey+&newkey_cnt)
|
|
226
|
+
,processed_dttm="%sysfunc(datetime(),%mf_fmtdttm())"dt
|
|
227
|
+
where keytable="&base_libds";
|
|
228
|
+
%end;
|
|
229
|
+
%mp_lockanytable(UNLOCK
|
|
230
|
+
,lib=%scan(&maxkeytable,1,.)
|
|
231
|
+
,ds=%scan(&maxkeytable,2,.)
|
|
232
|
+
,ref=Updating maxkeyvalues with maxkey=%eval(&maxkey+&newkey_cnt)
|
|
233
|
+
,ctl_ds=&locktable
|
|
234
|
+
)
|
|
235
|
+
%end;
|
|
236
|
+
|
|
237
|
+
/* fill in the missing retained key values */
|
|
238
|
+
%let tempvar=%mf_getuniquename();
|
|
239
|
+
data &outds(drop=&tempvar);
|
|
240
|
+
retain &tempvar %eval(&maxkey+1);
|
|
241
|
+
set &tempds2;
|
|
242
|
+
if &retained_key =. then &retained_key=&tempvar;
|
|
243
|
+
&tempvar=&tempvar+1;
|
|
244
|
+
run;
|
|
245
|
+
|
|
246
|
+
%mend mp_retainedkey;
|
|
247
|
+
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
@file mp_runddl.sas
|
|
3
|
+
@brief An opinionated way to execute DDL files in SAS.
|
|
4
|
+
@details When delivering projects there should be seperation between the DDL
|
|
5
|
+
used to generate the tables and the sample data used to populate them.
|
|
6
|
+
|
|
7
|
+
This macro expects certain folder structure - eg:
|
|
8
|
+
|
|
9
|
+
rootlib
|
|
10
|
+
|-- LIBREF1
|
|
11
|
+
| |__ mytable.ddl
|
|
12
|
+
| |__ someothertable.ddl
|
|
13
|
+
|-- LIBREF2
|
|
14
|
+
| |__ table1.ddl
|
|
15
|
+
| |__ table2.ddl
|
|
16
|
+
|-- LIBREF3
|
|
17
|
+
|__ table3.ddl
|
|
18
|
+
|__ table4.ddl
|
|
19
|
+
|
|
20
|
+
Only files with the .ddl suffix are executed. The parent folder name is used
|
|
21
|
+
as the libref.
|
|
22
|
+
Files should NOT contain the `proc sql` statement - this is to prevent
|
|
23
|
+
statements being executed if there is an err condition.
|
|
24
|
+
|
|
25
|
+
Usage:
|
|
26
|
+
|
|
27
|
+
%mp_runddl(/some/rootlib) * execute all libs ;
|
|
28
|
+
|
|
29
|
+
%mp_runddl(/some/rootlib, inc=LIBREF1 LIBREF2) * include only these libs;
|
|
30
|
+
|
|
31
|
+
%mp_runddl(/some/rootlib, exc=LIBREF3) * same as above ;
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
@param path location of the DDL folder structure
|
|
35
|
+
@param inc= list of librefs to include
|
|
36
|
+
@param exc= list of librefs to exclude (takes precedence over inc=)
|
|
37
|
+
|
|
38
|
+
@version 9.3
|
|
39
|
+
@author Allan Bowe
|
|
40
|
+
@source https://github.com/sasjs/core
|
|
41
|
+
|
|
42
|
+
**/
|
|
43
|
+
|
|
44
|
+
%macro mp_runddl(path, inc=, exc=
|
|
45
|
+
)/*/STORE SOURCE*/;
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
%mend mp_runddl;
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
/**
|
|
2
|
+
@file mp_searchcols.sas
|
|
3
|
+
@brief Searches all columns in a library
|
|
4
|
+
@details
|
|
5
|
+
Scans a set of libraries and creates a dataset containing all source tables
|
|
6
|
+
containing one or more of a particular set of columns
|
|
7
|
+
|
|
8
|
+
Usage:
|
|
9
|
+
|
|
10
|
+
%mp_searchcols(libs=sashelp work, cols=name sex age)
|
|
11
|
+
|
|
12
|
+
@param libs=(SASHELP) Space separated list of libraries to search for columns
|
|
13
|
+
@param cols= Space separated list of column names to search for (not case
|
|
14
|
+
sensitive)
|
|
15
|
+
@param outds=(mp_searchcols) the table to create with the results. Will have
|
|
16
|
+
one line per table match.
|
|
17
|
+
@param match=(ANY) The match type. Valid values:
|
|
18
|
+
@li ANY - The table contains at least one of the columns
|
|
19
|
+
@li WILD - The table contains a column with a name that partially matches
|
|
20
|
+
|
|
21
|
+
@version 9.2
|
|
22
|
+
@author Allan Bowe
|
|
23
|
+
**/
|
|
24
|
+
|
|
25
|
+
%macro mp_searchcols(libs=sashelp
|
|
26
|
+
,cols=
|
|
27
|
+
,outds=mp_searchcols
|
|
28
|
+
,match=ANY
|
|
29
|
+
)/*/STORE SOURCE*/;
|
|
30
|
+
|
|
31
|
+
%put &sysmacroname process began at %sysfunc(datetime(),datetime19.);
|
|
32
|
+
|
|
33
|
+
/* get the list of tables in the library */
|
|
34
|
+
proc sql;
|
|
35
|
+
create table _data_ as
|
|
36
|
+
select distinct upcase(libname) as libname
|
|
37
|
+
, upcase(memname) as memname
|
|
38
|
+
, upcase(name) as name
|
|
39
|
+
from dictionary.columns
|
|
40
|
+
%if %sysevalf(%superq(libs)=,boolean)=0 %then %do;
|
|
41
|
+
where upcase(libname) in ("IMPOSSIBLE",
|
|
42
|
+
%local x;
|
|
43
|
+
%do x=1 %to %sysfunc(countw(&libs));
|
|
44
|
+
"%upcase(%scan(&libs,&x))"
|
|
45
|
+
%end;
|
|
46
|
+
)
|
|
47
|
+
%end;
|
|
48
|
+
order by 1,2,3;
|
|
49
|
+
|
|
50
|
+
%local tempds;
|
|
51
|
+
%let tempds=&syslast;
|
|
52
|
+
data &outds;
|
|
53
|
+
set &tempds;
|
|
54
|
+
length cols matchcols $32767;
|
|
55
|
+
cols=upcase(symget('cols'));
|
|
56
|
+
colcount=countw(cols);
|
|
57
|
+
by libname memname name;
|
|
58
|
+
if _n_=1 then do;
|
|
59
|
+
putlog "Searching libs: &libs";
|
|
60
|
+
putlog "Searching cols: " cols;
|
|
61
|
+
end;
|
|
62
|
+
if first.memname then do;
|
|
63
|
+
sumcols=0;
|
|
64
|
+
retain matchcols;
|
|
65
|
+
matchcols='';
|
|
66
|
+
end;
|
|
67
|
+
%if &match=ANY %then %do;
|
|
68
|
+
if findw(cols,name,,'spit') then do;
|
|
69
|
+
sumcols+1;
|
|
70
|
+
matchcols=cats(matchcols)!!' '!!cats(name);
|
|
71
|
+
end;
|
|
72
|
+
%end;
|
|
73
|
+
%else %if &match=WILD %then %do;
|
|
74
|
+
if _n_=1 then do;
|
|
75
|
+
retain wcount;
|
|
76
|
+
wcount=countw(cols);
|
|
77
|
+
drop wcount;
|
|
78
|
+
end;
|
|
79
|
+
do i=1 to wcount;
|
|
80
|
+
length curword $32;
|
|
81
|
+
curword=scan(cols,i,' ');
|
|
82
|
+
drop curword;
|
|
83
|
+
if index(name,cats(curword)) then do;
|
|
84
|
+
sumcols+1;
|
|
85
|
+
matchcols=cats(matchcols)!!' '!!cats(curword);
|
|
86
|
+
end;
|
|
87
|
+
end;
|
|
88
|
+
%end;
|
|
89
|
+
|
|
90
|
+
if last.memname then do;
|
|
91
|
+
if sumcols>0 then output;
|
|
92
|
+
if sumcols=colcount then putlog "Full Match: " libname memname;
|
|
93
|
+
end;
|
|
94
|
+
keep libname memname sumcols matchcols;
|
|
95
|
+
run;
|
|
96
|
+
|
|
97
|
+
proc sort; by descending sumcols memname libname; run;
|
|
98
|
+
|
|
99
|
+
proc sql;
|
|
100
|
+
drop table &tempds;
|
|
101
|
+
%put &sysmacroname process finished at %sysfunc(datetime(),datetime19.);
|
|
102
|
+
|
|
103
|
+
%mend mp_searchcols;
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
/**
|
|
2
|
+
@file
|
|
3
|
+
@brief Searches all data in a library
|
|
4
|
+
@details
|
|
5
|
+
Scans an entire library and creates a copy of any table
|
|
6
|
+
containing a specific string OR numeric value. Only
|
|
7
|
+
matching records are written out.
|
|
8
|
+
If both a string and numval are provided, the string
|
|
9
|
+
will take precedence.
|
|
10
|
+
|
|
11
|
+
Usage:
|
|
12
|
+
|
|
13
|
+
%mp_searchdata(lib=sashelp, string=Jan)
|
|
14
|
+
%mp_searchdata(lib=sashelp, ds=bird, numval=1)
|
|
15
|
+
%mp_searchdata(lib=sashelp, ds=class, string=l,outobs=5)
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
Outputs zero or more tables to an MPSEARCH library with specific records.
|
|
19
|
+
|
|
20
|
+
@param [in] lib= The libref to search (should be already assigned)
|
|
21
|
+
@param [in] ds= The dataset to search (leave blank to search entire library)
|
|
22
|
+
@param [in] string= String value to search (case sensitive, can be partial)
|
|
23
|
+
@param [in] numval= Numeric value to search (must be exact)
|
|
24
|
+
@param [out] outloc= (0) Optionally specify the directory in which to
|
|
25
|
+
create the the output datasets with matching rows. By default it will
|
|
26
|
+
write them to a temporary subdirectory within the WORK folder.
|
|
27
|
+
@param [out] outlib= (MPSEARCH) Assign a different libref to the output
|
|
28
|
+
library containing the matching datasets / records
|
|
29
|
+
@param [in] outobs= set to a positive integer to restrict the number of
|
|
30
|
+
observations
|
|
31
|
+
@param [in] filter_text= (1=1) Add a (valid) filter clause to further filter
|
|
32
|
+
the results.
|
|
33
|
+
|
|
34
|
+
<h4> SAS Macros </h4>
|
|
35
|
+
@li mf_getuniquename.sas
|
|
36
|
+
@li mf_getvarlist.sas
|
|
37
|
+
@li mf_getvartype.sas
|
|
38
|
+
@li mf_mkdir.sas
|
|
39
|
+
@li mf_nobs.sas
|
|
40
|
+
|
|
41
|
+
@version 9.2
|
|
42
|
+
@author Allan Bowe
|
|
43
|
+
**/
|
|
44
|
+
|
|
45
|
+
%macro mp_searchdata(lib=
|
|
46
|
+
,ds=
|
|
47
|
+
,string= /* the query will use a contains (?) operator */
|
|
48
|
+
,numval= /* numeric must match exactly */
|
|
49
|
+
,outloc=0
|
|
50
|
+
,outlib=MPSEARCH
|
|
51
|
+
,outobs=-1
|
|
52
|
+
,filter_text=%str(1=1)
|
|
53
|
+
)/*/STORE SOURCE*/;
|
|
54
|
+
|
|
55
|
+
%local table_list table table_num table colnum col start_tm check_tm vars type
|
|
56
|
+
coltype;
|
|
57
|
+
%put process began at %sysfunc(datetime(),datetime19.);
|
|
58
|
+
|
|
59
|
+
%if &syscc ge 4 %then %do;
|
|
60
|
+
%put %str(WAR)NING: SYSCC=&syscc on macro entry;
|
|
61
|
+
%return;
|
|
62
|
+
%end;
|
|
63
|
+
|
|
64
|
+
%if &string = %then %let type=N;
|
|
65
|
+
%else %let type=C;
|
|
66
|
+
|
|
67
|
+
%if "&outloc"="0" %then %do;
|
|
68
|
+
%let outloc=%sysfunc(pathname(work))/%mf_getuniquename();
|
|
69
|
+
%end;
|
|
70
|
+
|
|
71
|
+
%mf_mkdir(&outloc)
|
|
72
|
+
libname &outlib "&outloc";
|
|
73
|
+
|
|
74
|
+
/* get the list of tables in the library */
|
|
75
|
+
proc sql noprint;
|
|
76
|
+
select distinct memname into: table_list separated by ' '
|
|
77
|
+
from dictionary.tables
|
|
78
|
+
where upcase(libname)="%upcase(&lib)"
|
|
79
|
+
%if &ds ne %then %do;
|
|
80
|
+
and upcase(memname)=%upcase("&ds")
|
|
81
|
+
%end;
|
|
82
|
+
;
|
|
83
|
+
/* check that we have something to check */
|
|
84
|
+
%if %length(&table_list)=0 %then %put library &lib contains no tables!;
|
|
85
|
+
/* loop through each table */
|
|
86
|
+
%else %do table_num=1 %to %sysfunc(countw(&table_list,%str( )));
|
|
87
|
+
%let table=%scan(&table_list,&table_num,%str( ));
|
|
88
|
+
%let vars=%mf_getvarlist(&lib..&table);
|
|
89
|
+
%if %length(&vars)=0 %then %do;
|
|
90
|
+
%put NO COLUMNS IN &lib..&table! This will be skipped.;
|
|
91
|
+
%end;
|
|
92
|
+
%else %do;
|
|
93
|
+
%let check_tm=%sysfunc(datetime());
|
|
94
|
+
/* prep input */
|
|
95
|
+
data &outlib..&table;
|
|
96
|
+
set &lib..&table;
|
|
97
|
+
where %unquote(&filter_text) and ( 0
|
|
98
|
+
/* loop through columns */
|
|
99
|
+
%do colnum=1 %to %sysfunc(countw(&vars,%str( )));
|
|
100
|
+
%let col=%scan(&vars,&colnum,%str( ));
|
|
101
|
+
%let coltype=%mf_getvartype(&lib..&table,&col);
|
|
102
|
+
%if &type=C and &coltype=C %then %do;
|
|
103
|
+
/* if a char column, see if it contains the string */
|
|
104
|
+
or ("&col"n ? "&string")
|
|
105
|
+
%end;
|
|
106
|
+
%else %if &type=N and &coltype=N %then %do;
|
|
107
|
+
/* if numeric match exactly */
|
|
108
|
+
or ("&col"n = &numval)
|
|
109
|
+
%end;
|
|
110
|
+
%end;
|
|
111
|
+
);
|
|
112
|
+
%if &outobs>-1 %then %do;
|
|
113
|
+
if _n_ > &outobs then stop;
|
|
114
|
+
%end;
|
|
115
|
+
run;
|
|
116
|
+
%put Search query for &table took
|
|
117
|
+
%sysevalf(%sysfunc(datetime())-&check_tm) seconds;
|
|
118
|
+
%if &syscc ne 0 %then %do;
|
|
119
|
+
%put %str(ERR)ROR: SYSCC=&syscc when processing &lib..&table;
|
|
120
|
+
%return;
|
|
121
|
+
%end;
|
|
122
|
+
%if %mf_nobs(&outlib..&table)=0 %then %do;
|
|
123
|
+
proc sql;
|
|
124
|
+
drop table &outlib..&table;
|
|
125
|
+
%end;
|
|
126
|
+
%end;
|
|
127
|
+
%end;
|
|
128
|
+
|
|
129
|
+
%put process finished at %sysfunc(datetime(),datetime19.);
|
|
130
|
+
|
|
131
|
+
%mend mp_searchdata;
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/**
|
|
2
|
+
@file
|
|
3
|
+
@brief Logs a key value pair a control dataset
|
|
4
|
+
@details If the dataset does not exist, it is created. Usage:
|
|
5
|
+
|
|
6
|
+
%mp_setkeyvalue(someindex,22,type=N)
|
|
7
|
+
%mp_setkeyvalue(somenewindex,somevalue)
|
|
8
|
+
|
|
9
|
+
<h4> SAS Macros </h4>
|
|
10
|
+
@li mf_existds.sas
|
|
11
|
+
|
|
12
|
+
<h4> Related Macros </h4>
|
|
13
|
+
@li mf_getvalue.sas
|
|
14
|
+
|
|
15
|
+
@param [in] key Provide a key on which to perform the lookup
|
|
16
|
+
@param [in] value Provide a value
|
|
17
|
+
@param [in] type= either C or N will populate valc and valn respectively.
|
|
18
|
+
C is default.
|
|
19
|
+
@param [out] libds= define the target table to hold the parameters
|
|
20
|
+
|
|
21
|
+
@version 9.2
|
|
22
|
+
@author Allan Bowe
|
|
23
|
+
@source https://github.com/sasjs/core
|
|
24
|
+
|
|
25
|
+
**/
|
|
26
|
+
|
|
27
|
+
%macro mp_setkeyvalue(key,value,type=C,libds=work.mp_setkeyvalue
|
|
28
|
+
)/*/STORE SOURCE*/;
|
|
29
|
+
|
|
30
|
+
%if not (%mf_existds(&libds)) %then %do;
|
|
31
|
+
data &libds (index=(key/unique));
|
|
32
|
+
length key $64 valc $2048 valn 8 type $1;
|
|
33
|
+
call missing(of _all_);
|
|
34
|
+
stop;
|
|
35
|
+
run;
|
|
36
|
+
%end;
|
|
37
|
+
|
|
38
|
+
proc sql;
|
|
39
|
+
delete from &libds
|
|
40
|
+
where key=symget('key');
|
|
41
|
+
insert into &libds
|
|
42
|
+
set key=symget('key')
|
|
43
|
+
%if &type=C %then %do;
|
|
44
|
+
,valc=symget('value')
|
|
45
|
+
,type='C'
|
|
46
|
+
%end;
|
|
47
|
+
%else %do;
|
|
48
|
+
,valn=symgetn('value')
|
|
49
|
+
,type='N'
|
|
50
|
+
%end;
|
|
51
|
+
;
|
|
52
|
+
|
|
53
|
+
quit;
|
|
54
|
+
|
|
55
|
+
%mend mp_setkeyvalue;
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
/**
|
|
2
|
+
@file
|
|
3
|
+
@brief Sorts a SAS dataset in place, preserving constraints
|
|
4
|
+
@details Generally if a dataset contains indexes, then it is not necessary to
|
|
5
|
+
sort it before performing operations such as merges / joins etc.
|
|
6
|
+
That said, there are a few edge cases where it can be desirable:
|
|
7
|
+
|
|
8
|
+
@li To allow adjacent records to be viewed directly in the dataset
|
|
9
|
+
@li To apply compression, or to remove deleted records
|
|
10
|
+
@li To improve performance for specific queries
|
|
11
|
+
|
|
12
|
+
This macro will only work for BASE (V9) engine libraries. It works by
|
|
13
|
+
creating a copy of the dataset (without data, WITH constraints) in the same
|
|
14
|
+
library, appending a sorted view into it, and finally - renaming it.
|
|
15
|
+
|
|
16
|
+
Example usage:
|
|
17
|
+
|
|
18
|
+
proc sql;
|
|
19
|
+
create table work.example as
|
|
20
|
+
select * from sashelp.class;
|
|
21
|
+
alter table work.example
|
|
22
|
+
add constraint pk primary key(name);
|
|
23
|
+
%mp_sortinplace(work.example)
|
|
24
|
+
|
|
25
|
+
@param [in] libds The libref.datasetname that needs to be sorted
|
|
26
|
+
|
|
27
|
+
<h4> SAS Macros </h4>
|
|
28
|
+
@li mf_existds.sas
|
|
29
|
+
@li mf_getengine.sas
|
|
30
|
+
@li mf_getquotedstr.sas
|
|
31
|
+
@li mf_getuniquename.sas
|
|
32
|
+
@li mf_getvarlist.sas
|
|
33
|
+
@li mf_nobs.sas
|
|
34
|
+
@li mp_abort.sas
|
|
35
|
+
@li mp_getpk.sas
|
|
36
|
+
|
|
37
|
+
<h4> Related Macros </h4>
|
|
38
|
+
@li mp_sortinplace.test.sas
|
|
39
|
+
|
|
40
|
+
@version 9.2
|
|
41
|
+
@author Allan Bowe
|
|
42
|
+
|
|
43
|
+
**/
|
|
44
|
+
|
|
45
|
+
%macro mp_sortinplace(libds
|
|
46
|
+
)/*/STORE SOURCE*/;
|
|
47
|
+
|
|
48
|
+
%local lib ds tempds1 tempds2 tempvw sortkey;
|
|
49
|
+
|
|
50
|
+
/* perform validations */
|
|
51
|
+
%mp_abort(iftrue=(%sysfunc(countc(&libds,.)) ne 1)
|
|
52
|
+
,mac=mp_sortinplace
|
|
53
|
+
,msg=%str(LIBDS (&libds) should have LIBREF.DATASET format)
|
|
54
|
+
)
|
|
55
|
+
%mp_abort(iftrue=(%mf_existds(&libds)=0)
|
|
56
|
+
,mac=mp_sortinplace
|
|
57
|
+
,msg=%str(&libds does not exist)
|
|
58
|
+
)
|
|
59
|
+
|
|
60
|
+
%let lib=%scan(&libds,1,.);
|
|
61
|
+
%let ds=%scan(&libds,2,.);
|
|
62
|
+
%mp_abort(iftrue=(%mf_getengine(&lib) ne V9)
|
|
63
|
+
,mac=mp_sortinplace
|
|
64
|
+
,msg=%str(&lib is not a BASE engine library)
|
|
65
|
+
)
|
|
66
|
+
|
|
67
|
+
/* grab a copy of the constraints so we know what to sort by */
|
|
68
|
+
%let tempds1=%mf_getuniquename(prefix=&sysmacroname);
|
|
69
|
+
%mp_getpk(lib=&lib,ds=&ds,outds=work.&tempds1)
|
|
70
|
+
|
|
71
|
+
%if %mf_nobs(work.&tempds1)=0 %then %do;
|
|
72
|
+
%put &sysmacroname: No PK found in &lib..&ds;
|
|
73
|
+
%put Sorting will not take place;
|
|
74
|
+
%return;
|
|
75
|
+
%end;
|
|
76
|
+
|
|
77
|
+
/* fallback sortkey is all fields */
|
|
78
|
+
%let sortkey=%mf_getvarlist(&libds);
|
|
79
|
+
|
|
80
|
+
/* overlay actual sort key if it exists */
|
|
81
|
+
data _null_;
|
|
82
|
+
set work.&tempds1;
|
|
83
|
+
call symputx('sortkey',coalescec(pk_fields,symget('sortkey')));
|
|
84
|
+
run;
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
/* create empty copy, with ALL constraints, in the same library */
|
|
88
|
+
%let tempds2=%mf_getuniquename(prefix=&sysmacroname);
|
|
89
|
+
proc append base=&lib..&tempds2 data=&libds(obs=0);
|
|
90
|
+
run;
|
|
91
|
+
|
|
92
|
+
/* create sorted view */
|
|
93
|
+
%let tempvw=%mf_getuniquename(prefix=&sysmacroname);
|
|
94
|
+
proc sql;
|
|
95
|
+
create view work.&tempvw as select * from &lib..&ds
|
|
96
|
+
order by %mf_getquotedstr(&sortkey,quote=N);
|
|
97
|
+
|
|
98
|
+
/* append sorted data */
|
|
99
|
+
proc append base=&lib..&tempds2 data=work.&tempvw;
|
|
100
|
+
run;
|
|
101
|
+
|
|
102
|
+
/* do validations */
|
|
103
|
+
%mp_abort(iftrue=(&syscc ne 0)
|
|
104
|
+
,mac=mp_sortinplace
|
|
105
|
+
,msg=%str(syscc=&syscc prior to replace operation)
|
|
106
|
+
)
|
|
107
|
+
%mp_abort(iftrue=(%mf_nobs(&lib..&tempds2) ne %mf_nobs(&lib..&ds))
|
|
108
|
+
,mac=mp_sortinplace
|
|
109
|
+
,msg=%str(new dataset has a different number of logical obs to the old)
|
|
110
|
+
)
|
|
111
|
+
|
|
112
|
+
/* drop old dataset */
|
|
113
|
+
proc sql;
|
|
114
|
+
drop table &lib..&ds;
|
|
115
|
+
|
|
116
|
+
/* rename the new dataset */
|
|
117
|
+
proc datasets library=&lib;
|
|
118
|
+
change &tempds2=&ds;
|
|
119
|
+
run;
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
%mend mp_sortinplace;
|