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 @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 */
176/* numeric values become SQL literals, so a special missing (.A-.Z, ._) must
177 carry its leading period - PUT writes only the letter, which PROC SQL reads
178 as a column name */
179data _null_;
180 set &ds1;
181 file &fref1 lrecl=33000;
182 length charval $32767;
183 if _n_=1 then put 'proc sql noprint;';
184 by descending processed_dttm key_hash is_pk;
185 if move_type='M' then do;
186 if first.key_hash then do;
187 put "update &outds set " @@;
188 end;
189 if IS_PK=0 then do;
190 put " " tgtvar_nm '=' @@;
191 cnt=count(oldval_char,'"');
192 charval=quote(trim(substr(oldval_char,1,32765-cnt)));
193 if tgtvar_type='C' then put charval @@;
194 else if missing(oldval_num) and oldval_num ne .
195 then put "." oldval_num @@;
196 else put oldval_num @@;
197 if not last.is_pk then put ',';
198 end;
199 else do;
200 if first.is_pk then put " where 1=1 " @@;
201 put " and " tgtvar_nm '=' @@;
202 cnt=count(oldval_char,'"');
203 charval=quote(trim(substr(oldval_char,1,32765-cnt)));
204 if tgtvar_type='C' then put charval @@;
205 else if missing(oldval_num) and oldval_num ne .
206 then put "." oldval_num @@;
207 else put oldval_num @@;
208 end;
209 end;
210 else if move_type='A' then do;
211 if first.key_hash then do;
212 put "update &outds set _____DELETE__THIS__RECORD_____='Yes' where 1=1 "@@;
213 end;
214 /* gating if - as only need PK now */
215 if is_pk=1;
216 put ' AND ' tgtvar_nm '=' @@;
217 cnt=count(newval_char,'"');
218 charval=quote(trim(substr(newval_char,1,32765-cnt)));
219 if tgtvar_type='C' then put charval @@;
220 else if missing(newval_num) and newval_num ne .
221 then put "." newval_num @@;
222 else put newval_num @@;
223 end;
224 else if move_type='D' then do;
225 if first.key_hash then do;
226 put "update &outds set _____DELETE__THIS__RECORD_____='No' " @@;
227 end;
228 if IS_PK=0 then do;
229 put " ," tgtvar_nm '=' @@;
230 cnt=count(oldval_char,'"');
231 charval=quote(trim(substr(oldval_char,1,32765-cnt)));
232 if tgtvar_type='C' then put charval @@;
233 else if missing(oldval_num) and oldval_num ne .
234 then put "." oldval_num @@;
235 else put oldval_num @@;
236 end;
237 else do;
238 if first.is_pk then put " where 1=1 " @@;
239 put " and " tgtvar_nm '=' @@;
240 cnt=count(oldval_char,'"');
241 charval=quote(trim(substr(oldval_char,1,32765-cnt)));
242 if tgtvar_type='C' then put charval @@;
243 else if missing(oldval_num) and oldval_num ne .
244 then put "." oldval_num @@;
245 else put oldval_num @@;
246 end;
247 end;
248 if last.key_hash then put ';';
249run;
250
251/* apply the modification statements */
252%inc &fref1/source2 lrecl=33000;
253
254%if &mdebug=0 %then %do;
255 proc sql;
256 drop table &ds1, &ds2, &ds3, &ds4, &ds5;
257 file &fref1 clear;
258%end;
259%else %do;
260 data _null_;
261 infile &fref1;
262 input;
263 if _n_=1 then putlog "Contents of SQL adjustments";
264 putlog _infile_;
265 run;
266%end;
267
268%mend mp_stripdiffs;
269/** @endcond */