24 min read

> "COBOL was designed from the ground up as a file processing language. Understanding sequential files is understanding COBOL's reason for existence."

Chapter 11: Sequential File Processing

"COBOL was designed from the ground up as a file processing language. Understanding sequential files is understanding COBOL's reason for existence."


Introduction

If there is one thing COBOL does better than any other programming language, it is processing files. While modern languages treat file I/O as an afterthought handled by libraries and frameworks, COBOL was built from its earliest days with file processing as its central purpose. The language's four divisions -- IDENTIFICATION, ENVIRONMENT, DATA, and PROCEDURE -- each play a defined role in the file processing model, and together they provide a structured, self-documenting approach to reading, writing, and transforming data stored in files.

In this chapter, you will learn how COBOL programs interact with sequential files: the most fundamental and widely used file organization in mainframe batch processing. Sequential files are read from beginning to end, one record at a time, and written in the same manner. They are the backbone of batch processing systems in banking, insurance, government, and every other industry that runs on mainframes. Payroll files, transaction logs, customer master files, report outputs, audit trails, and data feeds between systems are all sequential files.

You will learn every aspect of sequential file processing: how to define files in the ENVIRONMENT DIVISION, describe their record layouts in the DATA DIVISION, and perform I/O operations in the PROCEDURE DIVISION. You will also learn the critical skill of file status checking, the classic sequential update algorithm, variable-length records, and report generation with page control. Starting with this chapter, every code example that involves files includes a companion JCL (Job Control Language) file showing how the program would be submitted on a z/OS mainframe.


11.1 The COBOL File Processing Model

COBOL's file processing model involves all four divisions of a program, each with a specific responsibility:

Division File-Related Responsibility
IDENTIFICATION DIVISION Names the program (no file-specific content)
ENVIRONMENT DIVISION Maps internal file names to external resources
DATA DIVISION Describes the record layout of each file
PROCEDURE DIVISION Contains the I/O verbs (OPEN, READ, WRITE, CLOSE)

This separation of concerns is one of COBOL's great strengths. The physical characteristics of a file (its name on disk, its record format, where it is stored) are isolated from the logical processing of its records. If a file moves to a different disk volume or its external name changes, only the ENVIRONMENT DIVISION (and the JCL) need to change -- the PROCEDURE DIVISION logic remains untouched.

The Flow of File Processing

Every COBOL file processing program follows a predictable lifecycle:

ENVIRONMENT DIVISION     DATA DIVISION          PROCEDURE DIVISION
     |                        |                        |
  SELECT file            FD file                   OPEN file
  ASSIGN TO ddname       01 record-layout          READ file
  FILE STATUS            05 fields...              WRITE file
  ORGANIZATION                                     CLOSE file
  ACCESS MODE

Let us examine each component in detail.


11.2 ENVIRONMENT DIVISION: File Definitions

The INPUT-OUTPUT SECTION of the ENVIRONMENT DIVISION contains the FILE-CONTROL paragraph, where you define each file your program uses. The SELECT statement is the key construct.

The SELECT...ASSIGN TO Clause

The SELECT statement links an internal file name (used throughout your COBOL program) to an external name (a DD name in JCL on z/OS, or a file path in GnuCOBOL):

       ENVIRONMENT DIVISION.

       INPUT-OUTPUT SECTION.
       FILE-CONTROL.
           SELECT EMPLOYEE-FILE
               ASSIGN TO EMPFILE
               ORGANIZATION IS SEQUENTIAL
               ACCESS MODE IS SEQUENTIAL
               FILE STATUS IS WS-EMP-STATUS.

Let us break down each clause:

SELECT file-name -- Declares the internal name you will use in your FD entry and all I/O statements. This name follows standard COBOL naming rules (up to 30 characters, letters, digits, and hyphens).

ASSIGN TO external-name -- Maps the internal file name to an external resource. On z/OS, EMPFILE maps to the DD statement //EMPFILE DD ... in the JCL. In GnuCOBOL, the behavior depends on the implementation; typically it maps to an environment variable or a file name.

ORGANIZATION IS SEQUENTIAL -- Specifies that records are stored and accessed in sequence. This is the default and can be omitted, but experienced programmers include it for clarity. Other organizations (INDEXED, RELATIVE) are covered in later chapters.

ACCESS MODE IS SEQUENTIAL -- Specifies that records are read or written one after another. For sequential files, this is the only valid access mode and can be omitted.

FILE STATUS IS ws-variable -- This is the most important clause for production-quality code. It tells COBOL to store a two-character status code in the specified WORKING-STORAGE variable after every I/O operation on this file. Always use it. We will discuss file status codes in detail in Section 11.8.

z/OS vs. GnuCOBOL: File Assignment

On IBM z/OS, the ASSIGN TO name maps to a DD statement in JCL:

      * COBOL program:
           SELECT EMPLOYEE-FILE
               ASSIGN TO EMPFILE.
//  JCL:
//EMPFILE  DD DSN=PROD.EMPLOYEE.MASTER,DISP=SHR

In GnuCOBOL, you have several options:

      * Option 1: ASSIGN TO a literal file path
           SELECT EMPLOYEE-FILE
               ASSIGN TO "employee.dat".

      * Option 2: ASSIGN TO a name, set via environment variable
      *   (export DD_EMPFILE=employee.dat)
           SELECT EMPLOYEE-FILE
               ASSIGN TO EMPFILE.

      * Option 3: LINE SEQUENTIAL for text files
           SELECT EMPLOYEE-FILE
               ASSIGN TO "employee.txt"
               ORGANIZATION IS LINE SEQUENTIAL.

