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:

  1. Direct lookup: A teller enters an account number and retrieves the balance and account details.
  2. Name search with browse: A teller enters a partial customer name and browses through matching records to find the correct account.
  3. 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

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

  1. 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?

  2. 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.

  3. 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)?

  4. 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.

  5. 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?

  6. 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?

  7. 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?