Relative Files

This content was generated with the assistance of AI and should be verified against the official documentation before being relied on in production.

The reference material consulted while preparing these pages is listed in the section bibliography.

This page is written against ISO/IEC 1989:2023 (edition 3, January 2023; an upcoming 4th edition is in ballot), in which relative files are part of the file-handling facility inherited from COBOL 85; against IBM Enterprise COBOL for z/OS 6.5 (PTFs through September 2026), where a relative file is a VSAM relative-record data set (RRDS) on z/OS 3.2; and against GnuCOBOL 3.2 (28 July 2023, the latest release), with which every program on it was compiled with cobc -x -Wall and run on 2026-10-20 (and again with -std=ibm, which printed the same results).

A relative file is a file of numbered record areas, called slots. The program does not search for a record: it names the slot number, and the access method goes straight to that slot. This page covers the SELECT entry for a relative file, the three access modes, the statements that work on it (WRITE, READ, REWRITE, START, DELETE), the way a business key is turned into a slot number, and the z/OS side, where the file is a VSAM RRDS defined with IDCAMS. The general FD, SELECT and OPEN rules are on File Concepts, SELECT and FD; the other two file organizations are on Sequential Files and Indexed Files and VSAM; the full two-character status table is on File Status Codes. The examples use the Circulation public-library scenario: the member master is kept in a relative file whose slot number is derived from the member’s card number.

Where the book is outdated: Taulli’s Modern Mainframe Development (2022, chapter 5) lists the VSAM data set types, KSDS, ESDS and RRDS, and shows the IDCAMS DEFINE CLUSTER command, but it does not cover the COBOL ORGANIZATION RELATIVE clause itself (no RELATIVE KEY, no START on a relative file, no DELETE by slot). This page supplies them from the official manuals. The book’s examples also run on GnuCOBOL through OpenCobolIDE, an unmaintained IDE (see Getting Started).

The idea: a file of numbered slots

Think of the file as a row of record areas. Slot 1 is the first, slot 10 is the tenth, and the number of a slot never changes when other slots are filled or emptied. A slot either holds a record or is empty. Reading slot 4 when it is empty is not an error in the file, it is a normal "no such record" answer (status 23).

Sequential Indexed Relative

How a record is found

Read in the order written

By the value of a key field inside the record

By the slot number, which is not part of the record

Gaps

None

None; the index decides the order

Yes; empty slots between records are normal

Delete

Not possible (rewrite the file)

DELETE, key can be reused

DELETE, the slot is empty and can be written again

Order of sequential reads

Order written

Ascending key

Ascending slot number, skipping empty slots

z/OS data set

QSAM or VSAM ESDS

VSAM KSDS

VSAM RRDS

The SELECT entry

SELECT [OPTIONAL] file-name ASSIGN TO assignment-name
    ORGANIZATION IS RELATIVE
    [ACCESS MODE IS {SEQUENTIAL | RANDOM | DYNAMIC}]
    [RELATIVE KEY IS data-name]
    [FILE STATUS IS data-name].
  • ORGANIZATION IS RELATIVE marks the file as a relative file. The organization is fixed when the file is created and cannot be changed afterwards.

  • ACCESS MODE defaults to SEQUENTIAL. RANDOM and DYNAMIC are the two modes that use the slot number to reach a record.

  • RELATIVE KEY names the data item that holds the slot number. It is required for RANDOM and DYNAMIC access. For SEQUENTIAL access it is optional, but needed if the program uses START or wants to know which slot each READ returned. In GnuCOBOL the words IS, KEY and MODE are optional; write them anyway for readability and portability.

  • The key item must be an unsigned integer without the P picture symbol, and it must not be a field of the file’s own record: the slot number is not part of the record. Enterprise COBOL states both rules; GnuCOBOL enforces them at compile time (see the diagnostics below).

  • The key item must be large enough for the highest slot number. A WRITE whose slot number has more significant digits than the key item can hold raises an invalid-key condition.

  • FILE STATUS is not specific to relative files but is strongly advised: it is how a program tells "empty slot" (23), "slot already in use" (22) and a real I/O error apart.

  • A relative file cannot be assigned to the console, a printer or a line-sequential device. Records may be fixed or variable length; on GnuCOBOL a variable-length record still reserves room for the largest record in the FD.

Access modes and the statements they allow

Statement SEQUENTIAL RANDOM DYNAMIC

READ file or READ file NEXT RECORD

Yes, next occupied slot

Not allowed

READ NEXT reads the next occupied slot

READ file (by slot number)

Not allowed

Yes, slot in the relative key

Yes, when the NEXT phrase is absent

WRITE

Next slot after the last written (output or extend)

Any empty slot, in any order

