Variable-length tables with OCCURS DEPENDING ON
A table loaded from input rarely fills every slot. You declare room for 500
branches, and today there are 212. So far you have handled that by keeping
a count and using it as every loop limit. OCCURS DEPENDING ON (ODO) builds
the count into the table itself.
IDENTIFICATION DIVISION.
PROGRAM-ID. ODODEMO.
DATA DIVISION.
WORKING-STORAGE SECTION.
01 WS-ITEM-COUNT PIC 9(2) VALUE ZERO.
01 WS-ITEM-TABLE.
05 WS-ITEM OCCURS 1 TO 50 TIMES
DEPENDING ON WS-ITEM-COUNT
INDEXED BY ITEM-IX.
10 WS-ITEM-CODE PIC X(4).
PROCEDURE DIVISION.
MOVE 3 TO WS-ITEM-COUNT
MOVE "A100" TO WS-ITEM-CODE(1)
MOVE "B200" TO WS-ITEM-CODE(2)
MOVE "C300" TO WS-ITEM-CODE(3)
DISPLAY "TABLE: [" WS-ITEM-TABLE "]"
DISPLAY "LENGTH: " FUNCTION LENGTH(WS-ITEM-TABLE)
MOVE 2 TO WS-ITEM-COUNT
DISPLAY "TABLE: [" WS-ITEM-TABLE "]"
SET ITEM-IX TO 1
SEARCH WS-ITEM
AT END DISPLAY "C300 NOT FOUND"
WHEN WS-ITEM-CODE(ITEM-IX) = "C300"
DISPLAY "C300 FOUND"
END-SEARCH
STOP RUN.
Output:
TABLE: [A100B200C300]
LENGTH: 000000012
TABLE: [A100B200]
C300 NOT FOUND
What DEPENDING ON changes
OCCURS 1 TO 50 TIMESsets the minimum and maximum. Storage is always reserved for the maximum, 50 × 4 bytes here.- The object of
DEPENDING ON(WS-ITEM-COUNT) gives the current number of elements. It must be a numeric integer defined outside the table, usually just before it. - Anything that treats the table as a whole uses the current size: a
DISPLAYorMOVEof the group,FUNCTION LENGTH, and, most usefully,SEARCHandSEARCH ALL. Cutting the count to 2 madeC300disappear from the search, although its bytes are still in memory.
That last point solves the SEARCH ALL problem from earlier: with ODO the
binary search only covers the loaded entries, so empty slots at the end
cannot break the key order.
Set the count first
Load in this order: set the count, then fill the elements. Or add 1 to the count before storing each element, as in the loading lesson. If the count is still 0 when you store element 1, references to it are outside the table's current size.
ODO does not protect you
The maximum in OCCURS 1 TO 50 is not enforced at run time (unless the
program is compiled with SSRANGE). If input sets the count to 75, the
program cheerfully treats 75 elements as valid, and whatever comes after the
table in storage is overwritten. Check the count against the maximum before
you use it:
ACCEPT WS-ITEM-COUNT
IF WS-ITEM-COUNT < 1 OR WS-ITEM-COUNT > 50
DISPLAY "INVALID ITEM COUNT"
MOVE 16 TO RETURN-CODE
STOP RUN
END-IF
A return code of 16 conventionally means "severe error". The job scheduler sees it and stops any dependent steps.
Records that vary in length
ODO is also how COBOL describes variable-length records: a customer record with 1 to 12 address lines, or an order with 1 to 99 items. The count field sits in the fixed part of the record, ahead of the table. You will see this in file layouts (copybooks) all the time.
On the job
Unexpected high values in an ODO count are a well-known source of storage overlays and S0C4 abends (a protection exception: the program touched memory it does not own). When a program abends in a table loop, display the ODO count first. It is often garbage from a bad input record.
Your task
The card authorisation batch loads today's credit limits, then checks purchase requests against them.
Input
- The number of accounts, which must be 1–20
- That many account records: account number
X(5)then credit limit9(5)V99(7 digits, 2 implied decimals).100010050000is account 10001, limit 500.00 - Purchase requests in the same layout (account, amount), until
END
Output
If the count is outside 1–20, display only INVALID ACCOUNT COUNT.
Otherwise display ACCOUNTS LOADED: nn, then one line per request:
ACCOUNTS LOADED: 03
10001 250.00 APPROVED
20002 200.01 DECLINED
99999 1.00 UNKNOWN ACCOUNT
A request is APPROVED if the amount is less than or equal to the limit,
otherwise DECLINED.
Make WS-ACCT a variable-length table with OCCURS 1 TO 20 TIMES
DEPENDING ON WS-ACCT-COUNT, load it, and look accounts up with SEARCH.
Format amounts with WS-AMOUNT-OUT.