LINE SEQUENTIAL is a GnuCOBOL extension that treats each record as a line of text terminated by a newline character. It is useful for working with text files on Unix/Windows systems but does not exist on z/OS.


11.3 DATA DIVISION: File and Record Descriptions

The FILE SECTION of the DATA DIVISION contains the FD (File Description) entry for each file defined in the SELECT statement.

The FD Entry

       DATA DIVISION.

       FILE SECTION.
       FD  EMPLOYEE-FILE
           RECORD CONTAINS 80 CHARACTERS
           BLOCK CONTAINS 0 RECORDS
           LABEL RECORDS ARE STANDARD
           DATA RECORD IS EMP-RECORD.

       01  EMP-RECORD.
           05  EMP-ID                  PIC X(06).
           05  EMP-LAST-NAME           PIC X(20).
           05  EMP-FIRST-NAME          PIC X(15).
           05  EMP-DEPARTMENT          PIC X(04).
           05  EMP-JOB-TITLE           PIC X(20).
           05  EMP-SALARY              PIC 9(07)V99.
           05  FILLER                  PIC X(06).

Let us examine each FD clause:

RECORD CONTAINS n CHARACTERS -- Specifies the fixed record length. For the example above, every record is exactly 80 bytes. The total of all field sizes in the 01-level record must equal this value (6 + 20 + 15 + 4 + 20 + 9 + 6 = 80).

BLOCK CONTAINS 0 RECORDS -- Tells the system to determine the optimal block size. On z/OS, BLOCK CONTAINS 0 defers to the JCL DCB parameter or system-managed blocking. Specifying 0 is a best practice because it allows the system to optimize I/O performance. You can also specify an explicit number (e.g., BLOCK CONTAINS 100 RECORDS), but system-determined blocking is almost always preferable.

LABEL RECORDS ARE STANDARD -- Indicates that standard header and trailer labels are present on the file. This is the norm for disk files on z/OS. While this clause is considered obsolete in the 2014 COBOL standard and can be omitted, you will see it in virtually every existing COBOL program.

DATA RECORD IS record-name -- Names the 01-level record description that follows. Like LABEL RECORDS, this clause is informational and can be omitted in modern COBOL, but is commonly seen in legacy code.

The 01-Level Record Description

The 01-level entry following the FD defines the record layout. This is the buffer that COBOL uses for I/O. When you READ a file, data is placed into this buffer. When you WRITE, data is taken from this buffer.

Key points about the record buffer: - It exists in the FILE SECTION, not WORKING-STORAGE - There is only one buffer per file (data is overwritten on each READ) - You should move data to WORKING-STORAGE for processing to avoid losing it on the next READ - FILLER accounts for unused bytes to make the record length match


11.4 Fixed-Length vs. Variable-Length Records

Fixed-Length Records

Fixed-length records are the most common format. Every record in the file is exactly the same length:

       FD  TRANSACTION-FILE
           RECORD CONTAINS 100 CHARACTERS.

On z/OS, fixed-length records use RECFM=FB (Fixed Block) in the JCL DCB parameter:

//TRANFILE DD DSN=YOUR.TRANS.FILE,DISP=SHR,
//         DCB=(RECFM=FB,LRECL=100,BLKSIZE=0)

Variable-Length Records

When records vary in size, use variable-length records. Common scenarios include order records with varying numbers of line items, or text records of different lengths.

       FD  ORDER-FILE
           RECORD CONTAINS 50 TO 1000 CHARACTERS.

       01  ORDER-RECORD.
           05  ORD-HEADER-DATA         PIC X(48).
           05  ORD-LINE-COUNT          PIC 9(02).
           05  ORD-LINE-ITEMS OCCURS 0 TO 50 TIMES
                   DEPENDING ON ORD-LINE-COUNT.
               10  OLI-PRODUCT-ID      PIC X(08).
               10  OLI-QUANTITY         PIC 9(03).
               10  OLI-UNIT-PRICE      PIC 9(05)V99.
               10  OLI-LINE-TOTAL      PIC X(01).

RECORD CONTAINS n TO m CHARACTERS -- Specifies the minimum and maximum record sizes. The actual size of each record varies based on the data.

OCCURS DEPENDING ON -- The variable-length table construct. The number of occurrences of ORD-LINE-ITEMS depends on the value of ORD-LINE-COUNT. If ORD-LINE-COUNT is 3, only 3 line items are stored in the record.

An alternative syntax uses RECORD IS VARYING:

       FD  TEXT-FILE
           RECORD IS VARYING IN SIZE
               FROM 10 TO 200 CHARACTERS
               DEPENDING ON WS-REC-LENGTH.

On z/OS, variable-length records use RECFM=VB (Variable Block). Each record is preceded by a 4-byte Record Descriptor Word (RDW) that contains the record's actual length. The LRECL in JCL includes the 4 bytes for the RDW:

//ORDFILE  DD DSN=YOUR.ORDER.FILE,DISP=SHR,
//         DCB=(RECFM=VB,LRECL=1004,BLKSIZE=0)

In this example, the maximum COBOL record is 1000 bytes. The LRECL is 1004 (1000 + 4 for the RDW).


11.5 PROCEDURE DIVISION: I/O Verbs

The PROCEDURE DIVISION contains the verbs that perform actual file I/O.

OPEN Statement

Before any file can be read or written, it must be opened. The OPEN statement specifies the mode:

           OPEN INPUT  EMPLOYEE-FILE
           OPEN OUTPUT REPORT-FILE
           OPEN EXTEND LOG-FILE
           OPEN I-O    UPDATE-FILE
