COBOL Module 8 – Indexed and relative files (VSAM concepts)
Indexed files: keys, access modes and INVALID KEY
Define COBOL indexed files with prime and alternate keys, read them by key or in key order, and handle INVALID KEY with statuses 21, 22, 23 and 43.
What you will learn
- Define an indexed file with a prime record key and an alternate key that allows duplicates
- Read, rewrite, delete and browse records by key in random, sequential and dynamic access
- Handle INVALID KEY and the file statuses 21, 22, 23 and 43, and predict where status 02 differs between compilers
- Map an indexed file onto a VSAM key-sequenced data set, its alternate index and its path
Before you start
What you’ll learn · Before you start
What you will learn
- Define an indexed file with a prime record key and an alternate key that allows duplicates
- Read, rewrite, delete and browse records by key in random, sequential and dynamic access
- Handle INVALID KEY and the file statuses 21, 22, 23 and 43, and predict where status 02 differs between compilers
- Map an indexed file onto a VSAM key-sequenced data set, its alternate index and its path
Before you start
On this page
A change of address touches one account, yet in a sequential file the program reads past every record before it and writes a new copy of the whole file. An indexed file finds account AC0003 by its key, rewrites that one record, and answers status 23 when an account such as AC0004 is not there.
This lesson builds a five-account indexed file with GnuCOBOL and runs every keyed statement on it: WRITE, READ, REWRITE, DELETE, START and READ NEXT, by account number and by city. Every run shows the FILE STATUS it set, two of them where GnuCOBOL 3.2 and IBM’s Enterprise COBOL disagree, and the last section maps the file onto the VSAM data set it becomes on z/OS.
An indexed file stores records by key and refuses a duplicate
The accounts arrive in the order the branches sent them, not in account order, and the last line repeats AC0002 with a different balance. Each line is one record: the account number, the holder’s name, the city and a balance of nine digits with two decimals.
AC0007SUNITA RAO PUNE 000120000
AC0002MOHAN DAS NAGPUR 000045050
AC0005FARHAN ALI PUNE 000310000
AC0001ANITA DESHMUKH NAGPUR 000250000
AC0003RAVI KUMAR PUNE 000150000
AC0002MOHAN DAS NAGPUR 000099999 acctload.cob reads the lines and writes each one to an indexed file: A file whose records are kept in the order of a key, so a program can read one record by its key or read them all in key order. ORGANIZATION IS INDEXED; on z/OS it is a VSAM key-sequenced data set.. Its SELECT entry says what the file is. RECORD KEY IS AC-NUMBER names the prime record key: The key that RECORD KEY names in an indexed file. Its values must be unique, they set the order of the records, and a random READ, REWRITE or DELETE finds its record by it., whose values, IBM’s RECORD KEY page says, “must be unique among records in the file”. ALTERNATE RECORD KEY IS AC-CITY names a second key, the alternate record key: A second key of an indexed file, named by ALTERNATE RECORD KEY, by which the same records can also be read and browsed. WITH DUPLICATES lets several records share one value., and WITH DUPLICATES lets several accounts share a city.
IDENTIFICATION DIVISION.
PROGRAM-ID. ACCTLOAD.
* Loads the indexed account file from a text file of accounts,
* one line each, and prints the FILE STATUS of every WRITE.
ENVIRONMENT DIVISION.
INPUT-OUTPUT SECTION.
FILE-CONTROL.
SELECT ACCT-IN ASSIGN TO "ACCTIN"
ORGANIZATION IS LINE SEQUENTIAL
FILE STATUS IS IN-STATUS.
SELECT ACCT-FILE ASSIGN TO "ACCTS"
ORGANIZATION IS INDEXED
ACCESS MODE IS RANDOM
RECORD KEY IS AC-NUMBER
ALTERNATE RECORD KEY IS AC-CITY WITH DUPLICATES
FILE STATUS IS AC-STATUS.
DATA DIVISION.
FILE SECTION.
FD ACCT-IN.
01 IN-REC PIC X(41).
FD ACCT-FILE.
01 ACCT-REC.
05 AC-NUMBER PIC X(6).
05 AC-NAME PIC X(16).
05 AC-CITY PIC X(10).
05 AC-BALANCE PIC 9(7)V99.
WORKING-STORAGE SECTION.
01 IN-STATUS PIC XX.
01 AC-STATUS PIC XX.
PROCEDURE DIVISION.
OPEN INPUT ACCT-IN
OPEN OUTPUT ACCT-FILE
DISPLAY "OPEN OUTPUT " AC-STATUS
READ ACCT-IN
PERFORM UNTIL IN-STATUS NOT = "00"
MOVE IN-REC TO ACCT-REC
WRITE ACCT-REC
INVALID KEY
DISPLAY "WRITE " AC-NUMBER " " AC-STATUS
" not written: the key is taken"
NOT INVALID KEY
DISPLAY "WRITE " AC-NUMBER " " AC-STATUS
END-WRITE
READ ACCT-IN
END-PERFORM
CLOSE ACCT-IN ACCT-FILE
STOP RUN. | Clause | What it tells the run time |
|---|---|
| ORGANIZATION IS INDEXED | the records are kept in the order of their keys |
| ACCESS MODE IS RANDOM | each statement names its record by the key field |
| RECORD KEY IS AC-NUMBER | the prime key, unique in the file |
| ALTERNATE RECORD KEY IS AC-CITY WITH DUPLICATES | a second key, which may repeat |
| FILE STATUS IS AC-STATUS | two characters set by every statement |
OPEN OUTPUT creates the file empty, and each WRITE stores the record under the key in AC-NUMBER, in any order. Run the load:
from session import files, run
# Six account lines, in the order the branches sent them, written to an indexed file keyed by account number.
files("acctload.cob", "accounts.txt")
run("""
cobc -x acctload.cob
DD_ACCTIN=accounts.txt DD_ACCTS=accounts.idx ./acctload
ls accounts.idx*
cobc --info | grep 'indexed file'
""") Output
$ cobc -x acctload.cob $ DD_ACCTIN=accounts.txt DD_ACCTS=accounts.idx ./acctload OPEN OUTPUT 00 WRITE AC0007 00 WRITE AC0002 00 WRITE AC0005 00 WRITE AC0001 00 WRITE AC0003 00 WRITE AC0002 22 not written: the key is taken $ ls accounts.idx* accounts.idx accounts.idx.1 $ cobc --info | grep 'indexed file' indexed file handler : BDB
Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 load_session.py
Five WRITEs returned 00. The second AC0002 returned 22, the invalid key condition: What a keyed WRITE, READ, REWRITE, DELETE or START reports when it cannot place or find a record: a status starting with 2, such as 22 for a taken key or 23 for a missing one. The INVALID KEY phrase runs then. for a key that is taken, so the INVALID KEY phrase ran and the WRITE stored nothing: the first AC0002 kept its balance of 450.50. The listing shows two files. accounts.idx holds the records under their account numbers, and accounts.idx.1 holds the city key; cobc names the handler that wrote them, BDB, the Berkeley DB library. Copy and back up the two files together, because neither is the whole file. Step through the load:
The drawing shows the whole scene; its 6 steps are listed below.
Every step as text
- The first line. READ brings the first line, AC0007 SUNITA RAO of PUNE, into IN-REC, and MOVE copies it into ACCT-REC, the record area of the indexed file.
- Placed by its key. WRITE stores the record under its prime key, AC0007. The program never says where the record goes; the key does, so the file is in key order whatever order the lines come in.
- The city key. The same WRITE adds the alternate key, PUNE, with the account number it belongs to, AC0007, to the second file, accounts.idx.1. AC-STATUS is 00.
- Lines 2 to 5. AC0002, AC0005, AC0001 and AC0003 arrive out of order. Each WRITE puts its record where its key belongs, so the file now reads AC0001, AC0002, AC0003, AC0005, AC0007, each WRITE with status 00.
- A key that is taken. The sixth line is AC0002 again, with a balance of 999.99. Its prime key is already in the file, so WRITE stores nothing, sets status 22 and runs the INVALID KEY phrase.
- What the load left. Five records in key order in accounts.idx, and their cities in accounts.idx.1. The first AC0002 kept its balance of 450.50, because a WRITE never replaces a record.
Random access reads, rewrites and deletes one record by its key
In random access, IBM’s ACCESS MODE page says, “the value placed in a record key data item specifies the record to be accessed”. acctmaint.cob opens the loaded file I-O, for input and output, and puts a key in AC-NUMBER before every statement:
IDENTIFICATION DIVISION.
PROGRAM-ID. ACCTMAINT.
* Reads, changes and deletes accounts by their number, in random
* access, and prints the FILE STATUS of each statement.
ENVIRONMENT DIVISION.
INPUT-OUTPUT SECTION.
FILE-CONTROL.
SELECT ACCT-FILE ASSIGN TO "ACCTS"
ORGANIZATION IS INDEXED
ACCESS MODE IS RANDOM
RECORD KEY IS AC-NUMBER
ALTERNATE RECORD KEY IS AC-CITY WITH DUPLICATES
FILE STATUS IS AC-STATUS.
DATA DIVISION.
FILE SECTION.
FD ACCT-FILE.
01 ACCT-REC.
05 AC-NUMBER PIC X(6).
05 AC-NAME PIC X(16).
05 AC-CITY PIC X(10).
05 AC-BALANCE PIC 9(7)V99.
WORKING-STORAGE SECTION.
01 AC-STATUS PIC XX.
PROCEDURE DIVISION.
OPEN I-O ACCT-FILE
MOVE "AC0004" TO AC-NUMBER
READ ACCT-FILE
INVALID KEY
DISPLAY "READ AC0004 " AC-STATUS
" no such account"
END-READ
MOVE "AC0003" TO AC-NUMBER
READ ACCT-FILE
NOT INVALID KEY
DISPLAY "READ AC0003 " AC-STATUS
" [" ACCT-REC "]"
END-READ
MOVE "MUMBAI" TO AC-CITY
REWRITE ACCT-REC
DISPLAY "REWRITE AC0003 " AC-STATUS " city now MUMBAI"
* The mistake: a REWRITE of AC0005 with no READ before it.
INITIALIZE ACCT-REC
MOVE "AC0005" TO AC-NUMBER
MOVE "MUMBAI" TO AC-CITY
REWRITE ACCT-REC
DISPLAY "REWRITE AC0005 " AC-STATUS " no READ before it"
READ ACCT-FILE
DISPLAY "READ AC0005 " AC-STATUS " [" ACCT-REC "]"
MOVE "AC0009" TO AC-NUMBER
DELETE ACCT-FILE RECORD
INVALID KEY
DISPLAY "DELETE AC0009 " AC-STATUS
" no such account"
END-DELETE
MOVE "AC0001" TO AC-NUMBER
DELETE ACCT-FILE RECORD
DISPLAY "DELETE AC0001 " AC-STATUS
READ ACCT-FILE
DISPLAY "READ AC0001 " AC-STATUS " deleted"
CLOSE ACCT-FILE
STOP RUN. from session import files, run
# The loaded file read, changed and deleted by key in random access.
files("acctload.cob", "acctmaint.cob", "accounts.txt")
run("""
cobc -x acctload.cob && cobc -x acctmaint.cob
DD_ACCTIN=accounts.txt DD_ACCTS=accounts.idx ./acctload > /dev/null
DD_ACCTS=accounts.idx ./acctmaint
""") Output
$ cobc -x acctload.cob && cobc -x acctmaint.cob $ DD_ACCTIN=accounts.txt DD_ACCTS=accounts.idx ./acctload > /dev/null $ DD_ACCTS=accounts.idx ./acctmaint READ AC0004 23 no such account READ AC0003 00 [AC0003RAVI KUMAR PUNE 000150000] REWRITE AC0003 00 city now MUMBAI REWRITE AC0005 00 no READ before it READ AC0005 00 [AC0005 MUMBAI 000000000] DELETE AC0009 23 no such account DELETE AC0001 00 READ AC0001 23 deleted
Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 maint_session.py
READ AC0004 returned 23, and its INVALID KEY phrase said so: there is no such account. READ AC0003 returned 00 with the whole record, RAVI KUMAR of PUNE, and after MUMBAI was moved into AC-CITY, REWRITE replaced the stored record with the changed one. DELETE AC0009 returned 23, DELETE AC0001 returned 00, and the READ of AC0001 after it returned 23, because the record is gone. Neither DELETE needed a READ first.
| Status | IBM’s meaning | Where the runs set it |
|---|---|---|
| 00 | successful completion | every statement that found or placed its record |
| 22 | a duplicate prime key, or an alternate key without DUPLICATES | the second WRITE of AC0002 |
| 23 | random access to a record that does not exist | READ AC0004, DELETE AC0009 |
The REWRITE of AC0005 is the line to remember. Nothing read AC0005 first: the program cleared the record area with INITIALIZE and moved in only the key and the new city, so REWRITE replaced FARHAN ALI and the balance of 3,100.00 with spaces and zeros. It returned 00, because a REWRITE in random access needs no READ: it writes the whole record area under the key it holds.
A REWRITE of fields that were never read
Wrong: MOVE the key and the one changed field into the record, then REWRITE, which writes every other field as the record area holds it, blanks after an INITIALIZE or another account’s data, and returns 00. Right: READ the record by its key, check for 00, change the field in the record the READ returned, then REWRITE it and check the status again.
Checkpoint
Check: Prime keys, the load and status 22 · READ, REWRITE and DELETE by key
START picks a place in key order, and READ NEXT reads on from it
A report of every account reads the whole file in account order. With dynamic access: ACCESS MODE IS DYNAMIC: in the same run, a program may read an indexed or relative file by key and also read on through it in order with START and READ NEXT., a program may do both in one run: read by key, and read on in key order. IBM describes START as a way of “positioning within an indexed or relative file for subsequent sequential record retrieval”: it reads nothing itself, and READ NEXT then reads the next record from the place it set.
IDENTIFICATION DIVISION.
PROGRAM-ID. ACCTBROWSE.
* Browses the account file in key order with START and READ
* NEXT, then tries two START statements on a key that is not
* in the file.
ENVIRONMENT DIVISION.
INPUT-OUTPUT SECTION.
FILE-CONTROL.
SELECT ACCT-FILE ASSIGN TO "ACCTS"
ORGANIZATION IS INDEXED
ACCESS MODE IS DYNAMIC
RECORD KEY IS AC-NUMBER
ALTERNATE RECORD KEY IS AC-CITY WITH DUPLICATES
FILE STATUS IS AC-STATUS.
DATA DIVISION.
FILE SECTION.
FD ACCT-FILE.
01 ACCT-REC.
05 AC-NUMBER PIC X(6).
05 AC-NAME PIC X(16).
05 AC-CITY PIC X(10).
05 AC-BALANCE PIC 9(7)V99.
WORKING-STORAGE SECTION.
01 AC-STATUS PIC XX.
PROCEDURE DIVISION.
OPEN INPUT ACCT-FILE
MOVE LOW-VALUES TO AC-NUMBER
START ACCT-FILE KEY IS GREATER THAN AC-NUMBER
DISPLAY "START > LOW-VALUES " AC-STATUS
PERFORM READ-NEXT
PERFORM UNTIL AC-STATUS NOT = "00"
DISPLAY "READ NEXT " AC-STATUS " " AC-NUMBER
" " AC-NAME
PERFORM READ-NEXT
END-PERFORM
DISPLAY "READ NEXT " AC-STATUS " the end"
MOVE "AC0006" TO AC-NUMBER
START ACCT-FILE KEY IS EQUAL TO AC-NUMBER
DISPLAY "START = AC0006 " AC-STATUS
MOVE "AC0004" TO AC-NUMBER
START ACCT-FILE KEY IS NOT LESS THAN AC-NUMBER
DISPLAY "START >= AC0004 " AC-STATUS
PERFORM READ-NEXT
DISPLAY "READ NEXT " AC-STATUS " " AC-NUMBER
" " AC-NAME
CLOSE ACCT-FILE
STOP RUN.
READ-NEXT.
READ ACCT-FILE NEXT RECORD. from session import files, run
# The loaded file browsed in key order with START and READ NEXT, then two START statements on missing keys.
files("acctload.cob", "acctbrowse.cob", "accounts.txt")
run("""
cobc -x acctload.cob && cobc -x acctbrowse.cob
DD_ACCTIN=accounts.txt DD_ACCTS=accounts.idx ./acctload > /dev/null
DD_ACCTS=accounts.idx ./acctbrowse
""") Output
$ cobc -x acctload.cob && cobc -x acctbrowse.cob $ DD_ACCTIN=accounts.txt DD_ACCTS=accounts.idx ./acctload > /dev/null $ DD_ACCTS=accounts.idx ./acctbrowse START > LOW-VALUES 00 READ NEXT 00 AC0001 ANITA DESHMUKH READ NEXT 00 AC0002 MOHAN DAS READ NEXT 00 AC0003 RAVI KUMAR READ NEXT 00 AC0005 FARHAN ALI READ NEXT 00 AC0007 SUNITA RAO READ NEXT 10 the end START = AC0006 23 START >= AC0004 00 READ NEXT 00 AC0005 FARHAN ALI
Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 browse_session.py
LOW-VALUES is below every key, so START KEY IS GREATER THAN LOW-VALUES put the place before AC0001. The READ NEXT loop then read AC0001, AC0002, AC0003, AC0005 and AC0007, the order of the keys, although AC0007 was written first, and the READ NEXT after the last account returned 10, the end of the file. START KEY IS EQUAL TO AC0006 returned 23: IBM’s START page says that when “the comparison is not satisfied by any record in the file, an invalid key condition exists”. KEY IS NOT LESS THAN AC0004 found no AC0004 to stop at, so it set the place at the next key, AC0005. Step through the browse:
The drawing shows the whole scene; its 7 steps are listed below.
Every step as text
- Open. OPEN INPUT opens the file in dynamic access, so the program may read by key and also read on in key order.
- Set the place. LOW-VALUES is below every key, so START KEY IS GREATER THAN LOW-VALUES puts the file's place before AC0001. START reads no record; it sets status 00.
- The first READ NEXT. READ NEXT reads the record after that place, AC0001 ANITA DESHMUKH, with status 00. It is the lowest key, although it was the fourth line written.
- Read on in key order. The loop displays each account and reads the next one, AC0002, AC0003, AC0005 and AC0007, in the order of their keys, not the order they arrived in.
- Past the last record. The READ NEXT after AC0007 finds no record and sets status 10. The test leaves the loop, as it does for a sequential file.
- A START on a missing key. START KEY IS EQUAL TO AC0006 asks for a record that is not in the file, so it sets status 23 and leaves no place to read from.
- NOT LESS THAN finds the next key. START KEY IS NOT LESS THAN AC0004 has no AC0004 to stop at, so it sets its place at the next key, AC0005, and the READ NEXT after it reads FARHAN ALI.
| ACCESS MODE | Reads | Writes and changes |
|---|---|---|
| SEQUENTIAL, the default | READ gives the next record in key order | WRITE in ascending key order; REWRITE and DELETE the record just read |
| RANDOM | READ by the key in the key field | WRITE, REWRITE and DELETE by key, in any order |
| DYNAMIC | READ by key, and START with READ NEXT in key order | as in random access |
Key idea
START only sets a place in key order and reports 23 when no key satisfies it; READ NEXT reads from that place, in the order of the keys, until status 10.
Sequential access writes in key order and rewrites only what it read
Sequential access makes a promise about order. IBM’s ACCESS MODE page says the records “are accessed in the sequence of ascending record key values”, and the file status table holds the run to it. seqrules.cob opens a new file OUTPUT in sequential access and writes AC0003, AC0001 and AC0005, in that order. Then it tries the two ways a REWRITE can go wrong:
IDENTIFICATION DIVISION.
PROGRAM-ID. SEQRULES.
* The rules of sequential access to an indexed file: WRITEs in
* ascending key order, and a REWRITE only after a READ of the
* same key. The file is listed at the end.
ENVIRONMENT DIVISION.
INPUT-OUTPUT SECTION.
FILE-CONTROL.
SELECT ACCT-FILE ASSIGN TO "ACCTS"
ORGANIZATION IS INDEXED
ACCESS MODE IS SEQUENTIAL
RECORD KEY IS AC-NUMBER
FILE STATUS IS AC-STATUS.
DATA DIVISION.
FILE SECTION.
FD ACCT-FILE.
01 ACCT-REC.
05 AC-NUMBER PIC X(6).
05 AC-NAME PIC X(16).
WORKING-STORAGE SECTION.
01 AC-STATUS PIC XX.
PROCEDURE DIVISION.
OPEN OUTPUT ACCT-FILE
MOVE "AC0003RAVI KUMAR" TO ACCT-REC
WRITE ACCT-REC
DISPLAY "WRITE AC0003 " AC-STATUS
MOVE "AC0001ANITA DESHMUKH" TO ACCT-REC
WRITE ACCT-REC
DISPLAY "WRITE AC0001 " AC-STATUS
MOVE "AC0005SUNITA RAO" TO ACCT-REC
WRITE ACCT-REC
DISPLAY "WRITE AC0005 " AC-STATUS
CLOSE ACCT-FILE
OPEN I-O ACCT-FILE
MOVE "AC0003RAVI KUMAR" TO ACCT-REC
REWRITE ACCT-REC
DISPLAY "REWRITE, no READ first " AC-STATUS
READ ACCT-FILE
DISPLAY "READ " AC-STATUS " " AC-NUMBER
MOVE "AC0004" TO AC-NUMBER
REWRITE ACCT-REC
DISPLAY "REWRITE, key now AC0004 " AC-STATUS
CLOSE ACCT-FILE
OPEN INPUT ACCT-FILE
READ ACCT-FILE
PERFORM UNTIL AC-STATUS NOT = "00"
DISPLAY "in the file: " ACCT-REC
READ ACCT-FILE
END-PERFORM
CLOSE ACCT-FILE
STOP RUN. from session import files, run
# Sequential access: WRITEs out of key order, a REWRITE with no READ, and a REWRITE after the prime key was changed.
files("seqrules.cob")
run("""
cobc -x seqrules.cob
DD_ACCTS=seq.idx ./seqrules
""") Output
$ cobc -x seqrules.cob $ DD_ACCTS=seq.idx ./seqrules WRITE AC0003 00 WRITE AC0001 21 WRITE AC0005 00 REWRITE, no READ first 43 READ 00 AC0003 REWRITE, key now AC0004 00 in the file: AC0004RAVI KUMAR in the file: AC0005SUNITA RAO
Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 seq_session.py
AC0001 came after AC0003, so its WRITE returned 21, which IBM’s table explains as “the ascending requirements for successive record key values were violated”, and AC0001 was not stored. The REWRITE before any READ returned 43: in sequential access the statement before a REWRITE or DELETE must be a successful READ, because there is no other way to say which record to replace.
The last REWRITE is the trap. The program read AC0003, moved AC0004 into AC-NUMBER and ran REWRITE. IBM’s table gives status 21 for exactly this, when “the prime record key value was changed by the program” between a successful READ and the next REWRITE, and the record stays as it was. GnuCOBOL 3.2 returned 00, and the listing shows what it did: RAVI KUMAR is now AC0004, and AC0003 has gone from the file.
A key change that works here and fails on z/OS
Never change the prime key between a READ and its REWRITE. To give a record a new key, WRITE it under the new key, check for 00, then DELETE the old key and check again, so that both compilers do the same thing and a failure leaves the old record in place.
Checkpoint
Check: START and READ NEXT in key order · Sequential access, statuses 21 and 43
An alternate key reads the same records in another order
A branch wants the accounts of one city. The alternate key AC-CITY gives the same records a second order, by city, without a second copy of the file. bycity.cob reads by it twice: once at random, with READ … KEY IS AC-CITY, and once with a START on PUNE and READ NEXT until the city changes. nodups.cob is acctload.cob with WITH DUPLICATES taken out of the SELECT entry:
IDENTIFICATION DIVISION.
PROGRAM-ID. BYCITY.
* Reads the accounts of one city through the alternate key
* AC-CITY: a random READ, then a browse from START.
ENVIRONMENT DIVISION.
INPUT-OUTPUT SECTION.
FILE-CONTROL.
SELECT ACCT-FILE ASSIGN TO "ACCTS"
ORGANIZATION IS INDEXED
ACCESS MODE IS DYNAMIC
RECORD KEY IS AC-NUMBER
ALTERNATE RECORD KEY IS AC-CITY WITH DUPLICATES
FILE STATUS IS AC-STATUS.
DATA DIVISION.
FILE SECTION.
FD ACCT-FILE.
01 ACCT-REC.
05 AC-NUMBER PIC X(6).
05 AC-NAME PIC X(16).
05 AC-CITY PIC X(10).
05 AC-BALANCE PIC 9(7)V99.
WORKING-STORAGE SECTION.
01 AC-STATUS PIC XX.
01 WANTED-CITY PIC X(10).
PROCEDURE DIVISION.
OPEN INPUT ACCT-FILE
MOVE "NAGPUR" TO AC-CITY
READ ACCT-FILE KEY IS AC-CITY
DISPLAY "READ KEY IS AC-CITY " AC-STATUS " " AC-NUMBER
" " AC-CITY
MOVE "PUNE" TO AC-CITY WANTED-CITY
START ACCT-FILE KEY IS EQUAL TO AC-CITY
DISPLAY "START AC-CITY = PUNE " AC-STATUS
READ ACCT-FILE NEXT RECORD
PERFORM UNTIL AC-STATUS NOT = "00" AND NOT = "02"
OR AC-CITY NOT = WANTED-CITY
DISPLAY "READ NEXT " AC-STATUS " " AC-NUMBER
" " AC-CITY
READ ACCT-FILE NEXT RECORD
END-PERFORM
DISPLAY "READ NEXT " AC-STATUS " the end"
CLOSE ACCT-FILE
STOP RUN. from session import files, run
# The accounts of one city through the alternate key, then the same load with no DUPLICATES on the city.
files("acctload.cob", "bycity.cob", "nodups.cob", "accounts.txt")
run("""
cobc -x acctload.cob && cobc -x bycity.cob && cobc -x nodups.cob
DD_ACCTIN=accounts.txt DD_ACCTS=accounts.idx ./acctload > /dev/null
DD_ACCTS=accounts.idx ./bycity
DD_ACCTIN=accounts.txt DD_ACCTS=nodups.idx ./nodups
""") Output
$ cobc -x acctload.cob && cobc -x bycity.cob && cobc -x nodups.cob $ DD_ACCTIN=accounts.txt DD_ACCTS=accounts.idx ./acctload > /dev/null $ DD_ACCTS=accounts.idx ./bycity READ KEY IS AC-CITY 00 AC0002 NAGPUR START AC-CITY = PUNE 00 READ NEXT 00 AC0007 PUNE READ NEXT 00 AC0005 PUNE READ NEXT 00 AC0003 PUNE READ NEXT 10 the end $ DD_ACCTIN=accounts.txt DD_ACCTS=nodups.idx ./nodups OPEN OUTPUT 00 WRITE AC0007 00 WRITE AC0002 00 WRITE AC0005 22 not written: the key is taken WRITE AC0001 22 not written: the key is taken WRITE AC0003 22 not written: the key is taken WRITE AC0002 22 not written: the key is taken
Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 city_session.py
The random READ for NAGPUR returned AC0002, not AC0001, because AC0002 was written first; IBM’s ALTERNATE RECORD KEY page says that “in random access, only the first record written in a series of records with duplicate keys can be retrieved”. The browse of PUNE read AC0007, AC0005 and AC0003, the order they were written in, and IBM’s READ page gives the same rule for sequential reading: such records “are made available in the order in which they were placed in the file”. The START changed the key of reference: The key whose order the next READ NEXT of an indexed file follows: the prime record key after OPEN, or the key that the last START named., so READ NEXT followed the city key instead of the account number.
Without WITH DUPLICATES the city must be unique, and the load stored only AC0007 and AC0002. AC0005 of PUNE and AC0001 of NAGPUR got 22, the status IBM gives for a duplicate alternate key without the DUPLICATES phrase, and so did AC0003 of PUNE; the second AC0002 got 22 for its prime key, as before.
| What happened | IBM’s Enterprise COBOL | GnuCOBOL 3.2 in these runs |
|---|---|---|
| WRITE of a second PUNE account, WITH DUPLICATES | 02 | 00 |
| READ NEXT when the next record has the same city | 02 | 00 |
| WRITE of a second PUNE account, no DUPLICATES | 22 | 22 |
Status 02 means “successful completion” with a duplicate key seen, and the handler of this GnuCOBOL build never set it. A program that treats 02 as an error fails on z/OS, and one that keeps reading while the status is 02 reads only one account per city here, so test for 00 or 02 as success and compare AC-CITY yourself, as bycity.cob does.
The order within one city
Records that share an alternate key come in the order they were written, never in prime-key order. When a report needs account order inside each city, sort the records, or make the account number part of the key.
On z/OS an indexed file is a VSAM key-sequenced data set
On z/OS the same SELECT entry opens a VSAM: Virtual Storage Access Method, the z/OS access method for key-sequenced, entry-sequenced, relative-record and linear data sets. COBOL's indexed and relative files are VSAM data sets on z/OS. data set. IBM’s Programming Guide pairs each COBOL organization with a VSAM one, and an indexed file is a KSDS: A key-sequenced data set: a VSAM data set whose records are stored and found by a key field. A COBOL indexed file on z/OS is a KSDS.. Its definition comes before any program runs: “You must predefine and catalog all VSAM data sets through the access method services DEFINE command.” The command is DEFINE CLUSTER, run by IDCAMS: Access method services, the z/OS utility that a job step runs as PGM=IDCAMS. Its commands define, load, print and delete VSAM data sets, such as DEFINE CLUSTER, REPRO and PRINT. More in “Relative files, VSAM organisations and IDCAMS”; INDEXED, its default organization, makes a KSDS, and KEYS gives the key’s length and its offset from byte 0, KEYS(6 0) for AC-NUMBER in the first six bytes. Step through what the account file needs:
The drawing shows the whole scene; its 6 steps are listed below.
Every step as text
- The ddname. ASSIGN TO ACCTS names a ddname, so the DD statement of the job step decides which data set the program opens, as for a sequential file.
- A key-sequenced data set. The data set is a VSAM key-sequenced data set, defined in advance with the IDCAMS command DEFINE CLUSTER. INDEXED is its default organization, and KEYS gives the key's length and offset.
- The alternate key needs an index. The city key needs a second VSAM data set, an alternate index, defined with DEFINE ALTERNATEINDEX. Its key must match the program's ALTERNATE RECORD KEY.
- Built from the records. BLDINDEX reads every record of the base cluster and builds the alternate index from them, so the index starts out matching the data.
- The path. DEFINE PATH relates the alternate index to the base cluster. The program reads the records by city through the path, which has a DD statement of its own.
- The same program. Nothing in the COBOL changes. The SELECT entry and the statements are the ones acctbrowse.cob and bycity.cob ran here; only the data sets behind the names differ.
The city key is a data set of its own, an alternate index: A VSAM data set that holds the values of an alternate key with the prime keys of the records that carry them, so that a KSDS can be read in the order of that key through a path.. IBM lists three commands: DEFINE ALTERNATEINDEX, DEFINE PATH, which “allows you to access the records of the base cluster through the alternate keys”, and BLDINDEX, which reads every record of the base cluster to build the index. “The ALTERNATE RECORD KEY definitions must match the definitions in the catalog entry.” Each path gets a DD statement of its own: in IBM’s example the base cluster’s is MAINA and the two paths’ are MAINA1 and MAINA2.
| What differs | GnuCOBOL 3.2 in these runs | Enterprise COBOL on z/OS |
|---|---|---|
| The records | accounts.idx, a Berkeley DB file | a KSDS, defined with DEFINE CLUSTER |
| The city key | accounts.idx.1, made by the WRITEs | an alternate index, built by BLDINDEX and read through a path |
| Choosing the file | DD_ACCTS=accounts.idx before the run | the DD statement ACCTS of the job step |
| More detail after an error | the two-character FILE STATUS | a second status item from VSAM as well |
That second item is named after the first, FILE STATUS IS AC-STATUS VSAM-CODE: IBM describes it as “a 6-byte alphanumeric group data item that has three subordinate 2-byte binary fields”, a return code, a function code and a feedback code, and “the VSAM status code contains meaningful values when the COBOL file status key is not 0”.
Checkpoint
Check: Alternate keys, duplicates and status 02 · The indexed file on z/OS as a KSDS
Practise: keys, statuses and access modes
These questions come from the six runs above, and each answer can be found in what they printed:
Practice that adapts to you
Practise this lesson
12 questions about 6 ideas of this lesson. Each answer is explained at once, and what you get right is kept in this browser.
An account maintenance run that checks every status
Where GnuCOBOL is installed, write a program that reads a file of transactions, A to add an account, C to change its city, X to close it and L to list a city, and applies each to the indexed file with a check of every status: 22 for an add of a taken key, 23 for a change or close of a missing one, and anything other than 00, 02 or 10 as an error that ends the run with return code 12. Test it with an add of AC0003, a change of AC0004, a close of AC0007 and a list of PUNE, and compare the file with accounts.txt afterwards.
Interview questions
Warm-up (fresher to 2–5 years): how do random, sequential and dynamic access to an indexed file differ?
Random access reads, writes, rewrites and deletes one record by the key placed in the key field, in any order. Sequential access reads the records in ascending key order, writes only in ascending key order, a WRITE out of order gets status 21, and rewrites or deletes only the record just read, or gets 43. Dynamic access allows both in one run: READ by key, and START followed by READ NEXT to read on in key order. A maintenance program that applies transactions in any order uses random access, a report over the whole file uses sequential access, and a program that finds a starting point and reads on from it, such as all the accounts of one city, uses dynamic access.
Warm-up (fresher to 2–5 years): what do file statuses 22 and 23 mean on an indexed file?
Both are invalid key conditions, so an INVALID KEY phrase runs for either. 22 means a WRITE or REWRITE would create a duplicate prime key, or a duplicate alternate key that was declared without DUPLICATES, and nothing was stored, as for the second AC0002 in this lesson. 23 means a random READ, REWRITE, DELETE or a START found no record with the key, as for READ AC0004 and START KEY IS EQUAL TO AC0006. A program checks for both after every keyed statement, because each needs a different answer: 22 is often a duplicate input record, and 23 a transaction for an account that does not exist.
Two harder questions start from programs that went wrong:
Model answers open with any pass: ₹79 for 1 week ($2.99), one payment that never renews.
-
Fresher to mid
After a change to an address-update program, some accounts lost their names and balances: the fields came back as spaces and zeros, yet every REWRITE returned status 00. The file is indexed and opened I-O in random access. What went wrong, and how do you fix and test it?
-
Mid to senior
A program lists the accounts of each branch through an alternate key declared WITH DUPLICATES. It finds the first account with START and READ NEXT, then keeps reading while the file status is 02. On z/OS it lists every account; after a move to GnuCOBOL it lists one per branch. Explain, and rewrite the loop.
Organisations named on this page
The keyed-file rules on this page, from START to the VSAM status item, are quoted from IBM’s Enterprise COBOL and z/OS manuals. IBM and the GnuCOBOL project have no part in MySmartCoPilot, and the files shown were written by GnuCOBOL 3.2 with its Berkeley DB handler.
Flashcards · 8 cards
Recall it, then check
Say the answer to yourself before you look: recalling it is what makes it stick.
-
What do RECORD KEY and ALTERNATE RECORD KEY declare?
RECORD KEY names the prime key of an indexed file, whose values must be unique. ALTERNATE RECORD KEY names a second key, and WITH DUPLICATES lets several records share its value.
-
What do file statuses 22 and 23 mean on an indexed file?
Both are invalid key conditions. 22: a WRITE or REWRITE would duplicate a prime key, or an alternate key without DUPLICATES, and nothing is stored. 23: a random READ, REWRITE, DELETE or START found no record with the key.
-
How can a REWRITE in random access blank a record's fields and still return 00?
It writes the whole record area under the key it holds, with no READ required. After INITIALIZE and a MOVE of the key and one field, every other field became spaces or zeros. READ first, change, then REWRITE.
-
What does START do on an indexed file?
It sets a place in key order and reads nothing; READ NEXT then reads on until status 10. KEY IS NOT LESS THAN a missing key lands on the next one, while KEY IS EQUAL TO a missing key returns 23.
-
What are the three access modes of an indexed file?
SEQUENTIAL, the default, works in key order. RANDOM reads and writes one record by its key. DYNAMIC does both in one run: READ by key, and START with READ NEXT.
-
What do statuses 21 and 43 mean in sequential access?
21: keys written out of ascending order, or, on z/OS, a prime key changed between a READ and its REWRITE. 43: a REWRITE or DELETE without a successful READ before it.
-
In what order do records that share an alternate key come back?
In the order they were written, never in prime-key order. A random READ by that key returns only the first of them.
-
What does an indexed file become on z/OS?
A VSAM key-sequenced data set, a KSDS, defined beforehand with IDCAMS DEFINE CLUSTER, whose KEYS give the key's length and offset. Each alternate key needs an alternate index, built by BLDINDEX and read through a path with its own DD statement.
Key takeaways
- An indexed file keeps its records in the order of a unique prime record key; a WRITE of a key that is taken stores nothing and returns 22, and GnuCOBOL kept the alternate key in a second file, accounts.idx.1.
- Random access reads, rewrites and deletes the record whose key is in the key field and returns 23 when there is none; a REWRITE writes the whole record area, so read the record before changing one field.
- START sets a place in key order without reading, and READ NEXT reads on from it until status 10; a START that no key satisfies returns 23.
- Sequential access writes keys in ascending order, or gets 21, and rewrites only the record just read, or gets 43; a changed prime key before the REWRITE is 21 on z/OS, while GnuCOBOL 3.2 moved the record.
- Records with the same alternate key come in the order they were written; IBM reports such duplicates with status 02, which this GnuCOBOL build returned as 00.
- On z/OS an indexed file is a VSAM KSDS defined with IDCAMS DEFINE CLUSTER, and each alternate key needs an alternate index and a path with its own DD statement.
Terms in this lesson
- alternate index
- A VSAM data set that holds the values of an alternate key with the prime keys of the records that carry them, so that a KSDS can be read in the order of that key through a path.
- alternate record key
- A second key of an indexed file, named by ALTERNATE RECORD KEY, by which the same records can also be read and browsed. WITH DUPLICATES lets several records share one value.
- dynamic access
- ACCESS MODE IS DYNAMIC: in the same run, a program may read an indexed or relative file by key and also read on through it in order with START and READ NEXT.
- indexed file
- A file whose records are kept in the order of a key, so a program can read one record by its key or read them all in key order. ORGANIZATION IS INDEXED; on z/OS it is a VSAM key-sequenced data set.
- invalid key condition
- What a keyed WRITE, READ, REWRITE, DELETE or START reports when it cannot place or find a record: a status starting with 2, such as 22 for a taken key or 23 for a missing one. The INVALID KEY phrase runs then.
- key of reference
- The key whose order the next READ NEXT of an indexed file follows: the prime record key after OPEN, or the key that the last START named.
- KSDS
- A key-sequenced data set: a VSAM data set whose records are stored and found by a key field. A COBOL indexed file on z/OS is a KSDS.
- prime record key
- The key that RECORD KEY names in an indexed file. Its values must be unique, they set the order of the records, and a random READ, REWRITE or DELETE finds its record by it.
- VSAM
- Virtual Storage Access Method, the z/OS access method for key-sequenced, entry-sequenced, relative-record and linear data sets. COBOL's indexed and relative files are VSAM data sets on z/OS.
References
- Enterprise COBOL for z/OS 6.5: File status key values (IBM)
- Enterprise COBOL for z/OS 6.5: ACCESS MODE clause (IBM)
- Enterprise COBOL for z/OS 6.5: RECORD KEY clause (IBM)
- Enterprise COBOL for z/OS 6.5: ALTERNATE RECORD KEY clause (IBM)
- Enterprise COBOL for z/OS 6.5: START statement (IBM)
- Enterprise COBOL for z/OS 6.5: READ statement, sequential access mode (IBM)
- Enterprise COBOL for z/OS 6.5 Programming Guide: Processing VSAM files (IBM)
- Enterprise COBOL for z/OS 6.5 Programming Guide: Defining VSAM files (IBM)
- Enterprise COBOL for z/OS 6.5 Programming Guide: Creating alternate indexes (IBM)
- Enterprise COBOL for z/OS 6.5 Programming Guide: Example: entries for alternate indexes (IBM)
- Enterprise COBOL for z/OS 6.5 Programming Guide: Using VSAM status codes (IBM)
- z/OS 3.2 DFSMS Access Method Services Commands: DEFINE CLUSTER optional parameters (IBM)
- z/OS Basic Skills: What is VSAM? (IBM)
- GnuCOBOL 3.2 Programmer's Guide (indexed files, START, READ) (GnuCOBOL project (GNU))
Report a problem with this lesson
Kept only in this browser. Your Learn progress