MODULE 6 · TABLES · 6/8

Two-dimensional tables

15 min30 XPExercise

Plenty of business data is a grid: sales by region and quarter, rates by zone and weight band, premiums by age band and risk class. In COBOL you build a grid by putting an OCCURS inside another OCCURS.

       IDENTIFICATION DIVISION.
       PROGRAM-ID. TWODIM.
       DATA DIVISION.
       WORKING-STORAGE SECTION.
       01  WS-SALES-TABLE.
           05  WS-REGION      OCCURS 2 TIMES.
               10  WS-QTR-SALES   PIC 9(5) OCCURS 4 TIMES.
       01  WS-R               PIC 9.
       01  WS-Q               PIC 9.
       01  WS-ROW-TOTAL       PIC 9(6).
       01  WS-COL-TOTAL       PIC 9(6).
       PROCEDURE DIVISION.
           INITIALIZE WS-SALES-TABLE
           MOVE 100 TO WS-QTR-SALES(1, 1)
           MOVE 250 TO WS-QTR-SALES(1, 3)
           MOVE 400 TO WS-QTR-SALES(2, 1)
           MOVE 50  TO WS-QTR-SALES(2, 4)
           DISPLAY "WHOLE TABLE: " WS-SALES-TABLE
           PERFORM VARYING WS-R FROM 1 BY 1 UNTIL WS-R > 2
               MOVE ZERO TO WS-ROW-TOTAL
               PERFORM VARYING WS-Q FROM 1 BY 1 UNTIL WS-Q > 4
                   ADD WS-QTR-SALES(WS-R, WS-Q) TO WS-ROW-TOTAL
               END-PERFORM
               DISPLAY "REGION " WS-R " TOTAL " WS-ROW-TOTAL
           END-PERFORM
           MOVE ZERO TO WS-COL-TOTAL
           PERFORM VARYING WS-R FROM 1 BY 1 UNTIL WS-R > 2
               ADD WS-QTR-SALES(WS-R, 1) TO WS-COL-TOTAL
           END-PERFORM
           DISPLAY "Q1 TOTAL " WS-COL-TOTAL
           STOP RUN.

Output:

WHOLE TABLE: 0010000000002500000000400000000000000050
REGION 1 TOTAL 000350
REGION 2 TOTAL 000450
Q1 TOTAL 000500

Reading the declaration

  • WS-REGION occurs 2 times. It is a group, one row per region.
  • Inside each row, WS-QTR-SALES occurs 4 times, one column per quarter.
  • A reference to WS-QTR-SALES needs two subscripts, outer first: WS-QTR-SALES(region, quarter). The comma is optional. (WS-R WS-Q) also works, but the comma makes it easier to read.
  • WS-REGION(2) on its own is a whole row: 20 bytes.

How it sits in memory

The WHOLE TABLE line shows the layout: all four quarters of region 1, then all four of region 2. This is called row-major order: the last subscript varies fastest. It matters when you hard-code a grid with REDEFINES: write the values one row at a time.

       01  WS-RATE-VALUES.
           05  FILLER         PIC X(16) VALUE "0450060008501200".
           05  FILLER         PIC X(16) VALUE "0650087512001850".
       01  WS-RATE-TABLE REDEFINES WS-RATE-VALUES.
           05  WS-ZONE        OCCURS 2 TIMES.
               10  WS-RATE        PIC 9(2)V99 OCCURS 4 TIMES.

Each FILLER is one zone's row of four 9(2)V99 rates, so WS-RATE(2, 3) is 12.00.

Row totals and column totals

The example shows the two classic traversals:

  • Row totals (per region): outer loop over rows, inner loop over columns, and reset the total at the start of each row.
  • Column totals (per quarter): fix the column, loop over the rows.

PERFORM ... VARYING ... AFTER from the loops module does the "every cell" walk in one statement, which is handy for initialising or printing a grid.

Two subscripts, two ranges

Each subscript must stay within its own OCCURS. With WS-R = 1 and WS-Q = 5, WS-QTR-SALES(WS-R, WS-Q) does not fail. It silently reads WS-QTR-SALES(2, 1), because that is the next 5 bytes in memory. Validate both values from input before using them.

On the job

Deeply nested tables are allowed, but you will rarely see more than three levels of OCCURS. When you meet a big multi-level table in a copybook, sketch it as a grid on paper before changing anything.

Your task

A courier charges by delivery zone and weight band:

Zone Up to 1 kg Up to 5 kg Up to 10 kg Over 10 kg
1 4.50 6.00 8.50 12.00
2 6.50 8.75 12.00 18.50
3 9.00 12.50 17.25 26.00

Hard-code this as a two-dimensional table (zone × band) using REDEFINES.

Read parcels, one per line, until END. Each line is a zone (9) then a weight in kg (9(3)V9, 4 digits, one implied decimal): 20025 is zone 2, 2.5 kg. For each parcel, pick the band ("up to" includes the limit, so exactly 1.0 kg is band 1), look up the charge and display:

ZONE 2 WEIGHT   2.5 CHARGE   8.75

A zone outside 1–3 gets INVALID ZONE instead of a charge and adds nothing to the total:

ZONE 5 WEIGHT   1.0 INVALID ZONE

Finish with TOTAL CHARGES: and the total. The starter has the input record, the edited output fields and the main loop.

fixed format
Run your program to see its output here. The first visible test's input and datasets are used.
Submit to grade your program against every test.