Loading...
Searching...
No Matches
mpe_loader.sas
Go to the documentation of this file.
1/**
2 @file
3 @brief Loads CSV and sends to the staging area for approval
4 @details this macro used to capture multiple CSVs (eg from one excel file) in
5 a staging area and send them all to a landing area. For simplicity this
6 functionality is now deprecated, and each load should be made as a seperate
7 request. If there is a use case to load multiple tables at once, the client
8 should manage this and load them seperately.
9
10 @param url= used for debugging (provided by stagedata stp)
11 @param dlm= use to provide alternative delimeters for CSVs (not just comma)
12 @param [in] termstr= (crlf) Always crlf from adapter, whereas loadfile service
13 figures it out
14
15 <h4> SAS Macros </h4>
16 @li dc_assignlib.sas
17 @li mf_getattrn.sas
18 @li mf_getuser.sas
19 @li mf_verifymacvars.sas
20 @li mp_abort.sas
21 @li mp_cntlout.sas
22 @li mp_dirlist.sas
23 @li mp_lockanytable.sas
24 @li mpe_accesscheck.sas
25 @li mpe_alerts.sas
26 @li mpe_xlmapvalidate.sas
27 @li mpe_loadfail.sas
28 @li mpe_runhook.sas
29
30 @version 9.2
31 @author 4GL Apps Ltd
32 @copyright 4GL Apps Ltd. This code may only be used within Data Controller
33 and may not be re-distributed or re-sold without the express permission of
34 4GL Apps Ltd.
35**/
36
37%macro mpe_loader(
38 mperef= /* name of subfolder containing the staged data */
39 ,mDebug=0 /* set to 1 for development or debugging */
40 ,submitted_reason_txt= /* populates column of same name in sumo_approvals*/
41 ,approver= /* allows a userid to be provided for direct approval email */
42 ,url= /* optional - url for debugging */
43 ,dlm=%str(,)
44 ,termstr=crlf
45 ,dc_dttmtfmt=E8601DT26.6
46 );
47%put entered mpe_loader from &=_program;
48%put &=url;
49%put &=termstr;
50%put &=dlm;
51 /* determine full path to CSV directory */
52%local now;
53%let now=&dc_dttmtfmt;
54%put &=now;
55
56/**
57 * get full path to package (only subdirectory passed through)
58 */
59%mp_abort(
60 iftrue=(%mf_verifymacvars(mperef mpelocapprovals)=0)
61 ,mac=bitemporal_dataloader
62 ,msg=%str(Missing: mperef mpelocapprovals)
63)
64
65%let csv_dir=%trim(&mpelocapprovals/&mperef);
66
67/* exit if package has already been uploaded */
68%local check;
69proc sql noprint;
70select count(*) into: check
71 from &mpelib..mpe_loads
72 where csv_dir="&mperef";
73%if &check %then %do;
74 %mp_abort(msg=Folder &mperef already has an entry in &mpelib..mpe_loads
75 ,mac=mpe_loader.sas);
76 %return;
77%end;
78
79/* get CSV directory contents */
80%mp_dirlist(path=&csv_dir,outds=WORK.getfiles)
81data WORK.csvs;
82 set WORK.getfiles;
83 if upcase(scan(filename,3,'.'))='CSV' then do;
84 lib=upcase(scan(filename,1,'.'));
85 ds=upcase(scan(filename,2,'.'));
86 output;
87 end;
88run;
89
90/* get table attributes */
91proc sql noprint;
92create table WORK.sumo_tables as
93 select a.filename, b.*
94 from WORK.csvs a
95 left join &mpelib..mpe_tables b
96 on a.lib=b.libref
97 and a.ds=b.dsn
98 where b.tx_from le &now
99 and &now lt b.tx_to;
100
101/* define user as meta user if available */
102%local user;
103%let user=%mf_getuser();
104
105/* check if there is actually a table to load */
106%if %mf_getattrn(WORK.sumo_tables,NLOBS)=0 %then %do;
107 %let msg=Table not registered in &mpelib..mpe_tables;
108 %mpe_loadfail(
109 status=&msg
110 ,now=&now
111 ,mperef=&mperef
112 ,dc_dttmtfmt=&dc_dttmtfmt.
113 )
114 %mp_abort(msg=&msg,mac=mpe_loader.sas);
115 %return;
116%end;
117
118proc sql;
119insert into &mpelib..mpe_loads
120 set USER_NM="&user"
121 ,STATUS='IN PROGRESS'
122 ,CSV_dir="&mperef"
123 ,PROCESSED_DTTM=&now
124 ,reason_txt = symget('submitted_reason_txt');
125
126
127/* import CSV */
128
129%let droplist=;
130%let attrib=;
131%let droplist=;
132%let libref=;
133%let DS=;
134
135/* get table info */
136data _null_;
137 set sumo_tables;
138 libds=upcase(cats(libref,'.',dsn));
139 call symputx('orig_libds',libds);
140 is_fmt=0;
141 if substr(cats(reverse(dsn)),1,3)=:'CF-' then do;
142 libds=scan(libds,1,'-');
143 putlog "Format Catalog Captured";
144 libds='work.fmtextract';
145 is_fmt=1;
146 end;
147 call symputx('is_fmt',is_fmt);
148 call symputx('libds',libds);
149 call symputx('FNAME',filename);
150 call symputx('LIBREF',libref);
151 call symputx('DS',dsn);
152 call symputx('LOADTYPE',loadtype);
153 call symputx('BUSKEY',buskey);
154 call symputx('VAR_TXFROM',var_txfrom);
155 call symputx('VAR_TXTO',var_txto);
156 call symputx('VAR_BUSFROM',var_busfrom);
157 call symputx('VAR_BUSTO',var_busto);
158 call symputx('VAR_PROCESSED',var_processed);
159 call symputx('RK_UNDERLYING',RK_UNDERLYING);
160 call symputx('POST_EDIT_HOOK',POST_EDIT_HOOK);
161 call symputx('NOTES',NOTES);
162 call symputx('PK',coalescec(RK_UNDERLYING,buskey));
163 call symputx('NUM_OF_APPROVALS_REQUIRED',NUM_OF_APPROVALS_REQUIRED,'l');
164 put (_all_)(=);
165 stop;
166run;
167
168%if %length(&ds)=0 %then %do;
169 %let msg=%str(ERR)OR: Unable to extract record from &mpelib..mpe_tables;
170 %mpe_loadfail(
171 status=FAILED
172 ,now=&now
173 ,mperef=&mperef
174 ,reason_txt=%quote(&msg)
175 ,dc_dttmtfmt=&dc_dttmtfmt.
176 )
177 %mp_abort(msg=&msg,mac=mpe_loader.sas);
178 %return;
179%end;
180
181/* export format catalog */
182%mp_cntlout(
183 iftrue=(&is_fmt=1)
184 ,libcat=&orig_libds
185 ,fmtlist=0
186 ,cntlout=work.fmtextract
187)
188
189/* user must have EDIT access to load a table */
190%mpe_accesscheck(&orig_libds
191 ,outds=work.sumo_access
192 ,user=&user
193 ,access_level=EDIT )
194%put exiting accesscheck;
195
196%if %mf_getattrn(work.sumo_access,NLOBS)=0 %then %do;
197 %let msg=%str(ERR)OR: User is not authorised to edit &orig_libds!;
198 %mpe_loadfail(
199 status=UNAUTHORISED
200 ,now=&now
201 ,mperef=&mperef
202 ,reason_txt=%quote(&msg)
203 ,dc_dttmtfmt=&dc_dttmtfmt.
204 )
205 %mp_abort(msg=&msg,mac=mpe_loader.sas);
206 %return;
207%end;
208
209%put now importing: "&csv_dir/&fname" termstr=&termstr;
210/* get the variables from the CSV */
211data vars_csv1(index=(idxname=(varnum name)) drop=infile);
212 infile "&csv_dir/&fname" lrecl=32767 dsd termstr=&termstr encoding='utf-8';
213 input;
214 length infile $32767;
215 infile=compress(_infile_,'"',);
216 infile=compress(infile,"'",);
217 format name $32.;
218 putlog 'received vars: ' infile;
219 call symputx('received_vars',infile,'l');
220 do varnum=1 to countw(infile,"&dlm");
221 /* keep writeable chars */
222 name=compress(upcase(scan(infile,varnum)),,'kw');
223 if name ne "_____DELETE__THIS__RECORD_____" then output;
224 end;
225 stop;
226run;
227%put received_vars = &received_vars;
228
229%dc_assignlib(WRITE,&libref)
230
231/* get list of variables and their formats */
232proc contents noprint data=&libds
233 out=vars(keep=name type length varnum format:);
234run;
235data vars(keep=name type length varnum format);
236 set vars(rename=(format=format2 type=type2));
237 name=upcase(name);
238 format2=upcase(format2);
239 /* not interested in transaction or processing dates
240 (append table must be supplied without them) */
241 if name not in ("&VAR_TXFROM","&VAR_TXTO","&VAR_PROCESSED"
242 ,"_____DELETE__THIS__RECORD_____");
243 if type2 in (2,6) then do;
244 length format $49.;
245 if format2='' then format=cats('$',length,'.');
246 else format=cats(format2,max(formatl,length),'.');
247 type='char';
248 end;
249 else do;
250 if format2='' then format=cats(length,'.');
251 else if format2=:'DATETIME' or format2=:'E8601DT' or format2=:'NLDATM'
252 then do;
253 format='DATETIME19.';
254 end;
255 else if format2=:'DATE' or format2=:'DDMMYY'
256 or format2=:'MMDDYY' or format2=:'YYMMDD'
257 or format2=:'E8601DA' or format2=:'B8601DA'
258 or format2=:'NLDATE'
259 then do;
260 format='DATE9.';
261 end;
262 else if format2='BEST' & formatl=0 then format=cats('BEST',length,'.');
263 else do;
264 if formatl=0 then formatl=length;
265 format=cats(format2,formatl,'.',formatd);
266 end;
267 type='num';
268 end;
269 put (_all_)(=);
270run;
271
272/* build attrib statement */
273data vars_attrib;
274 length attrib_statement $32767 type2 $20;
275 set vars end=lastobs;
276 retain attrib_statement;
277
278 if type='char' then type2='$';
279 str1=catx(' ',name,'length=',cats(type2,length));
280 attrib_statement=trim(attrib_statement)!!' '!!trim(str1);
281
282 if lastobs then call symputx('ATTRIB',attrib_statement,'L');
283run;
284
285/* build input statement - first get vars in right order
286 and join with target formats*/
287proc sql noprint;
288create table vars_csv2 as
289 select b.*
290 from vars_csv1 a
291 left join vars_attrib b
292 on a.name=b.name
293 order by a.varnum;
294
295
296/* now build input statement */
297data final_check;
298 set vars_csv2 end=lastobs;
299 length input_statement $32767 type2 $20 droplist $32767;
300 retain input_statement droplist;
301
302 /* Build input statement - CATCH EXCEPTIONS HERE!*/
303 if name in ('QUOTE_DTTM') then do;
304 name=cats(name,'2');
305 droplist=catx(' ',trim(droplist),name);
306 type2='$20.';/* converted below */
307 end;
308 else if type='char' then type2=cats('$CHAR', length,'.');
309 else if format='DATE9.' then type2='ANYDTDTE.';
310 else if format='DATETIME19.' then type2='ANYDTDTM.';
311 else if format=:'TIME' then type2='ANYDTTME.';
312 else if name='' then do;/* additional vars in input data */
313 name='_____DELETE__THIS__VARIABLE_____';
314 droplist=catx(' ',trim(droplist),'_____DELETE__THIS__VARIABLE_____');
315 type2='$1.';
316 end;
317 else type2='best32.';
318 * else type2=cats(length,'.');
319
320 input_statement=catx(' ',input_statement,name,':',type2);
321
322 if lastobs then do;
323 call symputx('INPUT', input_statement,'L');
324 if trim(droplist) ne '' then
325 call symputx('droplist',"drop "!!droplist!!';','l');
326 end;
327run;
328
329%let mpeloadstop=0;
330
331data work.STAGING_DS;
332 &droplist;
333 infile "&csv_dir/&fname" dsd dlm="&dlm" lrecl=32767
334 firstobs=2 missover termstr=&termstr encoding='utf-8';
335 attrib _____DELETE__THIS__RECORD_____ length=$3 &attrib ;
336 if _n_=1 then call missing (of _all_);
337 missing a b c d e f g h i j k l m n o p q r s t u v w x y z _;
338 input
339 %if %scan(%quote(&received_vars),1)=_____DELETE__THIS__RECORD_____ %then %do;
340 _____DELETE__THIS__RECORD_____: $3.
341 %end;
342 &input;
343
344 %if %index(%quote(&attrib.),UNLIKELY_VAR ) %then %do;
345 /*UNLIKELY_VAR=input(UNLIKELY_VAR2,ANYDTDTM21.);*/
346 /* SPECIAL LOGIC FOR SPECIAL VARS */
347 %end;
348
349 if _error_ ne 0 then do;
350 putlog _infile_;
351 call symputx('mpeloadstop',_n_);
352 stop;
353 end;
354 /* remove all blank rows */
355 if compress(cats(of _all_),'.')=' ' then delete;
356run;
357
358%if &mpeloadstop>0 %then %do;
359 %if %symexist(SYSPRINTTOLOG) %then %let logloc=&SYSPRINTTOLOG;
360 %else %let logloc=%qsysfunc(getoption(LOG));
361 %put redirecting log output to capture return message;
362 %put currentloc=&logloc;
363 filename tmp temp;
364 proc printto log=tmp;run;
365 data _null_;
366 &droplist;
367 infile "&csv_dir/&fname" dsd dlm="&dlm" lrecl=32767 firstobs=2
368 missover termstr=&termstr;
369 attrib &attrib ;
370 input
371 %if %scan(%quote(&received_vars),1)=_____DELETE__THIS__RECORD_____
372 %then %do;
373 _____DELETE__THIS__RECORD_____: $3.
374 %end;
375 &input;
376 if _error_ then stop;
377 run;
378 /* get log back */
379 proc printto log=&logloc;run;
380 data _null_; infile tmp; input; putlog _infile_;run;
381 /* scan log for invalid data warnings */
382 data _null_;
383 infile tmp;
384 input;
385 length msg1 msg2 msg3 msg4 msg5 msg url $32767;
386 if index(_infile_,'NOTE: Invalid data for') then do;
387 msg1=_infile_;
388 input;
389 msg2=_infile_;
390 input;
391 msg3=_infile_;
392 input;
393 msg4=_infile_;
394 input;
395 msg5=_infile_;
396 url=symget('url');
397 msg=catx('\n',msg1,msg2,msg3,msg4,msg5,'\n',url);
398 call symputx('msg',msg);
399 stop;
400 end;
401 run;
402
403 %mpe_loadfail(
404 status=FAILED
405 ,now=&now
406 ,mperef=&mperef
407 ,reason_txt=%superq(msg)
408 ,dc_dttmtfmt=&dc_dttmtfmt.
409 )
410 %return;
411%end;
412
413/* check that the table is unique on PK */
414proc sort data=work.STAGING_DS dupout=work.MPE_DUPS (keep=&pk) nodupkey;
415 by &pk;
416run;
417%if %mf_getattrn(work.MPE_DUPS,NLOBS)>0 %then %do;
418 %local duplist;
419 data _null_;
420 set work.mpe_dups;
421 %do i=1 %to %sysfunc(countw(&pk));
422 %let iWord=%scan(&pk,&i);
423 call symputx('duplist',symget('duplist')!!
424 " &iWord="!!cats(&iWord));
425 %end;
426 run;
427 %let msg=This upload contains duplicates on the Primary Key columns %trim(
428 )(&pk) \n Please remove the duplicates and try again. %trim(
429 )\n &duplist \n ;
430 %mp_abort(msg=%superq(msg),mac=mpe_loader.sas);
431 %return;
432%end;
433
434%if &syscc gt 4 %then %do;
435 %let msg=SYSCC=&syscc prior to post edit hook (%superq(syserrortext));
436 %mpe_loadfail(
437 status=FAILED - &syscc
438 ,now=&now
439 ,mperef=&mperef
440 ,reason_txt=%superq(msg)
441 ,dc_dttmtfmt=&dc_dttmtfmt.
442 )
443 %return;
444%end;
445
446/* If a Complex Excel Upload, needs to have the load ref added to the table */
447%mpe_xlmapvalidate(&mperef,work.staging_ds,&mpelib,&orig_libds)
448
449/* Run the Post Edit Hook prior to creation of staging folder */
450%mpe_runhook(POST_EDIT_HOOK)
451
452/* stop if err */
453%if &syscc gt 4 %then %do;
454 %let msg=ERR in post edit hook (&post_edit_hook);
455 %mpe_loadfail(
456 status=FAILED - &syscc
457 ,now=&now
458 ,mperef=&mperef
459 ,reason_txt=%quote(&msg)
460 ,dc_dttmtfmt=&dc_dttmtfmt.
461 )
462 %return;
463%end;
464
465
466/**
467 * send to approve process
468 */
469
470/* create a dataset key (datetime plus 3 digit random number plus PID) */
471
472/* send dataset to approvals subfolder with same name as subfolder */
473libname approval "&mpelocapprovals/&mperef";
474data approval.&mperef;
475 set work.staging_ds;
476run;
477proc export data=approval.&mperef
478 outfile="&mpelocapprovals/&mperef/&mperef..csv"
479 dbms=csv
480 replace;
481run;
482
483/* update the control dataset with relevant info */
484data append_app;
485 if 0 then set &mpelib..mpe_submit;/* get formats */
486 call missing (of _all_);
487 TABLE_ID="&mperef";
488 submit_status_cd='SUBMITTED';
489 submitted_by_nm="%mf_getuser()";
490 base_lib="&libref";
491 base_ds="&ds";
492 submitted_on_dttm=&now;
493 submitted_reason_txt=symget('submitted_reason_txt');
494 input_vars=%mf_getattrn(approval.&mperef,NVARS);
495 input_obs=%mf_getattrn(approval.&mperef,NLOBS);
496 num_of_approvals_required=&NUM_OF_APPROVALS_REQUIRED;
497 num_of_approvals_remaining=&NUM_OF_APPROVALS_REQUIRED;
498 reviewed_by_nm='';
499 reviewed_on_dttm=.;
500run;
501
502%mp_lockanytable(LOCK,lib=&mpelib,ds=mpe_submit,
503 ref=%str(&mperef update in &_program),
504 ctl_ds=&mpelib..mpe_lockanytable
505)
506proc append base= &mpelib..mpe_submit data=append_app;
507run;
508%mp_lockanytable(UNLOCK,
509 lib=&mpelib,ds=mpe_submit,
510 ctl_ds=&mpelib..mpe_lockanytable
511)
512
513/* send email to REVIEW members */
514%put sending mpe_alerts;
515%mpe_alerts(alert_event=SUBMITTED
516 , alert_lib=&libref
517 , alert_ds=&ds
518 , dsid=&mperef
519)
520/* DISABLE EMAIL FOR NOW
521 %let b2=REASON: %quote(&submitted_reason_txt);
522
523 %local URLNOTES;
524 %if %length(&notes)>0 %then %let URLNOTES=%quote(%sysfunc(urlencode(&notes)));
525
526 %let b3=%str(Click to review / approve: )%trim(
527 )%str(http://&_srvname:&_srvport&_url?_PROGRAM=/Web/approvals&)%trim(
528 )TABLEID=&dsid%str(&)BASETABLE=&libref..&ds%str(&)NOTES=&URLNOTES;
529
530 %let b4=%str(Reference ID: &mperef);
531*/
532
533%put mpe_loader finishing up with syscc=&syscc;
534%if &syscc le 4 %then %do;
535 %local dur;
536 data _null_;
537 now=symget('now');
538 dur=%sysfunc(datetime())-&now;
539 call symputx('dur',dur,'l');
540 putlog 'Updating mpe_loads with the following query:';
541 putlog "update &mpelib..mpe_loads set STATUS='SUCCESS'";
542 putlog " , duration=" dur;
543 putlog " , processed_dttm=" now;
544 putlog " , approvals = '&libref..&ds'";
545 putlog " where CSV_DIR='&mperef';";
546 run;
547 proc sql;
548 update &mpelib..mpe_loads set STATUS='SUCCESS'
549 , duration=&dur
550 , processed_dttm=&now
551 , approvals = "&libref..&ds"
552 where CSV_DIR="&mperef";
553%end;
554%else %do;
555 %mpe_loadfail(
556 status="FAILED - &syscc"
557 ,now=&now
558 ,approvals=&libref..&ds
559 ,mperef=&mperef
560 ,dc_dttmtfmt=&dc_dttmtfmt.
561 )
562 %return;
563%end;
564
565%mend mpe_loader;