Loading...
Searching...
No Matches
mp_loadformat.sas
Go to the documentation of this file.
1/**
2 @file
3 @brief Loads a format catalog from a staging dataset
4 @details When loading staged data, it is common to receive only the records
5 that have actually changed. However, when loading a format catalog, if
6 records are missing they are presumed to be no longer required.
7
8 This macro will augment a staging dataset with other records from the same
9 format, to prevent loss of data - UNLESS the input dataset contains a marker
10 column, specifying that a particular row needs to be deleted (`delete_col=`).
11
12 Positions of formats are made using the FMTROW variable - this must be present
13 and unique (on TYPE / FMTNAME / FMTROW).
14
15 This macro can also be used to identify which records would be (or were)
16 considered new, modified or deleted (`loadtarget=`) by creating the following
17 tables:
18
19 @li work.outds_add
20 @li work.outds_del
21 @li work.outds_mod
22
23 For example usage, see test (under Related Macros)
24
25 @param [in] libcat The format catalog to be loaded
26 @param [in] libds The staging table to load
27 @param [in] loadtarget= (NO) Set to YES to actually load the target catalog
28 @param [in] delete_col= (_____DELETE__THIS__RECORD_____) The column used to
29 mark a record for deletion. Values should be "Yes" or "No".
30 @param [out] auditlibds= (0) For change tracking, set to the libds of an audit
31 table as defined in mddl_dc_difftable.sas
32 @param [in] locklibds= (0) For multi-user (parallel) situations, set to the
33 libds of the DC lock table as defined in the mddl_dc_locktable.sas macro.
34 @param [out] outds_add= (0) Set a libds here to see the new records added
35 @param [out] outds_del= (0) Set a libds here to see the records deleted
36 @param [out] outds_mod= (0) Set a libds here to see the modified records
37 @param [in] mdebug= (0) Set to 1 to enable DEBUG messages and preserve outputs
38
39 <h4> SAS Macros </h4>
40 @li mf_existds.sas
41 @li mf_existvar.sas
42 @li mf_getuniquename.sas
43 @li mf_nobs.sas
44 @li mp_abort.sas
45 @li mp_aligndecimal.sas
46 @li mp_cntlout.sas
47 @li mp_lockanytable.sas
48 @li mp_rowhash.sas
49 @li mp_storediffs.sas
50
51 <h4> Related Macros </h4>
52 @li mddl_dc_difftable.sas
53 @li mddl_dc_locktable.sas
54 @li mp_loadformat.test.1.sas
55 @li mp_loadformat.test.2.sas
56 @li mp_lockanytable.sas
57 @li mp_stackdiffs.sas
58
59
60 @version 9.2
61 @author Allan Bowe
62
63**/
64
65%macro mp_loadformat(libcat,libds
66 ,loadtarget=NO
67 ,auditlibds=0
68 ,locklibds=0
69 ,delete_col=_____DELETE__THIS__RECORD_____
70 ,outds_add=0
71 ,outds_del=0
72 ,outds_mod=0
73 ,mdebug=0
74);
75/* set up local macro variables and temporary tables (with a prefix) */
76%local err msg prefix dslist i var fmtlist ibufsize;
77%let dslist=base_fmts template inlibds ds1 stagedata storediffs del1 del2;
78%if &outds_add=0 %then %let dslist=&dslist outds_add;
79%if &outds_del=0 %then %let dslist=&dslist outds_del;
80%if &outds_mod=0 %then %let dslist=&dslist outds_mod;
81%let prefix=%substr(%mf_getuniquename(),1,21);
82%do i=1 %to %sysfunc(countw(&dslist));
83 %let var=%scan(&dslist,&i);
84 %local &var;
85 %let &var=%upcase(&prefix._&var);
86%end;
87
88/* in DC, format catalogs maybe specified in the libds with a -FC extension */
89%let libcat=%scan(&libcat,1,-);
90
91/* perform input validations */
92%mp_abort(
93 iftrue=(%mf_existds(&libds)=0)
94 ,mac=&sysmacroname
95 ,msg=%str(&libds could not be found)
96)
97%mp_abort(
98 iftrue=(%mf_existvar(&libds,FMTROW)=0)
99 ,mac=&sysmacroname
100 ,msg=%str(FMTROW not found in &libds)
101)
102%let err=0;
103%let msg=0;
104data _null_;
105 if _n_=1 then putlog "&sysmacroname entry vars:";
106 set sashelp.vmacro;
107 where scope="&sysmacroname";
108 value=upcase(value);
109 if &mdebug=0 then put name '=' value;
110 if name=:'LOAD' and value not in ('YES','NO') then do;
111 call symputx('msg',"invalid value for "!!name!!":"!!value);
112 call symputx('err',1);
113 stop;
114 end;
115 else if name='LIBCAT' then do;
116 if exist(value,'CATALOG') le 0 then do;
117 call symputx('msg',"Unable to open catalog: "!!value);
118 call symputx('err',1);
119 stop;
120 end;
121 end;
122 else if (name=:'OUTDS' or name in ('DELETE_COL','LOCKLIBDS','AUDITLIBDS'))
123 and missing(value) then do;
124 call symputx('msg',"missing value in var: "!!name);
125 call symputx('err',1);
126 stop;
127 end;
128run;
129data _null_;
130 set &libds;
131 if missing(fmtrow) then do;
132 call symputx('msg',"missing fmtrow in format: "!!FMTNAME);
133 call symputx('err',1);
134 stop;
135 end;
136run;
137
138%mp_abort(
139 iftrue=(&err ne 0)
140 ,mac=&sysmacroname
141 ,msg=%str(&msg)
142)
143
144%local cnt;
145proc sql noprint;
146select count(distinct catx('|',type,fmtname,fmtrow)) into: cnt from &libds;
147%mp_abort(
148 iftrue=(&cnt ne %mf_nobs(&libds))
149 ,mac=&sysmacroname
150 ,msg=%str(Non-unique primary key on &libds)
151)
152
153/**
154 * First, extract only relevant formats from the catalog
155 */
156proc sql noprint;
157select distinct
158 case
159 when type='N' then upcase(fmtname)
160 when type='C' then cats('$',upcase(fmtname))
161 when type='I' then cats('@',upcase(fmtname))
162 when type='J' then cats('@$',upcase(fmtname))
163 else "&sysmacroname:UNHANDLED"
164 end
165 into: fmtlist separated by ' '
166 from &libds;
167
168%mp_cntlout(libcat=&libcat,fmtlist=&fmtlist,cntlout=&base_fmts)
169
170/* get a hash of the row */
171%local cvars nvars;
172%let cvars=TYPE FMTNAME START END LABEL PREFIX FILL SEXCL EEXCL HLO DECSEP
173 DIG3SEP DATATYPE LANGUAGE;
174%let nvars=FMTROW MIN MAX DEFAULT LENGTH FUZZ MULT NOEDIT;
175data &base_fmts/note2err;
176 set &base_fmts;
177 length fmthash $32;
178 %mp_rowhash(
179 md5_col=fmthash
180 ,cvars=&cvars
181 ,nvars=&nvars
182 )
183run;
184
185/**
186 * Ensure input table and base_formats have consistent lengths and types
187 */
188data &inlibds/nonote2err;
189 length &delete_col $3 FMTROW 8 start end label $32767 fmthash $32;
190 if 0 then set &base_fmts;
191 set &libds;
192 by type fmtname notsorted;
193 if &delete_col='' then &delete_col='No';
194 fmtname=upcase(fmtname);
195 type=upcase(type);
196 if missing(type) then do;
197 if substr(fmtname,1,1)='@' then do;
198 if substr(fmtname,2,1)='$' then type='J';
199 else type='I';
200 end;
201 else do;
202 if substr(fmtname,1,1)='$' then type='C';
203 else type='N';
204 end;
205 end;
206 if type in ('N','I') then do;
207 %mp_aligndecimal(start,width=16)
208 %mp_aligndecimal(end,width=16)
209 end;
210
211 %mp_rowhash(
212 md5_col=fmthash
213 ,cvars=&cvars
214 ,nvars=&nvars
215 )
216run;
217
218/**
219 * Identify new records
220 */
221proc sql;
222create table &outds_add(drop=&delete_col) as
223 select a.*
224 from &inlibds a
225 left join &base_fmts b
226 on a.type=b.type and a.fmtname=b.fmtname and a.fmtrow=b.fmtrow
227 where b.fmtname is null
228 and upcase(a.&delete_col) ne "YES"
229 order by type, fmtname, fmtrow;
230
231/**
232 * Identify modified records
233 */
234create table &outds_mod (drop=&delete_col) as
235 select a.*
236 from &inlibds a
237 inner join &base_fmts b
238 on a.type=b.type and a.fmtname=b.fmtname and a.fmtrow=b.fmtrow
239 where upcase(a.&delete_col) ne "YES"
240 and a.fmthash ne b.fmthash
241 order by type, fmtname, fmtrow;
242
243/**
244 * Identify deleted records
245 */
246create table &outds_del(drop=&delete_col) as
247 select a.*
248 from &inlibds a
249 inner join &base_fmts b
250 on a.type=b.type and a.fmtname=b.fmtname and a.fmtrow=b.fmtrow
251 where upcase(a.&delete_col)="YES"
252 order by type, fmtname, fmtrow;
253
254/**
255 * Identify fully deleted formats (where every record is removed)
256 * These require to be explicitly deleted in proc format
257 * del1 - identify _partial_ deletes
258 * del2 - exclude these, and also formats that come with _additions_
259 */
260create table &del1 as
261 select a.*
262 from &base_fmts a
263 left join &outds_del b
264 on a.type=b.type and a.fmtname=b.fmtname and a.fmtrow=b.fmtrow
265 where b.fmtrow is null;
266
267create table &del2 as
268 select * from &outds_del
269 where cats(type,fmtname) not in (select cats(type,fmtname) from &outds_add)
270 and cats(type,fmtname) not in (select cats(type,fmtname) from &del1);
271
272
273%mp_abort(
274 iftrue=(&syscc ne 0)
275 ,mac=&sysmacroname
276 ,msg=%str(SYSCC=&syscc prior to load prep)
277)
278
279%if &loadtarget=YES %then %do;
280 /* new records plus base records that are not deleted or modified */
281 data &ds1;
282 merge &base_fmts(in=base)
283 &outds_mod(in=mod)
284 &outds_add(in=add)
285 &outds_del(in=del);
286 if not del and not mod;
287 by type fmtname fmtrow;
288 run;
289 /* add back the modified records */
290 data &stagedata;
291 set &ds1 &outds_mod;
292 run;
293 proc sort;
294 by type fmtname fmtrow;
295 run;
296%end;
297/* mp abort needs to run outside of conditional blocks */
298%mp_abort(
299 iftrue=(&syscc ne 0)
300 ,mac=&sysmacroname
301 ,msg=%str(SYSCC=&syscc prior to actual load)
302)
303%if &loadtarget=YES %then %do;
304 %if %mf_nobs(&stagedata)=0 and %mf_nobs(&del2)=0 %then %do;
305 %put There are no changes to load in &libcat!;
306 %return;
307 %end;
308 %if &locklibds ne 0 %then %do;
309 /* prevent parallel updates */
310 %mp_lockanytable(LOCK
311 ,lib=%scan(&libcat,1,.)
312 ,ds=%scan(&libcat,2,.)-FC
313 ,ref=MP_LOADFORMAT commencing format load
314 ,ctl_ds=&locklibds
315 )
316 %end;
317 /* do the actual load */
318 proc format lib=&libcat cntlin=&stagedata;
319 run;
320 /* apply any full deletes */
321 %if %mf_nobs(&del2)>0 %then %do;
322 %local delfmtlist;
323 proc sql noprint;
324 select distinct case when type='N' then cats(fmtname,'.FORMAT')
325 when type='C' then cats(fmtname,'.FORMATC')
326 when type='J' then cats(fmtname,'.INFMTC')
327 when type='I' then cats(fmtname,'.INFMT')
328 else cats(fmtname,'.BADENTRY!!!') end
329 into: delfmtlist
330 separated by ' '
331 from &del2;
332 proc catalog catalog=&libcat;
333 delete &delfmtlist;
334 quit;
335 %end;
336 %if &locklibds ne 0 %then %do;
337 /* unlock the table */
338 %mp_lockanytable(UNLOCK
339 ,lib=%scan(&libcat,1,.)
340 ,ds=%scan(&libcat,2,.)-FC
341 ,ref=MP_LOADFORMAT completed format load
342 ,ctl_ds=&locklibds
343 )
344 %end;
345 /* track the changes */
346 %if &auditlibds ne 0 %then %do;
347 %if &locklibds ne 0 %then %do;
348 %mp_lockanytable(LOCK
349 ,lib=%scan(&auditlibds,1,.)
350 ,ds=%scan(&auditlibds,2,.)
351 ,ref=MP_LOADFORMAT commencing audit table load
352 ,ctl_ds=&locklibds
353 )
354 %end;
355
356 %mp_storediffs(&libcat-FC
357 ,&base_fmts
358 ,TYPE FMTNAME FMTROW
359 ,delds=&outds_del
360 ,modds=&outds_mod
361 ,appds=&outds_add
362 ,outds=&storediffs
363 ,mdebug=&mdebug
364 )
365
366 proc append base=&auditlibds data=&storediffs;
367 run;
368
369 %if &locklibds ne 0 %then %do;
370 %mp_lockanytable(UNLOCK
371 ,lib=%scan(&auditlibds,1,.)
372 ,ds=%scan(&auditlibds,2,.)
373 ,ref=MP_LOADFORMAT commencing audit table load
374 ,ctl_ds=&locklibds
375 )
376 %end;
377 %end;
378%end;
379%mp_abort(
380 iftrue=(&syscc ne 0)
381 ,mac=&sysmacroname
382 ,msg=%str(SYSCC=&syscc after load)
383)
384
385%if &mdebug=0 %then %do;
386 proc datasets lib=work;
387 delete &prefix:;
388 run;
389 %put &sysmacroname exit vars:;
390 %put _local_;
391%end;
392%mend mp_loadformat;