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