Case Study 1: Customer Account Lookup System at Atlantic Commerce Bank

Background

Atlantic Commerce Bank (ACB) serves 3.6 million customers through 120 branches and a digital banking platform. Their customer account master file is stored as a VSAM KSDS (Key-Sequenced Data Set) on an IBM z16 mainframe. This file is the authoritative source for all customer account information: balances, personal details, account status, and transaction history metadata.

Branch tellers, customer service representatives, and automated systems need to access this file in three distinct ways:

  1. By account number (the primary key) -- A teller enters the account number to retrieve the customer's record instantly.
  2. By customer name (an alternate key) -- A customer calls the help desk without their account number, and the representative searches by name.
  3. By range -- A branch manager requests all accounts in a specific account number range to generate a branch-specific report.

Each access pattern requires a different COBOL access mode: random access for direct lookup, random access via alternate key for name search, and dynamic access for sequential browsing within a range. The program must handle all three patterns efficiently and must be robust against not-found conditions, duplicate alternate keys, and file I/O errors.

Robert Nakamura, a senior COBOL developer at ACB, was tasked with building the Customer Account Lookup System (CUSLKUP) to demonstrate all three access patterns against a VSAM KSDS file, with full error handling and companion JCL for cluster definition.


The Problem

File Layout

The customer account VSAM KSDS has the following record layout:

Field PIC Offset Length Description
Account Number 9(10) 1 10 Primary key
Customer Name X(30) 11 30 Alternate key (with duplicates)
Address Line 1 X(30) 41 30 Street address
City X(20) 71 20 City
State X(2) 91 2 State code
ZIP Code 9(5) 93 5 ZIP code
Account Type X(1) 98 1 C/S/M/L
Balance S9(11)V99 COMP-3 99 7 Current balance
Open Date 9(8) 106 8 YYYYMMDD
Last Activity 9(8) 114 8 YYYYMMDD
Status X(1) 122 1 A/I/X/F
Filler X(28) 123 28 Reserved

Total record length: 150 bytes.

Access Requirements

  1. Random access by primary key: Given an account number, retrieve the record directly. If not found, display an appropriate message.

  2. Alternate key access by name: Given a customer name, retrieve all matching records (names are not unique). Display all accounts belonging to that customer.

  3. Dynamic access (sequential browse): Given a starting account number, browse forward through a range of records (useful for branch-specific reporting where branch accounts share a common prefix).


The Solution

