Loading...
Searching...
No Matches
mp_stripdiffs.sas
Go to the documentation of this file.
1/**
2 @file
3 @brief Generates a stage dataset to revert diffs tracked in an audit table
4 @details A big benefit of tracking data changes in an audit table is that
5 those changes can be subsequently reverted if necessary!
6
7 This macro prepares a staging dataset containing those differences - eg for:
8
9 @li deleted rows - these are re-inserted
10 @li changed rows - differences are reverted
11 @li added rows - marked with `_____DELETE__THIS__RECORD_____="YES"`
12
13 These changes are NOT applied to the base table - a staging dataset is
14 simply prepared for an ETL process to action. In Data Controller, this
15 dataset is used directly as an input to the APPROVE process (so that the
16 reversion diffs can be reviewed prior to being applied).
17
18
19 @param [in] libds Base library.dataset (will not be modified). The library
20 must be assigned.
21 @param [in] loadref Unique identifier for the version to be reverted. This
22 change, plus ALL SUBSEQUENT CHANGES, will be reverted in the output table.
23 @param [in] difftable The dataset containing the diffs. Definition available
24 in mddl_dc_difftable.sas
25 @param [in] filtervar= (0) If provided, the contents of this macro variable
26 will be applied as an additional filter against &libds
27 @param [out] outds= (work.mp_stripdiffs) Output table containing the diffs.
28 Has the same format as the base datset, plus a
29 `_____DELETE__THIS__RECORD_____` variable.
30 @param [in] mdebug= set to 1 to enable DEBUG messages and preserve outputs
31
32 <h4> SAS Macros </h4>
33 @li mf_getuniquefileref.sas
34 @li mf_getuniquename.sas
35 @li mf_getvarlist.sas
36 @li mf_islibds.sas
37 @li mf_wordsinstr1butnotstr2.sas
38 @li mp_abort.sas
39 @li mp_ds2squeeze.sas
40
41 <h4> Related Macros </h4>
42 @li mddl_dc_difftable.sas
43 @li mp_stackdiffs.sas
44 @li mp_storediffs.sas
45 @li mp_stripdiffs.test.sas
46
47 @version 9.2
48 @author Allan Bowe
49**/
50/** @cond */
51
52%macro mp_stripdiffs(libds
53 ,loadref
54 ,difftable
55 ,filtervar=0
56 ,outds=work.mp_stripdiffs
57 ,mdebug=0
58)/*/STORE SOURCE*/;
59%local dbg;
60%if &mdebug=1 %then %do;
61 %put &sysmacroname entry vars:;
62 %put _local_;
63%end;
64%else %let dbg=*;
65
66%let libds=%upcase(&libds);
67
68/* safety checks */
69%mp_abort(iftrue= (&syscc ne 0)
70 ,mac=&sysmacroname
71 ,msg=%str(SYSCC=&syscc on entry. Clean session required!)
72)
73%let libds=%upcase(&libds);
74%mp_abort(iftrue= (%mf_islibds(&libds)=0)
75 ,mac=&sysmacroname
76 ,msg=%str(Invalid library.dataset reference - %superq(libds))
77)
78
79/* set up unique and temporary vars */
80%local ds1 ds2 ds3 ds4 ds5 fref1 filterstr;
81%let fref1=%mf_getuniquefileref();
82%if &filtervar ne 0 %then %let filterstr=%superq(&filtervar);
83%else %let filterstr=%str(1=1);
84
85/* get timestamp of the diff to be reverted */
86%local ts;
87proc sql noprint;
88select put(processed_dttm,datetime19.6) into: ts
89 from &difftable where load_ref="&loadref";
90%mp_abort(iftrue= (&sqlobs=0)
91 ,mac=&sysmacroname
92 ,msg=%str(Load ref %superq(loadref) not found!)
93)
94
95/* extract diffs for this base table from this timestamp onwards */
96%let ds1=%upcase(work.%mf_getuniquename(prefix=mpsd_diffs));
97create table &ds1 (drop=libref dsn) as
98 select * from &difftable
99 where upcase(cats(libref))="%scan(&libds,1,.)"
100 and upcase(cats(dsn))="%scan(&libds,2,.)"
101 and processed_dttm ge "&ts"dt
102 order by processed_dttm desc, key_hash, is_pk;
103
104/* extract key values only */
105%let ds2=%upcase(work.%mf_getuniquename(prefix=mpsd_pks));
106%local keyhash processed;
107%let keyhash=%upcase(%mf_getuniquename(prefix=mpsdvar_keyhash));
108%let processed=%upcase(%mf_getuniquename(prefix=mpsdvar_processed));
109create table &ds2 as
110 select key_hash as &keyhash,
111 tgtvar_nm,
112 tgtvar_type,
113 coalescec(oldval_char,newval_char) as charval,
114 coalesce(oldval_num, newval_num) as numval,
115 processed_dttm as &processed
116 from &ds1
117 where is_pk=1
118 order by &keyhash, &processed;
119
120/* grab pk values */
121%local pk;
122select distinct upcase(tgtvar_nm) into: pk separated by ' ' from &ds2;
123
124%let ds3=%upcase(work.%mf_getuniquename(prefix=mpsd_keychar));
125proc transpose data=&ds2(where=(tgtvar_type='C'))
126 out=&ds3(drop=_name_);
127 by &keyhash &processed;
128 id TGTVAR_NM;
129 var charval;
130run;
131
132%let ds4=%upcase(work.%mf_getuniquename(prefix=mpsd_keynum));
133proc transpose data=&ds2(where=(tgtvar_type='N'))
134 out=&ds4(drop=_name_);
135 by &keyhash &processed;
136 id TGTVAR_NM;
137 var numval;
138run;
139/* shorten the lengths */
140%mp_ds2squeeze(&ds3,outds=&ds3)
141%mp_ds2squeeze(&ds4,outds=&ds4)
142
143/* now merge to get all key values and de-dup */
144%let ds5=%upcase(work.%mf_getuniquename(prefix=mpsd_merged));
145data &ds5;
146 length &keyhash $32 &processed 8;
147 merge &ds3 &ds4;
148 by &keyhash &processed;
149 if not missing(&keyhash);
150run;
151proc sort data=&ds5 nodupkey;
152 by &pk;
153run;
154
155/* join to base table for preliminary stage DS */
156proc sql;
157create table &outds as select "No " as _____DELETE__THIS__RECORD_____
158 %do x=1 %to %sysfunc(countw(&pk,%str( )));
159 ,a.%scan(&pk,&x,%str( ))
160 %end;
161 %local notpkcols;
162 %let notpkcols=%upcase(%mf_getvarlist(&libds));
163 %let notpkcols=%mf_wordsinstr1butnotstr2(str1=&notpkcols,str2=&pk);
164 %do x=1 %to %sysfunc(countw(&notpkcols,%str( )));
165 ,b.%scan(&notpkcols,&x,%str( ))
166 %end;
167 from &ds5 a
168 left join &libds (where=(&filterstr)) b
169 on 1=1
170%do x=1 %to %sysfunc(countw(&pk,%str( )));
171 and a.%scan(&pk,&x,%str( ))=b.%scan(&pk,&x,%str( ))
172%end;
173;
174
175/* create SAS code to apply to stage_ds */
176data _null_;
177 set &ds1;
178 file &fref1 lrecl=33000;
179 length charval $32767;
180 if _n_=1 then put 'proc sql noprint;';
181 by descending processed_dttm key_hash is_pk;
182 if move_type='M' then do;
183 if first.key_hash then do;
184 put "update &outds set " @@;
185 end;
186 if IS_PK=0 then do;
187 put " " tgtvar_nm '=' @@;
188 cnt=count(oldval_char,'"');
189 charval=quote(trim(substr(oldval_char,1,32765-cnt)));
190 if tgtvar_type='C' then put charval @@;
191 else put oldval_num @@;
192 if not last.is_pk then put ',';
193 end;
194 else do;
195 if first.is_pk then put " where 1=1 " @@;
196 put " and " tgtvar_nm '=' @@;
197 cnt=count(oldval_char,'"');
198 charval=quote(trim(substr(oldval_char,1,32765-cnt)));
199 if tgtvar_type='C' then put charval @@;
200 else put oldval_num @@;
201 end;
202 end;
203 else if move_type='A' then do;
204 if first.key_hash then do;
205 put "update &outds set _____DELETE__THIS__RECORD_____='Yes' where 1=1 "@@;
206 end;
207 /* gating if - as only need PK now */
208 if is_pk=1;
209 put ' AND ' tgtvar_nm '=' @@;
210 cnt=count(newval_char,'"');
211 charval=quote(trim(substr(newval_char,1,32765-cnt)));
212 if tgtvar_type='C' then put charval @@;
213 else put newval_num @@;
214 end;
215 else if move_type='D' then do;
216 if first.key_hash then do;
217 put "update &outds set _____DELETE__THIS__RECORD_____='No' " @@;
218 end;
219 if IS_PK=0 then do;
220 put " ," tgtvar_nm '=' @@;
221 cnt=count(oldval_char,'"');
222 charval=quote(trim(substr(oldval_char,1,32765-cnt)));
223 if tgtvar_type='C' then put charval @@;
224 else put oldval_num @@;
225 end;
226 else do;
227 if first.is_pk then put " where 1=1 " @@;
228 put " and " tgtvar_nm '=' @@;
229 cnt=count(oldval_char,'"');
230 charval=quote(trim(substr(oldval_char,1,32765-cnt)));
231 if tgtvar_type='C' then put charval @@;
232 else put oldval_num @@;
233 end;
234 end;
235 if last.key_hash then put ';';
236run;
237
238/* apply the modification statements */
239%inc &fref1/source2 lrecl=33000;
240
241%if &mdebug=0 %then %do;
242 proc sql;
243 drop table &ds1, &ds2, &ds3, &ds4, &ds5;
244 file &fref1 clear;
245%end;
246%else %do;
247 data _null_;
248 infile &fref1;
249 input;
250 if _n_=1 then putlog "Contents of SQL adjustments";
251 putlog _infile_;
252 run;
253%end;
254
255%mend mp_stripdiffs;
256/** @endcond */