COBOL Module 7 – Sequential files and the classic batch patterns
Control-break processing with subtotals
Write COBOL control breaks over sorted input: a subtotal at each change of key, lower levels closed first, a final break, and totals that reconcile.
What you will learn
- Design single- and multi-level control-break logic over sorted input
- Handle the first record, the last record and an empty file correctly
- Prove a report's totals against the input's record count, amount total and trailer record
- Detect input that is out of order and stop the run with a return code
Before you start
What you’ll learn · Before you start
What you will learn
- Design single- and multi-level control-break logic over sorted input
- Handle the first record, the last record and an empty file correctly
- Prove a report's totals against the input's record count, amount total and trailer record
- Detect input that is out of order and stop the run with a return code
Before you start
On this page
Nine deposits sorted by region and branch have to come out with a total for each of their four branches, each of their two regions and the whole file, 4,772.48. The program sees each record once, in order, so it can only print a branch’s total at the moment it reads the first record of the next branch.
That moment is a control break: The point in a sorted file where the key of the record just read differs from the key of the record before, so the group with the old key has ended and its totals are printed.. The programs below print one level of subtotals, then two, close every level at the end of the file, and are then made to fail the way control-break reports fail in production, and made safe.
A control break is a change of key between two sorted records
brk1.cob keeps the branch of the record before in PREV-BRANCH and compares it with the branch of every record it reads. While they are equal, it adds the deposit to BRANCH-TOTAL. When they differ, the previous branch has ended: BRANCH-BREAK prints its total, adds it to GRAND-TOTAL, sets BRANCH-TOTAL back to 0 and saves the new branch. Right after the priming READ, the first record’s branch goes into PREV-BRANCH, so the first record starts a group instead of ending one:
EASTBR01AC0001000250000
EASTBR01AC0002000012500
EASTBR02AC0003000004999
EASTBR02AC0004000001250
WESTBR03AC0005000030000
WESTBR03AC0006000000999
WESTBR04AC0007000150000
WESTBR04AC0008000020000
WESTBR04AC0009000007500 IDENTIFICATION DIVISION.
PROGRAM-ID. BRK1.
* One control level: a total for each branch and a grand total,
* over deposits sorted by branch. The records are synthetic.
ENVIRONMENT DIVISION.
INPUT-OUTPUT SECTION.
FILE-CONTROL.
SELECT TXN-FILE ASSIGN TO "TXNIN"
ORGANIZATION IS LINE SEQUENTIAL
FILE STATUS IS TXN-STATUS.
DATA DIVISION.
FILE SECTION.
FD TXN-FILE.
01 TXN-REC.
05 TX-REGION PIC X(4).
05 TX-BRANCH PIC X(4).
05 TX-ACCT PIC X(6).
05 TX-AMOUNT PIC 9(7)V99.
WORKING-STORAGE SECTION.
01 TXN-STATUS PIC XX.
88 TXN-AT-END VALUE "10".
01 PREV-BRANCH PIC X(4).
01 RECORDS-READ PIC 9(5) VALUE 0.
01 BRANCH-TOTAL PIC 9(9)V99 VALUE 0.
01 GRAND-TOTAL PIC 9(9)V99 VALUE 0.
01 SHOW-AMOUNT PIC ZZZ,ZZZ,ZZ9.99.
PROCEDURE DIVISION.
OPEN INPUT TXN-FILE
PERFORM READ-TXN
MOVE TX-BRANCH TO PREV-BRANCH
PERFORM UNTIL TXN-AT-END
IF TX-BRANCH NOT = PREV-BRANCH
PERFORM BRANCH-BREAK
END-IF
MOVE TX-AMOUNT TO SHOW-AMOUNT
DISPLAY " " TX-BRANCH " " TX-ACCT " " SHOW-AMOUNT
ADD TX-AMOUNT TO BRANCH-TOTAL
PERFORM READ-TXN
END-PERFORM
IF RECORDS-READ > 0
PERFORM BRANCH-BREAK
END-IF
MOVE GRAND-TOTAL TO SHOW-AMOUNT
DISPLAY "GRAND TOTAL " SHOW-AMOUNT
CLOSE TXN-FILE
STOP RUN.
BRANCH-BREAK.
MOVE BRANCH-TOTAL TO SHOW-AMOUNT
DISPLAY " " PREV-BRANCH " TOTAL " SHOW-AMOUNT
ADD BRANCH-TOTAL TO GRAND-TOTAL
MOVE 0 TO BRANCH-TOTAL
MOVE TX-BRANCH TO PREV-BRANCH.
READ-TXN.
READ TXN-FILE
EVALUATE TRUE
WHEN TXN-STATUS = "00"
ADD 1 TO RECORDS-READ
WHEN TXN-AT-END
CONTINUE
WHEN OTHER
DISPLAY "READ failed, status " TXN-STATUS
CLOSE TXN-FILE
MOVE 12 TO RETURN-CODE
STOP RUN
END-EVALUATE. from session import files, run
# One control level: the nine deposits, sorted by branch, with a total at every change of branch.
files("brk1.cob", "deposits.txt")
run("""
cobc -x brk1.cob
DD_TXNIN=deposits.txt ./brk1
""") Output
$ cobc -x brk1.cob
$ DD_TXNIN=deposits.txt ./brk1
BR01 AC0001 2,500.00
BR01 AC0002 125.00
BR01 TOTAL 2,625.00
BR02 AC0003 49.99
BR02 AC0004 12.50
BR02 TOTAL 62.49
BR03 AC0005 300.00
BR03 AC0006 9.99
BR03 TOTAL 309.99
BR04 AC0007 1,500.00
BR04 AC0008 200.00
BR04 AC0009 75.00
BR04 TOTAL 1,775.00
GRAND TOTAL 4,772.48
Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 brk1_session.py
Four branch totals and a grand total of 4,772.48 came out. Step through the reads that change the branch:
The drawing shows the whole scene; its 6 steps are listed below.
Every step as text
- The first record. The priming READ brings in AC0001 of BR01, and the MOVE right after it copies BR01 into PREV-BRANCH, so the first record opens a group.
- Same branch. AC0001 and AC0002 are both BR01, equal to PREV-BRANCH, so each deposit is only added to BRANCH-TOTAL, which reaches 2,625.00.
- The first break. AC0003 is BR02. The comparison fails, so BRANCH-BREAK prints BR01's total, adds it to GRAND-TOTAL, sets BRANCH-TOTAL to 0 and saves BR02.
- BR02 and BR03. BR02 collects 49.99 and 12.50 and closes at AC0005 with 62.49. BR03 collects 300.00 and 9.99 and closes at AC0007 with 309.99.
- The end of the file. BR04 collects three deposits, 1,775.00. The READ after AC0009 returns status 10, so no record ever differs from BR04.
- The final break. After the loop, RECORDS-READ is 9, so BRANCH-BREAK runs once more. BR04's total prints and the grand total becomes 4,772.48.
The last branch, BR04, has no next record to end it, so after the loop brk1.cob performs BRANCH-BREAK once more: the final break: The break a control-break program performs after its read loop, when the file had records, to close the last group of every level, which no next record will close.. It does that only when RECORDS-READ is above 0, because a file without records has no group to close.
All of this depends on the order of the file. A break means “this group has ended” only if every record of the group comes before the next group, so the input must be sorted on the fields the program compares, in the order it compares them.
Checkpoint
Check: A control break on one key
A higher-level break closes every lower level first
brk2.cob adds a level above the branch: the region. A change of region also ends the branch that was open, even if the next branch code happened to be the same, so the test asks about the higher key first. The EVALUATE takes the first WHEN that is true: a new region performs BRANCH-BREAK and then REGION-BREAK, and a new branch in the same region performs BRANCH-BREAK alone.
IDENTIFICATION DIVISION.
PROGRAM-ID. BRK2.
* Two control levels over deposits sorted by region and branch:
* a total for each branch, for each region and for the run. A
* change of region closes the branch first, then the region.
ENVIRONMENT DIVISION.
INPUT-OUTPUT SECTION.
FILE-CONTROL.
SELECT TXN-FILE ASSIGN TO "TXNIN"
ORGANIZATION IS LINE SEQUENTIAL
FILE STATUS IS TXN-STATUS.
DATA DIVISION.
FILE SECTION.
FD TXN-FILE.
01 TXN-REC.
05 TX-REGION PIC X(4).
05 TX-BRANCH PIC X(4).
05 TX-ACCT PIC X(6).
05 TX-AMOUNT PIC 9(7)V99.
WORKING-STORAGE SECTION.
01 TXN-STATUS PIC XX.
88 TXN-AT-END VALUE "10".
01 PREV-REGION PIC X(4).
01 PREV-BRANCH PIC X(4).
01 RECORDS-READ PIC 9(5) VALUE 0.
01 BRANCH-TOTAL PIC 9(9)V99 VALUE 0.
01 REGION-TOTAL PIC 9(9)V99 VALUE 0.
01 GRAND-TOTAL PIC 9(9)V99 VALUE 0.
01 SHOW-AMOUNT PIC ZZZ,ZZZ,ZZ9.99.
PROCEDURE DIVISION.
OPEN INPUT TXN-FILE
PERFORM READ-TXN
PERFORM START-KEYS
PERFORM UNTIL TXN-AT-END
EVALUATE TRUE
WHEN TX-REGION NOT = PREV-REGION
PERFORM BRANCH-BREAK
PERFORM REGION-BREAK
WHEN TX-BRANCH NOT = PREV-BRANCH
PERFORM BRANCH-BREAK
END-EVALUATE
MOVE TX-AMOUNT TO SHOW-AMOUNT
DISPLAY " " TX-ACCT " " SHOW-AMOUNT
ADD TX-AMOUNT TO BRANCH-TOTAL
PERFORM READ-TXN
END-PERFORM
PERFORM FINAL-BREAK
MOVE GRAND-TOTAL TO SHOW-AMOUNT
DISPLAY "GRAND TOTAL " SHOW-AMOUNT
CLOSE TXN-FILE
STOP RUN.
START-KEYS.
MOVE TX-REGION TO PREV-REGION
MOVE TX-BRANCH TO PREV-BRANCH.
BRANCH-BREAK.
MOVE BRANCH-TOTAL TO SHOW-AMOUNT
DISPLAY " " PREV-BRANCH " TOTAL " SHOW-AMOUNT
ADD BRANCH-TOTAL TO REGION-TOTAL
MOVE 0 TO BRANCH-TOTAL
MOVE TX-BRANCH TO PREV-BRANCH.
REGION-BREAK.
MOVE REGION-TOTAL TO SHOW-AMOUNT
DISPLAY PREV-REGION " REGION TOTAL " SHOW-AMOUNT
ADD REGION-TOTAL TO GRAND-TOTAL
MOVE 0 TO REGION-TOTAL
MOVE TX-REGION TO PREV-REGION.
FINAL-BREAK.
IF RECORDS-READ > 0
PERFORM BRANCH-BREAK
PERFORM REGION-BREAK
END-IF.
READ-TXN.
READ TXN-FILE
EVALUATE TRUE
WHEN TXN-STATUS = "00"
ADD 1 TO RECORDS-READ
WHEN TXN-AT-END
CONTINUE
WHEN OTHER
DISPLAY "READ failed, status " TXN-STATUS
CLOSE TXN-FILE
MOVE 12 TO RETURN-CODE
STOP RUN
END-EVALUATE. from session import files, run
# Two control levels: the same deposits with branch, region and grand totals.
files("brk2.cob", "deposits.txt")
run("""
cobc -x brk2.cob
DD_TXNIN=deposits.txt ./brk2
""") Output
$ cobc -x brk2.cob
$ DD_TXNIN=deposits.txt ./brk2
AC0001 2,500.00
AC0002 125.00
BR01 TOTAL 2,625.00
AC0003 49.99
AC0004 12.50
BR02 TOTAL 62.49
EAST REGION TOTAL 2,687.49
AC0005 300.00
AC0006 9.99
BR03 TOTAL 309.99
AC0007 1,500.00
AC0008 200.00
AC0009 75.00
BR04 TOTAL 1,775.00
WEST REGION TOTAL 2,084.99
GRAND TOTAL 4,772.48
Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 brk2_session.py
The totals roll upwards, one level at a time. A deposit is added only to BRANCH-TOTAL. BRANCH-BREAK adds the branch total into REGION-TOTAL, and REGION-BREAK adds the region total into GRAND-TOTAL, so each level’s total is the sum of the totals printed under it: 2,625.00 and 62.49 make EAST’s 2,687.49. Follow the break at AC0005, where the region changes from EAST to WEST:
The drawing shows the whole scene; its 5 steps are listed below.
Every step as text
- The record. AC0005 arrives with region WEST and branch BR03. PREV-REGION still holds EAST and PREV-BRANCH holds BR02.
- The region first. The first WHEN of the EVALUATE compares the regions. WEST is not EAST, so both levels close, and the branch test is never needed.
- Close the branch. BRANCH-BREAK prints BR02 TOTAL 62.49, adds it to REGION-TOTAL, where EAST's 2,625.00 becomes 2,687.49, sets BRANCH-TOTAL to 0 and saves BR03.
- Close the region. REGION-BREAK prints EAST REGION TOTAL 2,687.49, adds it to GRAND-TOTAL, sets REGION-TOTAL to 0 and saves WEST.
- The new group. Only now is AC0005's 300.00 added, to the BRANCH-TOTAL of BR03, the first deposit of a new branch in a new region.
At the end of the file, FINAL-BREAK closes both levels in the same order, branch first, and only then does the program print the grand total.
Key idea
Compare keys from the highest level down, close levels from the lowest up, and add each total only into the level directly above it; the end of the file closes every level.
Three bugs break this logic in production, each with one wrong line: a break printed before the first record, a last group lost at the end of the file, and a total added into the wrong level. Three kinds of input test the program at its edges: an empty file, a file of one record and a file that is out of order. A trailer record: A last record that the sender writes after the data, holding the number of records and their total, so that the program reading the file can check it read all of it. from the sender, holding the count and the total of the records, lets the program prove its own totals. Reconciling means comparing the count and the sum the program read with the figures the sender wrote before the transfer. Report Writer’s CONTROL FOOTING with SUM, from the previous lesson, declares the same breaks instead of coding them.
Checkpoint
Check: Several levels, their order and their roll-up
Practise: breaks and levels
The questions below use the nine deposits and the two programs, and each answer can be checked against the sessions.
Practice that adapts to you
Practise this lesson
6 questions about 4 ideas of this lesson. Each answer is explained at once, and what you get right is kept in this browser.
Three control-break bugs, reproduced, and the inputs that catch them Pro: opens with a Pro, Premium or Ultimate pass
Each classic control-break bug takes one wrong line. This part makes each one in a copy of brk2.cob, shows exactly what it prints, and names the test input that would have caught it before a branch manager did.
In this part:
- A break before the first record, and a last group that never prints
- Totals rolled into the wrong level, under a grand total that still balances
1 diagram · 1 runnable example (Python) · about 5 minutes
Opening this part with your pass…
This part opens with any MySmartCoPilot pass.
- Pro pass · 1 week₹79$2.99
- 30 days₹179$5.99
- 1 year₹1,699$45
One payment; a pass never renews. The price shown is the total you pay: no fees or charges are added at checkout.
Passes can’t be bought right now, so this part can’t be opened at the moment. The free part above stands on its own. If you have a pass, enter your licence key.
It also opens every other in-depth part in Learn and every Pro tool. Sales are final, except where the law gives you a right to withdraw, for example in the EU and the UK (refund policy).
Edge cases and proof: an empty file, one record, unsorted input and a trailer that reconciles Pro: opens with a Pro, Premium or Ultimate pass
A control-break report is right only for the inputs it was tested on. This part runs brk2.cob on the three inputs that matter most, then builds brk3.cob, which refuses unsorted input and proves its totals against the sender’s trailer.
In this part:
- An empty file, a single deposit and an unsorted file, run
- A sequence check and a trailer record that prove the report
6 runnable examples (COB, Python, Text) · about 5 minutes
Opening this part with your pass…Pro part. It opens with the same pass as the part above.
Interview questions
A warm-up comes first, answered here; the three that follow begin where a set of totals stopped agreeing:
-
Fresher to mid · Warm-up
What is a control break, and why does a control-break program need sorted input?
A control break is the point at which the key of the record just read differs from the key of the record before, so the group of records with the old key has ended. The program keeps the previous key, compares it with every record, and at a change prints the old group's total, adds it into the level above, resets it and saves the new key; after the loop, a final break closes the last group. The break only means "this group has ended" if every record of a group comes before the next group, which is true only when the file is sorted on the break fields, major field first, in the order the program compares them. On a file that is not sorted, a group is closed and opened again, and its total is split in two.
-
Fresher to mid
After a tidy-up of a branch and region report, the last branch and the last region of every run no longer print, and the grand total is lower than the day's deposits by exactly their amount. Every other total is right. What happened, and how do you fix and test it?
-
Mid to senior
Two regional managers report that their region totals are wrong by the same amount in opposite directions: one too low, the next one too high. The grand total agrees with the day's deposits. What kind of bug is this, how do you find it, and how do you test for it?
-
Senior to staff
Operations want the nightly deposits report by region and branch to prove itself before the next job step runs. Design the checks the program makes and the return codes it ends with, and say which problems each check can and cannot catch.
Organisations named on this page
MySmartCoPilot has no association with IBM or with the GnuCOBOL project. IBM’s documentation is cited for EBCDIC ordering and file processing on z/OS, GnuCOBOL’s guide for Report Writer, and the reports above were run with GnuCOBOL 3.2.
Flashcards · 7 cards
Recall it, then check
Say the answer to yourself before you look: recalling it is what makes it stick.
-
What is a control break?
A change of key between two records of a sorted file. When the branch just read differs from the one before, the previous branch's group has ended: print its total, reset it and save the new key.
-
Why does a control-break program save the first record's key right after the priming READ?
So that the first record starts a group instead of ending one; otherwise a break with nothing in it prints before the first record.
-
What is the final break, and when does it run?
After the loop, one more break closes the last group, which no next record ends. It runs only when at least one record was read.
-
Why must the input of a control-break report be sorted?
A break means that a group has ended only if all its records come before the next group, so the file is sorted on the compared fields, in the order they are compared.
-
With region and branch levels, which key is tested first, and which level is closed first?
The highest key, the region, is tested first; the lowest level, the branch, is closed first. A new region therefore closes its last branch, then the region itself.
-
Into which total does each level's total go?
Only into the level directly above: a deposit into the branch total, the branch total into its region's, the region total into the grand total.
-
What does a trailer record let a report program prove?
The sender writes the count and the total of its records in the trailer, so the program can compare what it read with those figures before anyone trusts the report.
Key takeaways
- A control break is a change of key between two records of a sorted file: the program keeps the previous key, prints and resets a group’s total when the key changes, and saves the first record’s key right after the priming READ.
- With several levels, the program tests the highest key first and closes the lowest level first, so a new region closes its last branch before the region itself.
- Each total is added only into the level directly above it, and after the loop a final break closes every level when the file had any records.
Terms in this lesson
- control break
- The point in a sorted file where the key of the record just read differs from the key of the record before, so the group with the old key has ended and its totals are printed.
- final break
- The break a control-break program performs after its read loop, when the file had records, to close the last group of every level, which no next record will close.
- trailer record
- A last record that the sender writes after the data, holding the number of records and their total, so that the program reading the file can check it read all of it.
References
- Enterprise COBOL for z/OS 6.5: READ statement (IBM)
- Enterprise COBOL for z/OS 6.5 Programming Guide: Processing QSAM files (IBM)
- Enterprise COBOL for z/OS 6.5 Programming Guide: Sorting and merging files (IBM)
- Enterprise COBOL for z/OS 6.5: EVALUATE statement (IBM)
- Db2 13 for z/OS: String comparisons (IBM)
- GnuCOBOL 3.2 Programmer's Guide (2.1.13 Report Writer Features) (GnuCOBOL project (GNU))
Related tools
Report a problem with this lesson
Kept only in this browser. Your Learn progress