Any empty slot, in any order

REWRITE

After a successful READ

Slot in the relative key

Slot in the relative key

DELETE

After a successful READ

Slot in the relative key

Slot in the relative key

START

Yes, needs a RELATIVE KEY

Not allowed

Yes

  • Sequential access reads the occupied slots in ascending slot number. After every successful READ the relative key item, if one was named, holds the slot number of the record just read. In sequential output, each WRITE puts the record in the next slot, and the key item receives that number.

  • Random access takes the slot number from the key item for every READ, WRITE, REWRITE and DELETE. A program that never walks the file in order should use random rather than dynamic access.

  • Dynamic access lets one program mix both: a keyed READ, then START to position, then READ NEXT for a run of records. This is the mode used for "find the slot, then list from there".

  • On z/OS the order in which slots are written when a file is loaded does not matter with random or dynamic access; with sequential output the records must be written in ascending order. Enterprise COBOL notes that loading sequentially is faster than loading randomly.

Loading the file by slot number

The member master has card numbers that start at 100, so the slot number is MEMBER-ID - 99: card 100 goes into slot 1, card 101 into slot 2. One late member, card 105, is stored too, which leaves slots 4 and 5 empty. A last WRITE repeats slot 6 on purpose to show the invalid-key path. The input is the line-sequential MEMBMAST of the sample data; the output file is MEMBSLOT.

       IDENTIFICATION DIVISION.
       PROGRAM-ID. SLOTLOAD.
      * Load the member slot file (RRDS-style) from MEMBMAST.
      * Slot number = MEMBER-ID - 99: card numbers start at 100.
       ENVIRONMENT DIVISION.
       INPUT-OUTPUT SECTION.
       FILE-CONTROL.
           SELECT MEMBER-IN ASSIGN TO "MEMBMAST"
               ORGANIZATION IS LINE SEQUENTIAL
               FILE STATUS IS WS-IN-STATUS.
           SELECT MEMBER-SLOTS ASSIGN TO "MEMBSLOT"
               ORGANIZATION IS RELATIVE
               ACCESS MODE IS RANDOM
               RELATIVE KEY IS WS-SLOT
               FILE STATUS IS WS-SLOT-STATUS.
       DATA DIVISION.
       FILE SECTION.
       FD  MEMBER-IN.
       COPY MEMBREC REPLACING ==MEMBER-RECORD== BY ==IN-RECORD==
                              ==MEMBER-ID==     BY ==IN-ID==
                              ==MEMBER-NAME==   BY ==IN-NAME==
                              ==MEMBER-JOINED== BY ==IN-JOINED==
                              ==MEMBER-FINES==  BY ==IN-FINES==
                              ==MEMBER-STATUS== BY ==IN-STATUS==
                              ==MEMBER-ACTIVE== BY ==IN-ACTIVE==
                              ==MEMBER-SUSPENDED== BY ==IN-SUSPENDED==.
       FD  MEMBER-SLOTS.
       COPY MEMBREC.
       WORKING-STORAGE SECTION.
       01  WS-IN-STATUS        PIC XX.
       01  WS-SLOT-STATUS      PIC XX.
       01  WS-SLOT             PIC 9(4).
       01  WS-EOF              PIC X VALUE "N".
           88  END-OF-MEMBERS  VALUE "Y".
       01  WS-LOADED           PIC 9(3) VALUE ZERO.
       PROCEDURE DIVISION.
       0000-MAIN-LINE.
           OPEN INPUT MEMBER-IN
           OPEN OUTPUT MEMBER-SLOTS
           PERFORM 8000-READ-MEMBER
           PERFORM 2000-LOAD-MEMBER UNTIL END-OF-MEMBERS
      * A member who joined later and was given card 105.
           INITIALIZE MEMBER-RECORD
           MOVE 105 TO MEMBER-ID
           MOVE "KATHERINE JOHNSON" TO MEMBER-NAME
           MOVE 20261020 TO MEMBER-JOINED
           SET MEMBER-ACTIVE TO TRUE
           PERFORM 2100-WRITE-SLOT
      * Writing the same slot again is an error.
           WRITE MEMBER-RECORD
               INVALID KEY
                   DISPLAY "Second write of slot " WS-SLOT
                       " failed, status " WS-SLOT-STATUS
           END-WRITE
           CLOSE MEMBER-IN MEMBER-SLOTS
           DISPLAY WS-LOADED " members stored"
           STOP RUN.
       2000-LOAD-MEMBER.
           MOVE IN-RECORD TO MEMBER-RECORD
           PERFORM 2100-WRITE-SLOT
           PERFORM 8000-READ-MEMBER.
       2100-WRITE-SLOT.
           COMPUTE WS-SLOT = MEMBER-ID - 99
           WRITE MEMBER-RECORD
               INVALID KEY
                   DISPLAY "Cannot store member " MEMBER-ID
                       ", status " WS-SLOT-STATUS
               NOT INVALID KEY
                   ADD 1 TO WS-LOADED
                   DISPLAY "Member " MEMBER-ID " -> slot " WS-SLOT
                       ", status " WS-SLOT-STATUS
           END-WRITE.
       8000-READ-MEMBER.
           READ MEMBER-IN
               AT END SET END-OF-MEMBERS TO TRUE
           END-READ.

