28 min read

> "Sequential files are like reading a book cover to cover. Indexed files are like using the index at the back — you can flip directly to what you need."

Chapter 12: Indexed File Processing (VSAM KSDS)

"Sequential files are like reading a book cover to cover. Indexed files are like using the index at the back — you can flip directly to what you need." — Maria Chen, during Derek Washington's first week on the ACCT-MASTER file

In Chapter 11, we worked exclusively with sequential files — reading records from beginning to end, one after another. That model serves many batch processing needs, but the real world demands more. When a bank teller types in an account number, the system cannot read through millions of records sequentially to find the right one. When a claims processor needs to look up a specific provider, the response must come in milliseconds, not minutes.

This chapter introduces indexed file processing through IBM's Virtual Storage Access Method (VSAM) Key-Sequenced Data Sets (KSDS). By the end, you will understand how VSAM organizes data on disk, how COBOL programs interact with indexed files through multiple access modes, and how to write robust programs that create, read, update, delete, and browse records — the full spectrum of CRUD operations that underpin virtually every enterprise COBOL application running today.

12.1 Why Indexed Files Matter

Consider GlobalBank's core banking system. The ACCT-MASTER file holds records for 4.7 million customer accounts. When Maria Chen's team processes an online account inquiry, the program must locate a specific account record within that file — typically in under 50 milliseconds. Sequential processing, which would require reading an average of 2.35 million records per lookup, is simply not viable.

Indexed files solve this problem by maintaining an index structure alongside the data records. Think of it like a library catalog: rather than walking through every shelf to find a book, you consult the catalog, which tells you exactly where to look. The index maps key values to physical disk locations, enabling direct access to any record by its key.

💡 Why This Matters Today: Over 70% of COBOL batch programs and virtually all CICS online programs interact with VSAM files. Understanding VSAM is not optional for a working COBOL developer — it is fundamental.

12.1.1 File Organization Options in COBOL

COBOL supports three primary file organizations:

Organization Access Pattern Best For VSAM Equivalent
Sequential First to last Reports, batch feeds ESDS
Indexed By key value Master files, lookups KSDS
Relative By record number Hash tables, arrays RRDS

This chapter focuses on indexed files (KSDS). Chapter 13 covers relative files (RRDS), and Chapter 14 brings them all together in advanced multi-file processing patterns.

12.2 VSAM Fundamentals

Before writing COBOL code, you need a mental model of how VSAM stores and retrieves data. VSAM is not just a file access method — it is an entire subsystem for managing data on direct-access storage devices (DASD).

12.2.1 Control Intervals and Control Areas

VSAM organizes data into two levels of physical grouping:

Control Interval (CI): The smallest unit of data that VSAM transfers between disk and memory. A CI contains:

  • One or more logical records
  • Free space for future inserts
  • Control information (RDF — Record Definition Fields, and CIDF — Control Interval Definition Field)

A typical CI size ranges from 512 bytes to 32,768 bytes (32 KB). The CI size significantly affects performance: larger CIs mean fewer I/O operations for sequential access but waste buffer space for random access.

┌──────────────────────────────────────────────────┐
│  Control Interval (CI)                            │
│ ┌────────┐ ┌────────┐ ┌────────┐ ┌──────┐ ┌────┐│
│ │Record 1│ │Record 2│ │Record 3│ │ Free │ │RDF ││
│ │        │ │        │ │        │ │Space │ │CIDF││
│ └────────┘ └────────┘ └────────┘ └──────┘ └────┘│
└──────────────────────────────────────────────────┘

Control Area (CA): A group of control intervals. Each CA contains:

  • A set of data CIs
  • An index CI (the sequence-set index record for that CA)
  • Free CIs for future expansion

📊 Performance Insight: When VSAM needs to insert a record and the target CI is full, it performs a CI split — moving roughly half the records in the CI to a free CI within the same CA. If the CA has no free CIs, a CA split occurs, which is more expensive. Proper free-space allocation (discussed in Section 12.2.3) minimizes splits.

12.2.2 The VSAM Index Structure

A KSDS maintains a B+ tree index with multiple levels:

  1. Sequence Set: The lowest level of the index. One index record per control area, containing the highest key in each CI of that CA, plus pointers to the CIs. The sequence set records are chained together for efficient sequential browsing.

  2. Index Set: Higher levels of the index tree. Each index record contains the highest key in each sequence-set record below it, plus pointers. For very large files, the index set may have multiple levels.

                    ┌─────────────────┐
                    │   Index Set     │
                    │ 5000 | 10000    │
                    └───┬──────┬──────┘
                   ┌────┘      └────┐
          ┌────────▼──────┐  ┌──────▼────────┐
          │ Sequence Set  │  │ Sequence Set  │
          │ 1000|2000|... │  │ 6000|7000|... │
          └───┬──┬──┬─────┘  └───┬──┬──┬─────┘
              │  │  │            │  │  │
              ▼  ▼  ▼            ▼  ▼  ▼
             CI  CI  CI         CI  CI  CI
           (data records)     (data records)

When a program requests a record by key, VSAM: 1. Searches the index set to find the right sequence-set record 2. Searches the sequence set to find the right CI 3. Reads the CI into the buffer pool 4. Scans within the CI for the specific record

For GlobalBank's 4.7 million account ACCT-MASTER file, this typically means 2-3 index levels plus one data I/O — roughly 3-4 disk reads for any record, compared to millions for sequential search.

12.2.3 VSAM Catalogs and Definition

VSAM files are defined through catalogs — system-level repositories that track every VSAM data set's attributes and location. You define a VSAM file using the IDCAMS utility (Access Method Services), typically through JCL:

//DEFCLUST EXEC PGM=IDCAMS
//SYSPRINT DD SYSOUT=*
//SYSIN    DD *
  DEFINE CLUSTER -
    (NAME(GLOBANK.ACCT.MASTER) -
     INDEXED -
     KEYS(10 0) -
     RECORDSIZE(350 350) -
     CYLINDERS(50 10) -
     FREESPACE(20 10) -
     SHAREOPTIONS(2 3)) -
  DATA -
    (NAME(GLOBANK.ACCT.MASTER.DATA) -
     CISZ(4096)) -
  INDEX -
    (NAME(GLOBANK.ACCT.MASTER.INDEX) -
     CISZ(2048))