Mode Purpose Allowed Operations
INPUT Read an existing file READ
OUTPUT Create a new file (overwrites if it exists) WRITE
EXTEND Append to an existing file WRITE (at end)
I-O Read and rewrite existing records READ, REWRITE

You can open multiple files in a single OPEN statement:

           OPEN INPUT  OLD-MASTER-FILE
                       TRANSACTION-FILE
                OUTPUT NEW-MASTER-FILE
                       ERROR-REPORT

Always check the file status after OPEN. An OPEN failure (file not found, permission denied, etc.) is one of the most common runtime errors in batch processing.

READ Statement

The READ statement retrieves the next record from a sequential file:

      * Basic READ with AT END
           READ EMPLOYEE-FILE
               AT END
                   SET END-OF-FILE TO TRUE
               NOT AT END
                   PERFORM PROCESS-RECORD
           END-READ

AT END -- Executes when there are no more records (end-of-file). This is file status code 10.

NOT AT END -- Executes when a record is successfully read.

END-READ -- The explicit scope terminator. Always use it.

You can also use the INTO clause to automatically move the record into a WORKING-STORAGE area:

      * READ INTO copies the record to WORKING-STORAGE
           READ EMPLOYEE-FILE INTO WS-EMPLOYEE-RECORD
               AT END
                   SET END-OF-FILE TO TRUE
           END-READ

READ...INTO is equivalent to a READ followed by a MOVE:

      * These two sequences are equivalent:
           READ EMPLOYEE-FILE INTO WS-EMPLOYEE-RECORD
      * is the same as:
           READ EMPLOYEE-FILE
           MOVE EMP-RECORD TO WS-EMPLOYEE-RECORD

The Priming Read Pattern

COBOL's standard file processing pattern uses a "priming read" -- an initial READ before entering the processing loop. This ensures that the AT END condition is checked before any processing occurs:

      * Priming read: read the first record
           READ EMPLOYEE-FILE
               AT END SET END-OF-FILE TO TRUE
           END-READ

      * Process until end of file
           PERFORM UNTIL END-OF-FILE
               PERFORM PROCESS-RECORD
               READ EMPLOYEE-FILE
                   AT END SET END-OF-FILE TO TRUE
               END-READ
           END-PERFORM

This pattern avoids processing an empty file (the priming read immediately sets END-OF-FILE if the file is empty) and ensures the READ always precedes the condition check in the PERFORM UNTIL.

WRITE Statement

The WRITE statement adds a record to an output file:

      * Write the record from the file section buffer
           WRITE REPORT-RECORD

      * Write FROM a working-storage area
           WRITE REPORT-RECORD FROM WS-DETAIL-LINE

Important: While READ operates on the file name (READ EMPLOYEE-FILE), WRITE operates on the record name (WRITE EMP-RECORD). This is a common source of confusion for new COBOL programmers.

The FROM clause is equivalent to a MOVE followed by a WRITE:

      * These two sequences are equivalent:
           WRITE REPORT-RECORD FROM WS-DETAIL-LINE
      * is the same as:
           MOVE WS-DETAIL-LINE TO REPORT-RECORD
           WRITE REPORT-RECORD

The ADVANCING Clause for Print Files

For report files, the ADVANCING clause controls line spacing:

      * Advance 1 line before printing (single space)
           WRITE RPT-LINE FROM WS-DETAIL
               AFTER ADVANCING 1 LINE

      * Advance 2 lines before printing (double space)
           WRITE RPT-LINE FROM WS-SUBTOTAL
               AFTER ADVANCING 2 LINES

      * Advance to top of next page
           WRITE RPT-LINE FROM WS-PAGE-HEADER
               AFTER ADVANCING PAGE
Clause Effect
BEFORE ADVANCING n LINES Print, then skip n lines
AFTER ADVANCING n LINES Skip n lines, then print
BEFORE ADVANCING PAGE Print, then skip to new page
AFTER ADVANCING PAGE Skip to new page, then print

An alternative to the ADVANCING clause is to use ASA carriage control characters in the first byte of each record (RECFM=FBA). The first byte is not printed but controls spacing:

Character Action
' ' (space) Single space before printing
'0' Double space before printing
'-' Triple space before printing
'1' Advance to top of next page
'+' No advance (overprint previous line)

CLOSE Statement

The CLOSE statement releases the file:

           CLOSE EMPLOYEE-FILE

      * Close multiple files at once
           CLOSE OLD-MASTER
                 TRANSACTION-FILE
                 NEW-MASTER
                 ERROR-REPORT

Always close files before your program ends. An unclosed output file may lose buffered records that have not been flushed to disk. Always check the file status after CLOSE as well.


11.6 The Classic Sequential File Processing Pattern

Nearly every sequential file processing program follows this pattern:

OPEN files
READ first record (priming read)
PERFORM UNTIL end-of-file
    Process current record
    READ next record
END-PERFORM
CLOSE files

Here is a complete, minimal example. See code/example-01-read-sequential.cob for the fully commented version.

       PROCEDURE DIVISION.
       0000-MAIN-CONTROL.
           PERFORM 1000-INITIALIZE
           PERFORM 2000-PROCESS-FILE
               UNTIL END-OF-FILE
           PERFORM 3000-TERMINATE
           STOP RUN
           .

       1000-INITIALIZE.
           OPEN INPUT EMPLOYEE-FILE
           IF NOT WS-EMP-SUCCESS
               DISPLAY 'ERROR OPENING FILE: ' WS-EMP-STATUS
               STOP RUN
           END-IF
           PERFORM 8000-READ-EMPLOYEE
           .

       2000-PROCESS-FILE.
           ADD 1 TO WS-RECORD-COUNT
           DISPLAY EMP-ID ' ' EMP-LAST-NAME ' ' EMP-SALARY
           PERFORM 8000-READ-EMPLOYEE
           .

       3000-TERMINATE.
           CLOSE EMPLOYEE-FILE
           DISPLAY 'RECORDS READ: ' WS-RECORD-COUNT
           .

       8000-READ-EMPLOYEE.
           READ EMPLOYEE-FILE
               AT END SET END-OF-FILE TO TRUE
           END-READ
           .