The COBOL Program

       IDENTIFICATION DIVISION.
       PROGRAM-ID.  CUSLKUP.
       AUTHOR.      ROBERT NAKAMURA.
       DATE-WRITTEN. 2025-01-10.
      *================================================================
      * PROGRAM:  CUSLKUP - CUSTOMER ACCOUNT LOOKUP SYSTEM
      * PURPOSE:  Demonstrate three access modes for a VSAM KSDS
      *           file: random by primary key, random by alternate
      *           key, and dynamic (START/READ NEXT) for range
      *           browsing. Full error handling for not-found,
      *           duplicate keys, and I/O errors.
      *================================================================

       ENVIRONMENT DIVISION.

       INPUT-OUTPUT SECTION.
       FILE-CONTROL.
           SELECT CUSTOMER-FILE
               ASSIGN TO CUSTMAST
               ORGANIZATION IS INDEXED
               ACCESS MODE IS DYNAMIC
               RECORD KEY IS FS-CF-ACCOUNT-NO
               ALTERNATE RECORD KEY IS FS-CF-CUST-NAME
                   WITH DUPLICATES
               FILE STATUS IS WS-CUST-STATUS.

       DATA DIVISION.

       FILE SECTION.

       FD  CUSTOMER-FILE.
       01  FS-CUSTOMER-RECORD.
           05  FS-CF-ACCOUNT-NO       PIC 9(10).
           05  FS-CF-CUST-NAME        PIC X(30).
           05  FS-CF-ADDRESS-1        PIC X(30).
           05  FS-CF-CITY             PIC X(20).
           05  FS-CF-STATE            PIC X(2).
           05  FS-CF-ZIP              PIC 9(5).
           05  FS-CF-ACCT-TYPE        PIC X(1).
               88  ACCT-CHECKING                 VALUE 'C'.
               88  ACCT-SAVINGS                  VALUE 'S'.
               88  ACCT-MONEY-MKT                VALUE 'M'.
               88  ACCT-LOAN                     VALUE 'L'.
           05  FS-CF-BALANCE          PIC S9(11)V99 COMP-3.
           05  FS-CF-OPEN-DATE        PIC 9(8).
           05  FS-CF-LAST-ACTIVITY    PIC 9(8).
           05  FS-CF-STATUS           PIC X(1).
               88  STAT-ACTIVE                   VALUE 'A'.
               88  STAT-INACTIVE                 VALUE 'I'.
               88  STAT-CLOSED                   VALUE 'X'.
               88  STAT-FROZEN                   VALUE 'F'.
           05  FILLER                 PIC X(28).

       WORKING-STORAGE SECTION.

      *----------------------------------------------------------------
      * FILE STATUS
      *----------------------------------------------------------------
       01  WS-CUST-STATUS            PIC X(2).
           88  CUST-OK                           VALUE "00".
           88  CUST-DUP-KEY                      VALUE "02".
           88  CUST-NOT-FOUND                    VALUE "23".
           88  CUST-EOF                          VALUE "10".
           88  CUST-KEY-SEQ-ERR                  VALUE "21".

      *----------------------------------------------------------------
      * SEARCH CRITERIA
      *----------------------------------------------------------------
       01  WS-SEARCH-ACCOUNT         PIC 9(10).
       01  WS-SEARCH-NAME            PIC X(30).
       01  WS-RANGE-START            PIC 9(10).
       01  WS-RANGE-END              PIC 9(10).
       01  WS-BROWSE-COUNT           PIC 9(5) VALUE ZERO.
       01  WS-MAX-BROWSE             PIC 9(5) VALUE 20.

      *----------------------------------------------------------------
      * DISPLAY FIELDS
      *----------------------------------------------------------------
       01  WS-DISP-BALANCE           PIC $Z,ZZZ,ZZZ,ZZ9.99-.
       01  WS-DISP-ACCT-TYPE        PIC X(12).
       01  WS-DISP-STATUS           PIC X(10).
       01  WS-DISP-DATE             PIC X(10).
       01  WS-DISP-COUNT            PIC Z,ZZ9.

      *----------------------------------------------------------------
      * PROCESSING COUNTERS
      *----------------------------------------------------------------
       01  WS-RECORDS-FOUND          PIC S9(5) COMP-3 VALUE 0.
       01  WS-LOOKUPS-PERFORMED      PIC S9(5) COMP-3 VALUE 0.
       01  WS-NOT-FOUND-COUNT        PIC S9(5) COMP-3 VALUE 0.

       PROCEDURE DIVISION.

       0000-MAIN-CONTROL.
           PERFORM 1000-INITIALIZE
           PERFORM 2000-DEMO-RANDOM-BY-KEY
           PERFORM 3000-DEMO-ALTERNATE-KEY
           PERFORM 4000-DEMO-RANGE-BROWSE
           PERFORM 5000-DEMO-NOT-FOUND
           PERFORM 8000-DISPLAY-SUMMARY
           PERFORM 9000-FINALIZE
           STOP RUN
           .

       1000-INITIALIZE.
           DISPLAY "============================================="
           DISPLAY " ATLANTIC COMMERCE BANK"
           DISPLAY " CUSTOMER ACCOUNT LOOKUP SYSTEM"
           DISPLAY "============================================="
           DISPLAY " "

           OPEN I-O CUSTOMER-FILE
           IF NOT CUST-OK
               DISPLAY "FATAL: Cannot open customer file. "
                       "Status: " WS-CUST-STATUS
               STOP RUN
           END-IF
           .

       2000-DEMO-RANDOM-BY-KEY.
      *    -------------------------------------------------------
      *    PATTERN 1: RANDOM ACCESS BY PRIMARY KEY
      *    The most common access pattern. Given an account
      *    number, retrieve the record directly using the
      *    KSDS primary index.
      *    -------------------------------------------------------
           DISPLAY "--- PATTERN 1: RANDOM ACCESS BY KEY ---"
           DISPLAY " "

      *    Look up a specific account
           MOVE 1234567890 TO WS-SEARCH-ACCOUNT
           PERFORM 2100-LOOKUP-BY-ACCOUNT
      *    Look up another account
           MOVE 0000000001 TO WS-SEARCH-ACCOUNT
           PERFORM 2100-LOOKUP-BY-ACCOUNT
           DISPLAY " "
           .

       2100-LOOKUP-BY-ACCOUNT.
      *    -------------------------------------------------------
      *    Perform a random READ by primary key.
      *    Move the key value to the record's key field,
      *    then execute READ with the file name.
      *    -------------------------------------------------------
           ADD 1 TO WS-LOOKUPS-PERFORMED
           MOVE WS-SEARCH-ACCOUNT TO FS-CF-ACCOUNT-NO

           READ CUSTOMER-FILE
               KEY IS FS-CF-ACCOUNT-NO
               INVALID KEY
                   DISPLAY "  Account " WS-SEARCH-ACCOUNT
                           " not found."
                   ADD 1 TO WS-NOT-FOUND-COUNT
               NOT INVALID KEY
                   ADD 1 TO WS-RECORDS-FOUND
                   PERFORM 7000-DISPLAY-CUSTOMER-RECORD
           END-READ
           .

       3000-DEMO-ALTERNATE-KEY.
      *    -------------------------------------------------------
      *    PATTERN 2: ACCESS BY ALTERNATE KEY (CUSTOMER NAME)
      *    The alternate key is defined WITH DUPLICATES, so
      *    multiple records may have the same name. We use
      *    READ with KEY IS alternate-key to retrieve the
      *    first match, then READ NEXT to get subsequent
      *    duplicates.
      *    -------------------------------------------------------
           DISPLAY "--- PATTERN 2: ALTERNATE KEY LOOKUP ---"
           DISPLAY " "
           MOVE "MARTINEZ, ELENA R" TO WS-SEARCH-NAME
           DISPLAY "  Searching for: " WS-SEARCH-NAME
           DISPLAY " "
           PERFORM 3100-LOOKUP-BY-NAME
           DISPLAY " "
           .

       3100-LOOKUP-BY-NAME.
      *    -------------------------------------------------------
      *    Read the first record matching the alternate key.
      *    If status "00" or "02" (duplicate exists), display
      *    the record and read the next duplicate.
      *    Status "02" means the record was found AND there
      *    are more records with the same alternate key.
      *    -------------------------------------------------------
           MOVE WS-SEARCH-NAME TO FS-CF-CUST-NAME
           MOVE ZERO TO WS-BROWSE-COUNT

           READ CUSTOMER-FILE
               KEY IS FS-CF-CUST-NAME
               INVALID KEY
                   DISPLAY "  No records found for: "
                           WS-SEARCH-NAME
                   ADD 1 TO WS-NOT-FOUND-COUNT
               NOT INVALID KEY
                   ADD 1 TO WS-RECORDS-FOUND
                   ADD 1 TO WS-BROWSE-COUNT
                   PERFORM 7000-DISPLAY-CUSTOMER-RECORD

      *            Check for more records with same name
      *            Status "02" = record found, duplicate exists
                   PERFORM UNTIL NOT CUST-DUP-KEY
                       AND NOT CUST-OK
                       READ CUSTOMER-FILE NEXT
                       END-READ

                       IF CUST-OK OR CUST-DUP-KEY
                           IF FS-CF-CUST-NAME =
                              WS-SEARCH-NAME
                               ADD 1 TO WS-RECORDS-FOUND
                               ADD 1 TO WS-BROWSE-COUNT
                               PERFORM
                                   7000-DISPLAY-CUSTOMER-RECORD
                           ELSE
                               EXIT PERFORM
                           END-IF
                       ELSE
                           EXIT PERFORM
                       END-IF
                   END-PERFORM
                   MOVE WS-BROWSE-COUNT TO WS-DISP-COUNT
                   DISPLAY "  Total accounts found: "
                           WS-DISP-COUNT
           END-READ
           .

       4000-DEMO-RANGE-BROWSE.
      *    -------------------------------------------------------
      *    PATTERN 3: DYNAMIC ACCESS (RANGE BROWSE)
      *    Position the file at a starting key using START,
      *    then read sequentially forward using READ NEXT.
      *    This is useful for branch-specific reports where
      *    all accounts in a branch share a common prefix.
      *    -------------------------------------------------------
           DISPLAY "--- PATTERN 3: RANGE BROWSE ---"
           DISPLAY " "

           MOVE 1000000000 TO WS-RANGE-START
           MOVE 1000999999 TO WS-RANGE-END
           DISPLAY "  Browsing accounts from "
                   WS-RANGE-START " to " WS-RANGE-END
           DISPLAY " "

           PERFORM 4100-BROWSE-RANGE
           DISPLAY " "
           .

       4100-BROWSE-RANGE.
      *    -------------------------------------------------------
      *    Use START to position the file, then READ NEXT
      *    to iterate through the range.
      *
      *    START positions the file's current record pointer
      *    at the first record whose key is >= the search key.
      *    It does NOT read the record -- the subsequent
      *    READ NEXT retrieves it.
      *    -------------------------------------------------------
           MOVE WS-RANGE-START TO FS-CF-ACCOUNT-NO
           MOVE ZERO TO WS-BROWSE-COUNT

      *    Position at first record >= start key
           START CUSTOMER-FILE
               KEY IS >= FS-CF-ACCOUNT-NO
               INVALID KEY
                   DISPLAY "  No records found in range."
                   EXIT PARAGRAPH
               NOT INVALID KEY
                   CONTINUE
           END-START

      *    Read sequentially through the range
           PERFORM UNTIL WS-BROWSE-COUNT >= WS-MAX-BROWSE
               READ CUSTOMER-FILE NEXT
               END-READ

               EVALUATE TRUE
                   WHEN CUST-OK
                   WHEN CUST-DUP-KEY
      *                Check if still within range
                       IF FS-CF-ACCOUNT-NO > WS-RANGE-END
                           DISPLAY "  End of range reached."
                           EXIT PERFORM
                       END-IF
                       ADD 1 TO WS-RECORDS-FOUND
                       ADD 1 TO WS-BROWSE-COUNT
                       PERFORM 7000-DISPLAY-CUSTOMER-RECORD
                   WHEN CUST-EOF
                       DISPLAY "  End of file reached."
                       EXIT PERFORM
                   WHEN OTHER
                       DISPLAY "  READ error. Status: "
                               WS-CUST-STATUS
                       EXIT PERFORM
               END-EVALUATE
           END-PERFORM

           MOVE WS-BROWSE-COUNT TO WS-DISP-COUNT
           DISPLAY "  Records browsed: " WS-DISP-COUNT
           .

       5000-DEMO-NOT-FOUND.
      *    -------------------------------------------------------
      *    Demonstrate proper handling of the not-found condition.
      *    Status "23" is the standard VSAM response for a
      *    record-not-found on a random READ.
      *    -------------------------------------------------------
           DISPLAY "--- PATTERN 4: NOT-FOUND HANDLING ---"
           DISPLAY " "

           MOVE 9999999999 TO WS-SEARCH-ACCOUNT
           PERFORM 2100-LOOKUP-BY-ACCOUNT

           MOVE 0 TO WS-SEARCH-ACCOUNT
           PERFORM 2100-LOOKUP-BY-ACCOUNT

           DISPLAY " "
           .

       7000-DISPLAY-CUSTOMER-RECORD.
      *    -------------------------------------------------------
      *    Format and display the current customer record.
      *    -------------------------------------------------------
           DISPLAY "  Account: " FS-CF-ACCOUNT-NO
           DISPLAY "  Name:    " FS-CF-CUST-NAME
           DISPLAY "  Address: " FS-CF-ADDRESS-1
           DISPLAY "           " FS-CF-CITY " "
                                 FS-CF-STATE " "
                                 FS-CF-ZIP

      *    Format account type
           EVALUATE TRUE
               WHEN ACCT-CHECKING
                   MOVE "CHECKING" TO WS-DISP-ACCT-TYPE
               WHEN ACCT-SAVINGS
                   MOVE "SAVINGS" TO WS-DISP-ACCT-TYPE
               WHEN ACCT-MONEY-MKT
                   MOVE "MONEY MARKET" TO WS-DISP-ACCT-TYPE
               WHEN ACCT-LOAN
                   MOVE "LOAN" TO WS-DISP-ACCT-TYPE
               WHEN OTHER
                   MOVE "UNKNOWN" TO WS-DISP-ACCT-TYPE
           END-EVALUATE
           DISPLAY "  Type:    " WS-DISP-ACCT-TYPE

      *    Format balance
           MOVE FS-CF-BALANCE TO WS-DISP-BALANCE
           DISPLAY "  Balance: " WS-DISP-BALANCE

      *    Format status
           EVALUATE TRUE
               WHEN STAT-ACTIVE
                   MOVE "ACTIVE" TO WS-DISP-STATUS
               WHEN STAT-INACTIVE
                   MOVE "INACTIVE" TO WS-DISP-STATUS
               WHEN STAT-CLOSED
                   MOVE "CLOSED" TO WS-DISP-STATUS
               WHEN STAT-FROZEN
                   MOVE "FROZEN" TO WS-DISP-STATUS
               WHEN OTHER
                   MOVE "UNKNOWN" TO WS-DISP-STATUS
           END-EVALUATE
           DISPLAY "  Status:  " WS-DISP-STATUS

      *    Format date
           STRING FS-CF-OPEN-DATE(5:2)
                       DELIMITED BY SIZE
                  "/" DELIMITED BY SIZE
                  FS-CF-OPEN-DATE(7:2)
                       DELIMITED BY SIZE
                  "/" DELIMITED BY SIZE
                  FS-CF-OPEN-DATE(1:4)
                       DELIMITED BY SIZE
               INTO WS-DISP-DATE
           END-STRING
           DISPLAY "  Opened:  " WS-DISP-DATE
           DISPLAY "  ----------------------------------------"
           .

       8000-DISPLAY-SUMMARY.
           DISPLAY " "
           DISPLAY "============================================="
           DISPLAY " LOOKUP SESSION SUMMARY"
           DISPLAY "============================================="
           MOVE WS-LOOKUPS-PERFORMED TO WS-DISP-COUNT
           DISPLAY "  Lookups performed:    " WS-DISP-COUNT
           MOVE WS-RECORDS-FOUND TO WS-DISP-COUNT
           DISPLAY "  Records found:        " WS-DISP-COUNT
           MOVE WS-NOT-FOUND-COUNT TO WS-DISP-COUNT
           DISPLAY "  Not-found responses:  " WS-DISP-COUNT
           DISPLAY "============================================="
           .

       9000-FINALIZE.
           CLOSE CUSTOMER-FILE
           IF NOT CUST-OK
               DISPLAY "WARNING: Close error on customer file."
                       " Status: " WS-CUST-STATUS
           END-IF
           .