Run on GnuCOBOL 3.2.0, compiled with cobc -x -Wall, no warnings:

Member 000100 -> slot 0001, status 00
Member 000101 -> slot 0002, status 00
Member 000102 -> slot 0003, status 00
Member 000105 -> slot 0006, status 00
Second write of slot 0006 failed, status 22
004 members stored

Points to notice:

  • The program fills the record, computes the slot and executes WRITE; the record contains no slot number, only the card number.

  • A WRITE to a slot that already holds a record fails with status 22, and the data in the file is unchanged. With INVALID KEY the phrase runs; without it, Enterprise COBOL and GnuCOBOL fall back on a declarative or on the FILE STATUS item, as explained on Error Handling and Declaratives.

  • OPEN OUTPUT creates an empty file (or empties an existing one). With ACCESS MODE RANDOM it is the right way to start a new file.

  • On GnuCOBOL 3.2.0 with the default file handler, the resulting MEMBSLOT is 408 bytes: six slots of 68 bytes, each an 8-byte header that holds the record length followed by the 60 data bytes. Slots 4 and 5, which were never written, are zero bytes, which is how the run time knows they are empty. This layout is an implementation detail of the run time and should not be relied on; a program must go through the COBOL statements.

Reading in slot order

A sequential READ returns the occupied slots in ascending order and skips the empty ones. The relative key item, if given, is updated after each READ.

       IDENTIFICATION DIVISION.
       PROGRAM-ID. SLOTLIST.
      * List the member slot file in slot order (sequential access).
       ENVIRONMENT DIVISION.
       INPUT-OUTPUT SECTION.
       FILE-CONTROL.
           SELECT MEMBER-SLOTS ASSIGN TO "MEMBSLOT"
               ORGANIZATION IS RELATIVE
               ACCESS MODE IS SEQUENTIAL
               RELATIVE KEY IS WS-SLOT
               FILE STATUS IS WS-STATUS.
       DATA DIVISION.
       FILE SECTION.
       FD  MEMBER-SLOTS.
       COPY MEMBREC.
       WORKING-STORAGE SECTION.
       01  WS-STATUS           PIC XX.
       01  WS-SLOT             PIC 9(4).
       01  WS-EOF              PIC X VALUE "N".
           88  END-OF-SLOTS    VALUE "Y".
       PROCEDURE DIVISION.
       0000-MAIN-LINE.
           OPEN INPUT MEMBER-SLOTS
           PERFORM 8000-READ-SLOT
           PERFORM UNTIL END-OF-SLOTS
               DISPLAY "slot " WS-SLOT "  " MEMBER-ID "  "
                   MEMBER-NAME
               PERFORM 8000-READ-SLOT
           END-PERFORM
           DISPLAY "last status " WS-STATUS ", slot key still "
               WS-SLOT
           CLOSE MEMBER-SLOTS
           STOP RUN.
       8000-READ-SLOT.
           READ MEMBER-SLOTS
               AT END SET END-OF-SLOTS TO TRUE
           END-READ.
slot 0001  000100  ADA LOVELACE
slot 0002  000101  GRACE HOPPER
slot 0003  000102  ALAN TURING
slot 0006  000105  KATHERINE JOHNSON
last status 10, slot key still 0006

Slots 4 and 5 do not appear, and slot 6 follows slot 3 directly. When AT END is reached (status 10) the key item is not meaningful; the program above shows only that GnuCOBOL leaves it at the last slot read, which is not a documented guarantee.

Deleting in sequential access

