> "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."
In This Chapter
- 12.1 Why Indexed Files Matter
- 12.2 VSAM Fundamentals
- 12.3 COBOL SELECT Statement for Indexed Files
- 12.4 Record Description for Indexed Files
- 12.5 READ Operations — Random and Sequential
- 12.6 WRITE, REWRITE, and DELETE Operations
- 12.7 Alternate Record Keys
- 12.8 VSAM File Status Codes — Defensive Programming
- 12.9 Complete CRUD Program — GlobalBank Account Maintenance
- 12.10 MedClaim Case Study: Provider Information Retrieval
- 12.11 Performance Considerations
- 12.12 Try It Yourself: Hands-On Exercises
- 12.13 Common Mistakes and Debugging Tips
- 12.14 OPEN Modes and Their Implications
- 12.15 Practical Walkthrough: Building a VSAM Program Step by Step
- 12.16 VSAM and GnuCOBOL Compatibility
- 12.17 Chapter Summary
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:
-
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.
-
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:
- Online (CICS) programs: BUFNI = index set levels + 1, BUFND = 2-3
- Batch sequential: BUFND = as many as possible, BUFNI = index set levels
- 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):
-
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)
-
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:
- Add a browse function that displays all students with GPAs above 3.5
- Use START and READ NEXT to iterate through all records
- 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):
- Modify the SELECT to include ALTERNATE RECORD KEY
- Write code to browse all students in a given major
- 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
Mistake 4: Using READ NEXT Without START
*--- 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:
- SELECT with ORGANIZATION IS INDEXED, ACCESS MODE IS DYNAMIC, FILE STATUS
- FD with record layout matching the cluster definition
- Working-storage status fields with 88-level conditions
- OPEN with status check
- I/O operations with INVALID KEY / NOT INVALID KEY
- Processing logic
- CLOSE
Step 5: Compile, Link, and Test
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).