Macros for SAS Application Developers
https://github.com/sasjs/core
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
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;
86proc sql noprint;
87select 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));
96create 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));
108create 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;
121select distinct upcase(tgtvar_nm) into: pk separated by ' ' from &ds2;
122
123%let ds3=%upcase(work.%mf_getuniquename(prefix=mpsd_keychar));
124proc transpose data=&ds2(where=(tgtvar_type='C'))
125 out=&ds3(drop=_name_);
126 by &keyhash &processed;
127 id TGTVAR_NM;
128 var charval;
129run;
130
131%let ds4=%upcase(work.%mf_getuniquename(prefix=mpsd_keynum));
132proc transpose data=&ds2(where=(tgtvar_type='N'))
133 out=&ds4(drop=_name_);
134 by &keyhash &processed;
135 id TGTVAR_NM;
136 var numval;
137run;
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));
144data &ds5;
145 length &keyhash $32 &processed 8;
146 merge &ds3 &ds4;
147 by &keyhash &processed;
148 if not missing(&keyhash);
149run;
150proc sort data=&ds5 nodupkey;
151 by &pk;
152run;
153
154/* join to base table for preliminary stage DS */
155proc sql;
156create 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 */
175data _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 ';';
235run;
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 */