In sequential access DELETE removes the record that the last READ returned. The INVALID KEY phrase must not be coded in this mode. The program below purges suspended members; it then shows what happens to a DELETE and a READ after the end of the file.

       IDENTIFICATION DIVISION.
       PROGRAM-ID. SLOTPURGE.
      * Sequential access: delete the suspended members.
       ENVIRONMENT DIVISION.
       INPUT-OUTPUT SECTION.
       FILE-CONTROL.
           SELECT MEMBER-SLOTS ASSIGN TO "MEMBSLOT"
               ORGANIZATION IS RELATIVE
               ACCESS MODE IS SEQUENTIAL
               RELATIVE KEY IS WS-SLOT
               FILE STATUS IS WS-STATUS.
       DATA DIVISION.
       FILE SECTION.
       FD  MEMBER-SLOTS.
       COPY MEMBREC.
       WORKING-STORAGE SECTION.
       01  WS-STATUS           PIC XX.
       01  WS-SLOT             PIC 9(4).
       01  WS-EOF              PIC X VALUE "N".
           88  END-OF-SLOTS    VALUE "Y".
       PROCEDURE DIVISION.
       0000-MAIN-LINE.
           OPEN I-O MEMBER-SLOTS
           PERFORM 8000-READ-SLOT
           PERFORM UNTIL END-OF-SLOTS
               IF MEMBER-SUSPENDED
                   DELETE MEMBER-SLOTS RECORD
                   DISPLAY "deleted slot " WS-SLOT " "
                       MEMBER-NAME " status " WS-STATUS
               END-IF
               PERFORM 8000-READ-SLOT
           END-PERFORM
      * The file position is at the end, so there is no current
      * record to delete and nothing more to read.
           DELETE MEMBER-SLOTS RECORD
           DISPLAY "DELETE after end: status " WS-STATUS
           READ MEMBER-SLOTS
           DISPLAY "READ after end:   status " WS-STATUS
           CLOSE MEMBER-SLOTS
           STOP RUN.
       8000-READ-SLOT.
           READ MEMBER-SLOTS
               AT END SET END-OF-SLOTS TO TRUE
           END-READ.
deleted slot 0003 ALAN TURING                    status 00
DELETE after end: status 43
READ after end:   status 46

Status 43 means that a DELETE or REWRITE was issued with no successful READ just before it; status 46 means a sequential READ was attempted with no valid next record. Both are logic errors in the program, not data conditions. The file now has an empty slot 3, and the listing program shows slots 1, 2 and 6 only.

Random and dynamic access: READ, START, REWRITE, DELETE, WRITE

The next program opens the file in dynamic access mode and exercises every statement in turn.

       IDENTIFICATION DIVISION.
       PROGRAM-ID. SLOTDYN.
      * Dynamic access: random READ, START, READ NEXT, REWRITE, DELETE.
       ENVIRONMENT DIVISION.
       INPUT-OUTPUT SECTION.
       FILE-CONTROL.
           SELECT MEMBER-SLOTS ASSIGN TO "MEMBSLOT"
               ORGANIZATION IS RELATIVE
               ACCESS MODE IS DYNAMIC
               RELATIVE KEY IS WS-SLOT
               FILE STATUS IS WS-STATUS.
       DATA DIVISION.
       FILE SECTION.
       FD  MEMBER-SLOTS.
       COPY MEMBREC.
       WORKING-STORAGE SECTION.
       01  WS-STATUS           PIC XX.
       01  WS-SLOT             PIC 9(4).
       01  WS-EOF              PIC X VALUE "N".
           88  END-OF-SLOTS    VALUE "Y".
       PROCEDURE DIVISION.
       0000-MAIN-LINE.
           OPEN I-O MEMBER-SLOTS
      * 1. Random READ of an occupied slot, then REWRITE it.
           MOVE 2 TO WS-SLOT
           READ MEMBER-SLOTS
               INVALID KEY DISPLAY "slot 2 empty?"
               NOT INVALID KEY
                   DISPLAY "read slot " WS-SLOT ": " MEMBER-NAME
                   MOVE ZERO TO MEMBER-FINES
                   REWRITE MEMBER-RECORD
                       INVALID KEY DISPLAY "rewrite failed " WS-STATUS
                   END-REWRITE
           END-READ
      * 2. Random READ of an empty slot and of a slot past the end.
           PERFORM VARYING WS-SLOT FROM 4 BY 3 UNTIL WS-SLOT > 7
               READ MEMBER-SLOTS
                   INVALID KEY
                       DISPLAY "slot " WS-SLOT " has no record, status "
                           WS-STATUS
                   NOT INVALID KEY
                       DISPLAY "slot " WS-SLOT " holds " MEMBER-NAME
               END-READ
           END-PERFORM
      * 3. START at the first occupied slot not less than 4, then
      *    read sequentially from there.
           MOVE 4 TO WS-SLOT
           START MEMBER-SLOTS KEY IS NOT LESS THAN WS-SLOT
               INVALID KEY DISPLAY "nothing from slot 4 on"
           END-START
           READ MEMBER-SLOTS NEXT RECORD
               AT END SET END-OF-SLOTS TO TRUE
           END-READ
           IF NOT END-OF-SLOTS
               DISPLAY "next after START: slot " WS-SLOT " "
                   MEMBER-NAME
           END-IF
      * 4. START beyond the last slot.
           MOVE 7 TO WS-SLOT
           START MEMBER-SLOTS KEY IS GREATER THAN WS-SLOT
               INVALID KEY DISPLAY "START past the end, status "
                   WS-STATUS
           END-START
      * 5. DELETE by key, then READ the emptied slot.
           MOVE 6 TO WS-SLOT
           DELETE MEMBER-SLOTS RECORD
               INVALID KEY DISPLAY "delete failed " WS-STATUS
               NOT INVALID KEY DISPLAY "slot 6 deleted"
           END-DELETE
           READ MEMBER-SLOTS
               INVALID KEY DISPLAY "slot 6 now empty, status "
                   WS-STATUS
           END-READ
      * 6. A slot freed earlier (by the purge) can be written again.
           MOVE 3 TO WS-SLOT
           MOVE 103 TO MEMBER-ID
           MOVE "EDSGER DIJKSTRA" TO MEMBER-NAME
           MOVE 20261020 TO MEMBER-JOINED
           MOVE ZERO TO MEMBER-FINES
           SET MEMBER-ACTIVE TO TRUE
           WRITE MEMBER-RECORD
               INVALID KEY DISPLAY "write failed " WS-STATUS
               NOT INVALID KEY DISPLAY "slot " WS-SLOT " reused"
           END-WRITE
           CLOSE MEMBER-SLOTS
           STOP RUN.