This structure -- with numbered paragraphs separating initialization, processing, termination, and I/O -- is a COBOL convention you will see in virtually every production program. The 8000-level paragraphs for I/O operations are a common numbering convention that groups utility routines at the end of the program.


11.7 Writing Sequential Files

Creating a new sequential file follows the same lifecycle but uses OPEN OUTPUT and WRITE instead of OPEN INPUT and READ. See code/example-02-write-sequential.cob for the full example.

           OPEN OUTPUT PRODUCT-FILE
           IF NOT WS-PROD-SUCCESS
               DISPLAY 'ERROR OPENING OUTPUT: ' WS-PROD-STATUS
               STOP RUN
           END-IF

           PERFORM VARYING WS-INDEX FROM 1 BY 1
               UNTIL WS-INDEX > WS-MAX-RECORDS
               WRITE PROD-RECORD
                   FROM WS-TABLE-ENTRY(WS-INDEX)
               IF NOT WS-PROD-SUCCESS
                   DISPLAY 'WRITE ERROR: ' WS-PROD-STATUS
                   STOP RUN
               END-IF
               ADD 1 TO WS-RECORDS-WRITTEN
           END-PERFORM

           CLOSE PRODUCT-FILE

OPEN EXTEND: Appending to a File

To add records to the end of an existing file without overwriting it, use OPEN EXTEND:

           OPEN EXTEND LOG-FILE
           WRITE LOG-RECORD FROM WS-LOG-ENTRY
           CLOSE LOG-FILE

This is useful for log files and audit trails where new entries are appended throughout the day.


11.8 File Status Codes: Your First Line of Defense

The FILE STATUS clause is arguably the single most important clause in the SELECT statement. Without it, a file I/O error causes your program to terminate with a cryptic system ABEND. With it, you can detect errors, take corrective action, and produce meaningful error messages.

Declaring File Status

       FILE-CONTROL.
           SELECT EMPLOYEE-FILE
               ASSIGN TO EMPFILE
               FILE STATUS IS WS-EMP-STATUS.

       WORKING-STORAGE SECTION.
       01  WS-EMP-STATUS              PIC XX VALUE SPACES.
           88  WS-EMP-SUCCESS         VALUE '00'.
           88  WS-EMP-EOF             VALUE '10'.

The file status variable is a 2-character alphanumeric field. After every I/O operation (OPEN, READ, WRITE, CLOSE, REWRITE, DELETE, START), COBOL stores the result in this field.

Common File Status Codes

Code Category Meaning
00 Success Operation completed successfully
02 Success Duplicate key detected (indexed files)
04 Success Record length does not match FD description
05 Success Optional file not present at OPEN (file created)
10 End of file READ reached end of file
22 Invalid key Duplicate key on WRITE (indexed files)
23 Invalid key Record not found on READ (indexed files)
30 Permanent error Unrecoverable I/O error
34 Permanent error Boundary violation (disk space exhausted)
35 Permanent error File not found at OPEN INPUT or I-O
37 Permanent error File mode conflict (e.g., wrong ORGANIZATION)
38 Permanent error File locked by another user/job
39 Permanent error File attribute mismatch (FD vs. actual file)
41 Logic error OPEN attempted on already-open file
42 Logic error CLOSE attempted on file not open
43 Logic error No successful READ before REWRITE/DELETE
46 Logic error No valid next record for sequential READ
47 Logic error READ on file not opened INPUT or I-O
48 Logic error WRITE on file not opened OUTPUT, I-O, or EXTEND
9x Implementor Implementor-defined (varies by compiler)

The first character indicates the category: - 0 = Successful - 1 = AT END condition - 2 = Invalid key (indexed/relative files) - 3 = Permanent error - 4 = Logic error - 9 = Implementor-defined

Checking File Status After Every I/O

Production-quality COBOL programs check the file status after every single I/O operation. See code/example-04-file-status.cob for a comprehensive example.

       8000-READ-EMPLOYEE.
           READ EMPLOYEE-FILE
               AT END
                   SET END-OF-FILE TO TRUE
               NOT AT END
                   CONTINUE
           END-READ

      *    Check for unexpected file status codes.
           IF NOT WS-EMP-SUCCESS AND NOT WS-EMP-EOF
               DISPLAY 'UNEXPECTED READ ERROR'
               DISPLAY 'FILE STATUS: ' WS-EMP-STATUS
               MOVE 12 TO RETURN-CODE
               STOP RUN
           END-IF
           .

