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 
40  <h4> Related Macros </h4>
41  @li mddl_dc_difftable.sas
42  @li mp_stackdiffs.sas
43  @li mp_storediffs.sas
44  @li mp_stripdiffs.test.sas
45 
46  @version 9.2
47  @author Allan Bowe
48 **/
49 /** @cond */
50 
51 %macro mp_stripdiffs(libds
52  ,loadref
53  ,difftable
54  ,filtervar=0
55  ,outds=work.mp_stripdiffs
56  ,mdebug=0
57 )/*/STORE SOURCE*/;
58 %local dbg;
59 %if &mdebug=1 %then %do;
60  %put &sysmacroname entry vars:;
61  %put _local_;
62 %end;
63 %else %let dbg=*;
64 
65 %let libds=%upcase(&libds);
66 
67 /* safety checks */
68 %mp_abort(iftrue= (&syscc ne 0)
69  ,mac=&sysmacroname
70  ,msg=%str(SYSCC=&syscc on entry. Clean session required!)
71 )
72 %let libds=%upcase(&libds);
73 %mp_abort(iftrue= (%mf_islibds(&libds)=0)
74  ,mac=&sysmacroname
75  ,msg=%str(Invalid library.dataset reference - %superq(libds))
76 )
77 
78 /* set up unique and temporary vars */
79 %local ds1 ds2 ds3 ds4 ds5 fref1 filterstr;
80 %let fref1=%mf_getuniquefileref();
81 %if &filtervar ne 0 %then %let filterstr=%superq(&filtervar);
82 %else %let filterstr=%str(1=1);
83 
84 /* get timestamp of the diff to be reverted */
85 %local ts;
86 proc sql noprint;
87 select put(processed_dttm,datetime19.6) into: ts
88  from &difftable where load_ref="&loadref";
89 %mp_abort(iftrue= (&sqlobs=0)
90  ,mac=&sysmacroname
91  ,msg=%str(Load ref %superq(loadref) not found!)
92 )
93 
94 /* extract diffs for this base table from this timestamp onwards */
95 %let ds1=%upcase(work.%mf_getuniquename(prefix=mpsd_diffs));
96 create table &ds1 (drop=libref dsn) as
97  select * from &difftable
98  where upcase(cats(libref))="%scan(&libds,1,.)"
99  and upcase(cats(dsn))="%scan(&libds,2,.)"
100  and processed_dttm ge "&ts"dt
101  order by processed_dttm desc, key_hash, is_pk;
102 
103 /* extract key values only */
104 %let ds2=%upcase(work.%mf_getuniquename(prefix=mpsd_pks));
105 %local keyhash processed;
106 %let keyhash=%upcase(%mf_getuniquename(prefix=mpsdvar_keyhash));
107 %let processed=%upcase(%mf_getuniquename(prefix=mpsdvar_processed));
108 create table &ds2 as
109  select key_hash as &keyhash,
110  tgtvar_nm,
111  tgtvar_type,
112  coalescec(oldval_char,newval_char) as charval,
113  coalesce(oldval_num, newval_num) as numval,
114  processed_dttm as &processed
115  from &ds1
116  where is_pk=1
117  order by &keyhash, &processed;
118 
119 /* grab pk values */
120 %local pk;
121 select distinct upcase(tgtvar_nm) into: pk separated by ' ' from &ds2;
122 
123 %let ds3=%upcase(work.%mf_getuniquename(prefix=mpsd_keychar));
124 proc transpose data=&ds2(where=(tgtvar_type='C'))
125  out=&ds3(drop=_name_);
126  by &keyhash &processed;
127  id TGTVAR_NM;
128  var charval;
129 run;
130 
131 %let ds4=%upcase(work.%mf_getuniquename(prefix=mpsd_keynum));
132 proc transpose data=&ds2(where=(tgtvar_type='N'))
133  out=&ds4(drop=_name_);
134  by &keyhash &processed;
135  id TGTVAR_NM;
136  var numval;
137 run;
138 /* shorten the lengths */
139 %mp_ds2squeeze(&ds3,outds=&ds3)
140 %mp_ds2squeeze(&ds4,outds=&ds4)
141 
142 /* now merge to get all key values and de-dup */
143 %let ds5=%upcase(work.%mf_getuniquename(prefix=mpsd_merged));
144 data &ds5;
145  length &keyhash $32 &processed 8;
146  merge &ds3 &ds4;
147  by &keyhash &processed;
148  if not missing(&keyhash);
149 run;
150 proc sort data=&ds5 nodupkey;
151  by &pk;
152 run;
153 
154 /* join to base table for preliminary stage DS */
155 proc sql;
156 create table &outds as select "No " as _____DELETE__THIS__RECORD_____
157  %do x=1 %to %sysfunc(countw(&pk,%str( )));
158  ,a.%scan(&pk,&x,%str( ))
159  %end;
160  %local notpkcols;
161  %let notpkcols=%upcase(%mf_getvarlist(&libds));
162  %let notpkcols=%mf_wordsinstr1butnotstr2(str1=&notpkcols,str2=&pk);
163  %do x=1 %to %sysfunc(countw(&notpkcols,%str( )));
164  ,b.%scan(&notpkcols,&x,%str( ))
165  %end;
166  from &ds5 a
167  left join &libds (where=(&filterstr)) b
168  on 1=1
169 %do x=1 %to %sysfunc(countw(&pk,%str( )));
170  and a.%scan(&pk,&x,%str( ))=b.%scan(&pk,&x,%str( ))
171 %end;
172 ;
173 
174 /* create SAS code to apply to stage_ds */
175 data _null_;
176  set &ds1;
177  file &fref1 lrecl=33000;
178  length charval $32767;
179  if _n_=1 then put 'proc sql noprint;';
180  by descending processed_dttm key_hash is_pk;
181  if move_type='M' then do;
182  if first.key_hash then do;
183  put "update &outds set " @@;
184  end;
185  if IS_PK=0 then do;
186  put " " tgtvar_nm '=' @@;
187  cnt=count(oldval_char,'"');
188  charval=quote(trim(substr(oldval_char,1,32765-cnt)));
189  if tgtvar_type='C' then put charval @@;
190  else put oldval_num @@;
191  if not last.is_pk then put ',';
192  end;
193  else do;
194  if first.is_pk then put " where 1=1 " @@;
195  put " and " tgtvar_nm '=' @@;
196  cnt=count(oldval_char,'"');
197  charval=quote(trim(substr(oldval_char,1,32765-cnt)));
198  if tgtvar_type='C' then put charval @@;
199  else put oldval_num @@;
200  end;
201  end;
202  else if move_type='A' then do;
203  if first.key_hash then do;
204  put "update &outds set _____DELETE__THIS__RECORD_____='Yes' where 1=1 "@@;
205  end;
206  /* gating if - as only need PK now */
207  if is_pk=1;
208  put ' AND ' tgtvar_nm '=' @@;
209  cnt=count(newval_char,'"');
210  charval=quote(trim(substr(newval_char,1,32765-cnt)));
211  if tgtvar_type='C' then put charval @@;
212  else put newval_num @@;
213  end;
214  else if move_type='D' then do;
215  if first.key_hash then do;
216  put "update &outds set _____DELETE__THIS__RECORD_____='No' " @@;
217  end;
218  if IS_PK=0 then do;
219  put " ," tgtvar_nm '=' @@;
220  cnt=count(oldval_char,'"');
221  charval=quote(trim(substr(oldval_char,1,32765-cnt)));
222  if tgtvar_type='C' then put charval @@;
223  else put oldval_num @@;
224  end;
225  else do;
226  if first.is_pk then put " where 1=1 " @@;
227  put " and " tgtvar_nm '=' @@;
228  cnt=count(oldval_char,'"');
229  charval=quote(trim(substr(oldval_char,1,32765-cnt)));
230  if tgtvar_type='C' then put charval @@;
231  else put oldval_num @@;
232  end;
233  end;
234  if last.key_hash then put ';';
235 run;
236 
237 /* apply the modification statements */
238 %inc &fref1/source2 lrecl=33000;
239 
240 %if &mdebug=0 %then %do;
241  proc sql;
242  drop table &ds1, &ds2, &ds3, &ds4, &ds5;
243  file &fref1 clear;
244 %end;
245 %else %do;
246  data _null_;
247  infile &fref1;
248  input;
249  if _n_=1 then putlog "Contents of SQL adjustments";
250  putlog _infile_;
251  run;
252 %end;
253 
254 %mend mp_stripdiffs;
255 /** @endcond */