read slot 0002: GRACE HOPPER
slot 0004 has no record, status 23
slot 0007 has no record, status 23
next after START: slot 0006 KATHERINE JOHNSON
START past the end, status 23
slot 6 deleted
slot 6 now empty, status 23
slot 0003 reused

What each step demonstrates:

  1. Random READ and REWRITE. The slot number goes into the key item, READ fills the record, the program changes it (clears Grace Hopper’s fine) and REWRITE replaces it in the same slot. In random and dynamic access a REWRITE does not need a preceding READ: it replaces whatever slot the key item names. The replacement may have a different length from the original for a relative file.

  2. Empty slot, slot past the end. Both produce the invalid-key condition with status 23. From the program’s point of view an empty slot and a slot beyond the end of the file are the same answer.

  3. START then READ NEXT. START does not read anything; it only positions the file. The comparison is made on the relative key item with numeric rules. KEY IS NOT LESS THAN 4 selects the first occupied slot whose number is 4 or more, which is slot 6 because 4 and 5 are empty. The following READ NEXT RECORD returns that record and sets the key item to 6.

  4. START that finds nothing. No occupied slot is greater than 7, so the invalid-key condition (status 23) is raised and the file position is undefined. A program must not READ NEXT after a failed START; it must START again or read by key.

  5. DELETE. The slot named by the key item is emptied and the record area is left untouched. A READ of the same slot now gives status 23.

  6. WRITE into an emptied slot. The slot freed by the purge earlier is reused, with a different member, and no other slot changes. This is the point of relative files: a slot number is stable for the life of the file.

The relations that START accepts on a relative file are equal to, greater than, not less than (equivalent to greater than or equal to) and the longer spellings of those. The key phrase is optional: with no KEY phrase, equal is assumed. On a relative file the named key data item must be the file’s RELATIVE KEY. The GnuCOBOL manual documents further forms (FIRST, LAST, LESS THAN, LESS THAN OR EQUAL TO, NOT GREATER THAN), which are not in the Enterprise COBOL 6.5 START format; they were not run for this page, so code only the IBM relations if the program must also compile on z/OS.

The listing after the dynamic run:

slot 0001  000100  ADA LOVELACE
slot 0002  000101  GRACE HOPPER
slot 0003  000103  EDSGER DIJKSTRA
last status 10, slot key still 0003

Turning a business key into a slot number

A relative file only works well when the program can compute the slot number from the key it has.

Direct mapping. The key is already a small dense integer: slot = key - offset. Library card numbers issued from 100 upwards, or branch numbers 1 to 40, work this way, as in the load program above. Gaps in the number range cost disk space, not time.

