Case Study 2: Online Account Balance Inquiry at Summit Federal Savings
Background
Summit Federal Savings operates a network of 85 branches and 200 ATM locations serving 1.8 million customers. Their account inquiry system is one of the most frequently accessed applications on the mainframe -- branch tellers perform an average of 340,000 balance inquiries per day, and the ATM network adds another 180,000. Every inquiry must be completed within 300 milliseconds to maintain acceptable response times at the teller window.
The inquiry system reads from a VSAM KSDS customer account file that is updated nightly by the batch cycle. During business hours, the file is accessed in read-only mode by hundreds of concurrent inquiry transactions. The file contains 1.8 million records, and any account can be requested at any time.
The inquiry program (ACCTINQ) must support three use cases:
- Direct lookup: A teller enters an account number and retrieves the balance and account details.
- Name search with browse: A teller enters a partial customer name and browses through matching records to find the correct account.
- Forward/backward browsing: After finding an account, the teller can browse forward or backward through adjacent accounts (useful for reviewing joint accounts or related accounts in the same number range).
Yuki Tanaka, a CICS COBOL developer at Summit Federal, designed the inquiry program to demonstrate indexed file access patterns typical of an online inquiry system, with particular emphasis on error handling for not-found conditions, browse positioning, and the simulation of forward and backward navigation.
The Problem
Performance Requirements
- Direct lookup by account number: less than 50 milliseconds
- Name search (first match): less than 100 milliseconds
- Browse forward (per record): less than 20 milliseconds
- Browse backward (per record): less than 50 milliseconds (more expensive than forward)
Error Handling Requirements
- Account not found: Display a clear message; do not abend
- End of file during browse: Display "no more records" and stop browsing
- Beginning of file during backward browse: Display "no earlier records" and stop
- Concurrent access conflict: Retry the read up to 3 times before reporting an error
- I/O error: Log the error with the file status code and return gracefully
Browsing Requirements
The teller must be able to: 1. Enter an account number and see the account details 2. Press "Next" to see the next account in sequence 3. Press "Previous" to see the prior account in sequence 4. Enter a partial name and see the first matching account 5. Continue browsing forward through additional name matches
The Solution
The COBOL Program
IDENTIFICATION DIVISION.
PROGRAM-ID. ACCTINQ.
AUTHOR. YUKI TANAKA.
DATE-WRITTEN. 2025-01-20.
*================================================================
* PROGRAM: ACCTINQ - ACCOUNT BALANCE INQUIRY
* PURPOSE: Simulate an online account inquiry system
* using VSAM KSDS indexed files. Demonstrates
* random read, START/READ NEXT for forward
* browsing, START/READ PREVIOUS for backward
* browsing, and comprehensive error handling
* for not-found and end-of-file conditions.
*================================================================
ENVIRONMENT DIVISION.
INPUT-OUTPUT SECTION.
FILE-CONTROL.
SELECT ACCOUNT-FILE
ASSIGN TO ACCTMAST
ORGANIZATION IS INDEXED
ACCESS MODE IS DYNAMIC
RECORD KEY IS FS-AF-ACCOUNT-NO
ALTERNATE RECORD KEY IS FS-AF-CUST-NAME
WITH DUPLICATES
FILE STATUS IS WS-ACCT-STATUS.
DATA DIVISION.
FILE SECTION.
FD ACCOUNT-FILE.
01 FS-ACCOUNT-RECORD.
05 FS-AF-ACCOUNT-NO PIC 9(10).
05 FS-AF-CUST-NAME PIC X(30).
05 FS-AF-ADDR-LINE-1 PIC X(30).
05 FS-AF-CITY PIC X(20).
05 FS-AF-STATE PIC X(2).
05 FS-AF-ZIP PIC X(5).
05 FS-AF-ACCT-TYPE PIC X(1).
88 IS-CHECKING VALUE 'C'.
88 IS-SAVINGS VALUE 'S'.
88 IS-MONEY-MKT VALUE 'M'.
88 IS-LOAN VALUE 'L'.
05 FS-AF-BALANCE PIC S9(11)V99 COMP-3.
05 FS-AF-AVAIL-BALANCE PIC S9(11)V99 COMP-3.
05 FS-AF-HOLD-AMOUNT PIC S9(9)V99 COMP-3.
05 FS-AF-OPEN-DATE PIC 9(8).
05 FS-AF-LAST-ACTIVITY PIC 9(8).
05 FS-AF-STATUS PIC X(1).
88 ACCT-ACTIVE VALUE 'A'.
88 ACCT-INACTIVE VALUE 'I'.
88 ACCT-CLOSED VALUE 'X'.
88 ACCT-FROZEN VALUE 'F'.
05 FS-AF-OD-LIMIT PIC S9(7)V99 COMP-3.
05 FS-AF-BRANCH PIC X(4).
05 FILLER PIC X(17).
WORKING-STORAGE SECTION.
*----------------------------------------------------------------
* FILE STATUS AND ERROR HANDLING
*----------------------------------------------------------------
01 WS-ACCT-STATUS PIC X(2).
88 ACCT-OK VALUE "00".
88 ACCT-DUP-ALT-KEY VALUE "02".
88 ACCT-EOF VALUE "10".
88 ACCT-KEY-NOT-FOUND VALUE "23".
88 ACCT-KEY-SEQ-ERR VALUE "21".
88 ACCT-NO-PREV-REC VALUE "46".
01 WS-RETRY-COUNT PIC 9(1) VALUE 0.
01 WS-MAX-RETRIES PIC 9(1) VALUE 3.
*----------------------------------------------------------------
* INQUIRY REQUEST FIELDS
*----------------------------------------------------------------
01 WS-REQUEST-TYPE PIC X(1).
88 REQ-DIRECT-LOOKUP VALUE 'D'.
88 REQ-NAME-SEARCH VALUE 'N'.
88 REQ-BROWSE-NEXT VALUE 'F'.
88 REQ-BROWSE-PREV VALUE 'B'.
88 REQ-QUIT VALUE 'Q'.
01 WS-INPUT-ACCOUNT PIC 9(10).
01 WS-INPUT-NAME PIC X(30).
01 WS-CURRENT-KEY PIC 9(10).
01 WS-BROWSE-ACTIVE PIC X(1) VALUE 'N'.
88 BROWSE-IS-ACTIVE VALUE 'Y'.
88 BROWSE-NOT-ACTIVE VALUE 'N'.
*----------------------------------------------------------------
* DISPLAY FORMATTING
*----------------------------------------------------------------
01 WS-DISP-BALANCE PIC $Z,ZZZ,ZZZ,ZZ9.99-.
01 WS-DISP-AVAIL PIC $Z,ZZZ,ZZZ,ZZ9.99-.
01 WS-DISP-HOLD PIC $Z,ZZZ,ZZ9.99-.
01 WS-DISP-OD-LIMIT PIC $Z,ZZZ,ZZ9.99-.
01 WS-DISP-TYPE PIC X(15).
01 WS-DISP-STATUS PIC X(10).
01 WS-DISP-DATE PIC X(10).
*----------------------------------------------------------------
* SESSION STATISTICS
*----------------------------------------------------------------
01 WS-STATS.
05 WS-TOTAL-INQUIRIES PIC S9(5) COMP-3 VALUE 0.
05 WS-DIRECT-LOOKUPS PIC S9(5) COMP-3 VALUE 0.
05 WS-NAME-SEARCHES PIC S9(5) COMP-3 VALUE 0.
05 WS-BROWSE-FORWARDS PIC S9(5) COMP-3 VALUE 0.
05 WS-BROWSE-BACKWARDS PIC S9(5) COMP-3 VALUE 0.
05 WS-NOT-FOUND-COUNT PIC S9(5) COMP-3 VALUE 0.
05 WS-IO-ERRORS PIC S9(5) COMP-3 VALUE 0.
01 WS-DISP-COUNT PIC Z,ZZ9.
PROCEDURE DIVISION.
0000-MAIN-CONTROL.
PERFORM 1000-INITIALIZE
PERFORM 2000-PROCESS-INQUIRIES
UNTIL REQ-QUIT
PERFORM 8000-SESSION-SUMMARY
PERFORM 9000-FINALIZE
STOP RUN
.
1000-INITIALIZE.
DISPLAY "============================================="
DISPLAY " SUMMIT FEDERAL SAVINGS"
DISPLAY " ACCOUNT BALANCE INQUIRY SYSTEM"
DISPLAY "============================================="
DISPLAY " "
OPEN INPUT ACCOUNT-FILE
IF NOT ACCT-OK
DISPLAY "FATAL: Cannot open account file. "
"Status: " WS-ACCT-STATUS
STOP RUN
END-IF
DISPLAY "System ready. Enter commands:"
DISPLAY " D nnnnnnnnn - Direct lookup by account"
DISPLAY " N name - Search by customer name"
DISPLAY " F - Browse forward (next)"
DISPLAY " B - Browse backward (previous)"
DISPLAY " Q - Quit"
DISPLAY " "
.
2000-PROCESS-INQUIRIES.
* -------------------------------------------------------
* Simulate receiving inquiry requests. In a real CICS
* program, these would come from the terminal. Here
* we demonstrate each pattern with hard-coded test data.
* -------------------------------------------------------
* Test 1: Direct lookup
DISPLAY ">>> INQUIRY: Direct lookup for 1234567890"
SET REQ-DIRECT-LOOKUP TO TRUE
MOVE 1234567890 TO WS-INPUT-ACCOUNT
PERFORM 3000-DIRECT-LOOKUP
* Test 2: Browse forward from current position
DISPLAY ">>> INQUIRY: Browse forward"
SET REQ-BROWSE-NEXT TO TRUE
PERFORM 4000-BROWSE-FORWARD
* Test 3: Browse forward again
DISPLAY ">>> INQUIRY: Browse forward"
SET REQ-BROWSE-NEXT TO TRUE
PERFORM 4000-BROWSE-FORWARD
* Test 4: Browse backward
DISPLAY ">>> INQUIRY: Browse backward"
SET REQ-BROWSE-PREV TO TRUE
PERFORM 5000-BROWSE-BACKWARD
* Test 5: Name search
DISPLAY ">>> INQUIRY: Name search for JOHNSON"
SET REQ-NAME-SEARCH TO TRUE
MOVE "JOHNSON" TO WS-INPUT-NAME
PERFORM 6000-NAME-SEARCH
* Test 6: Not-found scenario
DISPLAY ">>> INQUIRY: Lookup non-existent account"
SET REQ-DIRECT-LOOKUP TO TRUE
MOVE 9999999999 TO WS-INPUT-ACCOUNT
PERFORM 3000-DIRECT-LOOKUP
* Done with demonstrations
SET REQ-QUIT TO TRUE
.
3000-DIRECT-LOOKUP.
* -------------------------------------------------------
* Random READ by primary key.
* On success, display the account details and establish
* the browse position for subsequent forward/backward.
* -------------------------------------------------------
ADD 1 TO WS-TOTAL-INQUIRIES
ADD 1 TO WS-DIRECT-LOOKUPS
MOVE WS-INPUT-ACCOUNT TO FS-AF-ACCOUNT-NO
MOVE ZERO TO WS-RETRY-COUNT
PERFORM 3100-READ-WITH-RETRY
EVALUATE TRUE
WHEN ACCT-OK
WHEN ACCT-DUP-ALT-KEY
MOVE FS-AF-ACCOUNT-NO TO WS-CURRENT-KEY
SET BROWSE-IS-ACTIVE TO TRUE
PERFORM 7000-DISPLAY-ACCOUNT
WHEN ACCT-KEY-NOT-FOUND
DISPLAY " "
DISPLAY " *** ACCOUNT NOT FOUND ***"
DISPLAY " Account number: "
WS-INPUT-ACCOUNT
DISPLAY " Please verify the account"
" number and try again."
DISPLAY " "
ADD 1 TO WS-NOT-FOUND-COUNT
SET BROWSE-NOT-ACTIVE TO TRUE
WHEN OTHER
DISPLAY " I/O ERROR on direct lookup."
DISPLAY " File status: "
WS-ACCT-STATUS
ADD 1 TO WS-IO-ERRORS
SET BROWSE-NOT-ACTIVE TO TRUE
END-EVALUATE
.
3100-READ-WITH-RETRY.
* -------------------------------------------------------
* Attempt a random READ with retry logic for transient
* errors (e.g., concurrent access conflicts). In a
* real CICS environment, the retry might include a
* brief delay (EXEC CICS DELAY).
* -------------------------------------------------------
PERFORM UNTIL WS-RETRY-COUNT >= WS-MAX-RETRIES
READ ACCOUNT-FILE
KEY IS FS-AF-ACCOUNT-NO
END-READ
IF ACCT-OK OR ACCT-DUP-ALT-KEY
OR ACCT-KEY-NOT-FOUND
EXIT PERFORM
ELSE
ADD 1 TO WS-RETRY-COUNT
IF WS-RETRY-COUNT < WS-MAX-RETRIES
DISPLAY " Retry " WS-RETRY-COUNT
" for account "
FS-AF-ACCOUNT-NO
END-IF
END-IF
END-PERFORM
.
4000-BROWSE-FORWARD.
* -------------------------------------------------------
* Read the next record in primary key sequence.
* If browse is not active (no prior lookup), position
* at the beginning of the file first.
* -------------------------------------------------------
ADD 1 TO WS-TOTAL-INQUIRIES
ADD 1 TO WS-BROWSE-FORWARDS
IF BROWSE-NOT-ACTIVE
* No previous position -- start from beginning
MOVE ZERO TO FS-AF-ACCOUNT-NO
START ACCOUNT-FILE
KEY IS >= FS-AF-ACCOUNT-NO
INVALID KEY
DISPLAY " *** FILE IS EMPTY ***"
EXIT PARAGRAPH
NOT INVALID KEY
CONTINUE
END-START
SET BROWSE-IS-ACTIVE TO TRUE
END-IF
* Read the next sequential record
READ ACCOUNT-FILE NEXT
END-READ
EVALUATE TRUE
WHEN ACCT-OK
WHEN ACCT-DUP-ALT-KEY
MOVE FS-AF-ACCOUNT-NO TO WS-CURRENT-KEY
PERFORM 7000-DISPLAY-ACCOUNT
WHEN ACCT-EOF
DISPLAY " "
DISPLAY " *** END OF FILE ***"
DISPLAY " No more accounts to display."
DISPLAY " "
SET BROWSE-NOT-ACTIVE TO TRUE
WHEN OTHER
DISPLAY " I/O ERROR during browse."
DISPLAY " File status: " WS-ACCT-STATUS
ADD 1 TO WS-IO-ERRORS
END-EVALUATE
.
5000-BROWSE-BACKWARD.
* -------------------------------------------------------
* Read the previous record in primary key sequence.
* This requires positioning at the current key
* using START, then using READ PREVIOUS.
*
* Note: READ PREVIOUS is a COBOL 2002 feature.
* On systems that do not support it, the backward
* browse must be simulated by reading from a saved
* position.
* -------------------------------------------------------
ADD 1 TO WS-TOTAL-INQUIRIES
ADD 1 TO WS-BROWSE-BACKWARDS
IF BROWSE-NOT-ACTIVE
DISPLAY " "
DISPLAY " *** NO CURRENT POSITION ***"
DISPLAY " Perform a lookup first, then browse."
DISPLAY " "
EXIT PARAGRAPH
END-IF
* Position at current key
MOVE WS-CURRENT-KEY TO FS-AF-ACCOUNT-NO
START ACCOUNT-FILE
KEY IS = FS-AF-ACCOUNT-NO
INVALID KEY
DISPLAY " Cannot reposition. Status: "
WS-ACCT-STATUS
SET BROWSE-NOT-ACTIVE TO TRUE
EXIT PARAGRAPH
NOT INVALID KEY
CONTINUE
END-START
* Read previous record
READ ACCOUNT-FILE PREVIOUS
END-READ
EVALUATE TRUE
WHEN ACCT-OK
WHEN ACCT-DUP-ALT-KEY
MOVE FS-AF-ACCOUNT-NO TO WS-CURRENT-KEY
PERFORM 7000-DISPLAY-ACCOUNT
WHEN ACCT-KEY-NOT-FOUND
WHEN ACCT-NO-PREV-REC
DISPLAY " "
DISPLAY " *** BEGINNING OF FILE ***"
DISPLAY " No earlier accounts to display."
DISPLAY " "
WHEN ACCT-EOF
DISPLAY " "
DISPLAY " *** BEGINNING OF FILE ***"
DISPLAY " Reached the start of the file."
DISPLAY " "
WHEN OTHER
DISPLAY " I/O ERROR during backward "
"browse."
DISPLAY " File status: " WS-ACCT-STATUS
ADD 1 TO WS-IO-ERRORS
END-EVALUATE
.
6000-NAME-SEARCH.
* -------------------------------------------------------
* Search by customer name using the alternate key.
* Position at the first matching (or next greater)
* name using START, then display matching records.
* This supports partial name searches: entering
* "JOHNSON" will find "JOHNSON, ALICE",
* "JOHNSON, ROBERT", etc.
* -------------------------------------------------------
ADD 1 TO WS-TOTAL-INQUIRIES
ADD 1 TO WS-NAME-SEARCHES
MOVE WS-INPUT-NAME TO FS-AF-CUST-NAME
* Position at the first name >= the search value
START ACCOUNT-FILE
KEY IS >= FS-AF-CUST-NAME
INVALID KEY
DISPLAY " "
DISPLAY " *** NO MATCHING NAMES ***"
DISPLAY " No accounts found for: "
WS-INPUT-NAME
DISPLAY " "
ADD 1 TO WS-NOT-FOUND-COUNT
EXIT PARAGRAPH
NOT INVALID KEY
CONTINUE
END-START
* Read and display matching records
* Stop when the name no longer starts with the
* search value (partial match)
PERFORM 6100-READ-NAME-MATCHES
.
6100-READ-NAME-MATCHES.
* -------------------------------------------------------
* Read sequentially through name matches.
* A partial match is detected by checking if the
* retrieved name starts with the search string.
* Uses reference modification to compare only the
* significant portion of the search name.
* -------------------------------------------------------
MOVE ZERO TO WS-RETRY-COUNT
PERFORM 3 TIMES
READ ACCOUNT-FILE NEXT
END-READ
EVALUATE TRUE
WHEN ACCT-OK
WHEN ACCT-DUP-ALT-KEY
* Check partial match using ref mod
IF FS-AF-CUST-NAME(1:
FUNCTION LENGTH(
FUNCTION TRIM(WS-INPUT-NAME)))
= FUNCTION TRIM(WS-INPUT-NAME)
MOVE FS-AF-ACCOUNT-NO
TO WS-CURRENT-KEY
SET BROWSE-IS-ACTIVE TO TRUE
PERFORM 7000-DISPLAY-ACCOUNT
ELSE
DISPLAY " (No more matches)"
EXIT PERFORM
END-IF
WHEN ACCT-EOF
DISPLAY " (End of file)"
EXIT PERFORM
WHEN OTHER
DISPLAY " Read error. Status: "
WS-ACCT-STATUS
EXIT PERFORM
END-EVALUATE
END-PERFORM
.
7000-DISPLAY-ACCOUNT.
* -------------------------------------------------------
* Format and display the account information in a
* teller-friendly format.
* -------------------------------------------------------
DISPLAY " "
DISPLAY " ========================================"
DISPLAY " ACCOUNT INFORMATION"
DISPLAY " ========================================"
DISPLAY " Account No: " FS-AF-ACCOUNT-NO
DISPLAY " Name: " FS-AF-CUST-NAME
DISPLAY " Address: " FS-AF-ADDR-LINE-1
DISPLAY " " FS-AF-CITY " "
FS-AF-STATE " "
FS-AF-ZIP
* Account type
EVALUATE TRUE
WHEN IS-CHECKING
MOVE "CHECKING" TO WS-DISP-TYPE
WHEN IS-SAVINGS
MOVE "SAVINGS" TO WS-DISP-TYPE
WHEN IS-MONEY-MKT
MOVE "MONEY MARKET" TO WS-DISP-TYPE
WHEN IS-LOAN
MOVE "LOAN" TO WS-DISP-TYPE
WHEN OTHER
MOVE "UNKNOWN" TO WS-DISP-TYPE
END-EVALUATE
DISPLAY " Type: " WS-DISP-TYPE
* Balance information
MOVE FS-AF-BALANCE TO WS-DISP-BALANCE
DISPLAY " Balance: " WS-DISP-BALANCE
MOVE FS-AF-AVAIL-BALANCE TO WS-DISP-AVAIL
DISPLAY " Available: " WS-DISP-AVAIL
IF FS-AF-HOLD-AMOUNT > ZERO
MOVE FS-AF-HOLD-AMOUNT TO WS-DISP-HOLD
DISPLAY " Hold: " WS-DISP-HOLD
END-IF
IF IS-CHECKING
MOVE FS-AF-OD-LIMIT TO WS-DISP-OD-LIMIT
DISPLAY " OD Limit: " WS-DISP-OD-LIMIT
END-IF
* Account status with visual indicator
EVALUATE TRUE
WHEN ACCT-ACTIVE
DISPLAY " Status: ACTIVE"
WHEN ACCT-INACTIVE
DISPLAY " Status: ** INACTIVE **"
WHEN ACCT-CLOSED
DISPLAY " Status: ** CLOSED **"
WHEN ACCT-FROZEN
DISPLAY " Status: *** FROZEN ***"
DISPLAY " WARNING: This account is frozen."
DISPLAY " Contact the Fraud Department"
" before proceeding."
WHEN OTHER
DISPLAY " Status: UNKNOWN ("
FS-AF-STATUS ")"
END-EVALUATE
* Activity dates
IF FS-AF-LAST-ACTIVITY > ZERO
STRING FS-AF-LAST-ACTIVITY(5:2)
DELIMITED BY SIZE
"/" DELIMITED BY SIZE
FS-AF-LAST-ACTIVITY(7:2)
DELIMITED BY SIZE
"/" DELIMITED BY SIZE
FS-AF-LAST-ACTIVITY(1:4)
DELIMITED BY SIZE
INTO WS-DISP-DATE
END-STRING
DISPLAY " Last Activity: " WS-DISP-DATE
END-IF
DISPLAY " Branch: " FS-AF-BRANCH
DISPLAY " ========================================"
DISPLAY " "
.
8000-SESSION-SUMMARY.
DISPLAY " "
DISPLAY "============================================="
DISPLAY " INQUIRY SESSION SUMMARY"
DISPLAY "============================================="
MOVE WS-TOTAL-INQUIRIES TO WS-DISP-COUNT
DISPLAY " Total inquiries: " WS-DISP-COUNT
MOVE WS-DIRECT-LOOKUPS TO WS-DISP-COUNT
DISPLAY " Direct lookups: " WS-DISP-COUNT
MOVE WS-NAME-SEARCHES TO WS-DISP-COUNT
DISPLAY " Name searches: " WS-DISP-COUNT
MOVE WS-BROWSE-FORWARDS TO WS-DISP-COUNT
DISPLAY " Browse forward: " WS-DISP-COUNT
MOVE WS-BROWSE-BACKWARDS TO WS-DISP-COUNT
DISPLAY " Browse backward: " WS-DISP-COUNT
MOVE WS-NOT-FOUND-COUNT TO WS-DISP-COUNT
DISPLAY " Not found: " WS-DISP-COUNT
MOVE WS-IO-ERRORS TO WS-DISP-COUNT
DISPLAY " I/O errors: " WS-DISP-COUNT
DISPLAY "============================================="
.
9000-FINALIZE.
CLOSE ACCOUNT-FILE
IF NOT ACCT-OK
DISPLAY "WARNING: Error closing account file."
" Status: " WS-ACCT-STATUS
END-IF
.
The Companion JCL with IDCAMS
//ACCTINQJ JOB (ACCT),'ACCT INQUIRY',
// CLASS=A,MSGCLASS=X,
// MSGLEVEL=(1,1),
// NOTIFY=&SYSUID
//*================================================================
//* JOB: ACCTINQJ
//* PURPOSE: DEFINE VSAM KSDS FOR ACCOUNT MASTER, LOAD DATA,
//* AND RUN THE ACCOUNT INQUIRY PROGRAM
//*================================================================
//*
//*-------- STEP 1: DELETE EXISTING CLUSTER -----------------------
//*
//DELETE EXEC PGM=IDCAMS
//SYSPRINT DD SYSOUT=*
//SYSIN DD *
DELETE SUMMIT.ACCOUNT.MASTER -
CLUSTER -
PURGE
IF LASTCC = 8 THEN -
SET MAXCC = 0
/*
/* ALSO DELETE ALTERNATE INDEX AND PATH IF THEY EXIST
/*
DELETE SUMMIT.ACCOUNT.MASTER.NAMEAIX -
PURGE
IF LASTCC = 8 THEN -
SET MAXCC = 0
/*
//*
//*-------- STEP 2: DEFINE THE VSAM KSDS CLUSTER ------------------
//*
//DEFINE EXEC PGM=IDCAMS,
// COND=(0,NE,DELETE)
//SYSPRINT DD SYSOUT=*
//SYSIN DD *
DEFINE CLUSTER -
(NAME(SUMMIT.ACCOUNT.MASTER) -
INDEXED -
RECORDS(2000000 200000) -
RECORDSIZE(150 150) -
KEYS(10 0) -
FREESPACE(15 10) -
SHAREOPTIONS(2 3) -
SPEED) -
DATA -
(NAME(SUMMIT.ACCOUNT.MASTER.DATA) -
CISZ(4096) -
VOLUMES(VOL001)) -
INDEX -
(NAME(SUMMIT.ACCOUNT.MASTER.INDEX) -
CISZ(2048))
/*
/* DEFINE ALTERNATE INDEX FOR CUSTOMER NAME LOOKUP
/* KEYS(30 10) = 30-byte key at offset 10
/* NONUNIQUEKEY = multiple accounts can share a name
/* UPGRADE = keep AIX synchronized with base cluster
/*
DEFINE AIX -
(NAME(SUMMIT.ACCOUNT.MASTER.NAMEAIX) -
RELATE(SUMMIT.ACCOUNT.MASTER) -
KEYS(30 10) -
NONUNIQUEKEY -
RECORDSIZE(64 500) -
RECORDS(2000000 200000) -
UPGRADE)
/*
/* DEFINE PATH TO ACCESS BASE CLUSTER VIA AIX
/*
DEFINE PATH -
(NAME(SUMMIT.ACCOUNT.MASTER.NAMEPATH) -
PATHENTRY(SUMMIT.ACCOUNT.MASTER.NAMEAIX))
/*
//*
//*-------- STEP 3: LOAD DATA FROM SEQUENTIAL FILE ----------------
//*
//LOAD EXEC PGM=IDCAMS,
// COND=(0,NE,DEFINE)
//SYSPRINT DD SYSOUT=*
//INFILE DD DSN=SUMMIT.ACCOUNT.SEQLOAD,
// DISP=SHR,
// DCB=(RECFM=FB,LRECL=150,BLKSIZE=27750)
//SYSIN DD *
REPRO INFILE(INFILE) -
OUTDATASET(SUMMIT.ACCOUNT.MASTER)
/*
//*
//*-------- STEP 4: BUILD THE ALTERNATE INDEX ---------------------
//*
//BLDAIX EXEC PGM=IDCAMS,
// COND=(0,NE,LOAD)
//SYSPRINT DD SYSOUT=*
//SYSIN DD *
BLDINDEX -
INDATASET(SUMMIT.ACCOUNT.MASTER) -
OUTDATASET(SUMMIT.ACCOUNT.MASTER.NAMEAIX)
/*
//*
//*-------- STEP 5: VERIFY THE CLUSTER ---------------------------
//*
//VERIFY EXEC PGM=IDCAMS,
// COND=(0,NE,BLDAIX)
//SYSPRINT DD SYSOUT=*
//SYSIN DD *
LISTCAT ENTRIES(SUMMIT.ACCOUNT.MASTER) -
ALL
LISTCAT ENTRIES(SUMMIT.ACCOUNT.MASTER.NAMEAIX) -
ALL
/*
//*
//*-------- STEP 6: RUN THE INQUIRY PROGRAM -----------------------
//*
//INQUIRY EXEC PGM=ACCTINQ,
// COND=(0,NE,BLDAIX)
//STEPLIB DD DSN=SUMMIT.PROD.LOADLIB,DISP=SHR
//ACCTMAST DD DSN=SUMMIT.ACCOUNT.MASTER,
// DISP=SHR
//ALTNAME DD DSN=SUMMIT.ACCOUNT.MASTER.NAMEPATH,
// DISP=SHR
//SYSOUT DD SYSOUT=*
Solution Walkthrough
Forward Browsing: READ NEXT
After a direct lookup or START positions the file cursor, READ NEXT retrieves the next record in primary key sequence:
READ ACCOUNT-FILE NEXT
END-READ
Each READ NEXT advances the cursor by one record. The operation is efficient because VSAM reads forward through the control intervals sequentially, often finding the next record in the same CI already in the buffer.
Backward Browsing: START + READ PREVIOUS
Backward browsing is more complex. The program must first reposition at the current record using START KEY IS =, then use READ PREVIOUS:
MOVE WS-CURRENT-KEY TO FS-AF-ACCOUNT-NO
START ACCOUNT-FILE
KEY IS = FS-AF-ACCOUNT-NO
READ ACCOUNT-FILE PREVIOUS
READ PREVIOUS is a COBOL 2002 feature. It retrieves the record immediately preceding the current cursor position in primary key sequence. This operation is more expensive than READ NEXT because VSAM may need to read a different CI (the previous one), which is not typically pre-buffered.
The file status "46" indicates that there is no previous record (the cursor is at the beginning of the file). The program handles this gracefully with a "beginning of file" message.
Partial Name Search with START >= and Reference Modification
The name search uses START KEY IS >= on the alternate key to position at the first name that is alphabetically equal to or greater than the search value:
MOVE WS-INPUT-NAME TO FS-AF-CUST-NAME
START ACCOUNT-FILE
KEY IS >= FS-AF-CUST-NAME
Subsequent READ NEXT operations retrieve records in alternate key sequence. The partial match is detected using reference modification:
IF FS-AF-CUST-NAME(1:
FUNCTION LENGTH(
FUNCTION TRIM(WS-INPUT-NAME)))
= FUNCTION TRIM(WS-INPUT-NAME)
This compares only the significant characters of the search name against the corresponding prefix of the retrieved name. Searching for "JOHNSON" matches "JOHNSON, ALICE", "JOHNSON, ROBERT", and "JOHNSTONBURG" -- the last being a false positive that could be filtered with additional logic.
Retry Logic for Transient Errors
The retry loop handles transient I/O errors that can occur in a shared file environment:
PERFORM UNTIL WS-RETRY-COUNT >= WS-MAX-RETRIES
READ ACCOUNT-FILE ...
IF ACCT-OK OR ACCT-DUP-ALT-KEY
OR ACCT-KEY-NOT-FOUND
EXIT PERFORM
ELSE
ADD 1 TO WS-RETRY-COUNT
END-IF
END-PERFORM
In a production CICS environment, a brief delay (EXEC CICS DELAY) would be inserted between retries to allow the conflicting process to complete. In this batch simulation, the retry is immediate.
Frozen Account Warning
The display routine includes business logic that warns the teller about frozen accounts:
WHEN ACCT-FROZEN
DISPLAY " Status: *** FROZEN ***"
DISPLAY " WARNING: This account is frozen."
DISPLAY " Contact the Fraud Department"
This demonstrates that inquiry programs are not purely technical -- they embed business rules about how certain conditions should be communicated to the end user.
Lessons Learned
1. READ PREVIOUS Requires Careful Positioning
Unlike READ NEXT (which simply advances the cursor), READ PREVIOUS requires that the cursor be positioned at a known record. The program must START at the current key before reading backward. Forgetting this step results in unpredictable behavior.
2. Partial Name Search Has False-Positive Risk
The START KEY IS >= approach positions at the first name that is alphabetically >= the search value. This means searching for "JOHN" would also match "JOHNSON", "JOHNSTON", and any name starting with "JOHN". The program must apply additional filtering after retrieval.
3. File Status "02" Has Different Meanings in Different Contexts
After a random READ by primary key, status "02" means the record was found and a duplicate alternate key exists. After a random READ by alternate key, status "02" means the record was found and another record with the same alternate key exists. The status is the same, but the context determines which key has the duplicate.
4. OPEN INPUT vs. OPEN I-O Affects Sharing
This program opens the file with OPEN INPUT because it only reads. This allows maximum sharing with other concurrent readers. If the program opened with OPEN I-O, some VSAM configurations would restrict concurrent access.
5. Browse State Must Be Tracked Explicitly
COBOL does not provide a way to query the current file cursor position. The program must maintain its own WS-CURRENT-KEY to know where it is positioned. This state tracking is essential for the browse forward/backward pattern.
Discussion Questions
-
The program opens the file as INPUT but demonstrates browse forward and backward. Could it also update records if opened as I-O? What changes to the COBOL program and JCL SHAREOPTIONS would be needed?
-
READ PREVIOUS is a COBOL 2002 feature. How would you simulate backward browsing on a compiler that does not support READ PREVIOUS? Consider saving keys in a WORKING-STORAGE table as you browse forward.
-
The name search displays up to 3 matching records. In a real system with potentially hundreds of "SMITH" accounts, how would you implement pagination (showing 10 at a time with "more" capability)?
-
The retry logic attempts 3 retries with no delay. In a CICS environment with hundreds of concurrent users, what retry strategy would be more appropriate? Consider exponential backoff and the impact on response time.
-
The SHAREOPTIONS(2 3) in the DEFINE CLUSTER allows multiple readers but controls how writes are handled. What would happen if a batch update program modified a record while this inquiry program was reading it? How does VSAM ensure data consistency?
-
The FREESPACE(15 10) allocates 15% CI free space and 10% CA free space. For a file that is primarily read (inquiry workload) versus one that is heavily updated (insert/delete workload), how would you adjust these values? What is the trade-off?
-
The JCL includes a VERIFY step (LISTCAT ALL) after building the alternate index. What information does LISTCAT provide, and why is it useful for debugging VSAM file problems? What metrics would you check to verify that the cluster was built correctly?