The IDCAMS DEFINE CLUSTER and JCL

//CUSLKPJ  JOB (ACCT),'CUSTOMER LOOKUP',
//         CLASS=A,MSGCLASS=X,
//         MSGLEVEL=(1,1),
//         NOTIFY=&SYSUID
//*================================================================
//* JOB:     CUSLKPJ
//* PURPOSE: DEFINE VSAM KSDS CLUSTER FOR CUSTOMER MASTER
//*          AND EXECUTE THE CUSTOMER LOOKUP PROGRAM
//*================================================================
//*
//*-------- STEP 1: DELETE EXISTING CLUSTER (IF EXISTS) -----------
//*
//DELETE   EXEC PGM=IDCAMS
//SYSPRINT DD SYSOUT=*
//SYSIN    DD *
  DELETE ATLCOM.CUSTOMER.MASTER -
         CLUSTER -
         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(ATLCOM.CUSTOMER.MASTER) -
          INDEXED -
          RECORDS(4000000 400000) -
          RECORDSIZE(150 150) -
          KEYS(10 0) -
          FREESPACE(20 10) -
          SHAREOPTIONS(2 3) -
          SPEED) -
    DATA -
         (NAME(ATLCOM.CUSTOMER.MASTER.DATA) -
          CONTROLINTERVALSIZE(4096)) -
    INDEX -
         (NAME(ATLCOM.CUSTOMER.MASTER.INDEX) -
          CONTROLINTERVALSIZE(2048))
  /*
  /* DEFINE THE ALTERNATE INDEX FOR CUSTOMER NAME
  /*
  DEFINE AIX -
         (NAME(ATLCOM.CUSTOMER.MASTER.NAMEAIX) -
          RELATE(ATLCOM.CUSTOMER.MASTER) -
          KEYS(30 10) -
          NONUNIQUEKEY -
          RECORDSIZE(64 500) -
          RECORDS(4000000 400000) -
          SHAREOPTIONS(2 3) -
          UPGRADE)
  /*
  /* DEFINE THE PATH FOR THE ALTERNATE INDEX
  /*
  DEFINE PATH -
         (NAME(ATLCOM.CUSTOMER.MASTER.NAMEPATH) -
          PATHENTRY(ATLCOM.CUSTOMER.MASTER.NAMEAIX))
/*
//*
//*-------- STEP 3: LOAD INITIAL DATA (REPRO FROM SEQ FILE) -------
//*
//LOAD     EXEC PGM=IDCAMS,
//         COND=(0,NE,DEFINE)
//SYSPRINT DD SYSOUT=*
//INFILE   DD DSN=ATLCOM.CUSTOMER.SEQLOAD,
//            DISP=SHR
//SYSIN    DD *
  REPRO INFILE(INFILE) -
        OUTDATASET(ATLCOM.CUSTOMER.MASTER)
/*
//*
//*-------- STEP 4: BUILD ALTERNATE INDEX -------------------------
//*
//BLDAIX   EXEC PGM=IDCAMS,
//         COND=(0,NE,LOAD)
//SYSPRINT DD SYSOUT=*
//SYSIN    DD *
  BLDINDEX -
    INDATASET(ATLCOM.CUSTOMER.MASTER) -
    OUTDATASET(ATLCOM.CUSTOMER.MASTER.NAMEAIX)
/*
//*
//*-------- STEP 5: RUN THE LOOKUP PROGRAM ------------------------
//*
//LOOKUP   EXEC PGM=CUSLKUP,
//         COND=(0,NE,BLDAIX)
//STEPLIB  DD DSN=ATLCOM.PROD.LOADLIB,DISP=SHR
//CUSTMAST DD DSN=ATLCOM.CUSTOMER.MASTER,
//            DISP=SHR
//ALTNAME  DD DSN=ATLCOM.CUSTOMER.MASTER.NAMEPATH,
//            DISP=SHR
//SYSOUT   DD SYSOUT=*

Solution Walkthrough

ACCESS MODE IS DYNAMIC

The SELECT statement specifies ACCESS MODE IS DYNAMIC, which allows both random and sequential access within the same program:

           SELECT CUSTOMER-FILE
               ASSIGN TO CUSTMAST
               ORGANIZATION IS INDEXED
               ACCESS MODE IS DYNAMIC
               RECORD KEY IS FS-CF-ACCOUNT-NO
               ALTERNATE RECORD KEY IS FS-CF-CUST-NAME
                   WITH DUPLICATES
               FILE STATUS IS WS-CUST-STATUS.

With DYNAMIC access, the program can use: - READ filename KEY IS key-field for random access - START filename KEY IS >= key-field followed by READ filename NEXT for sequential browsing - READ filename NEXT after a random READ to continue sequentially from the retrieved position

Random Access by Primary Key

The simplest access pattern. Move the desired key value to the record key field and execute a keyed READ:

           MOVE WS-SEARCH-ACCOUNT TO FS-CF-ACCOUNT-NO
           READ CUSTOMER-FILE
               KEY IS FS-CF-ACCOUNT-NO
               INVALID KEY ...
               NOT INVALID KEY ...
           END-READ

VSAM consults the primary index and retrieves the record in a single I/O operation (or two, if the index entry is not cached). File status "23" indicates the key was not found.

Alternate Key Access with Duplicates

The alternate key on customer name is defined WITH DUPLICATES because multiple customers can share the same name. The first READ retrieves the first matching record. If file status is "02" (record found, duplicate key exists), subsequent READ NEXT statements retrieve additional records with the same key:

           READ CUSTOMER-FILE
               KEY IS FS-CF-CUST-NAME
               NOT INVALID KEY
                   PERFORM UNTIL NOT CUST-DUP-KEY
                       READ CUSTOMER-FILE NEXT

Status "02" after a READ means "the record was returned successfully, AND there are more records with the same alternate key value." This is the signal to continue reading.

The START statement positions the file cursor without reading a record:

           MOVE WS-RANGE-START TO FS-CF-ACCOUNT-NO
           START CUSTOMER-FILE
               KEY IS >= FS-CF-ACCOUNT-NO

KEY IS >= means "position at the first record whose key is greater than or equal to the specified value." This is essential for range browsing because the exact starting key might not exist in the file. After START, successive READ NEXT statements retrieve records in key sequence until the range end is reached or EOF is encountered.

The IDCAMS DEFINE CLUSTER

The JCL demonstrates the complete lifecycle of a VSAM KSDS:

  1. DELETE: Removes any existing cluster. The IF LASTCC = 8 THEN SET MAXCC = 0 handles the case where the cluster does not exist (LASTCC=8 from the DELETE is not an error condition).

  2. DEFINE CLUSTER: Creates the KSDS with: - KEYS(10 0): 10-byte primary key starting at offset 0 - FREESPACE(20 10): 20% free space in each CI, 10% of CAs left empty -- this accommodates future inserts without immediate CI splits - SHAREOPTIONS(2 3): Allows concurrent read access from multiple jobs

  3. DEFINE AIX: Creates the alternate index with: - KEYS(30 10): 30-byte alternate key starting at offset 10 (the customer name field) - NONUNIQUEKEY: Allows duplicate name values - UPGRADE: Automatically updates the AIX when the base cluster is modified

  4. DEFINE PATH: Creates a logical pathway from the AIX to the base cluster, which the COBOL program uses via the DD statement for the alternate key.

  5. BLDINDEX: Builds the initial alternate index from the base cluster data.


Lessons Learned

1. File Status "02" Is Not an Error

Status "02" after a READ means the record was found successfully and another record with the same key exists. Many programmers mistakenly treat any non-"00" status as an error. With alternate keys that allow duplicates, "02" is an expected and important status.

2. START Does Not Read -- It Only Positions

A common mistake is assuming that START retrieves a record. It does not. START only positions the file cursor. The first record is retrieved by the subsequent READ NEXT. This means the record buffer is unchanged after a successful START.

3. DYNAMIC Access Mode Enables All Patterns in One Program

With ACCESS MODE IS DYNAMIC, a single OPEN statement enables random reads, alternate key reads, and sequential browsing. This eliminates the need for multiple file definitions or multiple program runs.

4. Alternate Index Maintenance Has a Performance Cost

When the UPGRADE option is specified on a DEFINE AIX, every write, rewrite, or delete to the base cluster also updates the alternate index. For high-volume update programs, this overhead can be significant. Some installations define AIX without UPGRADE and rebuild the index periodically in batch.

5. FREESPACE Trades Disk for Insert Performance

The 20% CI free space and 10% CA free space reserve disk space to accommodate future inserts. Without free space, every insert into a full CI triggers a CI split (VSAM redistributes records into two CIs), which is expensive. The trade-off: more free space means more disk allocated but fewer splits.


Discussion Questions

  1. The program opens the file with OPEN I-O even though it only reads records. Why use I-O instead of INPUT? What are the implications for file sharing with other programs?

  2. The alternate key is defined as the customer name field (30 bytes). What happens if a customer's name is stored with trailing spaces ("SMITH" padded to 30 characters)? Would a search for "SMITH" (5 characters) match?

  3. The range browse limits results to 20 records (WS-MAX-BROWSE). In a real system with 3.6 million records, what would happen if no limit were imposed and the range covered the entire file? How should the program handle very large ranges?

  4. The DEFINE CLUSTER specifies RECORDS(4000000 400000). The first number is the primary allocation and the second is the secondary allocation. How does VSAM use these values? What happens if the file grows beyond both allocations?

  5. The BLDINDEX step builds the alternate index after the initial load. What happens to the alternate index when records are added or deleted during normal operation? How does the UPGRADE parameter affect this?

  6. File status "21" (KEY SEQUENCE ERROR) can occur during sequential READ NEXT operations. What causes this condition, and how would you handle it in a production program?

  7. The JCL assigns the alternate index path to a DD named ALTNAME, but the COBOL program does not reference this DD name. How does VSAM know to associate the alternate key access with this DD? What role does the PATH definition play?