Division/remainder (hashing). When the key is large or sparse, the Enterprise COBOL manual describes a randomizing routine, most often division/remainder: divide the key by the number of slots and add one to the remainder, which is always a valid slot number. The price is that two keys can land in the same slot (a collision), and the program must then choose another slot or keep an overflow area. The following program uses a five-slot file, where cards 100 and 105 both leave remainder 0.

       IDENTIFICATION DIVISION.
       PROGRAM-ID. SLOTHASH.
      * Division/remainder randomizing: slot = MOD(id, slots) + 1.
      * Ids 100 and 105 collide when the file has five slots.
       ENVIRONMENT DIVISION.
       INPUT-OUTPUT SECTION.
       FILE-CONTROL.
           SELECT MEMBER-HASH ASSIGN TO "MEMBHASH"
               ORGANIZATION IS RELATIVE
               ACCESS MODE IS RANDOM
               RELATIVE KEY IS WS-SLOT
               FILE STATUS IS WS-STATUS.
       DATA DIVISION.
       FILE SECTION.
       FD  MEMBER-HASH.
       COPY MEMBREC.
       WORKING-STORAGE SECTION.
       01  WS-STATUS           PIC XX.
       01  WS-SLOT             PIC 9(4).
       01  WS-SLOTS            PIC 9(4) VALUE 5.
       01  WS-IDS-VALUES.
           05  FILLER          PIC 9(6) VALUE 100.
           05  FILLER          PIC 9(6) VALUE 101.
           05  FILLER          PIC 9(6) VALUE 102.
           05  FILLER          PIC 9(6) VALUE 105.
       01  WS-IDS REDEFINES WS-IDS-VALUES.
           05  WS-ID           PIC 9(6) OCCURS 4 TIMES.
       01  WS-N                PIC 9.
       PROCEDURE DIVISION.
       0000-MAIN-LINE.
           OPEN OUTPUT MEMBER-HASH
           PERFORM VARYING WS-N FROM 1 BY 1 UNTIL WS-N > 4
               INITIALIZE MEMBER-RECORD
               MOVE WS-ID (WS-N) TO MEMBER-ID
               COMPUTE WS-SLOT =
                   FUNCTION MOD (MEMBER-ID, WS-SLOTS) + 1
               WRITE MEMBER-RECORD
                   INVALID KEY
                       DISPLAY "Member " MEMBER-ID " collides in slot "
                           WS-SLOT ", status " WS-STATUS
                   NOT INVALID KEY
                       DISPLAY "Member " MEMBER-ID " -> slot " WS-SLOT
               END-WRITE
           END-PERFORM
           CLOSE MEMBER-HASH
           STOP RUN.
Member 000100 -> slot 0001
Member 000101 -> slot 0002
Member 000102 -> slot 0003
Member 000105 collides in slot 0001, status 22

The collision is reported by the file system itself (status 22 on WRITE), so the program needs no table to detect it. Resolving it is the program’s job, and this is why a hashed relative file is more work than an indexed file, which handles all of this internally. After the run the file holds three records and is 204 bytes on GnuCOBOL (three 68-byte slots), because the highest slot ever written was 3.

File status codes for relative files

Status Meaning on a relative file

00

Successful.

10

Sequential READ or READ NEXT: no next occupied slot (end of file).

22

WRITE to a slot that already holds a record.

23

READ, REWRITE, DELETE or START found no record: the slot is empty or beyond the end of the file (Enterprise COBOL), or no slot satisfies the START relation.

24

Boundary violation: the slot number is outside what the file can hold (on GnuCOBOL 3.2.0, WRITE with the key item set to 0).

35

OPEN INPUT or OPEN I-O of a file that does not exist.

43

DELETE or REWRITE in sequential access with no successful READ immediately before it.

46

Sequential READ with no valid next record (here, after AT END had already been reached).

48

WRITE on a file that is not open for output (tested on GnuCOBOL 3.2.0 with a file opened INPUT).

The codes 10, 22, 23, 24, 43 and 46 were produced by the programs on this page; 35 and 48 by small separate tests. The exact assignment of every code, and the VSAM feedback codes that Enterprise COBOL adds, are in File Status Codes.

On z/OS: the file is a VSAM RRDS

A COBOL relative file on z/OS is a VSAM relative-record data set. Nothing in the COBOL source says so: the SELECT entry names a ddname, and the DD statement points at a data set that was defined as an RRDS.

//DEFRRDS  EXEC PGM=IDCAMS
//SYSPRINT DD SYSOUT=*
//SYSIN    DD *
  DEFINE CLUSTER (NAME(USERID.CIRC.MEMBSLOT) -
         NUMBERED -
         RECORDSIZE(60 60) -
         RECORDS(100 20))
