data _null_; length ssa1 $ 43 ssa2 $ 32 ssa3 $ 9; retain blanks ' ' wirenum 0 chkpnum 0; 1 infile acctsam dli ssa=(ssa1,ssa2,ssa3) call=func pcb=pcbindex status=st segment=seg; /* get hold next WIRETRAN segment from WIRETRN database */ func = 'GHN '; ssa1 = ' '; ssa2 = ' '; ssa3 = ' '; 2 pcbindex = 5; 3 input @1 wiressn $char11. @12 wireacct $char12. @24 accttype $char1. @25 wiredate mmddyy8. @33 wiretime time8. @41 wireammt pd5.2 @46 wiredesc $char40.; if st ¬= ' ' then if st = 'GB' then do; _error_ = 0; go to reptotal; end; else link abendit; 4 if wirenum/5 = chkpnum then link chkp; 5 amount = abs(wireammt); /* insert debit or credit segment into ACCTDBD database */ 6 if accttype = 'C' then do; ssa2 = 'CHCKACCT (ACNUMBER= '|| wireacct ||')'; if wireammt > 0 then ssa3 = 'CHCKCRDT'; else ssa3 = 'CHCKDEBT'; end; else 7 if accttype = 'S' then do; ssa2 = 'SAVEACCT (ACNUMBER= ' || wireacct || ')'; if wireammt > 0 then ssa3 = 'SAVECRDT'; else ssa3 = 'SAVEDEBT'; end; 8 else do; file log; put / '***** Invalid ' accttype= 'for ' wiressn= wireacct= '*****'; return; end; 9 ssa1 = 'CUSTOMER (SSNUMBER= ' || wiressn || ')'; func = 'ISRT'; pcbindex = 4; file acctsam dli; 10 put @1 amount pd5.2 @6 wiredate mmddyy6. @14 wiretime time8. @22 wiredesc $char40. @62 blanks $char19.; 11 if st ¬= ' ' then if st = 'GE' then do; _error_ = 0; file log; if seg = 'CUSTOMER' then if accttype = 'C' then put / '***** No CHCKACCT segment with ' wiressn= wireacct= '*****'; else put / '***** No SAVEACCT segment with ' wiressn= wireacct= '*****'; else put / '***** No CUSTOMER segment with ' wiressn= '*****'; return; end; else link abendit; /* get hold checking or savings segment from ACCTDBD database */ 12 ssa3 = ' '; func = 'GHU'; input @1 acnumber $char12. @13 balance pd5.2 @18 stmtdate mmddyy6. @26 stmt_bal pd5.2; 13 if st ¬= ' ' then link abendit; /* replace checking or savings segment into ACCTDBD database */ balance = balance + wireammt; ssa1 = ' '; ssa2 = ' '; func = 'REPL'; put @1 acnumber $char12. @13 balance pd5.2 @18 stmtdate mmddyy6. @26 stmt_bal pd5.2; if st ¬= ' ' then link abendit; /* delete WIRETRAN segment from WIRETRN database */ 14 func = 'DLET'; ssa1 = ' '; pcbindex = 5; put @1 wiressn $char11. @12 wireacct $char12. @24 accttype $char1. @25 wiredate mmddyy8. @33 wiretime time8. @41 wireammt pd5.2 @46 wiredesc $char40.; 15 if st ¬= ' ' then link abendit; 16 wirenum +1; return; 17 reptotal: file log; put // 'Number of Wire Transactions Posted =' wirenum 5. / ' Number of CHKP Calls Issued =' chkpnum 5.; stop; 18 chkp: chkpnum +1; func = 'CHKP'; pcbindex = 1; file acctsam dli; put @1 'SAS' @4 chkpnum z5.; if st ¬= ' ' then link abendit; func = 'GHU '; ssa1 = 'WIRETRAN (SSNACCT = ' || wiressn || wireacct || ')'; pcbindex = 5; input; if st ¬= ' ' then link abendit; return; 19 abendit: file log; put _all_; abort; run;
1 | The program uses the ACCTSAM PSB. It contains PCBs for the AcctDBD database and a PCB for the WireTrn database, both of which are needed in this program. |
2 | PCBINDEX is set to point to the WIRETRN PCB. |
3 | The INPUT statement issues the GHN call to retrieve a WIRETRAN segment. If the call
is not successful, and there is a GB status code (end-of-database), _ERROR_ is reset to 0 and the program branches to the REPTOTAL
subroutine, which
prints a summary report. For any other non-blank status code, the program skips to
the ABENDIT subroutine, which forces an abend.
|
4 | If the GHN call is successful, the program continues with a test to determine whether
a CHKP call should be issued. Two accumulator variables, WIRENUM and CHKPNUM, are
evaluated. WIRENUM is a value that is incremented each time an AcctDBD database record
is successfully updated. CHKPNUM is a value incremented each time a CHKP call
is issued.
A CHKP call is issued anytime the WIRENUM value divided by five equals CHKPNUM. That
is, after five successful updates the program links to the subroutine labeled CHKP
to issue the CHKP call. After the CHKP call, the program repositions itself in the
database and continues processing the DATA step. (See item 18.)
|
5 | The program goes on to set up for the REPL call that updates the balance information in the CHCKACCT and SAVEACCT segments of the AcctDBD database. The absolute value of WIREAMMT is saved. |
6 | The value of the ACCTTYPE field is checked. If the ACCTTYPE is C (checking), a qualified SSA for the CHCKACCT segment is built by concatenating literal values with the value
of the WIREACCT variable from WIRETRAN. The value of WIREAMMT is checked to build another, unqualified SSA that specifies the segment type to insert. If WIREAMMT is greater than 0, the SSA specifies the CHCKCRDT segment. If WIREAMMT is less than or equal to 0, the SSA specifies
CHCKDEBT.
|
7 | These statements are identical to the preceding group of statements, except that they build SSAs that define a savings account segment path rather than a checking account segment path. |
8 | If
the value of ACCTTYPE is not C or S , the account type is not valid for the DATA step and an explanatory message is written to the log.
Processing returns to the beginning
of the DATA step again.
|
9 | A qualified SSA for the CUSTOMER segment is built by concatenating literals with the value of WIRESSN from WIRETRAN. An ISRT call using the AcctDBD PCB is set up. |
10 | The ISRT call is issued. Depending on the ACCTTYPE and the value of WIREAMMT, the inserted segment is a CHCKCRDT, CHCKDEBT, SAVECRDT, or SAVEDEBT segment, as specified by the SSAs. Since all four transaction segment types have the same format, only one PUT statement is needed. |
11 | This series of statements checks the status code after the ISRT call and writes explanatory
messages to the SAS log if the status
code is GE (segment not found). If the status code is a non-blank code other than GE , the program skips to the ABENDIT subroutine. Note that a FILE statement is issued,
changing the output destination from the DL/I database to the SAS log.
|
12 | If the ISRT call is successful, the account balance must be updated to reflect the amount of the processed transaction. First, a GHU call is set up. The variable SSA3 is set to blank, but SSA1 (for the CUSTOMER segment) and SSA2 (for the CHCKACCT or SAVEACCT segment) are still in effect. The INPUT statement issues the GHU call, which retrieves the parent CHCKACCT or SAVEACCT segment for the segment just added by the ISRT call. |
13 | If the GHU call fails, the program skips to the ABENDIT subroutine. Otherwise, the program updates the BALANCE value by adding the value of WIREAMMT from the wire transaction and issues a REPL call to replace the CHCKACCT or SAVEACCT segment retrieved by the GHU call. If the REPL call fails, the program branches to the ABENDIT subroutine. |
14 | If the REPL call is successful, a DLET call is issued for the WireTrn database. The WIRETRAN segment just used to update the AcctDBD database (retrieved with a GHN or GHU call earlier) is deleted. Because wire transaction segments are deleted as they are processed, this program can be restarted. That is, if the program stops for some reason (such as a system failure), it can be started again without any danger of duplicate transactions being added to the AcctDBD database. |
15 | If the DLET call is not successful, the program links to the ABENDIT subroutine. |
16 | If the DLET call is successful, the WIRENUM accumulator variable is incremented, and processing returns to the beginning of the DATA step. |
17 | This subroutine is executed when a get call to the WireTrn database returns a GB (end-of-database) status code (see item 2).
|
18 | This subroutine issues the CHKP call after every fifth update. (See item 4.) If the
CHKP call is not successful, the program links to the ABENDIT subroutine. If the CHKP
call is successful, the database position has been lost. Therefore, a GHU call is
set up to re-retrieve the WIRETRAN
segment that is retrieved by the previous GHN call. Because the values from the segment
are
still in the program data vector, the INPUT statement issuing the GHU call does not
need to specify variable names.
If the GHU call fails
for any reason, the program links to the ABENDIT subroutine. If the
call succeeds, the program resumes processing at the assignment statement
that follows the LINK CHKP statement.
|
19 | These statements are executed when a bad status code is returned by one of the calls in the program. The contents of the program data vector are printed on the SAS log, and the program abends. |
filename tranin '<your.sas.tranin>' disp=shr; data _null_; length ssa1 $31 ssa2 $32 ssa3 $9; retain blanks ' ' wirenum 0 chkpnum 0; /* get data from TRANIN flatfile */ 1 infile tranin eof=reptotal; input @1 cust_ssn $char11. @12 acct_num $char12. @24 accttype $char1. @25 wiredate mmddyy8. @33 wiretime time8. @41 wireammt pd5.2 @46 wiredesc $char40.; if _error_ then link abendit; 2 if wirenum/5 = chkpnum then link chkp; 3 amount = abs(wireammt); 4 if accttype = 'C' then do; ssa2 = 'CHCKACCT (ACNUMBER =' || acct_num || ')'; if wireammt < 0 then ssa3 = 'CHCKCRDT'; else ssa3 = 'CHCKDEBT'; end; else if accttype = 'S' then do; ssa2 = 'SAVEACCT (ACNUMBER =' || acct_num || ')'; if wireammt < 0 then ssa3 = 'SAVECRDT'; else ssa3 = 'SAVEDEBT'; end; else do; file log; put / '***** Invalid ' accttype= 'for ' cust_ssn= acct_num= '*****'; return; end; /* insert debit or credit segment into ACCTDBD database */ 5 infile acctsam dli ssa=(ssa1,ssa2,ssa3) call=func pcb=pcbindex status=st segment=seg; ssa1 = 'CUSTOMER(SSNUMBER =' || CUST_SSN || ')'; func = 'ISRT'; pcbindex = 4; file acctsam dli; put @1 amount pd5.2 @6 wiredate mmddyy6. @14 wiretime time8. @22 wiredesc $char40. @62 blanks $char19.; 6 if st ¬= ' ' then if st = 'GE' then do; _error_ = 0; file log; if seg = 'CUSTOMER' then if accttype = 'C' then put / '***** No CHCKACCT segment with ' cust_ssn= acct_num= ' *****'; else put / '***** No SAVEACCT segment with ' cust_ssn= acct_num= ' *****'; else put / '***** No CUSTOMER segment with ' cust_ssn= '*****'; return; end; else link abendit; /* get hold checking or savings segment from ACCTDBD database */ ssa3 = ' '; 7 func = 'GHU'; input @1 acnumber $char12. @13 balance pd5.2 @18 stmtdate mmddyy6. @26 stmt_bal pd5.2; if st ¬= ' ' then link abendit; balance = balance + wireammt; /* replace checking or savings segment into ACCTDBD database */ ssa1 = ' '; ssa2 = ' '; func = 'REPL'; 8 put @1 acnumber $char12. @13 balance pd5.2 @18 stmtdate mmddyy6. @26 stmt_bal pd5.2; if st ¬= ' ' then link abendit; 9 if wireammt > 0 then debtnum +1; else crdtnum +1; 10 wirenum +1; return; reptotal: file log; put // 'Number of debit transactions posted =' debtnum 8. / 'Number of credit transactions posted =' crdtnum 8.; stop; 11 chkp: chkpnum +1; func = 'CHKP'; pcbindex = 1; file acctsam dli; put @1 'SAS' @4 chkpnum z5.; if st ¬= ' ' then link abendit; return; abendit: file log; put _all_; abort; run; filename tranin clear;
1 | The standard INFILE statement specifies the external sequential file containing the data to update AcctDBD. The fileref is TranIn. When the end-of-file condition is set, the program branches to the REPTOTAL subroutine to print a summary report. The standard INPUT statement reads a record from TranIn. If any error occurs, the program links to the ABENDIT subroutine. |
2 | As in the previous example, this program issues CHKP calls after every fifth update. If the value of WIRENUM divided by five is equal to the value of CHKPNUM, the program links to a section that issues the CHKP call. |
3 | The DATA step sets up for the REPL call that updates balance information in the CHCKACCT and SAVEACCT segments of the AcctDBD database. The absolute value of WIREAMMT is saved. |
4 | Depending on the value of ACCTTYPE, SSAs are built for the CHCKACCT and either the CHCKDEBT or CHCKCRDT segments, or for the SAVEACCT and either the SAVEDEBT or SAVECRDT segments. |
5 | The DL/I INFILE statement specifies the ACCTSAM PSB. An ISRT call for the AcctDBD database is formatted and issued. Depending on the account type and transaction type, a new CHCKCRDT, CHCKDEBT, SAVECRDT, or SAVEDEBT segment is inserted. |
6 | This section checks status codes and prints explanatory messages on the SAS log if
the status code is GE (segment not found). For other non-blank status codes, the program links to the
ABENDIT subroutine.
|
7 | If the ISRT call is successful, a GHU call is issued to retrieve the parent of the added segment. The status code is checked after the call and, if it is not successful, the program links to the ABENDIT routine. |
8 | If the GHU call is successful, the account balance is updated by a REPL call. The status code is checked after the call and, if it is not successful, the program links to the ABENDIT routine. |
9 | Accumulator variables count the number of debits and credits posted by the program. These values are used to print a summary report. |
10 | The WIRENUM variable is incremented. It is used to determine whether a CHKP call is needed. (See item 2.) |
11 | This section is like the one in Example 1: Updating a Database, but no GHU call is issued to re-establish database position because there is no database position to maintain. (This is because the wire transactions are not coming from an IMS database on which the program can reposition.) |
filename tranin '<your.sas.tranin>' disp=shr; 1 filename restart '<your.sas.restart>' disp=shr; data _null_; length ssa1 $31 ssa2 $32 ssa3 $9 chkpnum 5; retain wireskip wirenum 0 chkpnum 0 first 1 debtnum crdtnum errnum 0 blanks ' '; infile restart eof=process; input @1 chkpid 5. @6 chkptime datetime13. @19 chkdebt 8. @27 chkcrdt 8. @35 chkerr 8.; wireskip = chkdebt + chkcrdt + chkerr; file log; put 'Restarting from checkpoint ' chkpid 'taken at ' chkptime datetime13. ' to bypass ' wireskip 'trans already processed'; do while(wireread < wireskip); infile tranin; input @1 cust_ssn $char11. @12 acct_num $char12. @24 accttype $char1. @25 wiredate mmddyy8. @33 wiretime time8. @41 wireammt pd5.2 @46 wiredesc $char40.; wireread + 1; end; debtnum = chkdebt; crdtnum = chkcrdt; wirenum = debtnum + crdtnum; errnum = chkerr; 2 process: infile tranin eof=reptotal; input @1 cust_ssn $char11. @12 acct_num $char12. @24 accttype $char1. @25 wiredate mmddyy8. @33 wiretime time8. @41 wireammt pd5.2 @46 wiredesc $char40.; if _error_ then link abendit; if wirenum/5 = chkpnum or first =1 then do; link chkp; first =0; end; amount = abs(wireammt); if accttype = 'C' then do; ssa2 = 'CHCKACCT (ACNUMBER= ' || acct_num || ')'; if wireammt < 0 then ssa3 = 'CHCKCRDT'; else ssa3 = 'CHCKDEBT'; end; else if accttype = 'S' then do; ssa2 = 'SAVEACCT (ACNUMBER= ' || acct_num || ')'; if wireammt < 0 then ssa3 = 'SAVECRDT'; else ssa3 = 'SAVEDEBT'; end; 3 else do; file log; put / '***** Invalid ' accttype= 'for ' cust_ssn= acct_num= '*****'; go to outerr; end; infile acctsam dli ssa=(ssa1,ssa2,ssa3) call=func pcb=pcbindex status=st segment=seg; ssa1 = 'CUSTOMER(SSNUMBER= ' || cust_ssn || ')'; func = 'ISRT'; pcbindex = 4; file acctsam dli; put @1 amount pd5.2 @6 wiredate mmddyy6. @14 wiretime time8. @22 wiredesc $char40. @62 blanks $char19.; if st ¬= ' ' then if st = 'GE' then do; _error_ = 0; file log; if seg = 'CUSTOMER' then if accttype = 'C' then put / '***** No CHCKACCT segment with ' cust_ssn= acct_num= '*****'; else put / '***** No SAVEACCT segment with ' cust_ssn= acct_num= '*****'; else put / '***** No CUSTOMER segment with ' cust_ssn= '*****'; go to outerr; end; else link abendit; ssa3 = ' '; func = 'GHU '; input @1 acnumber $char12. @13 balance pd5.2 @18 stmtdate mmddyy6. @26 stmt_bal pd5.2; if st ¬= ' ' then link abendit; balance = balance + wireammt; ssa1 = ' '; ssa2 = ' '; func = 'REPL'; put @1 acnumber $char12. @13 balance pd5.2 @18 stmtdate mmddyy6. @26 stmt_bal pd5.2; if st ¬= ' ' then link abendit; if wireammt > 0 then debtnum = debtnum +1; else crdtnum = crdtnum +1; wirenum = wirenum +1; return; reptotal: file log; put // 'Number of debit transactions posted =' debtnum 8. / 'Number of credit transactions posted =' crdtnum 8.; stop; 4 chkp: chkpnum +1; chkptime = datetime(); file log; put @1 'Next checkpoint will be' @25 chkpnum @30 chkptime datetime13. @43 debtnum @51 crdtnum @59 errnum; func = 'CHKP'; pcbindex = 1; file acctsam dli; put @1 'SAS' @4 chkpnum z5.; if st ¬= ' ' then link abendit; return; outerr: errnum = errnum +1; return; abendit: file log; put _all_; abort; run; filename tranin clear; filename restart clear;
1 | This group of statements initiates the restart, if a restart is necessary. The standard
INFILE statement points to a file with fileref
Restart. The Restart file has one record, a "control card" with data that determines
where processing should resume in the sequential input file. The data in the RESTART
file is taken from the last checkpoint message written on the SAS log by the program that ended before completing processing.
The message includes the number and time of the last checkpoint, and the values of
the accumulator variables counting the number of debit transactions posted (CHCKDEBT),
credit transactions posted (CHCKCRDT), and the number of bad records in the TranIn
file (CHKERR).
The RESTART DD statement can be dummied out to execute the program normally (not
as a restart). If RESTART is dummied out in the control language, end-of-file occurs
immediately,
and the program skips to the PROCESS subroutine (see item 6), as indicated by the
EOF= option.
The WIRESKIP variable is the sum of CHCKDEBT, CHCKCRDT, and CHKERR. That is, WIRESKIP
represents the number
of records in TranIn that were processed by the program before the last checkpoint.
A message is written to the SAS log that shows the checkpoint from which processing
resumes.
To position itself at the correct TranIn record, the program reads the number of records
indicated by the WIRESKIP variable. In other words, the program re-reads all records
that were read in the first execution
of the program, up to the last checkpoint.
The values of DEBTNUM,
CRDTNUM, WIRENUM, and ERRNUM are reset so that the final report shows
the correct number of transactions. Otherwise, the report would show
only the number of transactions processed in the restarted execution.
|
2 | These statements are the same as the statements in Example 2: Incorrectly Updating a Database without Recovery Logic except that they are labeled "PROCESS." If the program is not being restarted, end-of-file for the INFILE Restart occurs immediately, and the program branches to this subroutine. |
3 | If
the value of ACCTTYPE is anything but C or S , the TranIn record is a bad record. The program prints a message on the SAS log and
branches to the OUTERR subroutine, which increments the ERRNUM accumulator variable.
|
4 | The
CHKP call is issued by this group of statements. This group is like
that in Example 2: Incorrectly Updating a Database without Recovery Logic except that a message about the checkpoint is also printed on the SAS log. This message
provides the necessary information for
a restart.
Note that the message is written to the SAS log before the CHKP call is actually issued,
so it is possible that a system failure could occur between the time the message is
written and the time the call is issued. Therefore, if a restart is necessary, you
should verify that the last checkpoint referenced in the SAS log is the same as the
last checkpoint in the DL/I log. This can be done by comparing checkpoint IDs.
|