Multi-level control breaks
Real reports rarely stop at one level. Sales by branch within region, payroll by department within division, claims by policy within agent within office. Each level gets its own subtotal, and every level rolls up into the one above it.
The input must be sorted on all the control fields, major to minor: region first, then branch within region.
Rules for more than one level
- Test the major key first. A change in region is a break at every
level, even if the branch code happens to be the same (
N/Afollowed byS/Ais a new branch too). Only if the region is unchanged do you look at the branch. - Break from the bottom up. On a region change, close the branch first (print its total, roll it into the region), then close the region (print, roll into the grand total).
- Each level resets only itself and stores its new key.
- At end of file, run every level's break, lowest first, then the grand total.
EVALUATE TRUE expresses rule 1 neatly, because it stops at the first
WHEN that is true:
IDENTIFICATION DIVISION.
PROGRAM-ID. TWOLEVEL.
DATA DIVISION.
WORKING-STORAGE SECTION.
01 SALE-DATA.
05 FILLER PIC X(6) VALUE "NA0010".
05 FILLER PIC X(6) VALUE "NA0020".
05 FILLER PIC X(6) VALUE "NB0005".
05 FILLER PIC X(6) VALUE "SA0100".
01 SALE-TABLE REDEFINES SALE-DATA.
05 SALE-ENTRY OCCURS 4 TIMES.
10 SALE-REGION PIC X.
10 SALE-BRANCH PIC X.
10 SALE-QTY PIC 9(4).
01 WS-I PIC 9.
01 WS-PREV-REGION PIC X.
01 WS-PREV-BRANCH PIC X.
01 WS-BRANCH-TOT PIC 9(5) VALUE 0.
01 WS-REGION-TOT PIC 9(5) VALUE 0.
01 WS-GRAND-TOT PIC 9(5) VALUE 0.
PROCEDURE DIVISION.
MOVE SALE-REGION(1) TO WS-PREV-REGION
MOVE SALE-BRANCH(1) TO WS-PREV-BRANCH
PERFORM VARYING WS-I FROM 1 BY 1 UNTIL WS-I > 4
EVALUATE TRUE
WHEN SALE-REGION(WS-I) NOT = WS-PREV-REGION
PERFORM BRANCH-BREAK
PERFORM REGION-BREAK
WHEN SALE-BRANCH(WS-I) NOT = WS-PREV-BRANCH
PERFORM BRANCH-BREAK
END-EVALUATE
ADD SALE-QTY(WS-I) TO WS-BRANCH-TOT
END-PERFORM
PERFORM BRANCH-BREAK
PERFORM REGION-BREAK
DISPLAY "GRAND " WS-GRAND-TOT " ***"
STOP RUN.
BRANCH-BREAK.
DISPLAY " BRANCH " WS-PREV-REGION WS-PREV-BRANCH
" " WS-BRANCH-TOT " *"
ADD WS-BRANCH-TOT TO WS-REGION-TOT
MOVE 0 TO WS-BRANCH-TOT
IF WS-I <= 4
MOVE SALE-BRANCH(WS-I) TO WS-PREV-BRANCH
END-IF.
REGION-BREAK.
DISPLAY "REGION " WS-PREV-REGION
" " WS-REGION-TOT " **"
ADD WS-REGION-TOT TO WS-GRAND-TOT
MOVE 0 TO WS-REGION-TOT
IF WS-I <= 4
MOVE SALE-REGION(WS-I) TO WS-PREV-REGION
END-IF.
Output:
BRANCH NA 00030 *
BRANCH NB 00005 *
REGION N 00035 **
BRANCH SA 00100 *
REGION S 00100 **
GRAND 00135 ***
Region N's total (35) is the sum of its two branch totals, and the grand total is the sum of the region totals. No detail value is ever added more than once; totals travel upward at the breaks.
Scaling up
With three or four levels the shape stays the same: one WHEN per level
from the top down, and each break paragraph performing the one below it
first. Many shops write it that way:
REGION-BREAK.
PERFORM BRANCH-BREAK
* then print the region total, roll it up, reset it
so a single PERFORM REGION-BREAK closes everything beneath it.
Presentation
Common conventions you'll be asked for:
- Asterisks by level:
*branch,**region,***grand total. - Indentation: lower-level totals indented less, so the eye finds the big numbers.
- A blank line or a new page after a major break. Starting each region on a new page (reset the line count to force headings) is a frequent requirement, and it is part of this module's challenge.
On the job
When a multi-level report doesn't foot (branch totals don't add up to the region), look for a total that is added at two levels, or a break paragraph that resets the wrong accumulator. Check it by hand with a three-record test file whose sums you can do in your head.
Your task
Finance wants the payroll with subtotals at two levels: department within division. The input is sorted by division, then department.
DD PAYIN — line sequential, 30 bytes: PAY-DIV X(3),
PAY-DEPT X(4), PAY-NAME X(15), PAY-SALARY 9(6)V99.
DD RPTOUT — the report. The starter has the layouts, headings and
detail printing; PRINT-DETAIL adds each salary to WS-DEPT-TOTAL.
- When the department changes: print the department total line (
*). - When the division changes: print the department total, then the
division total line (
**) and a blank line. - At end of file: both final breaks, then the grand total (
***).
PAYROLL BY DIVISION AND DEPARTMENT
DIV DEPT NAME SALARY
FIN ACCT ALICE MORGAN 52,000.00
FIN ACCT BRIAN KEANE 48,500.00
DEPT ACCT 100,500.00 *
FIN AUDT CLARE HOGAN 61,000.00
DEPT AUDT 61,000.00 *
DIVISION FIN 161,500.00 **
OPS ACCT DAVID NASH 39,999.99
DEPT ACCT 39,999.99 *
...
GRAND TOTAL 361,000.74 ***
Watch the FIN ACCT → OPS ACCT step: the department code is the same,
but a new division always closes the department too.