Case Study 1: Customer Account Lookup System at Atlantic Commerce Bank
Background
Atlantic Commerce Bank (ACB) serves 3.6 million customers through 120 branches and a digital banking platform. Their customer account master file is stored as a VSAM KSDS (Key-Sequenced Data Set) on an IBM z16 mainframe. This file is the authoritative source for all customer account information: balances, personal details, account status, and transaction history metadata.
Branch tellers, customer service representatives, and automated systems need to access this file in three distinct ways:
- By account number (the primary key) -- A teller enters the account number to retrieve the customer's record instantly.
- By customer name (an alternate key) -- A customer calls the help desk without their account number, and the representative searches by name.
- By range -- A branch manager requests all accounts in a specific account number range to generate a branch-specific report.
Each access pattern requires a different COBOL access mode: random access for direct lookup, random access via alternate key for name search, and dynamic access for sequential browsing within a range. The program must handle all three patterns efficiently and must be robust against not-found conditions, duplicate alternate keys, and file I/O errors.
Robert Nakamura, a senior COBOL developer at ACB, was tasked with building the Customer Account Lookup System (CUSLKUP) to demonstrate all three access patterns against a VSAM KSDS file, with full error handling and companion JCL for cluster definition.
The Problem
File Layout
The customer account VSAM KSDS has the following record layout:
| Field | PIC | Offset | Length | Description |
|---|---|---|---|---|
| Account Number | 9(10) | 1 | 10 | Primary key |
| Customer Name | X(30) | 11 | 30 | Alternate key (with duplicates) |
| Address Line 1 | X(30) | 41 | 30 | Street address |
| City | X(20) | 71 | 20 | City |
| State | X(2) | 91 | 2 | State code |
| ZIP Code | 9(5) | 93 | 5 | ZIP code |
| Account Type | X(1) | 98 | 1 | C/S/M/L |
| Balance | S9(11)V99 COMP-3 | 99 | 7 | Current balance |
| Open Date | 9(8) | 106 | 8 | YYYYMMDD |
| Last Activity | 9(8) | 114 | 8 | YYYYMMDD |
| Status | X(1) | 122 | 1 | A/I/X/F |
| Filler | X(28) | 123 | 28 | Reserved |
Total record length: 150 bytes.
Access Requirements
-
Random access by primary key: Given an account number, retrieve the record directly. If not found, display an appropriate message.
-
Alternate key access by name: Given a customer name, retrieve all matching records (names are not unique). Display all accounts belonging to that customer.
-
Dynamic access (sequential browse): Given a starting account number, browse forward through a range of records (useful for branch-specific reporting where branch accounts share a common prefix).
The Solution
The COBOL Program
IDENTIFICATION DIVISION.
PROGRAM-ID. CUSLKUP.
AUTHOR. ROBERT NAKAMURA.
DATE-WRITTEN. 2025-01-10.
*================================================================
* PROGRAM: CUSLKUP - CUSTOMER ACCOUNT LOOKUP SYSTEM
* PURPOSE: Demonstrate three access modes for a VSAM KSDS
* file: random by primary key, random by alternate
* key, and dynamic (START/READ NEXT) for range
* browsing. Full error handling for not-found,
* duplicate keys, and I/O errors.
*================================================================
ENVIRONMENT DIVISION.
INPUT-OUTPUT SECTION.
FILE-CONTROL.
SELECT CUSTOMER-FILE
ASSIGN TO CUSTMAST
ORGANIZATION IS INDEXED
ACCESS MODE IS DYNAMIC
RECORD KEY IS FS-CF-ACCOUNT-NO
ALTERNATE RECORD KEY IS FS-CF-CUST-NAME
WITH DUPLICATES
FILE STATUS IS WS-CUST-STATUS.
DATA DIVISION.
FILE SECTION.
FD CUSTOMER-FILE.
01 FS-CUSTOMER-RECORD.
05 FS-CF-ACCOUNT-NO PIC 9(10).
05 FS-CF-CUST-NAME PIC X(30).
05 FS-CF-ADDRESS-1 PIC X(30).
05 FS-CF-CITY PIC X(20).
05 FS-CF-STATE PIC X(2).
05 FS-CF-ZIP PIC 9(5).
05 FS-CF-ACCT-TYPE PIC X(1).
88 ACCT-CHECKING VALUE 'C'.
88 ACCT-SAVINGS VALUE 'S'.
88 ACCT-MONEY-MKT VALUE 'M'.
88 ACCT-LOAN VALUE 'L'.
05 FS-CF-BALANCE PIC S9(11)V99 COMP-3.
05 FS-CF-OPEN-DATE PIC 9(8).
05 FS-CF-LAST-ACTIVITY PIC 9(8).
05 FS-CF-STATUS PIC X(1).
88 STAT-ACTIVE VALUE 'A'.
88 STAT-INACTIVE VALUE 'I'.
88 STAT-CLOSED VALUE 'X'.
88 STAT-FROZEN VALUE 'F'.
05 FILLER PIC X(28).
WORKING-STORAGE SECTION.
*----------------------------------------------------------------
* FILE STATUS
*----------------------------------------------------------------
01 WS-CUST-STATUS PIC X(2).
88 CUST-OK VALUE "00".
88 CUST-DUP-KEY VALUE "02".
88 CUST-NOT-FOUND VALUE "23".
88 CUST-EOF VALUE "10".
88 CUST-KEY-SEQ-ERR VALUE "21".
*----------------------------------------------------------------
* SEARCH CRITERIA
*----------------------------------------------------------------
01 WS-SEARCH-ACCOUNT PIC 9(10).
01 WS-SEARCH-NAME PIC X(30).
01 WS-RANGE-START PIC 9(10).
01 WS-RANGE-END PIC 9(10).
01 WS-BROWSE-COUNT PIC 9(5) VALUE ZERO.
01 WS-MAX-BROWSE PIC 9(5) VALUE 20.
*----------------------------------------------------------------
* DISPLAY FIELDS
*----------------------------------------------------------------
01 WS-DISP-BALANCE PIC $Z,ZZZ,ZZZ,ZZ9.99-.
01 WS-DISP-ACCT-TYPE PIC X(12).
01 WS-DISP-STATUS PIC X(10).
01 WS-DISP-DATE PIC X(10).
01 WS-DISP-COUNT PIC Z,ZZ9.
*----------------------------------------------------------------
* PROCESSING COUNTERS
*----------------------------------------------------------------
01 WS-RECORDS-FOUND PIC S9(5) COMP-3 VALUE 0.
01 WS-LOOKUPS-PERFORMED PIC S9(5) COMP-3 VALUE 0.
01 WS-NOT-FOUND-COUNT PIC S9(5) COMP-3 VALUE 0.
PROCEDURE DIVISION.
0000-MAIN-CONTROL.
PERFORM 1000-INITIALIZE
PERFORM 2000-DEMO-RANDOM-BY-KEY
PERFORM 3000-DEMO-ALTERNATE-KEY
PERFORM 4000-DEMO-RANGE-BROWSE
PERFORM 5000-DEMO-NOT-FOUND
PERFORM 8000-DISPLAY-SUMMARY
PERFORM 9000-FINALIZE
STOP RUN
.
1000-INITIALIZE.
DISPLAY "============================================="
DISPLAY " ATLANTIC COMMERCE BANK"
DISPLAY " CUSTOMER ACCOUNT LOOKUP SYSTEM"
DISPLAY "============================================="
DISPLAY " "
OPEN I-O CUSTOMER-FILE
IF NOT CUST-OK
DISPLAY "FATAL: Cannot open customer file. "
"Status: " WS-CUST-STATUS
STOP RUN
END-IF
.
2000-DEMO-RANDOM-BY-KEY.
* -------------------------------------------------------
* PATTERN 1: RANDOM ACCESS BY PRIMARY KEY
* The most common access pattern. Given an account
* number, retrieve the record directly using the
* KSDS primary index.
* -------------------------------------------------------
DISPLAY "--- PATTERN 1: RANDOM ACCESS BY KEY ---"
DISPLAY " "
* Look up a specific account
MOVE 1234567890 TO WS-SEARCH-ACCOUNT
PERFORM 2100-LOOKUP-BY-ACCOUNT
* Look up another account
MOVE 0000000001 TO WS-SEARCH-ACCOUNT
PERFORM 2100-LOOKUP-BY-ACCOUNT
DISPLAY " "
.
2100-LOOKUP-BY-ACCOUNT.
* -------------------------------------------------------
* Perform a random READ by primary key.
* Move the key value to the record's key field,
* then execute READ with the file name.
* -------------------------------------------------------
ADD 1 TO WS-LOOKUPS-PERFORMED
MOVE WS-SEARCH-ACCOUNT TO FS-CF-ACCOUNT-NO
READ CUSTOMER-FILE
KEY IS FS-CF-ACCOUNT-NO
INVALID KEY
DISPLAY " Account " WS-SEARCH-ACCOUNT
" not found."
ADD 1 TO WS-NOT-FOUND-COUNT
NOT INVALID KEY
ADD 1 TO WS-RECORDS-FOUND
PERFORM 7000-DISPLAY-CUSTOMER-RECORD
END-READ
.
3000-DEMO-ALTERNATE-KEY.
* -------------------------------------------------------
* PATTERN 2: ACCESS BY ALTERNATE KEY (CUSTOMER NAME)
* The alternate key is defined WITH DUPLICATES, so
* multiple records may have the same name. We use
* READ with KEY IS alternate-key to retrieve the
* first match, then READ NEXT to get subsequent
* duplicates.
* -------------------------------------------------------
DISPLAY "--- PATTERN 2: ALTERNATE KEY LOOKUP ---"
DISPLAY " "
MOVE "MARTINEZ, ELENA R" TO WS-SEARCH-NAME
DISPLAY " Searching for: " WS-SEARCH-NAME
DISPLAY " "
PERFORM 3100-LOOKUP-BY-NAME
DISPLAY " "
.
3100-LOOKUP-BY-NAME.
* -------------------------------------------------------
* Read the first record matching the alternate key.
* If status "00" or "02" (duplicate exists), display
* the record and read the next duplicate.
* Status "02" means the record was found AND there
* are more records with the same alternate key.
* -------------------------------------------------------
MOVE WS-SEARCH-NAME TO FS-CF-CUST-NAME
MOVE ZERO TO WS-BROWSE-COUNT
READ CUSTOMER-FILE
KEY IS FS-CF-CUST-NAME
INVALID KEY
DISPLAY " No records found for: "
WS-SEARCH-NAME
ADD 1 TO WS-NOT-FOUND-COUNT
NOT INVALID KEY
ADD 1 TO WS-RECORDS-FOUND
ADD 1 TO WS-BROWSE-COUNT
PERFORM 7000-DISPLAY-CUSTOMER-RECORD
* Check for more records with same name
* Status "02" = record found, duplicate exists
PERFORM UNTIL NOT CUST-DUP-KEY
AND NOT CUST-OK
READ CUSTOMER-FILE NEXT
END-READ
IF CUST-OK OR CUST-DUP-KEY
IF FS-CF-CUST-NAME =
WS-SEARCH-NAME
ADD 1 TO WS-RECORDS-FOUND
ADD 1 TO WS-BROWSE-COUNT
PERFORM
7000-DISPLAY-CUSTOMER-RECORD
ELSE
EXIT PERFORM
END-IF
ELSE
EXIT PERFORM
END-IF
END-PERFORM
MOVE WS-BROWSE-COUNT TO WS-DISP-COUNT
DISPLAY " Total accounts found: "
WS-DISP-COUNT
END-READ
.
4000-DEMO-RANGE-BROWSE.
* -------------------------------------------------------
* PATTERN 3: DYNAMIC ACCESS (RANGE BROWSE)
* Position the file at a starting key using START,
* then read sequentially forward using READ NEXT.
* This is useful for branch-specific reports where
* all accounts in a branch share a common prefix.
* -------------------------------------------------------
DISPLAY "--- PATTERN 3: RANGE BROWSE ---"
DISPLAY " "
MOVE 1000000000 TO WS-RANGE-START
MOVE 1000999999 TO WS-RANGE-END
DISPLAY " Browsing accounts from "
WS-RANGE-START " to " WS-RANGE-END
DISPLAY " "
PERFORM 4100-BROWSE-RANGE
DISPLAY " "
.
4100-BROWSE-RANGE.
* -------------------------------------------------------
* Use START to position the file, then READ NEXT
* to iterate through the range.
*
* START positions the file's current record pointer
* at the first record whose key is >= the search key.
* It does NOT read the record -- the subsequent
* READ NEXT retrieves it.
* -------------------------------------------------------
MOVE WS-RANGE-START TO FS-CF-ACCOUNT-NO
MOVE ZERO TO WS-BROWSE-COUNT
* Position at first record >= start key
START CUSTOMER-FILE
KEY IS >= FS-CF-ACCOUNT-NO
INVALID KEY
DISPLAY " No records found in range."
EXIT PARAGRAPH
NOT INVALID KEY
CONTINUE
END-START
* Read sequentially through the range
PERFORM UNTIL WS-BROWSE-COUNT >= WS-MAX-BROWSE
READ CUSTOMER-FILE NEXT
END-READ
EVALUATE TRUE
WHEN CUST-OK
WHEN CUST-DUP-KEY
* Check if still within range
IF FS-CF-ACCOUNT-NO > WS-RANGE-END
DISPLAY " End of range reached."
EXIT PERFORM
END-IF
ADD 1 TO WS-RECORDS-FOUND
ADD 1 TO WS-BROWSE-COUNT
PERFORM 7000-DISPLAY-CUSTOMER-RECORD
WHEN CUST-EOF
DISPLAY " End of file reached."
EXIT PERFORM
WHEN OTHER
DISPLAY " READ error. Status: "
WS-CUST-STATUS
EXIT PERFORM
END-EVALUATE
END-PERFORM
MOVE WS-BROWSE-COUNT TO WS-DISP-COUNT
DISPLAY " Records browsed: " WS-DISP-COUNT
.
5000-DEMO-NOT-FOUND.
* -------------------------------------------------------
* Demonstrate proper handling of the not-found condition.
* Status "23" is the standard VSAM response for a
* record-not-found on a random READ.
* -------------------------------------------------------
DISPLAY "--- PATTERN 4: NOT-FOUND HANDLING ---"
DISPLAY " "
MOVE 9999999999 TO WS-SEARCH-ACCOUNT
PERFORM 2100-LOOKUP-BY-ACCOUNT
MOVE 0 TO WS-SEARCH-ACCOUNT
PERFORM 2100-LOOKUP-BY-ACCOUNT
DISPLAY " "
.
7000-DISPLAY-CUSTOMER-RECORD.
* -------------------------------------------------------
* Format and display the current customer record.
* -------------------------------------------------------
DISPLAY " Account: " FS-CF-ACCOUNT-NO
DISPLAY " Name: " FS-CF-CUST-NAME
DISPLAY " Address: " FS-CF-ADDRESS-1
DISPLAY " " FS-CF-CITY " "
FS-CF-STATE " "
FS-CF-ZIP
* Format account type
EVALUATE TRUE
WHEN ACCT-CHECKING
MOVE "CHECKING" TO WS-DISP-ACCT-TYPE
WHEN ACCT-SAVINGS
MOVE "SAVINGS" TO WS-DISP-ACCT-TYPE
WHEN ACCT-MONEY-MKT
MOVE "MONEY MARKET" TO WS-DISP-ACCT-TYPE
WHEN ACCT-LOAN
MOVE "LOAN" TO WS-DISP-ACCT-TYPE
WHEN OTHER
MOVE "UNKNOWN" TO WS-DISP-ACCT-TYPE
END-EVALUATE
DISPLAY " Type: " WS-DISP-ACCT-TYPE
* Format balance
MOVE FS-CF-BALANCE TO WS-DISP-BALANCE
DISPLAY " Balance: " WS-DISP-BALANCE
* Format status
EVALUATE TRUE
WHEN STAT-ACTIVE
MOVE "ACTIVE" TO WS-DISP-STATUS
WHEN STAT-INACTIVE
MOVE "INACTIVE" TO WS-DISP-STATUS
WHEN STAT-CLOSED
MOVE "CLOSED" TO WS-DISP-STATUS
WHEN STAT-FROZEN
MOVE "FROZEN" TO WS-DISP-STATUS
WHEN OTHER
MOVE "UNKNOWN" TO WS-DISP-STATUS
END-EVALUATE
DISPLAY " Status: " WS-DISP-STATUS
* Format date
STRING FS-CF-OPEN-DATE(5:2)
DELIMITED BY SIZE
"/" DELIMITED BY SIZE
FS-CF-OPEN-DATE(7:2)
DELIMITED BY SIZE
"/" DELIMITED BY SIZE
FS-CF-OPEN-DATE(1:4)
DELIMITED BY SIZE
INTO WS-DISP-DATE
END-STRING
DISPLAY " Opened: " WS-DISP-DATE
DISPLAY " ----------------------------------------"
.
8000-DISPLAY-SUMMARY.
DISPLAY " "
DISPLAY "============================================="
DISPLAY " LOOKUP SESSION SUMMARY"
DISPLAY "============================================="
MOVE WS-LOOKUPS-PERFORMED TO WS-DISP-COUNT
DISPLAY " Lookups performed: " WS-DISP-COUNT
MOVE WS-RECORDS-FOUND TO WS-DISP-COUNT
DISPLAY " Records found: " WS-DISP-COUNT
MOVE WS-NOT-FOUND-COUNT TO WS-DISP-COUNT
DISPLAY " Not-found responses: " WS-DISP-COUNT
DISPLAY "============================================="
.
9000-FINALIZE.
CLOSE CUSTOMER-FILE
IF NOT CUST-OK
DISPLAY "WARNING: Close error on customer file."
" Status: " WS-CUST-STATUS
END-IF
.
The IDCAMS DEFINE CLUSTER and JCL
//CUSLKPJ JOB (ACCT),'CUSTOMER LOOKUP',
// CLASS=A,MSGCLASS=X,
// MSGLEVEL=(1,1),
// NOTIFY=&SYSUID
//*================================================================
//* JOB: CUSLKPJ
//* PURPOSE: DEFINE VSAM KSDS CLUSTER FOR CUSTOMER MASTER
//* AND EXECUTE THE CUSTOMER LOOKUP PROGRAM
//*================================================================
//*
//*-------- STEP 1: DELETE EXISTING CLUSTER (IF EXISTS) -----------
//*
//DELETE EXEC PGM=IDCAMS
//SYSPRINT DD SYSOUT=*
//SYSIN DD *
DELETE ATLCOM.CUSTOMER.MASTER -
CLUSTER -
PURGE
IF LASTCC = 8 THEN -
SET MAXCC = 0
/*
//*
//*-------- STEP 2: DEFINE THE VSAM KSDS CLUSTER ------------------
//*
//DEFINE EXEC PGM=IDCAMS,
// COND=(0,NE,DELETE)
//SYSPRINT DD SYSOUT=*
//SYSIN DD *
DEFINE CLUSTER -
(NAME(ATLCOM.CUSTOMER.MASTER) -
INDEXED -
RECORDS(4000000 400000) -
RECORDSIZE(150 150) -
KEYS(10 0) -
FREESPACE(20 10) -
SHAREOPTIONS(2 3) -
SPEED) -
DATA -
(NAME(ATLCOM.CUSTOMER.MASTER.DATA) -
CONTROLINTERVALSIZE(4096)) -
INDEX -
(NAME(ATLCOM.CUSTOMER.MASTER.INDEX) -
CONTROLINTERVALSIZE(2048))
/*
/* DEFINE THE ALTERNATE INDEX FOR CUSTOMER NAME
/*
DEFINE AIX -
(NAME(ATLCOM.CUSTOMER.MASTER.NAMEAIX) -
RELATE(ATLCOM.CUSTOMER.MASTER) -
KEYS(30 10) -
NONUNIQUEKEY -
RECORDSIZE(64 500) -
RECORDS(4000000 400000) -
SHAREOPTIONS(2 3) -
UPGRADE)
/*
/* DEFINE THE PATH FOR THE ALTERNATE INDEX
/*
DEFINE PATH -
(NAME(ATLCOM.CUSTOMER.MASTER.NAMEPATH) -
PATHENTRY(ATLCOM.CUSTOMER.MASTER.NAMEAIX))
/*
//*
//*-------- STEP 3: LOAD INITIAL DATA (REPRO FROM SEQ FILE) -------
//*
//LOAD EXEC PGM=IDCAMS,
// COND=(0,NE,DEFINE)
//SYSPRINT DD SYSOUT=*
//INFILE DD DSN=ATLCOM.CUSTOMER.SEQLOAD,
// DISP=SHR
//SYSIN DD *
REPRO INFILE(INFILE) -
OUTDATASET(ATLCOM.CUSTOMER.MASTER)
/*
//*
//*-------- STEP 4: BUILD ALTERNATE INDEX -------------------------
//*
//BLDAIX EXEC PGM=IDCAMS,
// COND=(0,NE,LOAD)
//SYSPRINT DD SYSOUT=*
//SYSIN DD *
BLDINDEX -
INDATASET(ATLCOM.CUSTOMER.MASTER) -
OUTDATASET(ATLCOM.CUSTOMER.MASTER.NAMEAIX)
/*
//*
//*-------- STEP 5: RUN THE LOOKUP PROGRAM ------------------------
//*
//LOOKUP EXEC PGM=CUSLKUP,
// COND=(0,NE,BLDAIX)
//STEPLIB DD DSN=ATLCOM.PROD.LOADLIB,DISP=SHR
//CUSTMAST DD DSN=ATLCOM.CUSTOMER.MASTER,
// DISP=SHR
//ALTNAME DD DSN=ATLCOM.CUSTOMER.MASTER.NAMEPATH,
// DISP=SHR
//SYSOUT DD SYSOUT=*
Solution Walkthrough
ACCESS MODE IS DYNAMIC
The SELECT statement specifies ACCESS MODE IS DYNAMIC, which allows both random and sequential access within the same program:
SELECT CUSTOMER-FILE
ASSIGN TO CUSTMAST
ORGANIZATION IS INDEXED
ACCESS MODE IS DYNAMIC
RECORD KEY IS FS-CF-ACCOUNT-NO
ALTERNATE RECORD KEY IS FS-CF-CUST-NAME
WITH DUPLICATES
FILE STATUS IS WS-CUST-STATUS.
With DYNAMIC access, the program can use:
- READ filename KEY IS key-field for random access
- START filename KEY IS >= key-field followed by READ filename NEXT for sequential browsing
- READ filename NEXT after a random READ to continue sequentially from the retrieved position
Random Access by Primary Key
The simplest access pattern. Move the desired key value to the record key field and execute a keyed READ:
MOVE WS-SEARCH-ACCOUNT TO FS-CF-ACCOUNT-NO
READ CUSTOMER-FILE
KEY IS FS-CF-ACCOUNT-NO
INVALID KEY ...
NOT INVALID KEY ...
END-READ
VSAM consults the primary index and retrieves the record in a single I/O operation (or two, if the index entry is not cached). File status "23" indicates the key was not found.
Alternate Key Access with Duplicates
The alternate key on customer name is defined WITH DUPLICATES because multiple customers can share the same name. The first READ retrieves the first matching record. If file status is "02" (record found, duplicate key exists), subsequent READ NEXT statements retrieve additional records with the same key:
READ CUSTOMER-FILE
KEY IS FS-CF-CUST-NAME
NOT INVALID KEY
PERFORM UNTIL NOT CUST-DUP-KEY
READ CUSTOMER-FILE NEXT
Status "02" after a READ means "the record was returned successfully, AND there are more records with the same alternate key value." This is the signal to continue reading.
Range Browse with START and READ NEXT
The START statement positions the file cursor without reading a record:
MOVE WS-RANGE-START TO FS-CF-ACCOUNT-NO
START CUSTOMER-FILE
KEY IS >= FS-CF-ACCOUNT-NO
KEY IS >= means "position at the first record whose key is greater than or equal to the specified value." This is essential for range browsing because the exact starting key might not exist in the file. After START, successive READ NEXT statements retrieve records in key sequence until the range end is reached or EOF is encountered.
The IDCAMS DEFINE CLUSTER
The JCL demonstrates the complete lifecycle of a VSAM KSDS:
-
DELETE: Removes any existing cluster. The
IF LASTCC = 8 THEN SET MAXCC = 0handles the case where the cluster does not exist (LASTCC=8 from the DELETE is not an error condition). -
DEFINE CLUSTER: Creates the KSDS with: -
KEYS(10 0): 10-byte primary key starting at offset 0 -FREESPACE(20 10): 20% free space in each CI, 10% of CAs left empty -- this accommodates future inserts without immediate CI splits -SHAREOPTIONS(2 3): Allows concurrent read access from multiple jobs -
DEFINE AIX: Creates the alternate index with: -
KEYS(30 10): 30-byte alternate key starting at offset 10 (the customer name field) -NONUNIQUEKEY: Allows duplicate name values -UPGRADE: Automatically updates the AIX when the base cluster is modified -
DEFINE PATH: Creates a logical pathway from the AIX to the base cluster, which the COBOL program uses via the DD statement for the alternate key.
-
BLDINDEX: Builds the initial alternate index from the base cluster data.
Lessons Learned
1. File Status "02" Is Not an Error
Status "02" after a READ means the record was found successfully and another record with the same key exists. Many programmers mistakenly treat any non-"00" status as an error. With alternate keys that allow duplicates, "02" is an expected and important status.
2. START Does Not Read -- It Only Positions
A common mistake is assuming that START retrieves a record. It does not. START only positions the file cursor. The first record is retrieved by the subsequent READ NEXT. This means the record buffer is unchanged after a successful START.
3. DYNAMIC Access Mode Enables All Patterns in One Program
With ACCESS MODE IS DYNAMIC, a single OPEN statement enables random reads, alternate key reads, and sequential browsing. This eliminates the need for multiple file definitions or multiple program runs.
4. Alternate Index Maintenance Has a Performance Cost
When the UPGRADE option is specified on a DEFINE AIX, every write, rewrite, or delete to the base cluster also updates the alternate index. For high-volume update programs, this overhead can be significant. Some installations define AIX without UPGRADE and rebuild the index periodically in batch.
5. FREESPACE Trades Disk for Insert Performance
The 20% CI free space and 10% CA free space reserve disk space to accommodate future inserts. Without free space, every insert into a full CI triggers a CI split (VSAM redistributes records into two CIs), which is expensive. The trade-off: more free space means more disk allocated but fewer splits.
Discussion Questions
-
The program opens the file with
OPEN I-Oeven though it only reads records. Why use I-O instead of INPUT? What are the implications for file sharing with other programs? -
The alternate key is defined as the customer name field (30 bytes). What happens if a customer's name is stored with trailing spaces ("SMITH" padded to 30 characters)? Would a search for "SMITH" (5 characters) match?
-
The range browse limits results to 20 records (
WS-MAX-BROWSE). In a real system with 3.6 million records, what would happen if no limit were imposed and the range covered the entire file? How should the program handle very large ranges? -
The DEFINE CLUSTER specifies
RECORDS(4000000 400000). The first number is the primary allocation and the second is the secondary allocation. How does VSAM use these values? What happens if the file grows beyond both allocations? -
The BLDINDEX step builds the alternate index after the initial load. What happens to the alternate index when records are added or deleted during normal operation? How does the UPGRADE parameter affect this?
-
File status "21" (KEY SEQUENCE ERROR) can occur during sequential READ NEXT operations. What causes this condition, and how would you handle it in a production program?
-
The JCL assigns the alternate index path to a DD named ALTNAME, but the COBOL program does not reference this DD name. How does VSAM know to associate the alternate key access with this DD? What role does the PATH definition play?