A more comprehensive approach uses EVALUATE for detailed error handling:

       9000-CHECK-FILE-STATUS.
           EVALUATE TRUE
               WHEN WS-STATUS-SUCCESS
                   CONTINUE
               WHEN WS-STATUS-EOF
                   CONTINUE
               WHEN WS-STATUS-NOT-FOUND
                   DISPLAY 'FILE NOT FOUND - CHECK JCL DD'
                   DISPLAY 'FILE STATUS: ' WS-FILE-STATUS
                   MOVE 12 TO RETURN-CODE
                   STOP RUN
               WHEN WS-STATUS-PERM-ERROR
                   DISPLAY 'PERMANENT I/O ERROR'
                   DISPLAY 'FILE STATUS: ' WS-FILE-STATUS
                   MOVE 12 TO RETURN-CODE
                   STOP RUN
               WHEN WS-STATUS-DISK-FULL
                   DISPLAY 'DISK SPACE EXHAUSTED'
                   DISPLAY 'INCREASE SPACE IN JCL'
                   MOVE 12 TO RETURN-CODE
                   STOP RUN
               WHEN OTHER
                   DISPLAY 'UNEXPECTED STATUS: '
                           WS-FILE-STATUS
                   MOVE 8 TO RETURN-CODE
                   STOP RUN
           END-EVALUATE
           .

Setting the Program Return Code

Notice the use of MOVE 12 TO RETURN-CODE. The RETURN-CODE special register communicates the program's exit status to the operating system and JCL. By convention:

Return Code Meaning
0 Successful completion
4 Warning (non-critical issue)
8 Error (significant problem)
12 Severe error (processing cannot continue)
16 Terminal error

JCL COND parameters and IF/THEN/ELSE steps use the return code to control subsequent job steps.


11.9 The Sequential File Update: Balanced Line Algorithm

The sequential file update is the most important pattern in mainframe batch processing. It reads two sorted input files -- an old master file and a transaction file -- and produces a new master file. This pattern is called the balanced line algorithm (also known as the sequential update or master file update).

The Concept

   Old Master File          Transaction File
   (sorted by key)          (sorted by key)
         |                        |
         +----> UPDATE PROGRAM <--+
                     |
              New Master File
              (sorted by key)

Both input files must be sorted on the same key field in the same order (typically ascending). The program compares the current key from each file and processes based on which key is lower.

The Algorithm

Read first record from Old Master
Read first record from Transaction
PERFORM UNTIL both files are at EOF
    IF master-key < transaction-key
        Write master record unchanged to new master
        Read next master record
    ELSE IF master-key = transaction-key
        Apply transaction to master record
        Write updated record to new master
        Read next master record
        Read next transaction record
    ELSE (master-key > transaction-key)
        Process unmatched transaction (ADD or error)
        Read next transaction record
    END-IF
END-PERFORM

The HIGH-VALUES Sentinel

When a file reaches end-of-file, its key is set to HIGH-VALUES (the highest possible value in the collating sequence). This ensures that the comparison logic works correctly even after one file is exhausted:

       8000-READ-OLD-MASTER.
           READ OLD-MASTER-FILE
               AT END
                   SET OLD-FILE-ENDED TO TRUE
                   MOVE HIGH-VALUES TO WS-OLD-KEY
               NOT AT END
                   MOVE OM-ACCOUNT-NUM TO WS-OLD-KEY
                   ADD 1 TO WS-MASTER-READ
           END-READ
           .

When the old master reaches EOF and its key becomes HIGH-VALUES, all remaining transactions will have keys less than HIGH-VALUES, so they will be processed as unmatched transactions (new records to add). Similarly, when the transaction file reaches EOF, all remaining master records will be written unchanged.

When both keys are HIGH-VALUES, both files are exhausted and processing stops.

Transaction Types

Typical transaction codes and their processing:

