Reports and Report Writer
|
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 WRITE ... ADVANCING and LINAGE come from COBOL 74 and 85 and the Report Writer, an optional module of COBOL 85, is part of the standard again since COBOL 2002; against IBM Enterprise COBOL for z/OS 6.5 (PTFs through September 2026) on z/OS 3.2, which supports the print statements and clauses but has no Report Writer module of its own; 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 report is a file of print lines. This page builds the Circulation reports in three steps: by hand with print-line layouts and WRITE AFTER ADVANCING, with page control through LINAGE, and with the Report Writer, which describes the report instead of coding it. The central idea is the control break: a summary line is printed whenever a key (the member, the date) changes in a sorted file. The input has to be in key order, which is the job of Sort and Merge. The file statements are on Sequential Files, and the edited pictures used for numbers and dates (ZZ9, 9999/99/99) are on The PICTURE Clause.
Where the book is outdated: Taulli’s Modern Mainframe Development (2022, chapter 6) treats report layout and testing as a matter of print lines, edited pictures and carriage control, and that is still how most z/OS batch reports are written. It does not mention the Report Writer. The Report Writer is a legitimate, standard COBOL feature that GnuCOBOL implements, and a section below shows it; on Enterprise COBOL a separate precompiler product would be required, so the hand-coded form remains the portable choice for z/OS.
The print line
A print file is a sequential file whose records are lines of text. The program builds each line in WORKING-STORAGE as a group of fields with FILLER for the spaces between, and writes it with WRITE ... FROM. Column headings are constants, and detail lines use edited pictures that insert spaces, slashes, commas, signs and a decimal point and suppress leading zeros. The record of the print file is wide enough for the widest line: 132 print positions is the traditional line printer width.
01 WS-DETAIL.
05 DL-MEMBER PIC 9(6).
05 FILLER PIC XX VALUE SPACES.
05 DL-TYPE PIC X(10).
05 DL-BOOK PIC 9(6).
05 FILLER PIC XX VALUE SPACES.
05 DL-DATE PIC 9999/99/99.
The FILLER items need VALUE SPACES because a print line built in storage is moved into the record as it is: unused bytes would carry whatever the storage held. The same rule applies to a line that is built field by field in the record itself, which is why MOVE SPACES TO PRINT-LINE comes first in that style.
WRITE with ADVANCING
WRITE record-name [FROM identifier]
{BEFORE | AFTER} ADVANCING {integer-or-identifier {LINE | LINES} | PAGE}
-
AFTER ADVANCING n LINESmoves the paper first and then prints the line:AFTER ADVANCING 2 LINESleaves one blank line before it.BEFORE ADVANCINGprints first and then moves.PAGEgoes to the first printable line of the next page (the next logical page when the file hasLINAGE). If the device cannot doPAGE, one line is advanced. -
Without an
ADVANCINGphrase, aWRITEon a print file behaves asAFTER ADVANCING 1 LINE. Use one style throughout a print file: allAFTER, or allBEFORE. Mixing them is allowed by the standard but produces different spacing on different systems (see the first test below). -
An
ADVANCINGphrase (or aLINAGEclause) makes the system put a carriage-control character in the first byte of each record, so the printer or the spool knows what to do. WithAFTERonly the characters are ASA characters (a blank for one line,0for two,-for three,+for no advance,1for a new page) and the data set has to beRECFM=FBA; withBEFOREthe machine characters are used and the data set isFBM. The compiler optionADVadds one byte to each record for the character; withNOADVthe program reserves the first byte itself. A print file for 132 columns is therefore declared withLRECL=133andRECFM=FBA. -
WRITE ... FROMkeeps the source line unchanged after the write, so a heading can be written again on each page.
A report with one control break
The first report lists the loan transactions grouped by member, with a count after each member and a grand total. LOANBYM is the transactions sorted by member (shell: sort -s -k1.2,1.7 LOANTRAN > LOANBYM; the COBOL sort is on the sort page). The program prints the heading, reads ahead, compares each record’s member with the previous one and prints the member total when it changes.
IDENTIFICATION DIVISION.
PROGRAM-ID. CBREAK1.
* Circulation report with a control break on the member.
ENVIRONMENT DIVISION.
INPUT-OUTPUT SECTION.
FILE-CONTROL.
SELECT LOAN-FILE ASSIGN TO "LOANBYM"
ORGANIZATION IS LINE SEQUENTIAL.
SELECT PRINT-FILE ASSIGN TO "REPORT1"
ORGANIZATION IS LINE SEQUENTIAL.
DATA DIVISION.
FILE SECTION.
FD LOAN-FILE.
01 LOAN-LINE PIC X(30).
FD PRINT-FILE.
01 PRINT-LINE PIC X(60).
WORKING-STORAGE SECTION.
COPY LOANREC.
01 WS-EOF PIC X VALUE "N".
88 END-OF-LOANS VALUE "Y".
01 WS-PREV-MEMBER PIC 9(6) VALUE ZERO.
01 WS-MEMBER-COUNT PIC 9(3) VALUE ZERO.
01 WS-TOTAL-COUNT PIC 9(3) VALUE ZERO.
01 WS-HEADING-1.
05 FILLER PIC X(30)
VALUE "CIRCULATION REPORT".
05 FILLER PIC X(20) VALUE SPACES.
01 WS-HEADING-2.
05 FILLER PIC X(8) VALUE "MEMBER".
05 FILLER PIC X(10) VALUE "TYPE".
05 FILLER PIC X(8) VALUE "BOOK".
05 FILLER PIC X(8) VALUE "DATE".
01 WS-DETAIL.
05 DL-MEMBER PIC 9(6).
05 FILLER PIC XX VALUE SPACES.
05 DL-TYPE PIC X(10).
05 DL-BOOK PIC 9(6).
05 FILLER PIC XX VALUE SPACES.
05 DL-DATE PIC 9999/99/99.
01 WS-MEMBER-TOTAL.
05 FILLER PIC X(9) VALUE " member ".
05 MT-MEMBER PIC 9(6).
05 FILLER PIC X(15) VALUE " transactions: ".
05 MT-COUNT PIC ZZ9.
01 WS-GRAND-TOTAL.
05 FILLER PIC X(20) VALUE "Total transactions: ".
05 GT-COUNT PIC ZZZ9.
PROCEDURE DIVISION.
0000-MAIN-LINE.
OPEN INPUT LOAN-FILE OUTPUT PRINT-FILE
WRITE PRINT-LINE FROM WS-HEADING-1 AFTER ADVANCING 1 LINE
WRITE PRINT-LINE FROM WS-HEADING-2 AFTER ADVANCING 2 LINES
PERFORM 8000-READ-LOAN
MOVE LOAN-MEMBER-ID TO WS-PREV-MEMBER
PERFORM UNTIL END-OF-LOANS
IF LOAN-MEMBER-ID NOT = WS-PREV-MEMBER
PERFORM 3000-MEMBER-BREAK
END-IF
PERFORM 2000-PRINT-DETAIL
PERFORM 8000-READ-LOAN
END-PERFORM
PERFORM 3000-MEMBER-BREAK
MOVE WS-TOTAL-COUNT TO GT-COUNT
WRITE PRINT-LINE FROM WS-GRAND-TOTAL AFTER ADVANCING 2 LINES
CLOSE LOAN-FILE PRINT-FILE
STOP RUN.
2000-PRINT-DETAIL.
MOVE LOAN-MEMBER-ID TO DL-MEMBER
IF LOAN-CHECKOUT
MOVE "checkout" TO DL-TYPE
ELSE
MOVE "return" TO DL-TYPE
END-IF
MOVE LOAN-BOOK-ID TO DL-BOOK
MOVE LOAN-DATE TO DL-DATE
WRITE PRINT-LINE FROM WS-DETAIL AFTER ADVANCING 1 LINE
ADD 1 TO WS-MEMBER-COUNT WS-TOTAL-COUNT.
3000-MEMBER-BREAK.
MOVE WS-PREV-MEMBER TO MT-MEMBER
MOVE WS-MEMBER-COUNT TO MT-COUNT
WRITE PRINT-LINE FROM WS-MEMBER-TOTAL
AFTER ADVANCING 1 LINE
MOVE ZERO TO WS-MEMBER-COUNT
MOVE LOAN-MEMBER-ID TO WS-PREV-MEMBER.
8000-READ-LOAN.
READ LOAN-FILE INTO LOAN-RECORD
AT END SET END-OF-LOANS TO TRUE
END-READ.
Compiled with cobc -x -Wall -I. cbreak1.cob (and with -std=ibm), no warnings, it writes REPORT1:
CIRCULATION REPORT
MEMBER TYPE BOOK DATE
000100 checkout 000001 2026/10/20
000100 return 000001 2026/10/20
member 000100 transactions: 2
000101 checkout 000002 2026/10/20
member 000101 transactions: 1
000102 checkout 000003 2026/10/20
member 000102 transactions: 1
Total transactions: 4
The first line of the file is empty because the first WRITE used AFTER ADVANCING 1 LINE: the paper moves one line before anything is printed. On a printer that is the top margin; in a text file it is a blank first line.
The structure of the control break is the part to learn:
-
The loop reads ahead: the first record is read before the loop, and each iteration compares the current record with the previous key, prints the total of the group that just ended if they differ, and then processes the current record.
-
The previous key is saved in a working-storage field. After a break the saved key is set to the new key and the group counter to zero.
-
After the end of the file the last group has not been closed, so the program performs the break paragraph once more, then prints the grand total.
-
The first record must not cause a break: the program sets the previous key from the first record before the loop starts.
A mix of print styles is a trap. In the first version of the program the first two WRITE statements were WRITE PRINT-LINE FROM WS-HEADING-1 (no phrase) and WRITE PRINT-LINE FROM WS-HEADING-2 AFTER ADVANCING 2 LINES, and the detail lines had no phrase either. GnuCOBOL 3.2.0 put the line terminator after the lines written without a phrase and before the lines written with AFTER, so the second heading ran into the first detail line:
MEMBER TYPE BOOK DATE000100 checkout000001 2026/10/20
The fix is the one in the program above: every WRITE on the print file says AFTER ADVANCING.
Page control with LINAGE
Printing a page header and counting lines by hand works, but the LINAGE clause lets the system count them:
FD PRINT-FILE
LINAGE IS body-lines LINES
[WITH FOOTING AT footing-line]
[LINES AT TOP top-lines]
[LINES AT BOTTOM bottom-lines].
-
The body is the number of lines on which the program prints. The top and bottom margins are blank lines before and after the body; the logical page is the body plus the two margins.
FOOTING ATmarks the line where the footing area starts, inside the body; if it is missing the footing area is the end of the body. -
The special register
LINAGE-COUNTERholds the current line number within the body.PAGEresets it to 1; a write adds the number of lines advanced. -
AT END-OF-PAGE(orEOP) on aWRITEruns after the line is printed, when the line fell in the footing area. It is the place for the page footing and the next page heading. If a line does not fit in the body, the system moves to the next page first (the page overflow), then runs theEOPstatement. -
LINAGEapplies to files openedOUTPUTorEXTEND, and not to VSAM files. In anSDit is syntax-checked and ignored. -
The values can be integers or data names; a data name is read at
OPEN OUTPUTand again at each new page.
The program below prints the book list on tiny pages of five body lines with a footing area from line 4, so that the page break is visible. The first heading is written with AFTER ADVANCING 1 LINE because a PAGE eject on a brand new file only adds blank lines; later headings use ADVANCING PAGE.
IDENTIFICATION DIVISION.
PROGRAM-ID. LINAGE1.
* Book list on short pages: LINAGE, LINAGE-COUNTER, END-OF-PAGE.
ENVIRONMENT DIVISION.
INPUT-OUTPUT SECTION.
FILE-CONTROL.
SELECT BOOK-FILE ASSIGN TO "BOOKMAST"
ORGANIZATION IS LINE SEQUENTIAL.
SELECT PRINT-FILE ASSIGN TO "REPORT3"
ORGANIZATION IS LINE SEQUENTIAL.
DATA DIVISION.
FILE SECTION.
FD BOOK-FILE.
01 BOOK-LINE PIC X(60).
FD PRINT-FILE
LINAGE IS 5 LINES
WITH FOOTING AT 4
LINES AT TOP 1
LINES AT BOTTOM 1.
01 PRINT-LINE PIC X(40).
WORKING-STORAGE SECTION.
COPY BOOKREC.
01 WS-EOF PIC X VALUE "N".
88 END-OF-BOOKS VALUE "Y".
01 WS-PAGE PIC 99 VALUE ZERO.
01 WS-HEADING.
05 FILLER PIC X(20) VALUE "BOOK LIST PAGE ".
05 HD-PAGE PIC Z9.
01 WS-DETAIL.
05 DT-ID PIC 9(6).
05 FILLER PIC XX VALUE SPACES.
05 DT-TITLE PIC X(30).
01 WS-FOOTING PIC X(40) VALUE "-- continued --".
PROCEDURE DIVISION.
0000-MAIN-LINE.
OPEN INPUT BOOK-FILE OUTPUT PRINT-FILE
PERFORM 2000-NEW-PAGE
PERFORM UNTIL END-OF-BOOKS
READ BOOK-FILE INTO BOOK-RECORD
AT END SET END-OF-BOOKS TO TRUE
NOT AT END PERFORM 3000-PRINT-BOOK
END-READ
END-PERFORM
DISPLAY "LINAGE-COUNTER at the end: " LINAGE-COUNTER
CLOSE BOOK-FILE PRINT-FILE
STOP RUN.
2000-NEW-PAGE.
ADD 1 TO WS-PAGE
MOVE WS-PAGE TO HD-PAGE
* The first heading needs no page eject on a new file.
IF WS-PAGE = 1
WRITE PRINT-LINE FROM WS-HEADING
AFTER ADVANCING 1 LINE
ELSE
WRITE PRINT-LINE FROM WS-HEADING
AFTER ADVANCING PAGE
END-IF.
3000-PRINT-BOOK.
MOVE BOOK-ID TO DT-ID
MOVE BOOK-TITLE TO DT-TITLE
WRITE PRINT-LINE FROM WS-DETAIL
AFTER ADVANCING 1 LINE
AT END-OF-PAGE
WRITE PRINT-LINE FROM WS-FOOTING
AFTER ADVANCING 1 LINE
PERFORM 2000-NEW-PAGE
END-WRITE.
Compiled with cobc -x -Wall -I. linage1.cob (and with -std=ibm), no warnings, it writes REPORT3 and shows the value of LINAGE-COUNTER at the end:
LINAGE-COUNTER at the end: 0000000003
BOOK LIST PAGE 1
000001 DATA STRUCTURES
000002 COBOL BASICS
-- continued --
BOOK LIST PAGE 2
000003 BATCH PROCESSING
000004 OLD LEDGERS
The top margin is the blank first line, and the first page holds the heading, two books and the -- continued -- footing at body line 4, which triggered the end-of-page condition. The two blank lines before page 2 are the bottom margin of page 1 and the top margin of page 2. On z/OS the LINAGE file must be allocated RECFM=FBA (or the system changes the record format), and the control characters are generated; for a SYSOUT data set the characters are what the printer or the spool reads.
Control breaks on two levels
A report with a hierarchy has one break per level. The nightly Circulation history report is sorted by date (the major key) and, within a date, by member (the minor key). It prints a total after each member, a total after each date and a grand total. The input is LOANHIST, six transactions of two days, in date, member order:
O00010000000220261019
O00010100000120261019
R00010100000120261019
O00010000000120261020
R00010000000220261020
O00010200000320261020
The rules of a multi-level break:
-
When a major key changes, all lower levels end too, and the lower-level total is printed first (minor, then major). When only the minor key changes, only the minor total is printed.
-
Each level has its own accumulator. When a minor group ends, its totals are added to the next level up, and then reset. Adding at the break, not at each record, keeps the work small.
-
The break test runs before the current record is processed and compares with the saved keys, which still describe the previous record.
-
At the end of the file all open levels are closed, minor first, then the final totals are printed.
The figure shows the order of the totals: a change of date closes the member first, then the day, and a change of member only the member. The program is the one-level program extended with a second set of counters and the EVALUATE that tests the major key first.
IDENTIFICATION DIVISION.
PROGRAM-ID. CBREAK2.
* Two-level control break: date (major), member (minor).
ENVIRONMENT DIVISION.
INPUT-OUTPUT SECTION.
FILE-CONTROL.
SELECT LOAN-FILE ASSIGN TO "LOANHIST"
ORGANIZATION IS LINE SEQUENTIAL.
SELECT PRINT-FILE ASSIGN TO "REPORT2"
ORGANIZATION IS LINE SEQUENTIAL.
DATA DIVISION.
FILE SECTION.
FD LOAN-FILE.
01 LOAN-LINE PIC X(30).
FD PRINT-FILE.
01 PRINT-LINE PIC X(60).
WORKING-STORAGE SECTION.
COPY LOANREC.
01 WS-EOF PIC X VALUE "N".
88 END-OF-LOANS VALUE "Y".
01 WS-PREV-DATE PIC 9(8) VALUE ZERO.
01 WS-PREV-MEMBER PIC 9(6) VALUE ZERO.
01 WS-MEMBER-TOTALS.
05 WS-MEMBER-OUT PIC 9(3) VALUE ZERO.
05 WS-MEMBER-IN PIC 9(3) VALUE ZERO.
01 WS-DATE-TOTALS.
05 WS-DATE-OUT PIC 9(3) VALUE ZERO.
05 WS-DATE-IN PIC 9(3) VALUE ZERO.
01 WS-GRAND-TOTALS.
05 WS-GRAND-OUT PIC 9(3) VALUE ZERO.
05 WS-GRAND-IN PIC 9(3) VALUE ZERO.
01 WS-DATE-HEADING.
05 FILLER PIC X(6) VALUE "DATE: ".
05 DH-DATE PIC 9999/99/99.
01 WS-MEMBER-LINE.
05 FILLER PIC X(4) VALUE SPACES.
05 FILLER PIC X(8) VALUE "Member ".
05 ML-MEMBER PIC 9(6).
05 FILLER PIC X(11) VALUE " checkouts".
05 ML-OUT PIC ZZ9.
05 FILLER PIC X(10) VALUE " returns".
05 ML-IN PIC ZZ9.
01 WS-DATE-LINE.
05 FILLER PIC X(18) VALUE " Day total ".
05 FILLER PIC X(11) VALUE " checkouts".
05 DL-OUT PIC ZZ9.
05 FILLER PIC X(10) VALUE " returns".
05 DL-IN PIC ZZ9.
01 WS-GRAND-LINE.
05 FILLER PIC X(18) VALUE "Grand total ".
05 FILLER PIC X(11) VALUE " checkouts".
05 GL-OUT PIC ZZ9.
05 FILLER PIC X(10) VALUE " returns".
05 GL-IN PIC ZZ9.
PROCEDURE DIVISION.
0000-MAIN-LINE.
OPEN INPUT LOAN-FILE OUTPUT PRINT-FILE
PERFORM 8000-READ-LOAN
IF NOT END-OF-LOANS
MOVE LOAN-DATE TO WS-PREV-DATE
MOVE LOAN-MEMBER-ID TO WS-PREV-MEMBER
PERFORM 3100-DATE-HEADING
END-IF
PERFORM UNTIL END-OF-LOANS
EVALUATE TRUE
WHEN LOAN-DATE NOT = WS-PREV-DATE
PERFORM 3000-MEMBER-BREAK
PERFORM 4000-DATE-BREAK
PERFORM 3100-DATE-HEADING
WHEN LOAN-MEMBER-ID NOT = WS-PREV-MEMBER
PERFORM 3000-MEMBER-BREAK
END-EVALUATE
PERFORM 2000-COUNT-LOAN
PERFORM 8000-READ-LOAN
END-PERFORM
PERFORM 3000-MEMBER-BREAK
PERFORM 4000-DATE-BREAK
MOVE WS-GRAND-OUT TO GL-OUT
MOVE WS-GRAND-IN TO GL-IN
WRITE PRINT-LINE FROM WS-GRAND-LINE
AFTER ADVANCING 2 LINES
CLOSE LOAN-FILE PRINT-FILE
STOP RUN.
2000-COUNT-LOAN.
IF LOAN-CHECKOUT
ADD 1 TO WS-MEMBER-OUT
ELSE
ADD 1 TO WS-MEMBER-IN
END-IF.
3000-MEMBER-BREAK.
MOVE WS-PREV-MEMBER TO ML-MEMBER
MOVE WS-MEMBER-OUT TO ML-OUT
MOVE WS-MEMBER-IN TO ML-IN
WRITE PRINT-LINE FROM WS-MEMBER-LINE
AFTER ADVANCING 1 LINE
ADD WS-MEMBER-OUT TO WS-DATE-OUT
ADD WS-MEMBER-IN TO WS-DATE-IN
INITIALIZE WS-MEMBER-TOTALS
MOVE LOAN-MEMBER-ID TO WS-PREV-MEMBER.
3100-DATE-HEADING.
MOVE LOAN-DATE TO DH-DATE
WRITE PRINT-LINE FROM WS-DATE-HEADING
AFTER ADVANCING 2 LINES.
4000-DATE-BREAK.
MOVE WS-DATE-OUT TO DL-OUT
MOVE WS-DATE-IN TO DL-IN
WRITE PRINT-LINE FROM WS-DATE-LINE
AFTER ADVANCING 1 LINE
ADD WS-DATE-OUT TO WS-GRAND-OUT
ADD WS-DATE-IN TO WS-GRAND-IN
INITIALIZE WS-DATE-TOTALS
MOVE LOAN-DATE TO WS-PREV-DATE.
8000-READ-LOAN.
READ LOAN-FILE INTO LOAN-RECORD
AT END SET END-OF-LOANS TO TRUE
END-READ.
Compiled with cobc -x -Wall -I. cbreak2.cob (and with -std=ibm), no warnings, it writes REPORT2:
DATE: 2026/10/19
Member 000100 checkouts 1 returns 0
Member 000101 checkouts 1 returns 1
Day total checkouts 2 returns 1
DATE: 2026/10/20
Member 000100 checkouts 1 returns 1
Member 000102 checkouts 1 returns 0
Day total checkouts 2 returns 1
Grand total checkouts 4 returns 2
The grand total (4 checkouts, 2 returns) is the sum of the two days, and each day is the sum of its members. The control totals are what an operator compares with the transaction count of the previous job step. If a third level is needed (a branch above the date, say), the pattern repeats: a test for the new major key first, with the cascade of breaks downward.
The Report Writer
The Report Writer moves the layout, the page handling and the control breaks out of the procedure code and into the Data Division. The program describes the report with an RD (report description) and its report groups, then uses three statements: INITIATE, GENERATE and TERMINATE. The system works out the page breaks, the control breaks and the totals.
Support
The Report Writer is part of the COBOL standard (COBOL 85 had it as an optional module, COBOL 2002 and later include it). GnuCOBOL 3.2 implements it. Enterprise COBOL for z/OS 6.5 does not include it. IBM’s route for Report Writer programs was a separate precompiler product that translates the Report Writer statements into ordinary COBOL; check the current availability with IBM before planning a z/OS use. On z/OS the hand-coded form above, or a DFSORT OUTFIL report (HEADER1, SECTIONS, TRAILER), is the usual substitute.
The pieces
-
FD ... REPORT IS report-name. The output file names the report; it has no record description of its own (the report groups supply the lines). -
REPORT SECTIONin the Data Division, with anRD report-name:-
CONTROLS ARE FINAL control-1 control-2 ...(orCONTROL IS): the control fields from the most to the least significant;FINALis the break after the last record. The fields are data items of the input record or of working storage. -
PAGE LIMIT IS n LINES,HEADING n,FIRST DETAIL n,LAST DETAIL n,FOOTING n: the page layout.
-
-
Report groups (level
01in theRD), each with aTYPE:-
REPORT HEADINGandREPORT FOOTING: once at the start and the end of the report. -
PAGE HEADINGandPAGE FOOTING: on every page;PAGE-COUNTERis a special register. -
CONTROL HEADINGandCONTROL FOOTING(with a control name orFINAL): when the control field starts and ends a group. -
DETAIL: printed by eachGENERATE.
-
-
Lines and columns. Inside a group,
LINE norLINE PLUS nsets the line,COLUMN nthe starting position,PICthe picture. An item gets its value fromVALUE(a constant),SOURCE(copied from a data item when the line is printed), orSUM(a running total of one or more data items, added at eachGENERATEand cleared at the footing). ASUMin a lower-level footing is rolled into the footing of the next level up automatically. -
Statements.
INITIATE report-nameopens the report and clears the sums;GENERATE detail-nameprints a detail group (and any headings or footings the control fields call for);TERMINATE report-nameprints the last footings and the report footing.
The Circulation report in the Report Writer
This program produces the same report as the hand-coded two-level program, from the same LOANHIST. The control fields are LOAN-DATE and LOAN-MEMBER-ID; the counts come from two working-storage flags that hold 1 or 0 for each transaction, because a SUM adds data items:
IDENTIFICATION DIVISION.
PROGRAM-ID. RWDEMO.
* Standard Report Writer: the two-level control break report.
ENVIRONMENT DIVISION.
INPUT-OUTPUT SECTION.
FILE-CONTROL.
SELECT LOAN-FILE ASSIGN TO "LOANHIST"
ORGANIZATION IS LINE SEQUENTIAL.
SELECT REPORT-FILE ASSIGN TO "REPORT4"
ORGANIZATION IS LINE SEQUENTIAL.
DATA DIVISION.
FILE SECTION.
FD LOAN-FILE.
COPY LOANREC.
FD REPORT-FILE
REPORT IS CIRCULATION-REPORT.
WORKING-STORAGE SECTION.
01 WS-EOF PIC X VALUE "N".
88 END-OF-LOANS VALUE "Y".
01 WS-OUT-ONE PIC 9 VALUE ZERO.
01 WS-IN-ONE PIC 9 VALUE ZERO.
01 WS-TYPE-TEXT PIC X(8).
REPORT SECTION.
RD CIRCULATION-REPORT
CONTROLS ARE FINAL LOAN-DATE LOAN-MEMBER-ID
PAGE LIMIT IS 30 LINES
HEADING 1
FIRST DETAIL 4
LAST DETAIL 28.
01 TYPE PAGE HEADING.
05 LINE 1.
10 COLUMN 1 PIC X(18) VALUE "CIRCULATION REPORT".
10 COLUMN 30 PIC X(5) VALUE "PAGE ".
10 COLUMN 35 PIC Z9 SOURCE PAGE-COUNTER.
01 TYPE CONTROL HEADING LOAN-DATE.
05 LINE PLUS 2.
10 COLUMN 1 PIC X(6) VALUE "DATE: ".
10 COLUMN 7 PIC 9999/99/99 SOURCE LOAN-DATE.
01 DETAIL-LINE TYPE DETAIL.
05 LINE PLUS 1.
10 COLUMN 5 PIC 9(6) SOURCE LOAN-MEMBER-ID.
10 COLUMN 13 PIC X(8) SOURCE WS-TYPE-TEXT.
10 COLUMN 23 PIC 9(6) SOURCE LOAN-BOOK-ID.
01 TYPE CONTROL FOOTING LOAN-MEMBER-ID.
05 LINE PLUS 1.
10 COLUMN 5 PIC X(7) VALUE "Member ".
10 COLUMN 12 PIC 9(6) SOURCE LOAN-MEMBER-ID.
10 COLUMN 22 PIC X(10) VALUE "checkouts ".
10 COLUMN 32 PIC ZZ9 SUM WS-OUT-ONE.
10 COLUMN 38 PIC X(8) VALUE "returns ".
10 COLUMN 46 PIC ZZ9 SUM WS-IN-ONE.
01 TYPE CONTROL FOOTING LOAN-DATE.
05 LINE PLUS 1.
10 COLUMN 3 PIC X(10) VALUE "Day total ".
10 COLUMN 22 PIC X(10) VALUE "checkouts ".
10 COLUMN 32 PIC ZZ9 SUM WS-OUT-ONE.
10 COLUMN 38 PIC X(8) VALUE "returns ".
10 COLUMN 46 PIC ZZ9 SUM WS-IN-ONE.
01 TYPE CONTROL FOOTING FINAL.
05 LINE PLUS 2.
10 COLUMN 1 PIC X(11) VALUE "Grand total".
10 COLUMN 22 PIC X(10) VALUE "checkouts ".
10 COLUMN 32 PIC ZZ9 SUM WS-OUT-ONE.
10 COLUMN 38 PIC X(8) VALUE "returns ".
10 COLUMN 46 PIC ZZ9 SUM WS-IN-ONE.
PROCEDURE DIVISION.
0000-MAIN-LINE.
OPEN INPUT LOAN-FILE OUTPUT REPORT-FILE
INITIATE CIRCULATION-REPORT
PERFORM UNTIL END-OF-LOANS
READ LOAN-FILE
AT END SET END-OF-LOANS TO TRUE
NOT AT END PERFORM 2000-GENERATE-LOAN
END-READ
END-PERFORM
TERMINATE CIRCULATION-REPORT
CLOSE LOAN-FILE REPORT-FILE
STOP RUN.
2000-GENERATE-LOAN.
MOVE ZERO TO WS-OUT-ONE WS-IN-ONE
IF LOAN-CHECKOUT
MOVE 1 TO WS-OUT-ONE
MOVE "checkout" TO WS-TYPE-TEXT
ELSE
MOVE 1 TO WS-IN-ONE
MOVE "return" TO WS-TYPE-TEXT
END-IF
GENERATE DETAIL-LINE.
Compiled with cobc -x -Wall -I. rwdemo.cob (and with -std=ibm), no warnings, it writes REPORT4. The file is 30 lines long because the report page is 30 lines, so the output below stops at the last non-blank line:
CIRCULATION REPORT PAGE 1
DATE: 2026/10/19
000100 checkout 000002
Member 000100 checkouts 1 returns 0
000101 checkout 000001
000101 return 000001
Member 000101 checkouts 1 returns 1
Day total checkouts 2 returns 1
DATE: 2026/10/20
000100 checkout 000001
000100 return 000002
Member 000100 checkouts 1 returns 1
000102 checkout 000003
Member 000102 checkouts 1 returns 0
Day total checkouts 2 returns 1
Grand total checkouts 4 returns 2
The totals are the same as in the hand-coded report: 4 checkouts and 2 returns on the grand total line. What changed is where the work is:
-
No break logic. The
READloop only sets the two flags and issuesGENERATE. The system compares the control fields with the previous record, prints the member and date footings in the correct order, and prints the date heading. -
No accumulators. The
SUMclause adds the flags and rolls the member sums up into the day sums and the grand sums by itself. ASUMcarries over only to a higher footing of the same report. -
The control field in a footing (
SOURCE LOAN-MEMBER-IDin the member footing) shows the value of the group that ended, because the system keeps the previous value for the footing. -
Page handling. The page heading, page counter and the page size come from the
RD; the trailing blank lines are the unused lines of the 30-line page.
The layout is also easier to change: moving a column or adding a field touches one line of the report group, and adding a level means one more control name and one more footing group.
Report Writer errors are reported at compile time; the one that beginners meet most is a control field that is not in the record or in working storage, and a SOURCE or SUM operand that is not defined, both reported as an undefined name. A GENERATE that names a report instead of a detail group produces a summary report with only the footings; its use is rare and is not covered here.
Testing a report
The text a program writes is easy to compare with a file, which makes reports a good subject for regression tests: keep the expected output of a known input file, run the program, and compare with diff (a z/OS job can do the same with IEBCOMPR, ICETOOL COMPARE or SUPERC). Test the cases where a break happens at the first record, at the last record, on every record (each key different) and never (one key), and an empty input file. See Testing COBOL Programs.
Common mistakes
-
Unsorted input. A control break works only on a file in key order; a key that comes back later prints a second group with the same key.
-
Forgetting the last group. The loop ends at end of file, and the last member and date have not been printed; perform the break paragraphs once more.
-
A break on the first record. Initialize the previous key from the first record, not to zero or spaces.
-
Resetting the total before printing it. Print and add to the next level first, then clear.
-
Mixing
WRITEstyles on a print file. UseAFTER ADVANCINGeverywhere, orBEFOREeverywhere. -
Unused bytes in a print line. Initialize group items that are built in the record with
MOVE SPACES, or build lines in working storage withVALUE SPACESfillers. -
A print file with the wrong record format on z/OS. Without
FBAthe control character becomes data and shows on the page. -
A
LINAGEfooting line outside the body. TheFOOTINGvalue must be between 1 and the body size. -
Hard-coding the page length. Use
LINAGE(or theRDpage limits) so that a change of paper size is one edit. -
Expecting the Report Writer on z/OS. Enterprise COBOL 6.5 has none; plan the hand-coded form or a precompiler.
References
-
Enterprise COBOL for z/OS 6.5 Language Reference: WRITE statement
-
Enterprise COBOL for z/OS 6.5 Language Reference: LINAGE clause
-
Enterprise COBOL for z/OS 6.5 Language Reference: PICTURE clause editing
-
Enterprise COBOL for z/OS 6.5 Programming Guide: Establishing record formats
-
Tom Taulli, Modern Mainframe Development, O’Reilly Media, 2022 (chapter 6, consulted for the topics only)