/*
//*
//STEP1    EXEC PGM=SLOTLOAD
//MEMBMAST DD DISP=SHR,DSN=USERID.CIRC.MEMBMAST
//MEMBSLOT DD DISP=SHR,DSN=USERID.CIRC.MEMBSLOT

This job is z/OS; not run here. The SELECT clauses in SLOTLOAD would read ASSIGN TO MEMBSLOT instead of the quoted file name used on GnuCOBOL, and the FILE STATUS clause may name a second item to receive the VSAM return and feedback codes. NUMBERED is the IDCAMS keyword that makes the cluster an RRDS; the space and volume parameters depend on the installation, so check them against the DFSMS access method services manual before running anything like it.

How the COBOL concepts map to the RRDS:

  • Slot = relative record number. In a fixed-length RRDS each record occupies one fixed-size slot, the first slot has relative record number 1, and slots can be skipped when the data set is loaded and left empty.

  • Fixed-length RRDS. Define the cluster with RECORDSIZE(n n) where n is at least the largest COBOL record. All records are padded to n. If the FD uses RECORD IS VARYING ... DEPENDING ON, a WRITE or REWRITE transfers the length from the DEPENDING ON item, VSAM pads it to the slot size, and a READ always returns the full slot size in that item.

  • Variable-length RRDS. There are no slots; free space defined for the cluster makes insertions cheaper, and relative record numbers can still be skipped. Define it with RECORDSIZE(avg max), where the average is smaller than the maximum, describe the variable-length records in the FD, and use VSAM variable-length RRDS support.

  • No alternate indexes. Alternate indexes are not supported for an RRDS. A second way into the data needs a second file, or a KSDS (see Indexed Files and VSAM).

  • Opening. A VSAM file is opened INPUT, OUTPUT, I-O or EXTEND; on z/OS OPEN OUTPUT and OPEN EXTEND have the same effect for a KSDS or RRDS. A REWRITE on a file not opened I-O sets status 49.

  • Sequential load. When an RRDS is loaded in sequence, the records go in ascending relative record number, and the key item receives the number of each record written.

  • DELETE and REWRITE by key. To replace a record randomly or dynamically, move the slot number to the relative key item and issue REWRITE, without a prior READ. DELETE needs the file open I-O.

Enterprise COBOL and GnuCOBOL compared

Point Enterprise COBOL 6.5 GnuCOBOL 3.2.0

File on disk

VSAM RRDS (fixed or variable length)

Run-time file with an 8-byte header per slot (default file handler, 3.2.0)

DELETE of an empty slot

Invalid key condition

Status 00 when the slot lies inside the file (tested), status 23 beyond the end

REWRITE of an empty slot

Invalid key condition

Status 00 and the record is stored in that slot when the slot lies inside the file (tested); status 23 beyond the end

READ ... NEXT RECORD on a RANDOM file

Not allowed by the Language Reference

Compiles without any diagnostic (tested)

INVALID KEY on a sequential READ

Not allowed for sequential access

Compiles silently and is never used (tested)

START with ACCESS MODE RANDOM

Not allowed

error: START not allowed with ACCESS MODE RANDOM

START relations

Equal, greater than, not less than (and their spellings)

Also FIRST, LAST, LESS THAN (manual; not run here)

The two rows on DELETE and REWRITE matter in practice: a program that relies on the invalid-key condition to detect "slot not in use" works on z/OS but not on GnuCOBOL 3.2.0, as the next program shows. Slot 4 lies inside the file and is empty; slot 12 is beyond the end.

       IDENTIFICATION DIVISION.
       PROGRAM-ID. SLOTQUIRK.
      * Operations on slots that hold no record (GnuCOBOL 3.2.0).
       ENVIRONMENT DIVISION.
       INPUT-OUTPUT SECTION.
       FILE-CONTROL.
           SELECT MEMBER-SLOTS ASSIGN TO "MEMBSLOT"
               ORGANIZATION IS RELATIVE
               ACCESS MODE IS DYNAMIC
               RELATIVE KEY IS WS-SLOT
               FILE STATUS IS WS-STATUS.
       DATA DIVISION.
       FILE SECTION.
       FD  MEMBER-SLOTS.
       COPY MEMBREC.
       WORKING-STORAGE SECTION.
       01  WS-STATUS           PIC XX.
       01  WS-SLOT             PIC 9(4).
       PROCEDURE DIVISION.
       0000-MAIN-LINE.
           OPEN I-O MEMBER-SLOTS
      * Slot 4 lies inside the file but holds no record.
           MOVE 4 TO WS-SLOT
           READ MEMBER-SLOTS
               INVALID KEY DISPLAY "READ   slot 4: status " WS-STATUS
           END-READ
           DELETE MEMBER-SLOTS RECORD
               INVALID KEY DISPLAY "DELETE slot 4: status " WS-STATUS
               NOT INVALID KEY
                   DISPLAY "DELETE slot 4: accepted, status "
                       WS-STATUS
           END-DELETE
           MOVE "NOBODY" TO MEMBER-NAME
           REWRITE MEMBER-RECORD
               INVALID KEY DISPLAY "REWRITE slot 4: status " WS-STATUS
               NOT INVALID KEY
                   DISPLAY "REWRITE slot 4: accepted, status "
                       WS-STATUS
           END-REWRITE
      * Slot 12 lies beyond the end of the file.
           MOVE 12 TO WS-SLOT
           REWRITE MEMBER-RECORD
               INVALID KEY DISPLAY "REWRITE slot 12: status " WS-STATUS
           END-REWRITE
           MOVE 0 TO WS-SLOT
           WRITE MEMBER-RECORD
               INVALID KEY DISPLAY "WRITE  slot 0: status " WS-STATUS
           END-WRITE
           CLOSE MEMBER-SLOTS
           STOP RUN.
READ   slot 4: status 23
DELETE slot 4: accepted, status 00
REWRITE slot 4: accepted, status 00
REWRITE slot 12: status 23
WRITE  slot 0: status 24

To stay portable, READ the slot first and test for status 23 before a REWRITE or DELETE in a program that must run on both systems.

Compiler diagnostics for mistakes in the SELECT entry

These fragments were compiled on their own; the messages are what cobc (GnuCOBOL) 3.2.0 printed.

A missing RELATIVE KEY with ACCESS MODE RANDOM:

bad1.cob:6: error: RELATIVE KEY clause is required for file 'MEMBER-SLOTS'

START on a file opened with ACCESS MODE RANDOM:

bad3.cob:21: error: START not allowed with ACCESS MODE RANDOM

A RELATIVE KEY that is a field of the file’s own record (RELATIVE KEY IS MEMBER-ID):

bad6.cob:9: error: RELATIVE KEY MEMBER-ID cannot be in file record belonging to MEMBER-SLOTS

A signed key (PIC S9(4)) and a key with decimals (PIC 9(2)V9):

bad7.cob:9: error: file MEMBER-SLOTS: RELATIVE KEY WS-SLOT must be unsigned
bad8.cob:9: error: file MEMBER-SLOTS: RELATIVE KEY WS-SLOT must be integer

A key item too small for the slot number (PIC 99 and slot 100) is not a file error at all, the value is truncated before the file statement runs:

bad9.cob:20: warning: value size exceeds data size [-Wtruncate]

Mistakes that GnuCOBOL 3.2.0 does not diagnose (tested): READ ... NEXT RECORD on an ACCESS MODE RANDOM file, READ ... KEY IS on a relative file, and INVALID KEY on a sequential READ. The Enterprise COBOL Language Reference does not allow them (the KEY phrase must not be specified for relative files, and the INVALID KEY phrase must not be specified for sequential access), so code review has to catch them.

Opening a file that was never created gives status 35 on both systems:

open missing 35

When a relative file fits, and why it is rare now

A relative file is a good fit when all of these hold:

  • the lookup key is, or can cheaply become, a dense integer (card number, branch number, terminal number, fixed code table);

  • nearly every access is by slot, in random order, and a sequential pass in slot order is the only other access;

  • records are added and removed over time, but a record keeps its slot;

  • the file is mostly full (empty slots still use space).

Examples are small code tables, work files numbered by a sequence, and checkpoint records numbered by job step.

They are rare in new code for these reasons:

  • a hashed key needs collision handling that an indexed file already provides;

  • a gap-heavy range wastes space;

  • an RRDS cannot have an alternate index, so a second search path needs another data set;

  • a table in WORKING-STORAGE with SEARCH ALL, a KSDS with a normal key, or a Db2 table covers nearly every case with less code (see Searching and Sorting Tables, Indexed Files and VSAM and Embedded SQL and Db2).

You will still meet them in old batch systems that use them as direct-access tables, and VSAM RRDS support continues in Enterprise COBOL 6.5. For modernization, a relative file normally becomes a keyed file or a table, with the slot-number computation turned into the key; see Legacy Features and Migration.

Common mistakes

  • Treating an empty slot as an error. Status 23 on a keyed READ is the normal answer for "not there". Test for it with INVALID KEY or the status item.

  • No FILE STATUS. Without one, an unexpected status falls to a declarative or ends the run; with one, the program must check it after every statement.

  • Reusing the key item between statements. A sequential READ and a successful READ NEXT overwrite the relative key item. Move the slot number back before the next keyed statement.

  • Reading after a failed START. The file position is undefined; do not READ NEXT.

  • Placing the slot number inside the record and naming it as the key. The relative key must be outside the record; keep a copy in the record if the record has to carry it.

  • A key item that is too small. The slot number is truncated silently by a MOVE or COMPUTE without ON SIZE ERROR.

  • Relying on INVALID KEY for an empty slot on REWRITE or DELETE. It works on z/OS and not on GnuCOBOL 3.2.0, see above.

  • Expecting a slot to be found by content. Searching a relative file for a name means reading all of it sequentially.