Working with Tables (Arrays) & the OCCURS Clause
COBOL Fundamentals
Chapter 6 · Working with Tables (Arrays) & the OCCURS Clause
Every field so far has held exactly one value. Real COBOL programs constantly need many values of the same shape — twelve months of sales figures, a hundred customer records held in memory at once — and OCCURS is the real clause that makes a single data item repeat into a table.
OCCURS: Defining a Table
Reusing Chapter 5's own real counted-loop variable, WS-I, here's a real one-dimensional table of twelve monthly sales figures:
OCCURS 12 TIMES tells COBOL to reserve twelve real copies of WS-SALES-AMT, one after another in storage. A single element is referenced by writing a subscript directly in parentheses after the item's name:
Subscripts Start at 1, Not 0
WS-SALES-AMT(1) is January, the genuine first element — not January minus one. Chapter 5's own PERFORM VARYING WS-I FROM 1 BY 1 UNTIL WS-I > 12 example above already assumes this directly: it starts WS-I at 1 and stops once it exceeds 12, the real count of elements — no adjustment needed, because COBOL never had a zeroth element to begin with.
INDEXED BY: A Real, More Efficient Alternative to a Subscript
A plain numeric field like WS-I works as a subscript, but COBOL also offers a genuinely different mechanism: an index, declared with INDEXED BY directly on the table itself. Extending Chapter 3's own real WS-CUSTOMER-RECORD into a table of a hundred customers:
WS-I holds an ordinary occurrence number (1, 2, 3, ...) that the runtime has to multiply by the element's own size every single time it's used to find the real storage address. A real index, by contrast, is stored directly as a displacement — it moves by the element's own size each step, not by a plain count of 1 — so the runtime can use it straight away with no multiplication needed. On a table accessed millions of times in a real batch job, that's a genuine, measurable difference, not a cosmetic one.
An indexed table is set and stepped with real SET statements rather than ordinary arithmetic:
SEARCH: Real Sequential Table Lookup
SEARCH walks an indexed table one element at a time, starting from wherever the index currently points — which is exactly why a real SET ... TO 1 normally comes first, to guarantee the search starts at the beginning rather than partway through:
AT END is the real clause that fires only if the whole table is walked with no match found. SEARCH genuinely requires the table to have been declared with INDEXED BY in the first place — it's the index itself that SEARCH advances automatically on every failed comparison.
SEARCH ALL: Real Binary Search
SEARCH ALL is a genuinely different algorithm — a real binary search, jumping to the midpoint of the remaining range each time rather than checking one element after another. That real speed comes with a real requirement: the table's own definition must declare which field it's sorted by, using ASCENDING KEY or DESCENDING KEY:
ASCENDING KEY IS WS-CUST-ID is a genuine promise to the compiler, not an instruction that sorts anything. If the real data actually loaded into the table isn't genuinely in ascending order by WS-CUST-ID, SEARCH ALL doesn't detect the mismatch — it just follows the binary-search algorithm anyway, and a real entry that's genuinely present in the table can come back as "not found," or the wrong entry can be returned, with no error raised anywhere.
Multi-Dimensional Tables
An OCCURS item can itself contain another OCCURS item, producing a genuine multi-dimensional table — here, quarterly sales figures across four real regions:
Referencing one specific element needs both real subscripts at once, separated by a space inside one set of parentheses — the region subscript first, then the quarter:
OCCURS DEPENDING ON: Real Variable-Length Tables
Every table so far has had a fixed, unchanging size. OCCURS DEPENDING ON lets a table's real size vary at runtime instead, between a stated minimum and maximum, driven by the current value of another field:
Here, an order can genuinely hold anywhere from 1 to 50 line items, and the true, current number in use is tracked in WS-LINE-COUNT — a real space-saving mechanism for records where the count of repeating items is itself unknown until the program actually runs.
Hands-On Exercises
Using this chapter's own real WS-MONTHLY-SALES example, explain in your own words why WS-SALES-AMT(1), not WS-SALES-AMT(0), is the correct reference for January's real figure, and why this genuinely matters for someone coming from a language where arrays start at 0.
Using this chapter's own real material, explain in your own words the specific, genuine reason an INDEXED BY index is faster than an ordinary numeric subscript like WS-I, and why SEARCH specifically requires a table to be declared with INDEXED BY in the first place.
A colleague declares WS-CUSTOMER-ENTRY OCCURS 100 TIMES ASCENDING KEY IS WS-CUST-ID, then loads real customer records into the table in the exact order they arrive from an unsorted file, without ever sorting them first. Using this chapter's own real material, explain in your own words what could genuinely go wrong the next time the program runs SEARCH ALL against that table.
Chapter 6 Quick Reference
- OCCURS n TIMES — defines a real table with n repeating elements; subscripts are 1-based, never 0
- Subscript — a plain numeric field (e.g.
WS-I) in parentheses after the item name - INDEXED BY — a real, genuinely faster displacement-based alternative to a subscript; set/advanced with
SET, not ordinary arithmetic - SEARCH — real sequential search from the index's current position; requires
INDEXED BY;AT ENDfor no match - SEARCH ALL — real binary search; requires
ASCENDING KEY/DESCENDING KEYdeclared and data genuinely sorted that way, or results are silently wrong - Multi-dimensional tables —
OCCURSnested insideOCCURS; multiple subscripts are space-separated in one set of parentheses - OCCURS m TO n TIMES DEPENDING ON — a real variable-length table, sized at runtime by another field's current value