Code Action Master Key Match Required?
A (Add) Create new master record No (key must NOT exist)
C (Change) Update existing master record Yes
D (Delete) Remove master record (don't write to new file) Yes

An ADD transaction with a matching master key is an error (duplicate). A CHANGE or DELETE transaction without a matching master key is also an error. These are written to an exception report.

See code/example-03-update-sequential.cob and its companion JCL for a complete implementation.


11.10 JCL for Sequential File Processing

On z/OS, JCL (Job Control Language) provides the connection between your COBOL program and the physical data sets on the mainframe. Every file referenced in your program's SELECT statement maps to a DD (Data Definition) statement in the JCL.

DD Statement Fundamentals

//EMPFILE  DD DSN=PROD.EMPLOYEE.MASTER,
//         DISP=SHR,
//         DCB=(RECFM=FB,LRECL=80,BLKSIZE=0)

DSN (Data Set Name) -- The fully qualified name of the data set on the mainframe. Names are limited to 44 characters with qualifiers separated by periods.

DISP (Disposition) -- Controls how the system handles the data set:

DISP Meaning
SHR Shared access (read-only, others can read too)
OLD Exclusive access (no other jobs can access it)
NEW Create a new data set
MOD Append to existing data set (or create if new)

For output files, DISP uses three subparameters: DISP=(status,normal-end,abnormal-end):

//NEWMAST  DD DSN=PROD.NEW.MASTER,
//         DISP=(NEW,CATLG,DELETE),
//         SPACE=(CYL,(5,5),RLSE)
  • NEW -- Data set does not exist; create it
  • CATLG -- Catalog the data set when the step completes normally
  • DELETE -- Delete the data set if the step ABENDs

DCB Parameters

The DCB (Data Control Block) specifies the physical attributes of the data set:

RECFM (Record Format): | Code | Meaning | |---|---| | F | Fixed-length, unblocked | | FB | Fixed-length, blocked | | V | Variable-length, unblocked | | VB | Variable-length, blocked | | FBA | Fixed-length, blocked, ASA carriage control | | VBA | Variable-length, blocked, ASA carriage control |

LRECL (Logical Record Length) -- The length of each record in bytes. For variable-length records, this is the maximum record length plus 4 (for the RDW).

BLKSIZE (Block Size) -- The size of each physical block on disk. Setting BLKSIZE=0 lets the system calculate the optimal block size based on the device type, which is the recommended practice.

SPACE Parameter

The SPACE parameter allocates disk space for new data sets:

//         SPACE=(CYL,(5,5),RLSE)
  • CYL -- Allocation unit (cylinders). Other options: TRK (tracks), nnn (bytes)
  • (5,5) -- Primary allocation of 5 cylinders, secondary allocation of 5 cylinders
  • RLSE -- Release unused space when the data set is closed

SYSOUT for Print Files

Report files are typically directed to the JES (Job Entry Subsystem) spool for online viewing or printing:

//RPTFILE  DD SYSOUT=*,
//         DCB=(RECFM=FBA,LRECL=133,BLKSIZE=0)

SYSOUT=* sends the output to the default output class. The report can then be viewed using SDSF (System Display and Search Facility) or printed to a physical printer.


11.11 ADVANCING and LINAGE: Print File Control

The ADVANCING Clause

For report programs, the ADVANCING clause controls printer spacing. It is used with the WRITE statement:

      * Single space (default if no ADVANCING specified)
           WRITE RPT-LINE AFTER ADVANCING 1 LINE

      * Double space
           WRITE RPT-LINE AFTER ADVANCING 2 LINES

      * Skip to top of next page
           WRITE RPT-LINE AFTER ADVANCING PAGE

The difference between BEFORE and AFTER: - BEFORE ADVANCING -- Write the record first, then advance - AFTER ADVANCING -- Advance first, then write the record

In practice, most programs use either the ADVANCING clause or ASA carriage control characters, but not both.

The LINAGE Clause

The LINAGE clause provides more detailed page control directly in the FD entry:

       FD  REPORT-FILE
           RECORD CONTAINS 133 CHARACTERS
           LINAGE IS 60 LINES
               WITH FOOTING AT 55
               LINES AT TOP 3
               LINES AT BOTTOM 3.
Clause Meaning
LINAGE IS 60 60 printable lines per page
WITH FOOTING AT 55 Footer area begins at line 55
LINES AT TOP 3 3 blank lines at top of each page
LINES AT BOTTOM 3 3 blank lines at bottom of each page

When using LINAGE, COBOL maintains the special register LINAGE-COUNTER that tracks the current line position on the page. You can test this to determine when to print footers or start a new page.

However, most production programs manage page control manually using line counters in WORKING-STORAGE, as shown in code/example-06-report-program.cob. This approach gives the programmer complete control over page layout.


11.12 Report Generation: The Control Break Pattern

The report program in code/example-06-report-program.cob demonstrates a complete report with headers, detail lines, department subtotals, and grand totals. The key pattern is the control break.

What Is a Control Break?

A control break occurs when a key field changes value between consecutive records. In a report sorted by department, a control break occurs each time the department changes. At each break, the program prints subtotals for the previous group.

The Control Break Algorithm

Read first record
Save department as PREVIOUS-DEPT
Print page headers

PERFORM UNTIL end-of-file
    IF current-dept NOT = previous-dept
        Print subtotals for previous-dept
        Reset subtotal accumulators
        Save current-dept as previous-dept
    END-IF
    Print detail line
    Accumulate subtotals
    Read next record
END-PERFORM

Print final subtotals (for last group)
Print grand totals

The critical detail is printing the final subtotals after the loop ends. Since the control break only fires when the department changes, the last group's subtotals would be lost without this final step.

Page Control in Reports

Reports must manage page breaks to ensure headers appear at the top of each page. A common approach uses a line counter:

       01  WS-LINE-COUNT          PIC 9(02) VALUE 99.
       01  WS-LINES-PER-PAGE      PIC 9(02) VALUE 55.

The line counter starts at 99 (greater than LINES-PER-PAGE) to force headers on the first page. After printing headers, the counter resets to a small number (e.g., 6 for the header lines). Each detail line increments the counter. When the counter exceeds the maximum, new page headers are printed.


11.13 Multiple File Processing: Matching and Merging

Beyond the sequential update, other common multi-file patterns include file matching and file merging.

File Matching

File matching reads two sorted files and identifies records that appear in both, only in the first, or only in the second. It uses the same balanced-line approach as the sequential update:

           EVALUATE TRUE
               WHEN KEY-A < KEY-B
      *            Record in file A only
                   PERFORM PROCESS-A-ONLY
                   READ FILE-A...
               WHEN KEY-A = KEY-B
      *            Record in both files
                   PERFORM PROCESS-MATCH
                   READ FILE-A...
                   READ FILE-B...
               WHEN KEY-A > KEY-B
      *            Record in file B only
                   PERFORM PROCESS-B-ONLY
                   READ FILE-B...
           END-EVALUATE

File Merging

File merging combines two sorted files into a single sorted output file. The algorithm writes the record with the lower key to the output file:

           IF KEY-A <= KEY-B
               WRITE MERGED-RECORD FROM RECORD-A
               READ FILE-A...
           ELSE
               WRITE MERGED-RECORD FROM RECORD-B
               READ FILE-B...
           END-IF

COBOL also provides a MERGE verb that handles this automatically:

           MERGE WORK-FILE
               ON ASCENDING KEY MERGE-KEY
               USING FILE-A FILE-B
               GIVING MERGED-FILE

The MERGE verb sorts and merges in a single operation, but the manual approach gives you more control over processing each record.


11.14 QSAM: Queued Sequential Access Method

On z/OS, sequential file I/O is managed by QSAM (Queued Sequential Access Method). QSAM handles buffering, blocking, and physical I/O transparently, so COBOL programs deal only with logical records.

How QSAM Works

When your program issues a READ, QSAM does not perform a physical disk I/O for each record. Instead, it reads an entire block of records into a buffer. Subsequent READs return records from the buffer until it is exhausted, at which point QSAM reads the next block. This "look-ahead" buffering dramatically improves performance.

Similarly, WRITE operations go to a buffer first. When the buffer is full (a complete block), QSAM writes the block to disk. The CLOSE operation flushes any partial block remaining in the buffer.

Block Size and Performance

Block size directly affects I/O performance. Larger blocks mean fewer physical I/O operations but more memory per buffer. The system-determined block size (BLKSIZE=0) is optimized for the device type and is recommended in most cases.

On a modern 3390 disk device: - Track size: 56,664 bytes - Half-track blocking is optimal for most cases - For LRECL=80 (FB), system-determined BLKSIZE is typically 27,920 (349 records per block)

Buffering

You can specify the number of I/O buffers using the BUFNO DCB subparameter:

//EMPFILE  DD DSN=YOUR.FILE,DISP=SHR,
//         DCB=(RECFM=FB,LRECL=80,BLKSIZE=0,BUFNO=5)

More buffers allow QSAM to perform read-ahead or write-behind operations in parallel with your program's processing. The default is typically 5 buffers. For very large files or performance-critical jobs, increasing BUFNO can improve throughput.


11.15 GnuCOBOL File Naming and LINE SEQUENTIAL

On GnuCOBOL, files are mapped to operating system files rather than z/OS data sets.

File Name Resolution

GnuCOBOL resolves file names in this order:

  1. Environment variable matching the ASSIGN name: export DD_EMPFILE=/data/employees.dat
  2. ASSIGN TO literal: ASSIGN TO "/data/employees.dat"
  3. Default: The ASSIGN name is used as the file name in the current directory

LINE SEQUENTIAL Organization

GnuCOBOL supports LINE SEQUENTIAL, which reads and writes standard text files:

           SELECT TEXT-FILE
               ASSIGN TO "report.txt"
               ORGANIZATION IS LINE SEQUENTIAL.

With LINE SEQUENTIAL: - Each record is terminated by a platform-specific line delimiter (newline on Unix, carriage return + newline on Windows) - Trailing spaces are removed from records on write - Records can vary in length without RECORD CONTAINS ... TO ... VARYING - There is no concept of blocking or RDW

This is useful for reading CSV files, configuration files, and text reports on non-mainframe platforms. It does not exist on z/OS.


11.16 Error Handling Best Practices

File I/O errors are among the most common problems in batch processing. Here are the best practices every COBOL programmer should follow:

1. Always Use FILE STATUS

Every SELECT statement should include a FILE STATUS clause. There are no exceptions to this rule in production code.

2. Check Status After Every I/O Operation

Check the file status after every OPEN, READ, WRITE, REWRITE, DELETE, START, and CLOSE. Not just some of them. All of them.

3. Provide Meaningful Error Messages

When an error occurs, display: - The operation that failed (OPEN, READ, WRITE, etc.) - The file name - The file status code - A human-readable description of the error - Contextual information (record count, key value, etc.)

           IF NOT WS-EMP-SUCCESS
               DISPLAY 'ERROR IN ' WS-IO-OPERATION
                       ' ON EMPLOYEE-FILE'
               DISPLAY 'FILE STATUS: ' WS-EMP-STATUS
               DISPLAY 'RECORDS PROCESSED: ' WS-REC-COUNT
               DISPLAY 'LAST KEY: ' WS-LAST-KEY
               MOVE 12 TO RETURN-CODE
               STOP RUN
           END-IF

4. Set Meaningful Return Codes

Use the RETURN-CODE special register to communicate the program's outcome to JCL. Follow the standard convention (0, 4, 8, 12, 16).

5. Close Files in the Terminate Paragraph

Ensure files are closed even when errors occur. Unclosed output files can lose data.

6. Use 88-Level Conditions for Status Values

Define 88-level condition names for common status values:

       01  WS-FILE-STATUS             PIC XX.
           88  WS-SUCCESS             VALUE '00'.
           88  WS-EOF                 VALUE '10'.
           88  WS-NOT-FOUND           VALUE '35'.
           88  WS-DISK-FULL           VALUE '34'.

This makes your PROCEDURE DIVISION code more readable than testing PIC XX values directly.


11.17 Common Patterns Summary

Read-Process-Write (Copy with Transformation)

Read an input file, transform each record, and write to an output file:

           OPEN INPUT IN-FILE OUTPUT OUT-FILE
           READ IN-FILE AT END SET EOF TO TRUE END-READ
           PERFORM UNTIL EOF
               PERFORM TRANSFORM-RECORD
               WRITE OUT-REC FROM WS-WORK-RECORD
               READ IN-FILE AT END SET EOF TO TRUE END-READ
           END-PERFORM
           CLOSE IN-FILE OUT-FILE

Filter Pattern

Read an input file and write only selected records:

           IF MEETS-CRITERIA
               WRITE OUT-REC FROM IN-REC
               ADD 1 TO WS-SELECTED
           ELSE
               ADD 1 TO WS-REJECTED
           END-IF

Control Break (Subtotals)

Detect changes in a sorted key field and print group subtotals:

           IF CURRENT-KEY NOT = PREVIOUS-KEY
               PERFORM PRINT-SUBTOTALS
               MOVE ZERO TO GROUP-ACCUMULATORS
               MOVE CURRENT-KEY TO PREVIOUS-KEY
           END-IF
           ADD AMOUNT TO GROUP-TOTAL
           ADD AMOUNT TO GRAND-TOTAL

Print page headers, detail lines, and footers with page breaks:

           IF WS-LINE-COUNT >= WS-LINES-PER-PAGE
               PERFORM PRINT-PAGE-HEADERS
           END-IF
           PERFORM PRINT-DETAIL-LINE
           ADD 1 TO WS-LINE-COUNT

11.18 Free-Format COBOL for File Processing

As mentioned in earlier chapters, COBOL supports a free-format source layout that removes the fixed-column restrictions. Here is what the file processing code looks like in free format. From this chapter forward, examples default to fixed format (the production standard), but you should be able to read both.

identification division.
program-id. SeqReadFree.

environment division.
input-output section.
file-control.
    select EmployeeFile
        assign to "employees.dat"
        organization is sequential
        file status is ws-file-status.

data division.
file section.
fd EmployeeFile
    record contains 80 characters.
01 emp-record.
    05 emp-id          pic x(06).
    05 emp-name        pic x(35).
    05 emp-dept        pic x(04).
    05 emp-salary      pic 9(07)v99.
    05 filler          pic x(26).

working-storage section.
01 ws-file-status      pic xx.
    88 ws-success      value '00'.
    88 ws-eof          value '10'.
01 ws-eof-flag         pic x value 'N'.
    88 end-of-file     value 'Y'.

procedure division.
    open input EmployeeFile
    if not ws-success
        display "Error opening file: " ws-file-status
        stop run
    end-if

    read EmployeeFile
        at end set end-of-file to true
    end-read

    perform until end-of-file
        display emp-id " " emp-name " " emp-salary
        read EmployeeFile
            at end set end-of-file to true
        end-read
    end-perform

    close EmployeeFile
    stop run.

Key differences in free format: - No column restrictions (code can start anywhere) - No Area A / Area B distinction - No sequence number area (columns 1-6) - No indicator area (column 7) - Mixed case is common (though keywords are still often uppercase) - Continuation lines do not need special characters


11.19 Performance Considerations

Block Size Optimization

As discussed in Section 11.14, block size has a direct impact on I/O performance. Let BLKSIZE=0 handle this in most cases. If you must specify it manually:

  • For FB files: BLKSIZE should be a multiple of LRECL
  • For VB files: BLKSIZE must be >= LRECL + 4
  • Half-track blocking is generally optimal: calculate the largest multiple of LRECL that fits in half a track (28,336 bytes for 3390)

Minimize I/O Operations

Each OPEN, READ, WRITE, and CLOSE is an I/O operation. To minimize overhead: - Process records in memory as much as possible before writing - Use BLOCK CONTAINS 0 for system-optimized blocking - Avoid opening and closing files repeatedly in a loop - For very large files, consider increasing BUFNO

Sequential Access Is Efficient

Sequential I/O is the most efficient access pattern on mainframes. QSAM's look-ahead buffering and the sequential nature of disk I/O (reading consecutive tracks) make sequential processing extremely fast. A well-tuned sequential read job can process millions of records per minute.


11.20 Chapter Examples Summary

This chapter includes six complete example programs with companion JCL:

Example Program Description
1 example-01-read-sequential.cob Read a sequential file and display contents
2 example-02-write-sequential.cob Create a new sequential file from WORKING-STORAGE
3 example-03-update-sequential.cob Sequential update: old master + transactions = new master
4 example-04-file-status.cob Comprehensive file status checking
5 example-05-variable-length.cob Variable-length records with OCCURS DEPENDING ON
6 example-06-report-program.cob Complete report with headers, control breaks, totals

Each .cob file has a corresponding .jcl file showing how to compile and run the program on z/OS.


11.21 Compiling and Running the Examples

On GnuCOBOL

# Compile Example 1
cobc -x example-01-read-sequential.cob -o seqread1

# Create a sample input file
echo "E00001SMITH               JOHN           ACCTSENIOR ACCOUNTANT   005500000      " > employees.dat
echo "E00002JONES               MARY           SALESSALES MANAGER      006200000      " >> employees.dat

# Set environment variable for file mapping
export DD_EMPFILE=employees.dat

# Run the program
./seqread1

On z/OS

Submit the companion JCL to compile, link-edit, and run the program. Modify the JCL to point to your actual data set names and load library.


Summary

Sequential file processing is the foundation of COBOL's power in business computing. In this chapter, you learned:

  • The COBOL file processing model spans all four divisions
  • SELECT...ASSIGN TO links internal file names to external resources
  • FD entries describe record layouts with RECORD CONTAINS and BLOCK CONTAINS
  • The I/O verbs (OPEN, READ, WRITE, CLOSE) are straightforward but must be used carefully
  • The priming read pattern is essential for correct sequential processing
  • File status codes must be checked after every I/O operation -- always
  • The sequential update (balanced line algorithm) is the cornerstone of mainframe batch processing
  • Variable-length records use RECORD CONTAINS n TO m and OCCURS DEPENDING ON
  • Reports use ASA carriage control or the ADVANCING clause for print formatting
  • Control breaks detect changes in sorted key fields to produce group subtotals
  • JCL DD statements connect your program's files to physical data sets
  • DCB parameters (RECFM, LRECL, BLKSIZE) describe the physical format
  • QSAM provides transparent buffering for sequential I/O on z/OS
  • GnuCOBOL supports LINE SEQUENTIAL for text file processing
  • Performance depends on block size, buffering, and efficient use of I/O operations

In the next chapter, we will explore indexed file processing using VSAM KSDS (Key Sequenced Data Sets), which allow direct access to records by key value -- a capability that sequential files do not provide.