Loading...
Searching...
No Matches
bitemporal_closeouts.sas
Go to the documentation of this file.
1/**
2 @file
3 @brief Closes out records
4 @details Closes out records from a temporal table by reference to a single
5 temporal range + business key. Only live records are closed out, so the
6 entire key should be provided in the input table EXCEPT the TECH_FROM.
7 All records matching the key (as per the input table) are closed out
8 on TECH_TO.
9
10 Returns an updated base table and `&mpelib..mpe_dataloads` table
11
12 Potential improvements - write the update statements as a text file and retain
13 for future reference!
14
15 @param [in] now= (DEFINE) Allows consistent tracking of tech dates. Should be
16 a date literal, not a numeric constant, for DB compatibility.
17 @param [in] load_type= Set to UPDATE if non-temporal, else assumed
18 to be TXTEMPORAL. Note that BITEMPORAL is treated the same as TXTEMPORAL
19 given that BUS_FROM should be supplied in the PK.
20 @param [in] tech_from= (tx_from_dttm) Technical FROM datetime variable.
21 Required on BASE table only.
22 @param [in] AUDITFOLDER= (0) Unquoted path to a directory into which a copy of
23 the generated delete program will be written
24
25 <h4> Global Variables </h4>
26 @li `dc_dttmtfmt`
27
28
29 <h4> SAS Macros </h4>
30 @li mp_abort.sas
31 @li mf_existvar.sas
32 @li mf_getattrn.sas
33 @li mf_getengine.sas
34 @li mf_getuniquelibref.sas
35 @li mf_getuniquename.sas
36 @li mf_getuser.sas
37 @li mf_getvartype.sas
38 @li mp_lockanytable.sas
39 @li dc_assignlib.sas
40
41
42 @version 9.2
43 @author 4GL Apps Ltd
44 @copyright 4GL Apps Ltd. This code may only be used within Data Controller
45 and may not be re-distributed or re-sold without the express permission of
46 4GL Apps Ltd.
47**/
48
49%macro bitemporal_closeouts(
50 tech_from=tx_from_dttm
51 ,tech_to = tx_to_dttm /* Technical TO datetime variable.
52 Req'd on BASE table only. */
53 ,base_lib=WORK /* Libref of the BASE table. */
54 ,base_dsn=BASETABLE /* Name of BASE table. */
55 ,append_lib=WORK /* Libref of the STAGING table. */
56 ,append_dsn=APPENDTABLE /* Name of STAGING table. */
57 ,PK= name sex /* Business key, space separated. */
58 /* Should INCLUDE BUS_FROM field if relevant. */
59 ,NOW=DEFINE
60 ,FILTER= /* supply a filter to limit the update */
61 ,AUDITFOLDER=0
62 ,loadtype=
63 ,loadtarget=YES /* if <> YES will return without changing anything */
64);
65%put ENTERING &sysmacroname;
66%local x var start;
67%let start=%sysfunc(datetime());
68%dc_assignlib(WRITE,&base_lib)
69%dc_assignlib(WRITE,&append_lib)
70
71%if &now=DEFINE %then %let now=&dc_dttmtfmt.;
72%put &=now;
73/**
74 * perform basic checks
75 */
76/* do tables exist? */
77%mp_abort(
78 iftrue=(%sysfunc(exist(&base_lib..&base_dsn)) ne 1),
79 msg=&base_lib..&base_dsn does not exist
80)
81%mp_abort(
82 iftrue=(%sysfunc(exist(&append_lib..&append_dsn))=0
83 and %sysfunc(exist(&append_lib..&append_dsn,VIEW))=0 ),
84 msg=&append_lib..&append_dsn does not exist
85)
86
87/* do TX columns exist? */
88%if &loadtype ne UPDATE %then %do;
89 %if not %mf_existvar(&base_lib..&base_dsn,&tech_from) %then %do;
90 %mp_abort(msg=&tech_from does not exist on &base_lib..&base_dsn)
91 %end;
92 %else %if not %mf_existvar(&base_lib..&base_dsn,&tech_to) %then %do;
93 %mp_abort(msg=&tech_to does not exist on &base_lib..&base_dsn)
94 %end;
95%end;
96/* do PK columns exist? */
97%do x=1 %to %sysfunc(countw(&PK));
98 %let var=%scan(&pk,&x,%str( ));
99 %if not %mf_existvar(&base_lib..&base_dsn,&var) %then %do;
100 %mp_abort(msg=&var does not exist on &base_lib..&base_dsn)
101 %end;
102 %else %if not %mf_existvar(&append_lib..&append_dsn,&var) %then %do;
103 %mp_abort(msg=&var does not exist on &append_lib..&append_dsn)
104 %end;
105%end;
106/* check uniqueness */
107proc sort data=&append_lib..&append_dsn
108 out=___closeout1 noduprecs dupout=___closeout1a;
109 by &pk;
110run;
111%if %mf_getattrn(___closeout1a,NLOBS)>0 %then
112 %put NOTE: dups on (&PK) in (&append_lib..&append_dsn);
113/* is &NOW value within a tolerance? Should not allow renegade closeouts.. */
114%local gap;
115%let gap=0;
116data _null_;
117 now=&now;
118 gap=intck('HOURS',now,datetime());
119 call symputx('gap',gap,'l');
120run;
121%mp_abort(
122 iftrue=(&gap > 24),
123 msg=NOW variable (&now) is not within a 24hr tolerance
124)
125
126/* have any warnings / errs occurred thus far? If so, abort */
127%mp_abort(
128 iftrue=(&syscc>0),
129 msg=Aborted due to SYSCC=&SYSCC status
130)
131
132/* set up folder */
133%local tmplib;%let tmplib=%mf_getuniquelibref();
134%if "&AUDITFOLDER"="0" %then %do;
135 filename tmp temp lrecl=10000;
136 libname &tmplib (work);
137%end;
138%else %do;
139 filename tmp "&AUDITFOLDER/deleterecords.sas" lrecl=10000;
140 libname &tmplib "&AUDITFOLDER";
141%end;
142
143/**
144 * Create closeout statements. If UPDATE approach and CAS engine, use the
145 * DeleteRows action (as regular SQL deletes are not supported).
146 * Otherwise, the deletions are sent as individual SQL statements
147 * to ensure pass-through utilisation. The update_cnt variable monitors
148 * how many records were actually updated on the target table.
149 */
150%local update_cnt etype;
151%let update_cnt=0;
152%let etype=%mf_getengine(&base_lib);
153%put &=etype;
154
155%if &loadtype=UPDATE and &etype=CAS %then %do;
156 /* create temp table for deletions */
157 %local delds;%let delds=%mf_getuniquename(prefix=DC);
158 data casuser.&delds &tmplib..deleterecords;
159 set work.___closeout1;
160 keep &pk;
161 run;
162 /* build the proc */
163 data _null_;
164 file tmp;
165 put "/* libname approve '&AUDITFOLDER'; */";
166 put 'proc cas;table.deleteRows result=r/ table={' ;
167 put " caslib='&base_lib',name='&base_dsn',where='1=1',";
168 put " whereTable={caslib='CASUSER',name='&delds'}";
169 put "};";
170 put "call symputx('update_cnt',r.RowsDeleted);";
171 put "quit;";
172 put "data;set casuser.&delds;putlog (_all_)(=);run;";
173 put '%put &=update_cnt;';
174 put "proc sql;drop table CASUSER.&delds;";
175 stop;
176 run;
177
178%end;
179%else %do;
180 data _null_;
181 set ___closeout1;
182 file tmp;
183 if _n_=1 then put 'proc sql noprint;' ;
184 length string $32767.;
185 %if &loadtype=UPDATE %then %do;
186 put "delete from &base_lib..&base_dsn where 1";
187 %end;
188 %else %do;
189 now=symget('now');
190 put "update &base_lib..&base_dsn set &tech_to= " now @;
191 %if %mf_existvar(&base_lib..&base_dsn,PROCESSED_DTTM) %then %do;
192 put " ,PROCESSED_DTTM=" now @;
193 %end;
194 put " where " now " lt &tech_to ";
195 %end;
196 %do x=1 %to %sysfunc(countw(&PK));
197 %let var=%scan(&pk,&x,%str( ));
198 %if %mf_getvartype(&base_lib..&base_dsn,&var)=C %then %do;
199 /* use single quotes to avoid ampersand resolution in data */
200 string=" & &var='"!!trim(prxchange("s/'/''/",-1,&var))!!"'";
201 %end;
202 %else %do;
203 string=cats(" & &var=",&var);
204 %end;
205 put string;
206 %end;
207 put "&filter ;";
208 put '%let update_cnt=%eval(&update_cnt+&sqlobs);';
209 put '%put update_cnt=&update_cnt;';
210 run;
211%end;
212
213%if &loadtarget ne YES %then %return;
214
215/* ensure we have a lock */
216%mp_lockanytable(LOCK,
217 lib=&base_lib,ds=&base_dsn
218 ,ref=bitemporal_closeouts
219 ,ctl_ds=&mpelib..mpe_lockanytable
220)
221
222options source2;
223%inc tmp;
224
225filename tmp clear;
226
227/**
228 * Update audit tracker
229 */
230
231%local newobs; %let newobs=%mf_getattrn(work.___closeout1,NLOBS);
232%local user; %let user=%mf_getuser();
233proc sql;
234insert into &mpelib..mpe_dataloads
235 set libref=%upcase("&base_lib")
236 ,DSN=%upcase("&base_dsn")
237 ,ETLSOURCE="&append_lib..&append_dsn contained &newobs records"
238 ,LOADTYPE="CLOSEOUT"
239 ,DELETED_RECORDS=&update_cnt
240 ,NEW_RECORDS=0
241 ,DURATION=%sysfunc(datetime())-&start
242 ,USER_NM="&user"
243 ,PROCESSED_DTTM=&now;
244quit;
245
246
247%mend bitemporal_closeouts;