/*

Key parameters explained:

Parameter Meaning Our Example
INDEXED KSDS organization Key-sequenced
KEYS(10 0) Key length 10, starting at offset 0 10-byte account number at beginning of record
RECORDSIZE(350 350) Average and maximum record length Fixed-length 350-byte records
FREESPACE(20 10) 20% free space per CI, 10% free CIs per CA Room for inserts
SHAREOPTIONS(2 3) Cross-region and cross-system sharing Multiple readers, one writer
CISZ(4096) CI size for data component 4 KB control intervals

⚠️ Common Pitfall: New developers often forget that a VSAM file must be defined before a COBOL program can use it. Unlike sequential files, which can be created on the fly, VSAM clusters require explicit definition via IDCAMS or equivalent. Derek Washington learned this lesson his first week — his program abended with a S013-68 because the VSAM cluster didn't exist yet.

12.2.4 VSAM Buffers and Performance

VSAM uses a buffer pool to cache CIs in memory. The number of buffers significantly affects performance:

  • BUFND (data buffers): More data buffers reduce physical I/O for sequential processing
  • BUFNI (index buffers): More index buffers keep index records in memory, speeding random access

A common tuning guideline: for random access, set BUFNI high enough to keep all index-set records in memory, plus at least 2-3 data buffers. For sequential access, maximize BUFND.

💡 Lab Tip: In GnuCOBOL, VSAM concepts are simulated through the runtime's indexed file support (typically using Berkeley DB or similar). The tuning parameters differ, but the COBOL code remains nearly identical. Your Student Mainframe Lab exercises work on both platforms.

12.3 COBOL SELECT Statement for Indexed Files

Now let us bridge from VSAM concepts to COBOL code. The SELECT statement in the ENVIRONMENT DIVISION tells the compiler how to access the file.

12.3.1 Basic SELECT Syntax

       ENVIRONMENT DIVISION.
       INPUT-OUTPUT SECTION.
       FILE-CONTROL.
           SELECT ACCT-MASTER-FILE
               ASSIGN TO ACCTMAST
               ORGANIZATION IS INDEXED
               ACCESS MODE IS DYNAMIC
               RECORD KEY IS ACCT-NUMBER
               FILE STATUS IS WS-ACCT-STATUS.

Let us examine each clause:

SELECT file-name: The logical name you will use throughout your COBOL program. Convention: use a descriptive name ending in -FILE.

ASSIGN TO: The external name that maps to the physical file through JCL DD statements or environment variables. In JCL, the DD name ACCTMAST links to the actual VSAM cluster:

//ACCTMAST DD DSN=GLOBANK.ACCT.MASTER,DISP=SHR

ORGANIZATION IS INDEXED: Specifies that the file has an index structure — a KSDS in VSAM terms.

ACCESS MODE: Determines how the program reads the file:

Access Mode Meaning Allowed Statements
SEQUENTIAL Records read/written in key order READ, WRITE
RANDOM Records accessed directly by key READ, WRITE, REWRITE, DELETE
DYNAMIC Both sequential and random in same program All of the above plus START, READ NEXT

RECORD KEY IS: The primary key field, which must be defined within the record description in the DATA DIVISION. The key uniquely identifies each record.

FILE STATUS IS: A two-byte field that receives a status code after every I/O operation. We will devote an entire section (12.8) to status codes, because checking them is non-negotiable in production code.

12.3.2 The Three Access Modes in Detail

ACCESS MODE IS SEQUENTIAL

In sequential mode, you read records in key order from beginning to end (or from a START position). This is efficient for batch reports or bulk processing. You can also write records, but they must be in ascending key order.

       SELECT RPT-ACCOUNT-FILE
           ASSIGN TO RPTACCT
           ORGANIZATION IS INDEXED
           ACCESS MODE IS SEQUENTIAL
           RECORD KEY IS RPT-ACCT-KEY
           FILE STATUS IS WS-RPT-STATUS.

ACCESS MODE IS RANDOM

In random mode, you move a key value to the record key field, then issue a READ, WRITE, REWRITE, or DELETE. The system uses the index to go directly to that record. You cannot use START or READ NEXT.

       SELECT ACCT-LOOKUP-FILE
           ASSIGN TO ACCTMAST
           ORGANIZATION IS INDEXED
           ACCESS MODE IS RANDOM
           RECORD KEY IS AL-ACCT-NUMBER
           FILE STATUS IS WS-AL-STATUS.

ACCESS MODE IS DYNAMIC

Dynamic mode is the most flexible — and the most commonly used in real-world programs. It allows both random access (READ with key, WRITE, REWRITE, DELETE) and sequential access (START, READ NEXT) in the same program. This is essential when you need to look up a specific record and then browse forward from that point.

       SELECT ACCT-MASTER-FILE
           ASSIGN TO ACCTMAST
           ORGANIZATION IS INDEXED
           ACCESS MODE IS DYNAMIC
           RECORD KEY IS ACCT-NUMBER
           FILE STATUS IS WS-ACCT-STATUS.

📊 Industry Practice: At GlobalBank, Maria Chen's team uses DYNAMIC access mode for almost every VSAM file. "You never know when a requirement change will need you to browse after a lookup," she explains. "Start with DYNAMIC and save yourself a recompile later."

12.4 Record Description for Indexed Files

The FD (File Description) and record layout in the DATA DIVISION follow the same rules as sequential files, with one crucial addition: the primary key field must be defined within the record at the position matching the KEYS parameter in the VSAM definition.

       DATA DIVISION.
       FILE SECTION.
       FD  ACCT-MASTER-FILE.
       01  ACCT-MASTER-RECORD.
           05  ACCT-NUMBER            PIC X(10).
           05  ACCT-HOLDER-NAME       PIC X(40).
           05  ACCT-TYPE              PIC X(02).
               88  ACCT-CHECKING      VALUE 'CH'.
               88  ACCT-SAVINGS       VALUE 'SA'.
               88  ACCT-MONEY-MARKET  VALUE 'MM'.
               88  ACCT-CD            VALUE 'CD'.
           05  ACCT-OPEN-DATE         PIC 9(08).
           05  ACCT-BALANCE           PIC S9(11)V99 COMP-3.
           05  ACCT-HOLD-AMOUNT       PIC S9(11)V99 COMP-3.
           05  ACCT-LAST-TXN-DATE     PIC 9(08).
           05  ACCT-LAST-TXN-AMT      PIC S9(09)V99 COMP-3.
           05  ACCT-STATUS-CODE       PIC X(02).
               88  ACCT-ACTIVE        VALUE 'AC'.
               88  ACCT-FROZEN        VALUE 'FR'.
               88  ACCT-CLOSED        VALUE 'CL'.
               88  ACCT-DORMANT       VALUE 'DO'.
           05  ACCT-BRANCH-CODE       PIC X(05).
           05  ACCT-OFFICER-ID        PIC X(06).
           05  FILLER                 PIC X(233).

⚠️ Critical Rule: The RECORD KEY field (ACCT-NUMBER) must be at the correct offset and length as defined in the VSAM cluster. If the cluster says KEYS(10 0), the key must be 10 bytes starting at offset 0 — meaning ACCT-NUMBER must be the first field in the record and exactly 10 bytes. A mismatch causes unpredictable behavior or an OPEN failure.

12.4.1 Working-Storage Fields for Indexed Processing

Every indexed file program needs supporting fields in WORKING-STORAGE:

       WORKING-STORAGE SECTION.
       01  WS-ACCT-STATUS            PIC XX.
           88  WS-ACCT-SUCCESS       VALUE '00'.
           88  WS-ACCT-DUP-KEY       VALUE '22'.
           88  WS-ACCT-NOT-FOUND     VALUE '23'.
           88  WS-ACCT-END-OF-FILE   VALUE '10'.
           88  WS-ACCT-SEQ-ERROR     VALUE '21'.

       01  WS-SEARCH-KEY             PIC X(10).
       01  WS-EOF-FLAG               PIC X VALUE 'N'.
           88  WS-END-OF-FILE        VALUE 'Y'.
           88  WS-NOT-END-OF-FILE    VALUE 'N'.

       01  WS-RECORD-COUNT           PIC 9(07) VALUE ZERO.
       01  WS-ERROR-COUNT            PIC 9(05) VALUE ZERO.

Note the 88-level conditions on the file status field. This is a Maria Chen best practice: "Define status codes as condition names. Then your code reads like English: IF WS-ACCT-NOT-FOUND instead of IF WS-ACCT-STATUS = '23'."

12.5 READ Operations — Random and Sequential

Reading records is the most common VSAM operation. The syntax depends on your access mode.

12.5.1 Random READ

In RANDOM or DYNAMIC access mode, you read a specific record by moving a value to the key field, then issuing a READ:

       PERFORM-ACCOUNT-LOOKUP.
           MOVE WS-SEARCH-KEY TO ACCT-NUMBER
           READ ACCT-MASTER-FILE
               INVALID KEY
                   PERFORM HANDLE-NOT-FOUND
               NOT INVALID KEY
                   PERFORM DISPLAY-ACCOUNT-INFO
           END-READ

The flow: 1. Move the desired key value to the RECORD KEY field (ACCT-NUMBER) 2. Issue the READ statement 3. VSAM searches the index for that key 4. If found, the record is placed in the record area (ACCT-MASTER-RECORD) 5. If not found, the INVALID KEY phrase executes

⚠️ Common Pitfall: The key field is part of the record buffer. After a successful READ, the entire record — including the key — is populated. But after an INVALID KEY, the record buffer contents are undefined. Never reference record fields after an unsuccessful READ.

12.5.2 Sequential READ with START

In SEQUENTIAL or DYNAMIC access mode, you can browse records in key order. The START statement positions the file at a specific key (or the nearest record >= that key), and then READ NEXT retrieves records sequentially:

       BROWSE-ACCOUNTS-BY-BRANCH.
      *    Position at the first account for branch 'BR001'
           MOVE 'BR001' TO WS-SEARCH-BRANCH
           MOVE SPACES TO ACCT-NUMBER
           MOVE WS-SEARCH-BRANCH TO ACCT-BRANCH-CODE
      *    We cannot START on a non-key field directly.
      *    Instead, we use an alternate key (see Section 12.7)
      *    or START on primary key and filter:
           MOVE LOW-VALUES TO ACCT-NUMBER
           START ACCT-MASTER-FILE
               KEY IS NOT LESS THAN ACCT-NUMBER
               INVALID KEY
                   SET WS-END-OF-FILE TO TRUE
               NOT INVALID KEY
                   PERFORM READ-NEXT-ACCOUNT
           END-START

           PERFORM UNTIL WS-END-OF-FILE
               PERFORM PROCESS-ACCOUNT
               PERFORM READ-NEXT-ACCOUNT
           END-PERFORM.

       READ-NEXT-ACCOUNT.
           READ ACCT-MASTER-FILE NEXT
               AT END
                   SET WS-END-OF-FILE TO TRUE
               NOT AT END
                   ADD 1 TO WS-RECORD-COUNT
           END-READ.

12.5.3 START Statement Options

The START statement supports several key comparison operators:

      *--- Position at exact key or first key >= value
           START file-name
               KEY IS NOT LESS THAN data-name
           END-START

      *--- Position at exact key only
           START file-name
               KEY IS EQUAL TO data-name
           END-START

      *--- Position at first key > value
           START file-name
               KEY IS GREATER THAN data-name
           END-START

💡 Power Technique — Generic Key START: You can START with a partial key. If ACCT-NUMBER is 10 bytes and you move '1234' to only the first 4 bytes (using reference modification), VSAM positions at the first record whose key begins with '1234'. This is called a generic key search and is extremely useful for range browsing:

       BROWSE-BY-PREFIX.
           MOVE SPACES TO ACCT-NUMBER
           MOVE '1234' TO ACCT-NUMBER(1:4)
           START ACCT-MASTER-FILE
               KEY IS NOT LESS THAN ACCT-NUMBER
               INVALID KEY
                   DISPLAY 'No accounts with prefix 1234'
               NOT INVALID KEY
                   PERFORM VARYING WS-IDX FROM 1 BY 1
                       UNTIL WS-END-OF-FILE
                       OR ACCT-NUMBER(1:4) NOT = '1234'
                       READ ACCT-MASTER-FILE NEXT
                           AT END
                               SET WS-END-OF-FILE TO TRUE
                       END-READ
                       IF NOT WS-END-OF-FILE
                           PERFORM PROCESS-MATCHING-ACCOUNT
                       END-IF
                   END-PERFORM
           END-START.

12.5.4 A Complete Browsing Example

Let us build a complete program that browses the GlobalBank ACCT-MASTER file, displaying all accounts within a range of account numbers:

       IDENTIFICATION DIVISION.
       PROGRAM-ID. ACCT-BROWSE.
      *=============================================================*
      * ACCT-BROWSE: Browse accounts within a key range             *
      * GlobalBank Core Banking System                              *
      *=============================================================*

       ENVIRONMENT DIVISION.
       INPUT-OUTPUT SECTION.
       FILE-CONTROL.
           SELECT ACCT-MASTER-FILE
               ASSIGN TO ACCTMAST
               ORGANIZATION IS INDEXED
               ACCESS MODE IS DYNAMIC
               RECORD KEY IS ACCT-NUMBER
               FILE STATUS IS WS-ACCT-STATUS.

       DATA DIVISION.
       FILE SECTION.
       FD  ACCT-MASTER-FILE.
       01  ACCT-MASTER-RECORD.
           05  ACCT-NUMBER            PIC X(10).
           05  ACCT-HOLDER-NAME       PIC X(40).
           05  ACCT-TYPE              PIC X(02).
           05  ACCT-OPEN-DATE         PIC 9(08).
           05  ACCT-BALANCE           PIC S9(11)V99 COMP-3.
           05  FILLER                 PIC X(289).

       WORKING-STORAGE SECTION.
       01  WS-ACCT-STATUS            PIC XX.
           88  WS-ACCT-SUCCESS       VALUE '00'.
           88  WS-ACCT-NOT-FOUND     VALUE '23'.
           88  WS-ACCT-EOF           VALUE '10'.

       01  WS-START-KEY              PIC X(10).
       01  WS-END-KEY                PIC X(10).
       01  WS-RECORD-COUNT           PIC 9(07) VALUE ZERO.
       01  WS-DISPLAY-BALANCE        PIC Z(10)9.99-.

       01  WS-EOF-FLAG               PIC X VALUE 'N'.
           88  WS-EOF                VALUE 'Y'.
           88  WS-NOT-EOF            VALUE 'N'.

       PROCEDURE DIVISION.
       0000-MAIN.
           PERFORM 1000-INITIALIZE
           PERFORM 2000-BROWSE-RANGE
           PERFORM 9000-TERMINATE
           STOP RUN.

       1000-INITIALIZE.
           MOVE '0001000000' TO WS-START-KEY
           MOVE '0001999999' TO WS-END-KEY
           OPEN INPUT ACCT-MASTER-FILE
           IF NOT WS-ACCT-SUCCESS
               DISPLAY 'OPEN ERROR: ' WS-ACCT-STATUS
               STOP RUN
           END-IF
           SET WS-NOT-EOF TO TRUE.

       2000-BROWSE-RANGE.
           MOVE WS-START-KEY TO ACCT-NUMBER
           START ACCT-MASTER-FILE
               KEY IS NOT LESS THAN ACCT-NUMBER
               INVALID KEY
                   DISPLAY 'No records at or above start key'
                   SET WS-EOF TO TRUE
               NOT INVALID KEY
                   PERFORM 2100-READ-NEXT
           END-START

           PERFORM UNTIL WS-EOF
               OR ACCT-NUMBER > WS-END-KEY
               PERFORM 2200-PROCESS-RECORD
               PERFORM 2100-READ-NEXT
           END-PERFORM

           DISPLAY 'Records found: ' WS-RECORD-COUNT.

       2100-READ-NEXT.
           READ ACCT-MASTER-FILE NEXT
               AT END
                   SET WS-EOF TO TRUE
               NOT AT END
                   CONTINUE
           END-READ.

       2200-PROCESS-RECORD.
           ADD 1 TO WS-RECORD-COUNT
           MOVE ACCT-BALANCE TO WS-DISPLAY-BALANCE
           DISPLAY ACCT-NUMBER ' '
                   ACCT-HOLDER-NAME ' '
                   WS-DISPLAY-BALANCE.

       9000-TERMINATE.
           CLOSE ACCT-MASTER-FILE.

🔗 Cross-Reference: This browsing pattern becomes the foundation for the online inquiry programs we build in Chapter 29 (CICS Fundamentals), where a user enters a partial account number and browses results on a screen.

12.5.5 Backward Browsing and Directional READ

Enterprise COBOL also supports reading in reverse order using READ PREVIOUS. This is particularly useful in CICS applications where a user might want to scroll backward through a list of records:

       BROWSE-BACKWARD.
      *    Position at a known key
           MOVE WS-START-KEY TO ACCT-NUMBER
           START ACCT-MASTER-FILE
               KEY IS EQUAL TO ACCT-NUMBER
               INVALID KEY
                   DISPLAY 'Start key not found'
                   GO TO BROWSE-BACKWARD-EXIT
           END-START

      *    Read backward (previous records in key sequence)
           PERFORM VARYING WS-IDX FROM 1 BY 1
               UNTIL WS-IDX > 10 OR WS-EOF
               READ ACCT-MASTER-FILE PREVIOUS
                   AT END
                       SET WS-EOF TO TRUE
                   NOT AT END
                       DISPLAY ACCT-NUMBER ' '
                               ACCT-HOLDER-NAME
               END-READ
           END-PERFORM.

       BROWSE-BACKWARD-EXIT.
           EXIT.

⚠️ Compatibility Note: READ PREVIOUS is supported in Enterprise COBOL but may not be available in all COBOL compilers. GnuCOBOL added support for READ PREVIOUS in version 3.x. If your compiler does not support it, you must implement backward browsing by reading forward and storing records in a table, then displaying them in reverse order.

12.5.6 The READ INTO Pattern

A common production pattern reads the record directly into a working-storage copy, preserving the file buffer:

       WORKING-STORAGE SECTION.
       01  WS-ACCT-WORK-RECORD.
           05  WS-WK-ACCT-NUMBER     PIC X(10).
           05  WS-WK-HOLDER-NAME     PIC X(40).
           05  WS-WK-ACCT-TYPE       PIC X(02).
           05  WS-WK-OPEN-DATE       PIC 9(08).
           05  WS-WK-BALANCE         PIC S9(11)V99 COMP-3.
           05  FILLER                PIC X(289).

       PROCEDURE DIVISION.
       READ-INTO-WORKING.
           MOVE WS-SEARCH-KEY TO ACCT-NUMBER
           READ ACCT-MASTER-FILE
               INTO WS-ACCT-WORK-RECORD
               INVALID KEY
                   PERFORM HANDLE-NOT-FOUND
               NOT INVALID KEY
      *            Now both ACCT-MASTER-RECORD and
      *            WS-ACCT-WORK-RECORD contain the data.
      *            We can safely work with the WS copy
      *            without worrying about buffer overlay.
                   PERFORM PROCESS-ACCOUNT-DATA
           END-READ.

The INTO clause copies the record to a working-storage area, which is safer when your program does complex processing that might inadvertently modify the file buffer. Maria Chen recommends this pattern whenever a program reads from one file while writing to another — it prevents subtle bugs caused by record area aliasing.

12.5.7 Locking Considerations for Multi-User Access

In production environments, multiple programs (or CICS transactions) often access the same VSAM file simultaneously. Understanding locking is crucial:

Exclusive control: When a program opens a file for I-O (update), VSAM obtains a lock on each CI it reads. The lock is held until the program reads a different CI or closes the file. This prevents other programs from modifying the same records simultaneously.

Read integrity: Programs that open the file for INPUT share read access. Multiple programs can read the same records simultaneously, but none can update while others are reading.

Deadlock potential: If program A holds a lock on CI-1 and needs CI-2, while program B holds CI-2 and needs CI-1, a deadlock occurs. VSAM's sharing options and CICS's enqueue mechanisms help manage this risk.

      *--- SHAREOPTIONS in the cluster definition affect locking:
      *    SHAREOPTIONS(1) — exclusive control (one user at a time)
      *    SHAREOPTIONS(2) — multiple readers, one writer
      *    SHAREOPTIONS(3) — multiple readers and writers
      *                      (application must handle integrity)
      *    SHAREOPTIONS(4) — like 3 but with read integrity

📊 Production Reality: At GlobalBank, the ACCT-MASTER file uses SHAREOPTIONS(2 3). During batch processing (overnight), only one program updates the file at a time. During the online day, multiple CICS regions can read the file, but updates are serialized through CICS's file control mechanisms. Priya Kapoor designed this scheme to balance throughput with data integrity.

12.5.8 Understanding Record Length and Key Position

A source of subtle bugs is the relationship between the COBOL record description and the VSAM cluster definition. These must agree precisely:

VSAM Cluster Definition:          COBOL Record Description:
  KEYS(10 0)                        ACCT-NUMBER PIC X(10)  ← offset 0
  RECORDSIZE(350 350)               ... total record = 350 bytes

If these don't match:
  - Key offset wrong → wrong data used as key → wrong records returned
  - Key length wrong → partial key match → unpredictable results
  - Record size wrong → truncation or padding → data corruption

Derek Washington learned this lesson when he modified the record layout to add a new field before the key, shifting the key from offset 0 to offset 5. The program compiled cleanly but returned garbage data on every read. "The compiler doesn't validate your record layout against the VSAM definition," Maria explained. "That's your job."

Best Practice — Record Layout Copybooks: Always define the record layout in a COPYBOOK (COPY member) and use the same COPYBOOK in every program that accesses the file. This prevents the kind of layout mismatch that causes silent data corruption. We cover COPYBOOKS in depth in Chapter 9.

12.6 WRITE, REWRITE, and DELETE Operations

Reading is only half the story. Production systems must create, update, and delete records.

12.6.1 WRITE — Adding New Records

The WRITE statement adds a new record to an indexed file:

       ADD-NEW-ACCOUNT.
           INITIALIZE ACCT-MASTER-RECORD
           MOVE WS-NEW-ACCT-NUMBER    TO ACCT-NUMBER
           MOVE WS-NEW-HOLDER-NAME    TO ACCT-HOLDER-NAME
           MOVE WS-NEW-ACCT-TYPE      TO ACCT-TYPE
           MOVE WS-TODAYS-DATE        TO ACCT-OPEN-DATE
           MOVE ZERO                  TO ACCT-BALANCE
           MOVE 'AC'                  TO ACCT-STATUS-CODE
           MOVE WS-BRANCH-CODE        TO ACCT-BRANCH-CODE

           WRITE ACCT-MASTER-RECORD
               INVALID KEY
                   EVALUATE TRUE
                       WHEN WS-ACCT-DUP-KEY
                           DISPLAY 'Account already exists: '
                                   ACCT-NUMBER
                       WHEN OTHER
                           DISPLAY 'WRITE error: '
                                   WS-ACCT-STATUS
                   END-EVALUATE
               NOT INVALID KEY
                   ADD 1 TO WS-RECORDS-WRITTEN
           END-WRITE.

Key rules for WRITE on indexed files: - The record key must be populated before the WRITE - The key must be unique (no duplicates for primary key) - In sequential access mode, records must be written in ascending key order - In random or dynamic mode, records can be written in any order - If the key already exists, INVALID KEY triggers and status code '22' is set

12.6.2 REWRITE — Updating Existing Records

REWRITE replaces an existing record with updated data. There is one critical rule: you must READ the record before you REWRITE it.

       UPDATE-ACCOUNT-BALANCE.
           MOVE WS-ACCT-TO-UPDATE TO ACCT-NUMBER
           READ ACCT-MASTER-FILE
               INVALID KEY
                   DISPLAY 'Account not found: ' ACCT-NUMBER
                   GO TO UPDATE-ACCOUNT-EXIT
           END-READ

      *    Record is now in buffer — modify fields
           ADD WS-TXN-AMOUNT TO ACCT-BALANCE
           MOVE WS-TODAYS-DATE TO ACCT-LAST-TXN-DATE
           MOVE WS-TXN-AMOUNT  TO ACCT-LAST-TXN-AMT

           REWRITE ACCT-MASTER-RECORD
               INVALID KEY
                   DISPLAY 'REWRITE error: ' WS-ACCT-STATUS
               NOT INVALID KEY
                   ADD 1 TO WS-RECORDS-UPDATED
           END-REWRITE.

       UPDATE-ACCOUNT-EXIT.
           EXIT.

⚠️ Critical Rule: You cannot change the primary key value during a REWRITE. If you need to change the key, you must DELETE the old record and WRITE a new one with the new key. This is because the primary key determines the record's physical position in the file.

🔴 Production War Story: James Okafor recalls a MedClaim incident where a junior developer attempted to REWRITE a record without first READing it. "The program didn't abend — it just corrupted data silently. The REWRITE used whatever was in the buffer from a previous operation. We spent three days tracking down why certain provider records had incorrect addresses. Now our code review checklist has 'READ before REWRITE' as item number one."

12.6.3 DELETE — Removing Records

DELETE removes a record from an indexed file:

       DELETE-CLOSED-ACCOUNT.
           MOVE WS-ACCT-TO-DELETE TO ACCT-NUMBER

      *    In RANDOM/DYNAMIC mode, we can DELETE by key
      *    without a prior READ:
           DELETE ACCT-MASTER-FILE
               INVALID KEY
                   DISPLAY 'Account not found: ' ACCT-NUMBER
               NOT INVALID KEY
                   ADD 1 TO WS-RECORDS-DELETED
                   DISPLAY 'Deleted: ' ACCT-NUMBER
           END-DELETE.

In RANDOM or DYNAMIC access mode, you can DELETE by simply populating the key — a prior READ is not required (unlike REWRITE). However, many shops still require a READ before DELETE as a safety practice, to verify you are deleting the correct record:

       SAFE-DELETE-ACCOUNT.
           MOVE WS-ACCT-TO-DELETE TO ACCT-NUMBER
           READ ACCT-MASTER-FILE
               INVALID KEY
                   DISPLAY 'Account not found for deletion'
                   GO TO SAFE-DELETE-EXIT
           END-READ

      *    Verify before deleting
           IF NOT ACCT-CLOSED
               DISPLAY 'WARNING: Attempt to delete active '
                       'account ' ACCT-NUMBER
               DISPLAY 'Status: ' ACCT-STATUS-CODE
               GO TO SAFE-DELETE-EXIT
           END-IF

           DELETE ACCT-MASTER-FILE
               INVALID KEY
                   DISPLAY 'DELETE error: ' WS-ACCT-STATUS
               NOT INVALID KEY
                   ADD 1 TO WS-RECORDS-DELETED
           END-DELETE.

       SAFE-DELETE-EXIT.
           EXIT.

Best Practice — Logical Delete: In production banking systems, records are rarely physically deleted. Instead, a status field is set to 'CL' (closed) or 'DL' (deleted), and the record remains in the file. Physical deletion happens only during periodic reorganization. This preserves audit trails and simplifies recovery.

12.7 Alternate Record Keys

So far, all our access has been through the primary key (ACCT-NUMBER). But what if you need to find accounts by holder name? Or by branch code? VSAM supports alternate indexes — additional index structures that map alternate key values to records.

12.7.1 Defining Alternate Keys in COBOL

       FILE-CONTROL.
           SELECT ACCT-MASTER-FILE
               ASSIGN TO ACCTMAST
               ORGANIZATION IS INDEXED
               ACCESS MODE IS DYNAMIC
               RECORD KEY IS ACCT-NUMBER
               ALTERNATE RECORD KEY IS ACCT-HOLDER-NAME
                   WITH DUPLICATES
               ALTERNATE RECORD KEY IS ACCT-BRANCH-CODE
                   WITH DUPLICATES
               FILE STATUS IS WS-ACCT-STATUS.

The WITH DUPLICATES clause is essential for non-unique keys. Account holder names and branch codes will have many duplicates. Only the primary key must be unique.

12.7.2 VSAM Alternate Index Setup

Before your COBOL program can use alternate keys, the alternate indexes must be defined in VSAM using IDCAMS:

//DEFAIX   EXEC PGM=IDCAMS
//SYSPRINT DD SYSOUT=*
//SYSIN    DD *
  DEFINE ALTERNATEINDEX -
    (NAME(GLOBANK.ACCT.MASTER.NAMEAIX) -
     RELATE(GLOBANK.ACCT.MASTER) -
     KEYS(40 10) -
     NONUNIQUEKEY -
     UPGRADE -
     RECORDSIZE(60 1000) -
     CYLINDERS(5 2))

  DEFINE PATH -
    (NAME(GLOBANK.ACCT.MASTER.NAMEPATH) -
     PATHENTRY(GLOBANK.ACCT.MASTER.NAMEAIX))

  BLDINDEX -
    INDATASET(GLOBANK.ACCT.MASTER) -
    OUTDATASET(GLOBANK.ACCT.MASTER.NAMEAIX)
/*

The three steps: 1. DEFINE ALTERNATEINDEX: Creates the alternate index cluster, specifying which base cluster it relates to and where the alternate key is located (KEYS(40 10) means 40 bytes starting at offset 10) 2. DEFINE PATH: Creates a path that links the alternate index to the base cluster, allowing access through the alternate key 3. BLDINDEX: Populates the alternate index by scanning the base cluster

The UPGRADE option means the alternate index is automatically updated whenever the base cluster changes. This is essential for data consistency.

12.7.3 Reading by Alternate Key

To read by an alternate key, move a value to that key field and issue a READ:

       LOOKUP-BY-NAME.
           MOVE 'CHEN, MARIA' TO ACCT-HOLDER-NAME
           READ ACCT-MASTER-FILE
               KEY IS ACCT-HOLDER-NAME
               INVALID KEY
                   DISPLAY 'No account found for name'
               NOT INVALID KEY
                   DISPLAY 'Found: ' ACCT-NUMBER ' '
                           ACCT-HOLDER-NAME ' '
                           ACCT-BALANCE
           END-READ.

The KEY IS phrase tells COBOL to use the alternate key instead of the primary key.

12.7.4 Browsing by Alternate Key

You can also START and browse by alternate key, which is powerful for producing sorted output or range queries:

       BROWSE-BY-BRANCH.
           MOVE 'BR001' TO ACCT-BRANCH-CODE
           START ACCT-MASTER-FILE
               KEY IS EQUAL TO ACCT-BRANCH-CODE
               INVALID KEY
                   DISPLAY 'No accounts for branch BR001'
                   SET WS-EOF TO TRUE
           END-START

           PERFORM UNTIL WS-EOF
               OR ACCT-BRANCH-CODE NOT = 'BR001'
               READ ACCT-MASTER-FILE NEXT
                   AT END
                       SET WS-EOF TO TRUE
               END-READ
               IF NOT WS-EOF
                   IF ACCT-BRANCH-CODE = 'BR001'
                       PERFORM DISPLAY-ACCOUNT
                   END-IF
               END-IF
           END-PERFORM.

💡 Design Insight: Alternate keys provide a powerful way to access the same data from multiple perspectives without maintaining separate files. However, each alternate index adds overhead to WRITE, REWRITE, and DELETE operations (because the alternate index must be updated too). At GlobalBank, Priya Kapoor limits alternate keys to three per VSAM file: "Every alternate index is a trade-off between read flexibility and write performance."

12.8 VSAM File Status Codes — Defensive Programming

This section is arguably the most important in the chapter. Every VSAM I/O operation sets a two-byte file status code. Ignoring these codes is a recipe for data corruption and unexplained failures.

12.8.1 The File Status Field

The file status is a two-byte alphanumeric field. The first byte indicates the general category; the second byte provides detail:

Status Meaning Typical Cause
00 Successful completion Operation worked as expected
02 Duplicate alternate key Record written/rewritten with non-unique alternate key (and WITH DUPLICATES is specified)
10 End of file READ NEXT past last record
21 Sequence error Writing out of order in sequential mode
22 Duplicate primary key WRITE with key that already exists
23 Record not found READ/START/DELETE — key does not exist
24 Boundary violation Writing past end of file space
30 Permanent I/O error Hardware failure, data set corruption
35 File not found OPEN — file does not exist
37 OPEN mode conflict File opened for output but defined as input-only
39 Attribute mismatch COBOL description doesn't match file definition
41 File already open OPEN on an already-open file
42 File not open I/O on a file that hasn't been opened
43 READ required before REWRITE/DELETE In sequential mode, no prior READ
44 Record length error Record doesn't match RECORDSIZE
46 READ failed — no valid next record Sequential READ with no prior positioning
47 READ on file not opened for input File opened OUTPUT or EXTEND
48 WRITE on file not opened for output File opened INPUT
49 REWRITE/DELETE on file not opened for I-O File opened INPUT or OUTPUT
9x VSAM-specific errors See VSAM return/reason codes

12.8.2 Building a Robust Status-Check Routine

Here is the pattern Maria Chen insists on for every GlobalBank program:

       WORKING-STORAGE SECTION.
       01  WS-ACCT-STATUS             PIC XX.
           88  WS-ACCT-SUCCESS        VALUE '00'.
           88  WS-ACCT-DUP-ALT       VALUE '02'.
           88  WS-ACCT-EOF            VALUE '10'.
           88  WS-ACCT-SEQ-ERROR      VALUE '21'.
           88  WS-ACCT-DUP-KEY        VALUE '22'.
           88  WS-ACCT-NOT-FOUND      VALUE '23'.
           88  WS-ACCT-BOUNDARY       VALUE '24'.
           88  WS-ACCT-PERM-ERROR     VALUE '30'.
           88  WS-ACCT-NOT-EXIST      VALUE '35'.
           88  WS-ACCT-ATTR-MISMATCH  VALUE '39'.

       01  WS-IO-OPERATION            PIC X(10).
       01  WS-ABEND-CODE              PIC 9(04).

       PROCEDURE DIVISION.

       CHECK-ACCT-STATUS.
           IF WS-ACCT-SUCCESS OR WS-ACCT-DUP-ALT
               CONTINUE
           ELSE
               EVALUATE TRUE
                   WHEN WS-ACCT-EOF
                       CONTINUE
                   WHEN WS-ACCT-NOT-FOUND
                       CONTINUE
                   WHEN WS-ACCT-DUP-KEY
                       DISPLAY 'DUPLICATE KEY ON '
                               WS-IO-OPERATION
                               ' KEY=' ACCT-NUMBER
                   WHEN WS-ACCT-PERM-ERROR
                       DISPLAY 'PERMANENT I/O ERROR ON '
                               WS-IO-OPERATION
                       MOVE 3000 TO WS-ABEND-CODE
                       PERFORM ABEND-PROGRAM
                   WHEN WS-ACCT-NOT-EXIST
                       DISPLAY 'FILE NOT FOUND ON OPEN'
                       MOVE 3500 TO WS-ABEND-CODE
                       PERFORM ABEND-PROGRAM
                   WHEN WS-ACCT-ATTR-MISMATCH
                       DISPLAY 'ATTRIBUTE MISMATCH ON OPEN'
                       MOVE 3900 TO WS-ABEND-CODE
                       PERFORM ABEND-PROGRAM
                   WHEN OTHER
                       DISPLAY 'UNEXPECTED STATUS: '
                               WS-ACCT-STATUS
                               ' ON ' WS-IO-OPERATION
                       MOVE 9999 TO WS-ABEND-CODE
                       PERFORM ABEND-PROGRAM
               END-EVALUATE
           END-IF.

       ABEND-PROGRAM.
           DISPLAY '*** PROGRAM ABENDING ***'
           DISPLAY 'OPERATION: ' WS-IO-OPERATION
           DISPLAY 'STATUS:    ' WS-ACCT-STATUS
           DISPLAY 'KEY VALUE: ' ACCT-NUMBER
           STOP RUN.

🔴 The Defensive Programming Imperative: "I have a rule," says Maria Chen. "If I see a READ, WRITE, REWRITE, DELETE, START, or OPEN in your code without a file status check immediately after, I send it back in code review. No exceptions. I've seen too many production disasters caused by unchecked status codes."

12.8.3 Extended Status Codes

For VSAM files, when the primary status code is '9x', the second byte contains additional information. Some shops use an extended status code approach:

       01  WS-VSAM-RETURN-CODE       PIC 9(02).
       01  WS-VSAM-COMPONENT-CODE    PIC 9(01).
       01  WS-VSAM-REASON-CODE       PIC 9(03).

The extended codes map to specific VSAM return codes and reason codes documented in the IBM VSAM manual. For example, VSAM return code 8, reason code 8 indicates a logical error such as "record not found" — which your COBOL program sees as file status '23'.

12.9 Complete CRUD Program — GlobalBank Account Maintenance

Let us now bring everything together in a complete, production-quality program. This program performs all four CRUD operations on the ACCT-MASTER file:

       IDENTIFICATION DIVISION.
       PROGRAM-ID. ACCT-MAINT.
      *=============================================================*
      * ACCT-MAINT: Account Master File Maintenance                 *
      * GlobalBank Core Banking System                              *
      *                                                             *
      * Operations:                                                 *
      *   A = Add new account                                       *
      *   U = Update existing account                               *
      *   D = Delete (logical) account                              *
      *   I = Inquire (display) account                             *
      *   B = Browse accounts by range                              *
      *                                                             *
      * Input:  TXNFILE  - Transaction requests (sequential)        *
      * I/O:    ACCTMAST - Account master (VSAM KSDS)              *
      * Output: RPTFILE  - Processing report (sequential)           *
      *=============================================================*

       ENVIRONMENT DIVISION.
       INPUT-OUTPUT SECTION.
       FILE-CONTROL.
           SELECT TXN-INPUT-FILE
               ASSIGN TO TXNFILE
               ORGANIZATION IS LINE SEQUENTIAL
               FILE STATUS IS WS-TXN-STATUS.

           SELECT ACCT-MASTER-FILE
               ASSIGN TO ACCTMAST
               ORGANIZATION IS INDEXED
               ACCESS MODE IS DYNAMIC
               RECORD KEY IS AM-ACCT-NUMBER
               FILE STATUS IS WS-ACCT-STATUS.

           SELECT REPORT-FILE
               ASSIGN TO RPTFILE
               ORGANIZATION IS LINE SEQUENTIAL
               FILE STATUS IS WS-RPT-STATUS.

       DATA DIVISION.
       FILE SECTION.
       FD  TXN-INPUT-FILE.
       01  TXN-INPUT-RECORD.
           05  TXN-OPER-CODE          PIC X(01).
               88  TXN-ADD            VALUE 'A'.
               88  TXN-UPDATE         VALUE 'U'.
               88  TXN-DELETE         VALUE 'D'.
               88  TXN-INQUIRE        VALUE 'I'.
               88  TXN-BROWSE         VALUE 'B'.
           05  TXN-ACCT-NUMBER        PIC X(10).
           05  TXN-DATA               PIC X(289).

       FD  ACCT-MASTER-FILE.
       01  ACCT-MASTER-RECORD.
           05  AM-ACCT-NUMBER         PIC X(10).
           05  AM-HOLDER-NAME         PIC X(40).
           05  AM-ACCT-TYPE           PIC X(02).
           05  AM-OPEN-DATE           PIC 9(08).
           05  AM-BALANCE             PIC S9(11)V99 COMP-3.
           05  AM-HOLD-AMOUNT         PIC S9(11)V99 COMP-3.
           05  AM-LAST-TXN-DATE       PIC 9(08).
           05  AM-LAST-TXN-AMT        PIC S9(09)V99 COMP-3.
           05  AM-STATUS-CODE         PIC X(02).
               88  AM-ACTIVE          VALUE 'AC'.
               88  AM-FROZEN          VALUE 'FR'.
               88  AM-CLOSED          VALUE 'CL'.
           05  AM-BRANCH-CODE         PIC X(05).
           05  AM-OFFICER-ID          PIC X(06).
           05  FILLER                 PIC X(233).

       FD  REPORT-FILE.
       01  REPORT-RECORD              PIC X(132).

       WORKING-STORAGE SECTION.
       01  WS-TXN-STATUS             PIC XX.
       01  WS-ACCT-STATUS            PIC XX.
           88  WS-ACCT-SUCCESS       VALUE '00'.
           88  WS-ACCT-DUP-ALT       VALUE '02'.
           88  WS-ACCT-EOF           VALUE '10'.
           88  WS-ACCT-DUP-KEY       VALUE '22'.
           88  WS-ACCT-NOT-FOUND     VALUE '23'.
           88  WS-ACCT-PERM-ERROR    VALUE '30'.
       01  WS-RPT-STATUS             PIC XX.

       01  WS-COUNTERS.
           05  WS-RECORDS-READ       PIC 9(07) VALUE ZERO.
           05  WS-ADDS-OK            PIC 9(07) VALUE ZERO.
           05  WS-ADDS-FAIL          PIC 9(07) VALUE ZERO.
           05  WS-UPDATES-OK         PIC 9(07) VALUE ZERO.
           05  WS-UPDATES-FAIL       PIC 9(07) VALUE ZERO.
           05  WS-DELETES-OK         PIC 9(07) VALUE ZERO.
           05  WS-DELETES-FAIL       PIC 9(07) VALUE ZERO.
           05  WS-INQUIRIES           PIC 9(07) VALUE ZERO.
           05  WS-ERRORS              PIC 9(07) VALUE ZERO.

       01  WS-TXN-UPDATE-DATA.
           05  WS-UPD-HOLDER-NAME    PIC X(40).
           05  WS-UPD-ACCT-TYPE      PIC X(02).
           05  WS-UPD-BRANCH-CODE    PIC X(05).
           05  WS-UPD-OFFICER-ID     PIC X(06).
           05  FILLER                PIC X(236).

       01  WS-TXN-ADD-DATA.
           05  WS-ADD-HOLDER-NAME    PIC X(40).
           05  WS-ADD-ACCT-TYPE      PIC X(02).
           05  WS-ADD-BRANCH-CODE    PIC X(05).
           05  WS-ADD-OFFICER-ID     PIC X(06).
           05  FILLER                PIC X(236).

       01  WS-TXN-BROWSE-DATA.
           05  WS-BRW-START-KEY      PIC X(10).
           05  WS-BRW-END-KEY        PIC X(10).
           05  FILLER                PIC X(269).

       01  WS-DISPLAY-BAL            PIC Z(10)9.99-.
       01  WS-IO-OPERATION           PIC X(10).

       01  WS-EOF-TXN                PIC X VALUE 'N'.
           88  WS-TXN-EOF            VALUE 'Y'.
           88  WS-TXN-NOT-EOF        VALUE 'N'.

       01  WS-EOF-BROWSE             PIC X VALUE 'N'.
           88  WS-BRW-EOF            VALUE 'Y'.
           88  WS-BRW-NOT-EOF        VALUE 'N'.

       01  WS-RPT-LINE               PIC X(132).
       01  WS-TODAYS-DATE            PIC 9(08).

       PROCEDURE DIVISION.
       0000-MAIN.
           PERFORM 1000-INITIALIZE
           PERFORM 2000-PROCESS-TRANSACTIONS
               UNTIL WS-TXN-EOF
           PERFORM 8000-WRITE-SUMMARY
           PERFORM 9000-TERMINATE
           STOP RUN.

       1000-INITIALIZE.
           ACCEPT WS-TODAYS-DATE FROM DATE YYYYMMDD
           OPEN INPUT  TXN-INPUT-FILE
           OPEN I-O    ACCT-MASTER-FILE
           OPEN OUTPUT REPORT-FILE

           IF WS-ACCT-STATUS NOT = '00'
               DISPLAY 'FATAL: Cannot open ACCT-MASTER: '
                       WS-ACCT-STATUS
               STOP RUN
           END-IF

           MOVE 'OPEN' TO WS-IO-OPERATION
           STRING 'ACCT-MAINT PROCESSING REPORT - '
                  WS-TODAYS-DATE
                  DELIMITED BY SIZE INTO WS-RPT-LINE
           WRITE REPORT-RECORD FROM WS-RPT-LINE
           MOVE SPACES TO WS-RPT-LINE
           WRITE REPORT-RECORD FROM WS-RPT-LINE

           PERFORM 2100-READ-TXN.

       2000-PROCESS-TRANSACTIONS.
           ADD 1 TO WS-RECORDS-READ
           EVALUATE TRUE
               WHEN TXN-ADD
                   PERFORM 3000-ADD-ACCOUNT
               WHEN TXN-UPDATE
                   PERFORM 4000-UPDATE-ACCOUNT
               WHEN TXN-DELETE
                   PERFORM 5000-DELETE-ACCOUNT
               WHEN TXN-INQUIRE
                   PERFORM 6000-INQUIRE-ACCOUNT
               WHEN TXN-BROWSE
                   PERFORM 7000-BROWSE-ACCOUNTS
               WHEN OTHER
                   STRING 'ERROR: Invalid operation code: '
                          TXN-OPER-CODE
                          ' for account ' TXN-ACCT-NUMBER
                          DELIMITED BY SIZE INTO WS-RPT-LINE
                   WRITE REPORT-RECORD FROM WS-RPT-LINE
                   ADD 1 TO WS-ERRORS
           END-EVALUATE
           PERFORM 2100-READ-TXN.

       2100-READ-TXN.
           READ TXN-INPUT-FILE
               AT END
                   SET WS-TXN-EOF TO TRUE
               NOT AT END
                   CONTINUE
           END-READ.

       3000-ADD-ACCOUNT.
           MOVE TXN-DATA TO WS-TXN-ADD-DATA
           INITIALIZE ACCT-MASTER-RECORD
           MOVE TXN-ACCT-NUMBER      TO AM-ACCT-NUMBER
           MOVE WS-ADD-HOLDER-NAME   TO AM-HOLDER-NAME
           MOVE WS-ADD-ACCT-TYPE     TO AM-ACCT-TYPE
           MOVE WS-TODAYS-DATE       TO AM-OPEN-DATE
           MOVE ZERO                 TO AM-BALANCE
           MOVE ZERO                 TO AM-HOLD-AMOUNT
           MOVE 'AC'                 TO AM-STATUS-CODE
           MOVE WS-ADD-BRANCH-CODE   TO AM-BRANCH-CODE
           MOVE WS-ADD-OFFICER-ID    TO AM-OFFICER-ID

           MOVE 'WRITE' TO WS-IO-OPERATION
           WRITE ACCT-MASTER-RECORD
               INVALID KEY
                   IF WS-ACCT-DUP-KEY
                       STRING 'ADD FAILED - Duplicate: '
                              AM-ACCT-NUMBER
                              DELIMITED BY SIZE
                              INTO WS-RPT-LINE
                   ELSE
                       STRING 'ADD FAILED - Status '
                              WS-ACCT-STATUS ': '
                              AM-ACCT-NUMBER
                              DELIMITED BY SIZE
                              INTO WS-RPT-LINE
                   END-IF
                   WRITE REPORT-RECORD FROM WS-RPT-LINE
                   ADD 1 TO WS-ADDS-FAIL
               NOT INVALID KEY
                   STRING 'ADDED: ' AM-ACCT-NUMBER
                          ' ' AM-HOLDER-NAME
                          DELIMITED BY SIZE INTO WS-RPT-LINE
                   WRITE REPORT-RECORD FROM WS-RPT-LINE
                   ADD 1 TO WS-ADDS-OK
           END-WRITE.

       4000-UPDATE-ACCOUNT.
           MOVE TXN-DATA TO WS-TXN-UPDATE-DATA
           MOVE TXN-ACCT-NUMBER TO AM-ACCT-NUMBER

           MOVE 'READ' TO WS-IO-OPERATION
           READ ACCT-MASTER-FILE
               INVALID KEY
                   STRING 'UPDATE FAILED - Not found: '
                          AM-ACCT-NUMBER
                          DELIMITED BY SIZE INTO WS-RPT-LINE
                   WRITE REPORT-RECORD FROM WS-RPT-LINE
                   ADD 1 TO WS-UPDATES-FAIL
                   GO TO 4000-EXIT
           END-READ

      *    Apply updates — only non-space fields
           IF WS-UPD-HOLDER-NAME NOT = SPACES
               MOVE WS-UPD-HOLDER-NAME TO AM-HOLDER-NAME
           END-IF
           IF WS-UPD-ACCT-TYPE NOT = SPACES
               MOVE WS-UPD-ACCT-TYPE TO AM-ACCT-TYPE
           END-IF
           IF WS-UPD-BRANCH-CODE NOT = SPACES
               MOVE WS-UPD-BRANCH-CODE TO AM-BRANCH-CODE
           END-IF
           IF WS-UPD-OFFICER-ID NOT = SPACES
               MOVE WS-UPD-OFFICER-ID TO AM-OFFICER-ID
           END-IF
           MOVE WS-TODAYS-DATE TO AM-LAST-TXN-DATE

           MOVE 'REWRITE' TO WS-IO-OPERATION
           REWRITE ACCT-MASTER-RECORD
               INVALID KEY
                   STRING 'UPDATE FAILED - Rewrite error '
                          WS-ACCT-STATUS ': '
                          AM-ACCT-NUMBER
                          DELIMITED BY SIZE INTO WS-RPT-LINE
                   WRITE REPORT-RECORD FROM WS-RPT-LINE
                   ADD 1 TO WS-UPDATES-FAIL
               NOT INVALID KEY
                   STRING 'UPDATED: ' AM-ACCT-NUMBER
                          DELIMITED BY SIZE INTO WS-RPT-LINE
                   WRITE REPORT-RECORD FROM WS-RPT-LINE
                   ADD 1 TO WS-UPDATES-OK
           END-REWRITE.

       4000-EXIT.
           EXIT.

       5000-DELETE-ACCOUNT.
           MOVE TXN-ACCT-NUMBER TO AM-ACCT-NUMBER

           MOVE 'READ' TO WS-IO-OPERATION
           READ ACCT-MASTER-FILE
               INVALID KEY
                   STRING 'DELETE FAILED - Not found: '
                          AM-ACCT-NUMBER
                          DELIMITED BY SIZE INTO WS-RPT-LINE
                   WRITE REPORT-RECORD FROM WS-RPT-LINE
                   ADD 1 TO WS-DELETES-FAIL
                   GO TO 5000-EXIT
           END-READ

      *    Logical delete — set status to closed
           MOVE 'CL' TO AM-STATUS-CODE
           MOVE WS-TODAYS-DATE TO AM-LAST-TXN-DATE

           MOVE 'REWRITE' TO WS-IO-OPERATION
           REWRITE ACCT-MASTER-RECORD
               INVALID KEY
                   STRING 'DELETE FAILED - Rewrite error '
                          WS-ACCT-STATUS ': '
                          AM-ACCT-NUMBER
                          DELIMITED BY SIZE INTO WS-RPT-LINE
                   WRITE REPORT-RECORD FROM WS-RPT-LINE
                   ADD 1 TO WS-DELETES-FAIL
               NOT INVALID KEY
                   STRING 'DELETED (logical): '
                          AM-ACCT-NUMBER
                          DELIMITED BY SIZE INTO WS-RPT-LINE
                   WRITE REPORT-RECORD FROM WS-RPT-LINE
                   ADD 1 TO WS-DELETES-OK
           END-REWRITE.

       5000-EXIT.
           EXIT.

       6000-INQUIRE-ACCOUNT.
           MOVE TXN-ACCT-NUMBER TO AM-ACCT-NUMBER

           MOVE 'READ' TO WS-IO-OPERATION
           READ ACCT-MASTER-FILE
               INVALID KEY
                   STRING 'INQUIRY - Not found: '
                          AM-ACCT-NUMBER
                          DELIMITED BY SIZE INTO WS-RPT-LINE
                   WRITE REPORT-RECORD FROM WS-RPT-LINE
                   GO TO 6000-EXIT
           END-READ

           MOVE AM-BALANCE TO WS-DISPLAY-BAL
           STRING 'INQUIRY: ' AM-ACCT-NUMBER
                  ' ' AM-HOLDER-NAME
                  ' BAL=' WS-DISPLAY-BAL
                  ' STS=' AM-STATUS-CODE
                  DELIMITED BY SIZE INTO WS-RPT-LINE
           WRITE REPORT-RECORD FROM WS-RPT-LINE
           ADD 1 TO WS-INQUIRIES.

       6000-EXIT.
           EXIT.

       7000-BROWSE-ACCOUNTS.
           MOVE TXN-DATA TO WS-TXN-BROWSE-DATA
           MOVE WS-BRW-START-KEY TO AM-ACCT-NUMBER
           SET WS-BRW-NOT-EOF TO TRUE

           STRING 'BROWSE: ' WS-BRW-START-KEY
                  ' TO ' WS-BRW-END-KEY
                  DELIMITED BY SIZE INTO WS-RPT-LINE
           WRITE REPORT-RECORD FROM WS-RPT-LINE

           MOVE 'START' TO WS-IO-OPERATION
           START ACCT-MASTER-FILE
               KEY IS NOT LESS THAN AM-ACCT-NUMBER
               INVALID KEY
                   STRING '  No records in range'
                          DELIMITED BY SIZE INTO WS-RPT-LINE
                   WRITE REPORT-RECORD FROM WS-RPT-LINE
                   SET WS-BRW-EOF TO TRUE
           END-START

           IF WS-BRW-NOT-EOF
               PERFORM 7100-READ-NEXT-BROWSE
           END-IF

           PERFORM UNTIL WS-BRW-EOF
               OR AM-ACCT-NUMBER > WS-BRW-END-KEY
               MOVE AM-BALANCE TO WS-DISPLAY-BAL
               STRING '  ' AM-ACCT-NUMBER
                      ' ' AM-HOLDER-NAME
                      ' ' WS-DISPLAY-BAL
                      DELIMITED BY SIZE INTO WS-RPT-LINE
               WRITE REPORT-RECORD FROM WS-RPT-LINE
               PERFORM 7100-READ-NEXT-BROWSE
           END-PERFORM.

       7100-READ-NEXT-BROWSE.
           MOVE 'READ NEXT' TO WS-IO-OPERATION
           READ ACCT-MASTER-FILE NEXT
               AT END
                   SET WS-BRW-EOF TO TRUE
               NOT AT END
                   CONTINUE
           END-READ.

       8000-WRITE-SUMMARY.
           MOVE SPACES TO WS-RPT-LINE
           WRITE REPORT-RECORD FROM WS-RPT-LINE
           STRING '=== PROCESSING SUMMARY ==='
                  DELIMITED BY SIZE INTO WS-RPT-LINE
           WRITE REPORT-RECORD FROM WS-RPT-LINE
           STRING 'Transactions read:  ' WS-RECORDS-READ
                  DELIMITED BY SIZE INTO WS-RPT-LINE
           WRITE REPORT-RECORD FROM WS-RPT-LINE
           STRING 'Adds successful:    ' WS-ADDS-OK
                  DELIMITED BY SIZE INTO WS-RPT-LINE
           WRITE REPORT-RECORD FROM WS-RPT-LINE
           STRING 'Adds failed:        ' WS-ADDS-FAIL
                  DELIMITED BY SIZE INTO WS-RPT-LINE
           WRITE REPORT-RECORD FROM WS-RPT-LINE
           STRING 'Updates successful:  ' WS-UPDATES-OK
                  DELIMITED BY SIZE INTO WS-RPT-LINE
           WRITE REPORT-RECORD FROM WS-RPT-LINE
           STRING 'Updates failed:      ' WS-UPDATES-FAIL
                  DELIMITED BY SIZE INTO WS-RPT-LINE
           WRITE REPORT-RECORD FROM WS-RPT-LINE
           STRING 'Deletes successful:  ' WS-DELETES-OK
                  DELIMITED BY SIZE INTO WS-RPT-LINE
           WRITE REPORT-RECORD FROM WS-RPT-LINE
           STRING 'Deletes failed:      ' WS-DELETES-FAIL
                  DELIMITED BY SIZE INTO WS-RPT-LINE
           WRITE REPORT-RECORD FROM WS-RPT-LINE
           STRING 'Inquiries:           ' WS-INQUIRIES
                  DELIMITED BY SIZE INTO WS-RPT-LINE
           WRITE REPORT-RECORD FROM WS-RPT-LINE
           STRING 'Errors:              ' WS-ERRORS
                  DELIMITED BY SIZE INTO WS-RPT-LINE
           WRITE REPORT-RECORD FROM WS-RPT-LINE.

       9000-TERMINATE.
           CLOSE TXN-INPUT-FILE
                 ACCT-MASTER-FILE
                 REPORT-FILE.

📊 Code Analysis: Notice the structure of this program. Every I/O operation checks status. Every error is logged to the report file. Counters track every outcome. The program uses logical deletes (setting status to 'CL') rather than physical deletes. This is production-quality code — the kind that runs reliably for years.

12.10 MedClaim Case Study: Provider Information Retrieval

Let us see how indexed files work in the insurance domain. MedClaim's PROVIDER-FILE stores information about healthcare providers — doctors, hospitals, clinics — that submit claims. James Okafor's team built a provider lookup module used by the claims adjudication system.

12.10.1 Provider File Layout

       FD  PROVIDER-FILE.
       01  PROVIDER-RECORD.
           05  PRV-PROVIDER-ID        PIC X(10).
           05  PRV-NPI-NUMBER         PIC 9(10).
           05  PRV-NAME               PIC X(50).
           05  PRV-SPECIALTY-CODE     PIC X(03).
           05  PRV-ADDRESS.
               10  PRV-STREET         PIC X(40).
               10  PRV-CITY           PIC X(25).
               10  PRV-STATE          PIC X(02).
               10  PRV-ZIP            PIC X(09).
           05  PRV-PHONE              PIC X(10).
           05  PRV-LICENSE-STATE      PIC X(02).
           05  PRV-LICENSE-NUMBER     PIC X(15).
           05  PRV-CONTRACT-STATUS    PIC X(02).
               88  PRV-IN-NETWORK     VALUE 'IN'.
               88  PRV-OUT-NETWORK    VALUE 'OT'.
               88  PRV-SUSPENDED      VALUE 'SU'.
               88  PRV-TERMINATED     VALUE 'TE'.
           05  PRV-EFF-DATE           PIC 9(08).
           05  PRV-TERM-DATE          PIC 9(08).
           05  PRV-MAX-ALLOWED        PIC S9(07)V99 COMP-3.
           05  FILLER                 PIC X(114).

12.10.2 Provider Lookup with Validation

The claims adjudication system calls this module to verify provider information before paying a claim:

       VALIDATE-PROVIDER.
           MOVE CLM-PROVIDER-ID TO PRV-PROVIDER-ID

           READ PROVIDER-FILE
               INVALID KEY
                   MOVE 'PRVNF' TO CLM-DENY-REASON
                   SET CLM-DENIED TO TRUE
                   STRING 'Provider not found: '
                          CLM-PROVIDER-ID
                          DELIMITED BY SIZE INTO WS-LOG-LINE
                   PERFORM WRITE-AUDIT-LOG
                   GO TO VALIDATE-PROVIDER-EXIT
           END-READ

      *    Check contract status
           EVALUATE TRUE
               WHEN PRV-IN-NETWORK
                   CONTINUE
               WHEN PRV-OUT-NETWORK
                   SET CLM-OUT-OF-NETWORK TO TRUE
               WHEN PRV-SUSPENDED
                   MOVE 'PRVSU' TO CLM-DENY-REASON
                   SET CLM-DENIED TO TRUE
                   PERFORM WRITE-SUSPENSION-ALERT
                   GO TO VALIDATE-PROVIDER-EXIT
               WHEN PRV-TERMINATED
                   MOVE 'PRVTE' TO CLM-DENY-REASON
                   SET CLM-DENIED TO TRUE
                   GO TO VALIDATE-PROVIDER-EXIT
           END-EVALUATE

      *    Check dates
           IF CLM-SERVICE-DATE < PRV-EFF-DATE
           OR (PRV-TERM-DATE > ZERO
               AND CLM-SERVICE-DATE > PRV-TERM-DATE)
               MOVE 'PRVDT' TO CLM-DENY-REASON
               SET CLM-DENIED TO TRUE
               GO TO VALIDATE-PROVIDER-EXIT
           END-IF

      *    Check max allowed
           IF CLM-BILLED-AMOUNT > PRV-MAX-ALLOWED
               MOVE PRV-MAX-ALLOWED TO CLM-ALLOWED-AMOUNT
               SET CLM-REDUCED TO TRUE
           END-IF

           SET CLM-PROVIDER-VALID TO TRUE.

       VALIDATE-PROVIDER-EXIT.
           EXIT.

⚖️ Real-World Context: This provider validation logic represents a fraction of what production claims systems check. Real adjudication engines verify provider licensure against state databases, check for sanctions, validate taxonomy codes, and cross-reference with fraud detection systems. But the core VSAM access pattern — look up by key, validate fields, make a decision — remains the same whether you are checking one field or fifty.

12.11 Performance Considerations

Efficient VSAM processing requires understanding the trade-offs between different access patterns.

12.11.1 Random vs. Sequential Performance

Metric Random Access Sequential Access
I/Os per record 3-4 (index + data) ~1/n (buffered)
Best for Low-volume lookups Bulk processing
Buffer strategy Maximize index buffers Maximize data buffers
CI size impact Smaller CIs waste less buffer Larger CIs mean fewer I/Os

12.11.2 Free Space Trade-offs

More Free Space Less Free Space
Fewer CI/CA splits More CI/CA splits
Fewer I/Os on insert More data per CI
Wasted disk space Better sequential read
Better for insert-heavy Better for read-heavy

12.11.3 Buffer Tuning Guidelines

Priya Kapoor's buffer allocation rules at GlobalBank:

  1. Online (CICS) programs: BUFNI = index set levels + 1, BUFND = 2-3
  2. Batch sequential: BUFND = as many as possible, BUFNI = index set levels
  3. Batch random: BUFNI = index set levels + 1, BUFND = 5-10

💡 Performance Tip: The single most impactful tuning action for VSAM is keeping the index set entirely in memory. For a 4.7 million record file with 4 KB CIs, the index set might be only 200 KB — a trivial amount of memory that eliminates thousands of I/O operations per batch run.

12.12 Try It Yourself: Hands-On Exercises

Exercise 12.1 — Basic VSAM KSDS Program

Using your Student Mainframe Lab (or GnuCOBOL with indexed file support):

  1. Define a STUDENT-FILE with the following record layout: - Student ID (key, 8 bytes) - Name (30 bytes) - Major (20 bytes) - GPA (3 bytes: 9V99) - Credit hours (3 bytes: 999)

  2. Write a COBOL program that: - Opens the file for OUTPUT - Loads 10 sample records - Closes the file - Reopens for I-O - Reads a specific student by ID - Updates that student's GPA - Displays the updated record

Exercise 12.2 — Browse with Range

Extend Exercise 12.1:

  1. Add a browse function that displays all students with GPAs above 3.5
  2. Use START and READ NEXT to iterate through all records
  3. Filter in your COBOL code (since GPA is not a key)

Exercise 12.3 — Alternate Key Access

Add an alternate key on the Major field (with duplicates):

  1. Modify the SELECT to include ALTERNATE RECORD KEY
  2. Write code to browse all students in a given major
  3. Count students per major

🧪 GnuCOBOL Note: GnuCOBOL supports alternate keys natively. Your SELECT statement is identical to mainframe COBOL. The runtime handles the alternate index automatically — no IDCAMS required.

12.13 Common Mistakes and Debugging Tips

Years of experience across GlobalBank and MedClaim have produced this list of the most common indexed file mistakes:

Mistake 1: Forgetting to Check File Status

      *--- WRONG — no status check
           READ ACCT-MASTER-FILE
           MOVE AM-BALANCE TO WS-DISPLAY-BAL

      *--- RIGHT — always check
           READ ACCT-MASTER-FILE
               INVALID KEY
                   PERFORM HANDLE-NOT-FOUND
               NOT INVALID KEY
                   MOVE AM-BALANCE TO WS-DISPLAY-BAL
           END-READ

Mistake 2: REWRITE Without Prior READ

      *--- WRONG — no prior READ
           MOVE NEW-DATA TO ACCT-MASTER-RECORD
           REWRITE ACCT-MASTER-RECORD

      *--- RIGHT — READ first, then modify, then REWRITE
           READ ACCT-MASTER-FILE ...
           MOVE NEW-NAME TO AM-HOLDER-NAME
           REWRITE ACCT-MASTER-RECORD

Mistake 3: Changing Primary Key on REWRITE

      *--- WRONG — cannot change primary key
           READ ACCT-MASTER-FILE ...
           MOVE NEW-ACCT-NUMBER TO AM-ACCT-NUMBER
           REWRITE ACCT-MASTER-RECORD

      *--- RIGHT — delete old, write new
           READ ACCT-MASTER-FILE ...
           MOVE ACCT-MASTER-RECORD TO WS-TEMP-RECORD
           DELETE ACCT-MASTER-FILE
           MOVE NEW-ACCT-NUMBER TO AM-ACCT-NUMBER
           WRITE ACCT-MASTER-RECORD
      *--- WRONG — no positioning
           READ ACCT-MASTER-FILE NEXT

      *--- RIGHT — START first to establish position
           MOVE WS-START-KEY TO AM-ACCT-NUMBER
           START ACCT-MASTER-FILE
               KEY IS NOT LESS THAN AM-ACCT-NUMBER
           END-START
           READ ACCT-MASTER-FILE NEXT

Mistake 5: Ignoring Status '02' (Duplicate Alternate Key)

Status '02' is a successful completion — the record was written, but it had a duplicate alternate key value. Many programmers treat any non-'00' status as an error:

      *--- WRONG — treats '02' as error
           IF WS-ACCT-STATUS NOT = '00'
               PERFORM ERROR-ROUTINE
           END-IF

      *--- RIGHT — '02' is acceptable
           IF WS-ACCT-SUCCESS OR WS-ACCT-DUP-ALT
               CONTINUE
           ELSE
               PERFORM ERROR-ROUTINE
           END-IF

12.14 OPEN Modes and Their Implications

The OPEN statement determines what operations are permitted on the file during program execution. Choosing the wrong OPEN mode is a common source of file status errors.

12.14.1 OPEN Modes for Indexed Files

OPEN Mode READ WRITE REWRITE DELETE START
INPUT Yes No No No Yes
OUTPUT No Yes No No No
I-O Yes Yes Yes Yes Yes
EXTEND No Yes No No No

OPEN INPUT: The file is opened for reading only. Any attempt to WRITE, REWRITE, or DELETE produces a status '48' or '49' error. This is the safest mode for programs that only need to read data — it guarantees the file cannot be accidentally modified.

OPEN OUTPUT: The file is opened for writing. On a mainframe, this typically empties the file first — all existing records are deleted. Be extremely careful with OPEN OUTPUT on a production file.

OPEN I-O: The file is opened for both reading and updating. This is required for REWRITE and DELETE operations. Most production maintenance programs use I-O mode.

OPEN EXTEND: The file is opened for appending. New records can be written (in ascending key order, after the last existing record), but existing records cannot be read or modified. Useful for adding records to the end of a file without affecting existing data.

      *--- Open for reading only (reports, inquiries)
           OPEN INPUT ACCT-MASTER-FILE
           IF WS-ACCT-STATUS NOT = '00'
               DISPLAY 'Cannot open for INPUT: '
                       WS-ACCT-STATUS
               STOP RUN
           END-IF

      *--- Open for full CRUD operations
           OPEN I-O ACCT-MASTER-FILE
           IF WS-ACCT-STATUS NOT = '00'
               DISPLAY 'Cannot open for I-O: '
                       WS-ACCT-STATUS
               STOP RUN
           END-IF

⚠️ Critical Warning: Never use OPEN OUTPUT on an existing production VSAM file unless you intend to delete all records. Derek Washington once used OPEN OUTPUT instead of OPEN I-O during testing, and it wiped out his test file. On a production system, this mistake would be catastrophic.

12.14.2 Closing Files Properly

Always CLOSE files when you are done. While the operating system will close files when the program terminates normally, an abnormal termination (ABEND) with unclosed files can leave VSAM in an inconsistent state:

       9000-TERMINATE.
      *    Close files in reverse order of opening
      *    (not required, but a good convention)
           CLOSE REPORT-FILE
           CLOSE ACCT-MASTER-FILE
           CLOSE TXN-INPUT-FILE.

A single CLOSE statement can close multiple files:

           CLOSE TXN-INPUT-FILE
                 ACCT-MASTER-FILE
                 REPORT-FILE.

12.14.3 JCL File Allocation

On the mainframe, the connection between your COBOL program's ASSIGN TO name and the physical VSAM file is made through JCL DD statements. Understanding this connection is essential for debugging file-not-found errors:

//MYJOB    JOB ...
//STEP1    EXEC PGM=ACCTMAIN
//*
//* The DD name matches the ASSIGN TO name in COBOL
//*
//ACCTMAST DD DSN=GLOBANK.ACCT.MASTER.VSAM,
//            DISP=SHR,
//            AMP=('BUFND=10,BUFNI=5')
//*
//TXNFILE  DD DSN=GLOBANK.TXN.DAILY.SEQ,DISP=SHR
//RPTFILE  DD SYSOUT=*

Key JCL parameters for VSAM: - DSN: The data set name of the VSAM cluster - DISP=SHR: Allow shared access (for INPUT or I-O with SHAREOPTIONS) - DISP=OLD: Exclusive access (required for some operations) - AMP: Access Method Parameters — override buffer counts, specify STRNO (concurrent requests), etc.

💡 Lab Tip: In GnuCOBOL, you set the file path through environment variables instead of JCL. The DD name from ASSIGN TO becomes the environment variable name:

export ACCTMAST=/home/student/data/acct-master.dat
export TXNFILE=/home/student/data/transactions.txt
export RPTFILE=/home/student/output/report.txt
./acctmain

12.15 Practical Walkthrough: Building a VSAM Program Step by Step

Let us walk through the complete process of creating a VSAM KSDS program from scratch, as Derek Washington experienced during his first week at GlobalBank.

Step 1: Understand the Requirements

Sarah Kim provides the business requirement: "We need a program that loads initial account data from a flat file extract into the ACCT-MASTER VSAM file."

Step 2: Design the Record Layout

Before writing any COBOL, design the record layout. This must match the VSAM cluster definition (or vice versa — in practice, the record layout and cluster definition are designed together):

Account Number:   10 bytes, alphanumeric, offset 0 (PRIMARY KEY)
Holder Name:      40 bytes, alphanumeric, offset 10
Account Type:      2 bytes, alphanumeric, offset 50
Open Date:         8 bytes, numeric (YYYYMMDD), offset 52
Balance:           7 bytes, packed decimal (S9(11)V99 COMP-3), offset 60
Status Code:       2 bytes, alphanumeric, offset 67
Branch Code:       5 bytes, alphanumeric, offset 69
FILLER:          276 bytes, offset 74
TOTAL:           350 bytes

Step 3: Define the VSAM Cluster

Work with the DBA (Tomas Rivera at MedClaim, or your instructor in the lab) to define the cluster:

  DEFINE CLUSTER -
    (NAME(STUDENT.ACCT.MASTER) -
     INDEXED -
     KEYS(10 0) -
     RECORDSIZE(350 350) -
     TRACKS(50 10) -
     FREESPACE(20 10))

Step 4: Write the COBOL Program

Now write the program, following the patterns from this chapter:

  1. SELECT with ORGANIZATION IS INDEXED, ACCESS MODE IS DYNAMIC, FILE STATUS
  2. FD with record layout matching the cluster definition
  3. Working-storage status fields with 88-level conditions
  4. OPEN with status check
  5. I/O operations with INVALID KEY / NOT INVALID KEY
  6. Processing logic
  7. CLOSE

Compile the program, run it against test data, and verify: - All records loaded (compare counts) - Status codes handled correctly (test with duplicates, invalid data) - File can be read back correctly (write a separate verification program)

Step 6: Code Review

Submit for code review. Maria Chen's checklist includes: - FILE STATUS checked after every I/O - INVALID KEY handled on every READ, WRITE, REWRITE, DELETE, START - COPYBOOK used for record layout - Counters for all processing paths - Summary report at end of run - No hardcoded values (use WORKING-STORAGE constants)

This systematic approach — requirements, design, define, code, test, review — applies to every VSAM program you will write.

12.16 VSAM and GnuCOBOL Compatibility

For students working with GnuCOBOL rather than a mainframe environment, here are the key differences and equivalences:

Mainframe VSAM GnuCOBOL
IDCAMS DEFINE CLUSTER File created on first OPEN OUTPUT
JCL DD statement Environment variable or config file
CISZ, FREESPACE Handled by runtime (BDB/ISAM)
BUFND, BUFNI Not applicable
SHAREOPTIONS OS-level file locking
Alternate indexes via IDCAMS Automatic — declared in SELECT

The COBOL code itself is nearly identical. The main difference is in the environment: no JCL, no IDCAMS, no catalog. GnuCOBOL creates the indexed file structure automatically when you first open the file for OUTPUT.

      * GnuCOBOL environment variable setup (bash):
      * export ACCTMAST=/home/student/data/acct-master.dat
      *
      * Or use the runtime configuration file:
      * setenv ACCTMAST /home/student/data/acct-master.dat

12.17 Chapter Summary

In this chapter, we have moved from sequential processing to the indexed file world — a fundamental shift in how COBOL programs access data. You have learned:

  • VSAM architecture: Control intervals, control areas, and the B+ tree index structure that enables both random and sequential access to data
  • The three access modes: SEQUENTIAL for batch processing, RANDOM for direct lookups, and DYNAMIC for programs that need both
  • CRUD operations: WRITE to create, READ to retrieve, REWRITE to update, and DELETE to remove records — each with its own rules and constraints
  • Browsing: The START/READ NEXT pattern for iterating through records in key order, including generic key searches for range queries
  • Alternate keys: Multiple access paths to the same data, enabling lookups by different fields without maintaining separate files
  • File status codes: The non-negotiable practice of checking every I/O operation's result, with specific handling for each status value
  • Production patterns: Logical deletes, read-before-rewrite, comprehensive error logging, and processing counters

The defensive programming theme runs through everything in this chapter. VSAM file processing is where data meets disk, and every unhandled error is a potential production incident. As Maria Chen says, "The difference between junior and senior COBOL developers is how they handle the INVALID KEY path."

In Chapter 13, we explore relative files (VSAM RRDS) — a different file organization that trades key-based access for lightning-fast direct access by record number. And in Chapter 14, we bring all file types together in advanced multi-file processing patterns that form the backbone of enterprise batch systems.


🔗 Connections: This chapter connects to Chapter 10 (Defensive Programming fundamentals), Chapter 11 (Sequential file processing), Chapter 29 (CICS online access to VSAM), Chapter 38 (Batch processing patterns), and Appendix E (VSAM reference).