Sequential 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 sequential files come from the original language and LINE SEQUENTIAL organization was standardized in 2023; against IBM Enterprise COBOL for z/OS 6.5 (PTFs through September 2026) on z/OS 3.2, where a sequential file is normally a QSAM data set and LINE SEQUENTIAL is available only for z/OS UNIX files; 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 unless the text says otherwise). z/OS examples are labelled "z/OS; not run here" and are checked against the Language Reference and the Programming Guide.
A sequential file is a file whose records are processed in the order in which they were written. This page covers the statements that work on it (OPEN, READ, WRITE, REWRITE, CLOSE), the difference between a record sequential file and a LINE SEQUENTIAL file, fixed-length, variable-length and multi-record files, and the algorithm that almost every batch system is built around: the balance-line update of an old master file with a file of sorted transactions. The general SELECT and FD rules and the mapping of an assignment name to a file are on File Concepts, SELECT and FD; the other organizations are on Indexed Files and VSAM and Relative Files; every status code mentioned here is in the table on File Status Codes; putting the transactions in key order is the job of Sort and Merge. The examples use the Circulation public-library scenario: the book master BOOKMAST is updated with the day’s loan transactions LOANTRAN.
Where the book is outdated: Taulli’s Modern Mainframe Development (2022, chapter 5) introduces SELECT, FD, sequential READ and WRITE, FILE STATUS and a master-file update, and the ideas are still current. Four details need correcting. A file is opened with OPEN INPUT, not "OPEN READ" (the compiler rejects the second form, see below). The FILE STATUS item is a separate two-character item in WORKING-STORAGE, not a field declared inside the record group, where it would become part of the data read from the file. A PC-style ASSIGN TO "Customers.dat" together with LINE SEQUENTIAL organization mixes two worlds: on z/OS batch the assignment name is a DD name and the file is a QSAM data set, and Enterprise COBOL supports LINE SEQUENTIAL only for z/OS UNIX files. And the book’s master-file update is described as a narrative; the complete, running balance-line version is below. Also note that the book’s examples run on GnuCOBOL through OpenCobolIDE, an unmaintained IDE (see Getting Started).
The sequential file model
A sequential file is a stream of records. There is no key and no slot number: the only operations that find a record are "give me the next one" and, after reading it, "replace this one". Three consequences follow, and they shape every program that uses such a file:
-
A file is read from the first record to the last, and a program that needs a record a second time closes and reopens the file, or keeps the record in storage.
-
A record cannot be inserted, deleted or lengthened in place. To change the set of records, a program writes a new file: copy what stays, change what changes, leave out what goes. The balance-line update below is that pattern.
-
REWRITEcan replace the record just read, but only with a record of the same length.
The organization is chosen in the SELECT entry. ORGANIZATION IS SEQUENTIAL (the default when the clause is omitted) is a record sequential file: the records are binary-safe, any byte value is allowed, and the record length comes from the FD. ORGANIZATION IS LINE SEQUENTIAL is a text file: each record is one line ended by a line terminator, so only printable data belongs in it.
SEQUENTIAL (record sequential) |
LINE SEQUENTIAL |
|
|---|---|---|
Record end |
Known from the |
A line terminator: a line feed on Unix-like systems, carriage return plus line feed on Windows |
Data allowed |
Any byte value, including packed and binary items |
Text; the terminator must not occur in a record |
Short record on |
Fixed: the full record area is written, trailing spaces included |
Trailing spaces are removed, so the line is as short as its data |
Short line on |
Not applicable |
The record area is filled with spaces on the right |
Long line on |
Not applicable |
The excess is returned as the next record, with status |
Standard |
COBOL 85 and earlier |
GnuCOBOL extension that COBOL 2023 standardized |
Enterprise COBOL 6.5 |
QSAM, VSAM ESDS, and z/OS UNIX files |
z/OS UNIX files only |
The Circulation LOANTRAN file is a LINE SEQUENTIAL file on the PC, which makes it easy to inspect with any editor. On z/OS the same data would be a fixed-block QSAM data set USERID.CIRC.LOANTRAN with 30-byte records and the same FD. The program text does not change when the file moves between the two, only the environment (the DD statement, the RECFM and LRECL) does.
OPEN, CLOSE and the four modes
OPEN names the mode, then the file names; one statement can open several files, each with its own mode:
OPEN {INPUT | OUTPUT | I-O | EXTEND} file-name-1 [file-name-2] ...
| Mode | What it does | Statements allowed |
|---|---|---|
|
The file must exist. Reading starts at the first record. |
|
|
Creates the file, or empties an existing one. Writing starts at the first record. Reading the old content afterwards is impossible: the content is gone. |
|
|
Positions after the last record; the old records stay. |
|
|
The file must exist. Records are read, and the one just read can be replaced. |
|
CLOSE ends the processing of a file and is the point where buffered output is written. A file that was opened must be closed before it is opened again, and a program that ends with files open has them closed by the run time, which GnuCOBOL reports as a warning (libcob: warning: implicit CLOSE of REJECT-FILE) and Enterprise COBOL handles through Language Environment. Code the CLOSE yourself.
The next program shows all four modes on one file. A fixed-length record sequential file LOANLOG is created with two records, extended with a third, opened I-O to replace the date of the second record, and read back; a second file LOANTXT shows the LINE SEQUENTIAL form of the first two records. WRITE ... FROM moves the working-storage record into the file’s record area and writes it in one statement, and READ ... INTO does the reverse.
IDENTIFICATION DIVISION.
PROGRAM-ID. SEQDEMO.
* OPEN OUTPUT, EXTEND, INPUT and I-O on one fixed-length file.
ENVIRONMENT DIVISION.
INPUT-OUTPUT SECTION.
FILE-CONTROL.
SELECT LOAN-LOG ASSIGN TO "LOANLOG"
ORGANIZATION IS SEQUENTIAL
FILE STATUS IS WS-STATUS.
SELECT LOAN-TEXT ASSIGN TO "LOANTXT"
ORGANIZATION IS LINE SEQUENTIAL
FILE STATUS IS WS-STATUS.
DATA DIVISION.
FILE SECTION.
FD LOAN-LOG.
01 LOG-RECORD PIC X(30).
FD LOAN-TEXT.
01 TEXT-RECORD PIC X(30).
WORKING-STORAGE SECTION.
COPY LOANREC.
01 WS-STATUS PIC XX.
01 WS-COUNT PIC 9(3) VALUE ZERO.
01 WS-EOF PIC X VALUE "N".
88 END-OF-LOG VALUE "Y".
PROCEDURE DIVISION.
0000-MAIN-LINE.
* 1. OUTPUT creates the file (or empties an existing one).
OPEN OUTPUT LOAN-LOG LOAN-TEXT
MOVE "O" TO LOAN-TYPE
MOVE 100 TO LOAN-MEMBER-ID
MOVE 1 TO LOAN-BOOK-ID
MOVE 20261020 TO LOAN-DATE
WRITE LOG-RECORD FROM LOAN-RECORD
WRITE TEXT-RECORD FROM LOAN-RECORD
MOVE "R" TO LOAN-TYPE
WRITE LOG-RECORD FROM LOAN-RECORD
WRITE TEXT-RECORD FROM LOAN-RECORD
CLOSE LOAN-LOG LOAN-TEXT
* 2. EXTEND adds after the last record; the old ones stay.
OPEN EXTEND LOAN-LOG
MOVE "O" TO LOAN-TYPE
MOVE 102 TO LOAN-MEMBER-ID
MOVE 3 TO LOAN-BOOK-ID
WRITE LOG-RECORD FROM LOAN-RECORD
CLOSE LOAN-LOG
* 3. I-O: READ a record, then REWRITE that same record.
OPEN I-O LOAN-LOG
READ LOAN-LOG INTO LOAN-RECORD
READ LOAN-LOG INTO LOAN-RECORD
MOVE 20261021 TO LOAN-DATE
REWRITE LOG-RECORD FROM LOAN-RECORD
DISPLAY "REWRITE status " WS-STATUS
CLOSE LOAN-LOG
* 4. INPUT: read everything back.
OPEN INPUT LOAN-LOG
PERFORM UNTIL END-OF-LOG
READ LOAN-LOG INTO LOAN-RECORD
AT END SET END-OF-LOG TO TRUE
NOT AT END
ADD 1 TO WS-COUNT
DISPLAY WS-COUNT " " LOAN-TYPE " "
LOAN-MEMBER-ID " " LOAN-BOOK-ID " "
LOAN-DATE
END-READ
END-PERFORM
CLOSE LOAN-LOG
DISPLAY "records read: " WS-COUNT
STOP RUN.
Compiled with cobc -x -Wall -I. seqdemo.cob (and with -std=ibm), no warnings, it prints:
REWRITE status 00
001 O 000100 000001 20261020
002 R 000100 000001 20261021
003 O 000102 000003 20261020
records read: 003
The files differ in size. LOANLOG is 90 bytes, three records of exactly 30 bytes with the trailing spaces of the FILLER written out; LOANTXT is 44 bytes, two lines of 21 data characters plus the line feed, because a LINE SEQUENTIAL WRITE drops trailing spaces:
$ ls -l LOANLOG LOANTXT
-rw-r--r-- 1 user group 90 Oct 20 18:26 LOANLOG
-rw-r--r-- 1 user group 44 Oct 20 18:26 LOANTXT
READ
A sequential READ returns the next record:
READ file-name [NEXT] RECORD [INTO identifier]
[AT END imperative-statement-1]
[NOT AT END imperative-statement-2]
[END-READ]
-
The
NEXTword is optional for sequential files (it matters on indexed and relative files with dynamic access). -
AT ENDruns when there is no next record, and the file status is10. AREADthat has neither anAT ENDphrase nor aFILE STATUSitem nor an error declarative ends the run unit at the end of the file, so every sequential read loop needs one of them; theFILE STATUSitem is the one you test in any case.NOT AT ENDruns only after a successful read, which is the right place for the work that uses the record. -
INTOmoves the record from the record area to a working-storage item after a successful read. It is aMOVEgoverned by the usual rules. For a variable-length file it moves only as many bytes as the record has, with the usual padding of the receiving item. -
After
AT ENDthe content of the record area is undefined. A program that processes "one more record" after the loop ended works on garbage or on the last record, depending on the system. The balance-line program below solves this by storing a high key value when the file ends. -
If the file has no records, the first
READgives status10. -
After a read that fails with status
30or any other error the position is undefined; do not read on.
The usual loop is a priming read followed by a loop that processes and then reads again, or an inline loop with NOT AT END. The first form has one READ statement and needs no flag tests inside the loop body:
PERFORM 8000-READ-LOAN
PERFORM UNTIL END-OF-LOANS
PERFORM 2000-PROCESS-LOAN
PERFORM 8000-READ-LOAN
END-PERFORM
WRITE
WRITE record-name [FROM identifier]
[{BEFORE | AFTER} ADVANCING {integer | identifier} {LINE | LINES}
| {mnemonic-name | PAGE}]
[AT {END-OF-PAGE | EOP} imperative-statement-1]
[NOT AT {END-OF-PAGE | EOP} imperative-statement-2]
[END-WRITE]
-
WRITEnames the record, not the file. It puts the record area on the file; withFROM, the source item is moved into the record area first and stays unchanged.WRITE rec FROM itemis therefore the same asMOVE item TO recfollowed byWRITE rec, except that the program does not touch the record area. -
After a successful
WRITEthe content of the record area is not guaranteed to be available to the program on every system (the standard leaves it undefined). Do not rely on it holding the data; use theFROMitem. -
The
ADVANCINGphrase controls the line spacing of a print file and is covered with the rest of report printing on Reports and Report Writer. -
A file opened
OUTPUTorEXTENDacceptsWRITE; aWRITEon a file openedINPUTraises status48. AWRITEon a full device or a data set without space raises a permanent error (status34on Enterprise COBOL when the extent is exhausted). -
INVALID KEYis not allowed on a sequentialWRITE; the error conditions are reported through the file status.
REWRITE
REWRITE record-name [FROM identifier]
-
The file must be open
I-O, and the last statement on the file must have been a successfulREAD. The record replaced is the one just read. -
On z/OS the new record must have the same length as the old one for a sequential file. Rewriting a different length is an error: the record is not replaced and the status is
44(boundary violation). -
In GnuCOBOL 3.2
REWRITEworks onLINE SEQUENTIALfiles as well (a 3.2 addition); the replacement has to keep the line length. The next program read and rewrote the first line ofLOANTRANwith status00. -
REWRITEdoes not move the file position; the nextREADreturns the record after the one replaced. -
In-place update is cheap and has a cost: after an abend in the middle of the pass, the file is half updated and the original is lost. Update by creating a new file (next section) when that matters, which in batch work is nearly always.
Fixed-length and variable-length records
A file whose FD has a single 01 item, or several 01 items of the same size, has fixed-length records. Variable-length records have two forms in the program:
-
Several
01items of different lengths under oneFD, orRECORD CONTAINS 10 TO 80 CHARACTERS: the record length is the length of the item written, and on input the length of the record read is the length that was stored. -
RECORD IS VARYING IN SIZE FROM min TO max CHARACTERS DEPENDING ON data-name: the length is explicit. Before aWRITEthe program moves the length to theDEPENDING ONitem; after aREADthe item holds the length of the record read. The item must be an integer inWORKING-STORAGEand must not be a field of the record.
On z/OS the record format is a property of the data set (RECFM=V, VB, F, FB, U, plus A or M for carriage control), set by the DCB parameters of the DD statement or by the program’s RECORDING MODE and BLOCK CONTAINS clauses. A variable-length record starts with a four-byte record descriptor word (RDW), and a block with a block descriptor word (BDW). Enterprise COBOL takes the record format from the FD when the RECORDING MODE clause is given and otherwise infers it from the record descriptions. BLOCK CONTAINS 0 RECORDS lets the system choose the best block size from the data set, which is the practice on z/OS. GnuCOBOL stores a variable-length record sequential record with its own four-byte length prefix, not an RDW.
The next program writes the Circulation titles as variable-length records of 6 + title length bytes and reads them back. The key technique is the COMPUTE: the length of the trimmed title plus the six-digit book number.
IDENTIFICATION DIVISION.
PROGRAM-ID. VARDEMO.
* Variable-length records: book id plus the title as long as it is.
ENVIRONMENT DIVISION.
INPUT-OUTPUT SECTION.
FILE-CONTROL.
SELECT BOOK-IN ASSIGN TO "BOOKMAST"
ORGANIZATION IS LINE SEQUENTIAL.
SELECT TITLE-FILE ASSIGN TO "TITLES"
ORGANIZATION IS SEQUENTIAL
FILE STATUS IS WS-STATUS.
DATA DIVISION.
FILE SECTION.
FD BOOK-IN.
01 BOOK-IN-LINE PIC X(60).
FD TITLE-FILE
RECORD IS VARYING IN SIZE FROM 7 TO 36 CHARACTERS
DEPENDING ON WS-LENGTH.
01 TITLE-RECORD.
05 TITLE-ID PIC 9(6).
05 TITLE-TEXT PIC X(30).
WORKING-STORAGE SECTION.
COPY BOOKREC.
01 WS-STATUS PIC XX.
01 WS-LENGTH PIC 9(3).
01 WS-EOF PIC X VALUE "N".
88 END-OF-FILE VALUE "Y".
PROCEDURE DIVISION.
0000-MAIN-LINE.
OPEN INPUT BOOK-IN
OPEN OUTPUT TITLE-FILE
PERFORM UNTIL END-OF-FILE
READ BOOK-IN INTO BOOK-RECORD
AT END SET END-OF-FILE TO TRUE
NOT AT END
MOVE BOOK-ID TO TITLE-ID
MOVE BOOK-TITLE TO TITLE-TEXT
COMPUTE WS-LENGTH = 6 + FUNCTION LENGTH(
FUNCTION TRIM(BOOK-TITLE TRAILING))
WRITE TITLE-RECORD
DISPLAY "wrote " TITLE-ID " length " WS-LENGTH
END-READ
END-PERFORM
CLOSE BOOK-IN TITLE-FILE
MOVE "N" TO WS-EOF
OPEN INPUT TITLE-FILE
PERFORM UNTIL END-OF-FILE
READ TITLE-FILE
AT END SET END-OF-FILE TO TRUE
NOT AT END
DISPLAY "read " TITLE-ID " length " WS-LENGTH
" [" TITLE-TEXT(1:WS-LENGTH - 6) "]"
END-READ
END-PERFORM
CLOSE TITLE-FILE
STOP RUN.
Compiled with cobc -x -Wall -I. vardemo.cob (and with -std=ibm), no warnings, it prints:
wrote 000001 length 021
wrote 000002 length 018
wrote 000003 length 022
wrote 000004 length 017
read 000001 length 021 [DATA STRUCTURES]
read 000002 length 018 [COBOL BASICS]
read 000003 length 022 [BATCH PROCESSING]
read 000004 length 017 [OLD LEDGERS]
The file TITLES is 94 bytes: the four records add up to 78 bytes, and the other 16 bytes are the four-byte length prefix that GnuCOBOL puts in front of each record. A fixed-length file of the same four titles would have taken 4 x 36 = 144 bytes. On z/OS the same program would produce RECFM=V or VB records, each with its four-byte RDW.
Source: Enterprise COBOL for z/OS 6.5 Language Reference: RECORD clause; Programming Guide: Establishing record formats; Processing files with variable-length records or multiple record descriptions
Files with several record types
A file can mix record layouts, typically a header, many details and a trailer with a count, and the first byte or two says which layout a record has. The FD then has one 01 item per layout; all of them share the same storage, the record area, so a program decides which one to look at after each READ. The next program copies the loan transactions to LOANTRL and adds a trailer record T with the number of details, then reads the file back and reacts to the first byte.
IDENTIFICATION DIVISION.
PROGRAM-ID. MULTIREC.
* One file, two record layouts: details and a trailer.
ENVIRONMENT DIVISION.
INPUT-OUTPUT SECTION.
FILE-CONTROL.
SELECT TRAN-IN ASSIGN TO "LOANTRAN"
ORGANIZATION IS LINE SEQUENTIAL.
SELECT TRAN-OUT ASSIGN TO "LOANTRL"
ORGANIZATION IS LINE SEQUENTIAL.
DATA DIVISION.
FILE SECTION.
FD TRAN-IN.
01 TRAN-IN-LINE PIC X(30).
FD TRAN-OUT.
01 DETAIL-RECORD PIC X(30).
01 TRAILER-RECORD.
05 TRAILER-TYPE PIC X.
05 TRAILER-COUNT PIC 9(6).
05 FILLER PIC X(23).
WORKING-STORAGE SECTION.
COPY LOANREC.
01 WS-EOF PIC X VALUE "N".
88 END-OF-FILE VALUE "Y".
01 WS-COUNT PIC 9(6) VALUE ZERO.
PROCEDURE DIVISION.
0000-MAIN-LINE.
OPEN INPUT TRAN-IN
OPEN OUTPUT TRAN-OUT
PERFORM UNTIL END-OF-FILE
READ TRAN-IN
AT END SET END-OF-FILE TO TRUE
NOT AT END
WRITE DETAIL-RECORD FROM TRAN-IN-LINE
ADD 1 TO WS-COUNT
END-READ
END-PERFORM
MOVE SPACES TO TRAILER-RECORD
MOVE "T" TO TRAILER-TYPE
MOVE WS-COUNT TO TRAILER-COUNT
WRITE TRAILER-RECORD
CLOSE TRAN-IN TRAN-OUT
* Read it back: the first byte says which layout the line has.
MOVE "N" TO WS-EOF
OPEN INPUT TRAN-OUT
PERFORM UNTIL END-OF-FILE
READ TRAN-OUT
AT END SET END-OF-FILE TO TRUE
NOT AT END
IF TRAILER-TYPE = "T"
DISPLAY "trailer: " TRAILER-COUNT
" detail records"
ELSE
MOVE DETAIL-RECORD TO LOAN-RECORD
DISPLAY "detail: " LOAN-TYPE " "
LOAN-MEMBER-ID " " LOAN-BOOK-ID
END-IF
END-READ
END-PERFORM
CLOSE TRAN-OUT
STOP RUN.
Compiled with cobc -x -Wall -I. multirec.cob (and with -std=ibm), no warnings, it prints:
detail: O 000100 000001
detail: O 000101 000002
detail: R 000100 000001
detail: O 000102 000003
trailer: 000004 detail records
Distinguishing the layouts by the first byte is a convention of the data, not something the compiler checks. A trailer count is how the next job in a batch stream verifies that it received everything the previous one wrote.
A line longer than the record
Reading a LINE SEQUENTIAL file whose lines are longer than the FD does not truncate silently in GnuCOBOL 3.2: the extra characters come back as the next record, and the first of the two reads has status 06. A file with the lines AB, ABCDEFGHIJKLMNOP and XY, read with PIC X(8), gave:
status 00 [AB ]
status 06 [ABCDEFGH]
status 00 [IJKLMNOP]
The short line was padded with spaces. Any program that reads text files tests for status 06, or sizes the record for the longest line expected.
What goes wrong: status codes of sequential statements
The next program provokes the common mistakes and prints the status after each:
IDENTIFICATION DIVISION.
PROGRAM-ID. SEQERR.
* Status codes that sequential statements really produce.
ENVIRONMENT DIVISION.
INPUT-OUTPUT SECTION.
FILE-CONTROL.
SELECT LOAN-FILE ASSIGN TO "LOANTRAN"
ORGANIZATION IS LINE SEQUENTIAL
FILE STATUS IS WS-STATUS.
SELECT MISSING-FILE ASSIGN TO "NOSUCH"
ORGANIZATION IS LINE SEQUENTIAL
FILE STATUS IS WS-STATUS.
DATA DIVISION.
FILE SECTION.
FD LOAN-FILE.
01 LOAN-LINE PIC X(30).
FD MISSING-FILE.
01 MISSING-LINE PIC X(30).
WORKING-STORAGE SECTION.
01 WS-STATUS PIC XX.
PROCEDURE DIVISION.
0000-MAIN-LINE.
OPEN INPUT MISSING-FILE
DISPLAY "open of a missing file: " WS-STATUS
READ LOAN-FILE
DISPLAY "read before open: " WS-STATUS
OPEN INPUT LOAN-FILE
OPEN INPUT LOAN-FILE
DISPLAY "second open: " WS-STATUS
MOVE "X" TO LOAN-LINE
WRITE LOAN-LINE
DISPLAY "write to an INPUT file: " WS-STATUS
REWRITE LOAN-LINE
DISPLAY "rewrite on an INPUT file: " WS-STATUS
READ LOAN-FILE
READ LOAN-FILE
READ LOAN-FILE
READ LOAN-FILE
DISPLAY "fourth read: " WS-STATUS
READ LOAN-FILE
DISPLAY "read past the end: " WS-STATUS
CLOSE LOAN-FILE
CLOSE LOAN-FILE
DISPLAY "close of a closed file: " WS-STATUS
STOP RUN.
Compiled with cobc -x -Wall -I. seqerr.cob (and with -std=ibm), no warnings, it prints the same eight lines both ways:
open of a missing file: 35
read before open: 47
second open: 41
write to an INPUT file: 48
rewrite on an INPUT file: 49
fourth read: 00
read past the end: 10
close of a closed file: 42
They are the codes the standard defines: 35 file not found on OPEN INPUT, 41 file already open, 42 close of a file that is not open, 47 READ on a file that is not open for input, 48 WRITE on a file that is not open for output, 49 REWRITE or DELETE on a file that is not open I-O, and 10 at end. All of them appear in the table on File Status Codes. The program has a FILE STATUS item for every file, so none of these errors ended the run; without the item, GnuCOBOL prints a libcob message and stops (see Error Handling and Declaratives).
The balance-line update
A sequential master file cannot be changed in place, and a transaction file is a list of changes in arrival order. The classic batch solution is the balance-line algorithm, also called the sequential master-file update or the match-merge: put the transactions in the same key order as the master, then read both files in step. The old master is never modified. Every master record ends up in the new master, changed or not, and the transactions that cannot be applied go to a reject report.
The conditions for it to work:
-
Both files are sorted ascending on the same key, here the book number. The loan transactions arrive in the order in which they happened, so a
SORTstep orders them first (see Sort and Merge). Transactions for the same key must stay in arrival order. -
The key of a file that has ended is set to a value higher than any real key,
999999here (HIGH-VALUESfor an alphanumeric key). This sentinel makes the end of one file simply "that file has nothing left that matches", and the loop ends when both keys are the sentinel. No special case is needed for the end of either file. -
One transaction is applied to the master record in storage; the master is written only when the next transaction belongs to a higher key. So any number of transactions for the same book, in any combination, fall out of the same code.
The figure shows why the loop needs only three branches. "Master lower" means that no transaction is left for this book, so the book is final. "Equal" applies the change and reads the next transaction, but not the next master, so a second transaction for the same book finds the same master record. "Master higher" means a transaction for a book that is not on the master (an unknown book number), which is a reject.
The program
The master BOOKMAST and the sorted transactions LOANSRT are line sequential files. The transactions were sorted by book number with a stable sort before the run (the shell command is sort -s -k1.8,1.13 LOANTRAN > LOANSRT; the portable COBOL version is on Sort and Merge), so the transactions of LOANTRAN are applied in this order: checkout of book 1, return of book 1, checkout of book 2, checkout of book 3. Records are read into the copybook items BOOK-RECORD and LOAN-RECORD with READ ... INTO, and written with WRITE ... FROM, so the FD items are plain PIC X areas and the working-storage copy is the one the logic uses. A checkout lowers the number of copies, a return raises it, and a checkout of a book with no copies left is rejected with a reason. Anything that is neither checkout nor return is rejected too.
IDENTIFICATION DIVISION.
PROGRAM-ID. BOOKUPD.
* Balance-line update: old book master + sorted loan
* transactions -> new book master + reject report.
ENVIRONMENT DIVISION.
INPUT-OUTPUT SECTION.
FILE-CONTROL.
SELECT OLD-MASTER ASSIGN TO "BOOKMAST"
ORGANIZATION IS LINE SEQUENTIAL
FILE STATUS IS WS-OLD-STATUS.
SELECT TRANS-FILE ASSIGN TO "LOANSRT"
ORGANIZATION IS LINE SEQUENTIAL
FILE STATUS IS WS-TRN-STATUS.
SELECT NEW-MASTER ASSIGN TO "BOOKNEW"
ORGANIZATION IS LINE SEQUENTIAL
FILE STATUS IS WS-NEW-STATUS.
SELECT REJECT-FILE ASSIGN TO "BOOKREJ"
ORGANIZATION IS LINE SEQUENTIAL
FILE STATUS IS WS-REJ-STATUS.
DATA DIVISION.
FILE SECTION.
FD OLD-MASTER.
01 OLD-MASTER-LINE PIC X(60).
FD TRANS-FILE.
01 TRANS-LINE PIC X(30).
FD NEW-MASTER.
01 NEW-MASTER-LINE PIC X(60).
FD REJECT-FILE.
01 REJECT-LINE PIC X(60).
WORKING-STORAGE SECTION.
COPY BOOKREC.
COPY LOANREC.
01 WS-OLD-STATUS PIC XX.
01 WS-TRN-STATUS PIC XX.
01 WS-NEW-STATUS PIC XX.
01 WS-REJ-STATUS PIC XX.
01 WS-MASTER-KEY PIC 9(6) VALUE ZERO.
01 WS-TRANS-KEY PIC 9(6) VALUE ZERO.
01 WS-COUNTS.
05 WS-MASTERS-COPIED PIC 9(3) VALUE ZERO.
05 WS-APPLIED PIC 9(3) VALUE ZERO.
05 WS-REJECTED PIC 9(3) VALUE ZERO.
01 WS-REASON PIC X(24).
PROCEDURE DIVISION.
0000-MAIN-LINE.
PERFORM 1000-OPEN-FILES
PERFORM 8100-READ-MASTER
PERFORM 8200-READ-TRANS
PERFORM 2000-MATCH-RECORDS
UNTIL WS-MASTER-KEY = 999999
AND WS-TRANS-KEY = 999999
PERFORM 9000-CLOSE-FILES
DISPLAY "masters written " WS-MASTERS-COPIED
DISPLAY "applied " WS-APPLIED
DISPLAY "rejected " WS-REJECTED
STOP RUN.
1000-OPEN-FILES.
OPEN INPUT OLD-MASTER TRANS-FILE
OPEN OUTPUT NEW-MASTER REJECT-FILE
IF WS-OLD-STATUS NOT = "00" OR WS-TRN-STATUS NOT = "00"
OR WS-NEW-STATUS NOT = "00" OR WS-REJ-STATUS NOT = "00"
DISPLAY "open failed " WS-OLD-STATUS WS-TRN-STATUS
WS-NEW-STATUS WS-REJ-STATUS
MOVE 12 TO RETURN-CODE
STOP RUN
END-IF.
2000-MATCH-RECORDS.
EVALUATE TRUE
WHEN WS-MASTER-KEY < WS-TRANS-KEY
PERFORM 2100-COPY-MASTER
WHEN WS-MASTER-KEY = WS-TRANS-KEY
PERFORM 2200-APPLY-TRANS
WHEN OTHER
MOVE "NO SUCH BOOK" TO WS-REASON
PERFORM 2300-REJECT-TRANS
END-EVALUATE.
2100-COPY-MASTER.
WRITE NEW-MASTER-LINE FROM BOOK-RECORD
ADD 1 TO WS-MASTERS-COPIED
PERFORM 8100-READ-MASTER.
2200-APPLY-TRANS.
EVALUATE TRUE
WHEN LOAN-CHECKOUT AND BOOK-COPIES = ZERO
MOVE "NO COPIES AVAILABLE" TO WS-REASON
PERFORM 2300-REJECT-TRANS
WHEN LOAN-CHECKOUT
SUBTRACT 1 FROM BOOK-COPIES
ADD 1 TO WS-APPLIED
WHEN LOAN-RETURN
ADD 1 TO BOOK-COPIES
ADD 1 TO WS-APPLIED
WHEN OTHER
MOVE "BAD TRANSACTION TYPE" TO WS-REASON
PERFORM 2300-REJECT-TRANS
END-EVALUATE
PERFORM 8200-READ-TRANS.
2300-REJECT-TRANS.
MOVE SPACES TO REJECT-LINE
STRING LOAN-TYPE " " LOAN-MEMBER-ID " " LOAN-BOOK-ID " "
WS-REASON DELIMITED BY SIZE INTO REJECT-LINE
WRITE REJECT-LINE
ADD 1 TO WS-REJECTED
IF WS-MASTER-KEY > WS-TRANS-KEY
PERFORM 8200-READ-TRANS
END-IF.
8100-READ-MASTER.
READ OLD-MASTER INTO BOOK-RECORD
AT END MOVE 999999 TO WS-MASTER-KEY
NOT AT END MOVE BOOK-ID TO WS-MASTER-KEY
END-READ.
8200-READ-TRANS.
READ TRANS-FILE INTO LOAN-RECORD
AT END MOVE 999999 TO WS-TRANS-KEY
NOT AT END MOVE LOAN-BOOK-ID TO WS-TRANS-KEY
END-READ.
9000-CLOSE-FILES.
CLOSE OLD-MASTER TRANS-FILE NEW-MASTER REJECT-FILE.
Compiled with cobc -x -Wall -I. bookupd.cob (and with -std=ibm), no warnings, it prints:
masters written 004
applied 004
rejected 000
The new master BOOKNEW holds the updated copy counts (each record shown as stored, without the trailing space that a LINE SEQUENTIAL write removes):
0000019780000000011DATA STRUCTURES 003002450A
0000029780000000028COBOL BASICS 001001999A
0000039780000000035BATCH PROCESSING 000001500A
0000049780000000042OLD LEDGERS 001000975W
Book 1 is checked out and returned (3 copies, unchanged), book 2 is checked out (2 to 1) and book 3 is checked out (1 to 0). The reject file BOOKREJ is empty because every transaction was valid. A second night, with BOOKNEW copied to BOOKMAST and four different sorted transactions, shows the rejects: a checkout of book 3 that no longer has a copy, a checkout of book 4 that works, a transaction of type X for book 4, and a checkout of book 9, which does not exist:
masters written 004
applied 001
rejected 003
and BOOKREJ contains:
O 000100 000003 NO COPIES AVAILABLE
X 000102 000004 BAD TRANSACTION TYPE
O 000102 000009 NO SUCH BOOK
Book 4 went from one copy to none, the new master holds 000000975W for it, and the other three books are unchanged. The last transaction, for book 9, has a higher key than the last master, so it is matched against the sentinel 999999 and handled by the "master higher" branch, exactly like an unknown number in the middle of the file.
If an input file is missing the program stops at once, before it reads anything. With LOANSRT removed, the status line shows which file failed and the return code 12 tells the job step that it did not work:
open failed 00350000
libcob: warning: implicit CLOSE of REJECT-FILE ('BOOKREJ')
libcob: warning: implicit CLOSE of NEW-MASTER ('BOOKNEW')
libcob: warning: implicit CLOSE of OLD-MASTER ('BOOKMAST')
The first line is the four statuses in the order master, transactions, new master and rejects: 00, 35, 00, 00. The STOP RUN with files still open is what produced the libcob warnings; Enterprise COBOL closes them through Language Environment without a message.
Points worth keeping in mind when the program is changed:
-
The program must keep working when the transaction file is empty (the master is simply copied) and when the master is empty (every transaction is a reject). Both follow from the sentinel keys.
-
A transaction file that is not in key order silently produces wrong results: an out-of-order transaction compares as "master higher" and becomes a reject, or lets the master move on past a book that still has transactions. Production versions check the sequence (
IF WS-TRANS-KEY < previous key) and stop with a sequence error. -
The control totals (masters written, applied and rejected) should reconcile: masters read equal masters written, and transactions read equal applied plus rejected. They are what operators compare with the previous step’s trailer count.
-
Adding a book (a transaction that creates a master record) and deleting one (a master that is dropped from the new file) are the same loop with two more branches: "master higher" with an add type creates a record and writes it, and an equal key with a delete type reads the next master without writing the current one. The master order is preserved because the new record is written at the place where its key falls.
-
The same algorithm with an indexed master (see Indexed Files and VSAM) needs no sorted input and no new file: a transaction reads the master by key and rewrites it. The balance-line form wins when the transactions touch a large share of the master, because both files are read once, in order.
On z/OS: the same program with DD statements
The program above does not change on z/OS except for the ASSIGN names, which become DD names. The job step supplies each file, and the new master and the reject report are created in the step (z/OS; not run here):
//BOOKUPD EXEC PGM=BOOKUPD
//STEPLIB DD DISP=SHR,DSN=USERID.CIRC.LOADLIB
//BOOKMAST DD DISP=SHR,DSN=USERID.CIRC.BOOKMAST
//LOANSRT DD DISP=SHR,DSN=USERID.CIRC.LOANSRT
//BOOKNEW DD DSN=USERID.CIRC.BOOKNEW,DISP=(NEW,CATLG,DELETE),
// RECFM=FB,LRECL=60,SPACE=(TRK,(5,1))
//BOOKREJ DD SYSOUT=*
//SYSOUT DD SYSOUT=*
Each assignment name in the SELECT entries (BOOKMAST, LOANSRT, BOOKNEW, BOOKREJ) is the DD name of one of those statements, and the ORGANIZATION IS LINE SEQUENTIAL clauses would be removed or replaced by ORGANIZATION IS SEQUENTIAL because the data sets are QSAM. Note the new-master disposition (NEW,CATLG,DELETE): a failed step leaves no half-written master in the catalog, which is what makes the write-a-new-file approach safer than updating in place. The step’s return code 12 from the open check is what a following COND or IF test in the job uses to skip the rest of the job.
Common mistakes
-
Processing the record area after
AT END. Its content is undefined; the balance-line loop uses a sentinel key instead. -
OPEN OUTPUTon a file that holds data you need. It empties the file. UseEXTENDto add, and a new data set (or file name) for a new version. -
No
FILE STATUS. An unexpected status then ends the run unit with a run-time message; with the item, the program sees35,41or47and can report which file was the problem. -
A
FILE STATUSitem inside the record. It is part of the data read; declare it inWORKING-STORAGE. -
Unsorted input to a match-merge. The result is wrong without any error message; sort the transactions first, with a stable sort that keeps arrival order within a key.
-
REWRITEwith a different length. The length of a sequential record cannot change in place. -
Using
LINE SEQUENTIALfor data with packed or binary items. A byte equal to the line terminator splits the record; keep binary data in record sequential files. -
Assuming the record area survives a
WRITE. UseWRITE ... FROMand keep the data in working storage. -
Reading a text file with a record shorter than its longest line. GnuCOBOL 3.2 splits the line and sets status
06. -
Mixing a PC file name with a z/OS
DDname.ASSIGN TO "Customers.dat"suits GnuCOBOL; on z/OS the assignment name is aDDname (see File Concepts, SELECT and FD).
References
-
Enterprise COBOL for z/OS 6.5 Language Reference: ORGANIZATION clause
-
Enterprise COBOL for z/OS 6.5 Language Reference: OPEN statement
-
Enterprise COBOL for z/OS 6.5 Language Reference: READ statement
-
Enterprise COBOL for z/OS 6.5 Language Reference: WRITE statement
-
Enterprise COBOL for z/OS 6.5 Language Reference: REWRITE statement
-
Enterprise COBOL for z/OS 6.5 Language Reference: CLOSE statement
-
Enterprise COBOL for z/OS 6.5 Language Reference: RECORD clause
-
Enterprise COBOL for z/OS 6.5 Programming Guide: Defining QSAM files and records in COBOL
-
Enterprise COBOL for z/OS 6.5 Programming Guide: Establishing record formats
-
Enterprise COBOL for z/OS 6.5 Programming Guide: Defining line-sequential files and records in COBOL
-
Enterprise COBOL for z/OS 6.5 Programming Guide: Using file status keys
-
Tom Taulli, Modern Mainframe Development, O’Reilly Media, 2022 (chapter 5, consulted for the topics only)