COBOLMainframe

COBOL Table Handling: OCCURS, INDEXED BY, SEARCH, and SEARCH ALL

TT
TopicTrick
COBOL Table Handling: OCCURS, INDEXED BY, SEARCH, and SEARCH ALL

COBOL tables (the COBOL term for arrays) are defined with the OCCURS clause and searched with SEARCH or SEARCH ALL. Unlike languages where arrays are first-class types, COBOL tables are embedded within the record hierarchy, making them a natural fit for the fixed-length, structure-oriented data that mainframe programs process. This guide covers everything from basic one-dimensional tables through variable-length DEPENDING ON tables and binary search.

Defining Tables with OCCURS

One-Dimensional Table

cobol
WORKING-STORAGE SECTION.
01 WS-MONTH-TABLE.
   05 WS-MONTH-ENTRY OCCURS 12 TIMES
                     INDEXED BY WS-MONTH-IDX.
      10 WME-NAME     PIC X(10).
      10 WME-DAYS     PIC 9(2) COMP.
      10 WME-QUARTER  PIC 9(1) COMP.

Each of the 12 entries has three fields. Total storage: 12 × 13 bytes = 156 bytes.

Access elements using a subscript (numeric field) or an index (defined by INDEXED BY):

cobol
*> Subscript access (numeric field):
MOVE 'JANUARY   ' TO WME-NAME(1).
MOVE 31 TO WME-DAYS(1).

*> Index access:
SET WS-MONTH-IDX TO 1.
MOVE 'JANUARY   ' TO WME-NAME(WS-MONTH-IDX).

Subscript vs Index

Both subscripts and indexes access table elements, but they work differently internally:

cobol
*> Subscript — any numeric field:
01 WS-SUB          PIC 9(4) COMP.
MOVE 3 TO WS-SUB.
DISPLAY WME-NAME(WS-SUB).       *> accesses element 3

*> Index — defined in OCCURS, manipulated with SET:
SET WS-MONTH-IDX TO 3.
DISPLAY WME-NAME(WS-MONTH-IDX). *> accesses element 3

The compiler converts a subscript to a byte offset at runtime: offset = (subscript - 1) × element_size. An index is already stored as a byte offset, so no multiplication is needed. For large tables accessed frequently, indexes are faster.

Index rules:

  • Use SET to assign a value (not MOVE or ADD)
  • Use SET index UP BY n or SET index DOWN BY n to increment/decrement
  • Indexes cannot be used in arithmetic statements
  • Indexes can be compared with IF: IF WS-MONTH-IDX > 6

Initializing Tables

VALUE on a Group Level

cobol
01 WS-FLAGS-TABLE.
   05 WS-FLAG OCCURS 100 TIMES PIC X VALUE 'N'.

All 100 flags initialize to 'N'. This works for simple tables where every element starts with the same value.

INITIALIZE Statement

cobol
INITIALIZE WS-MONTH-TABLE.    *> numeric fields → 0, alpha fields → spaces

INITIALIZE WS-FLAGS-TABLE
    REPLACING ALPHANUMERIC BY 'N'.

PERFORM VARYING

For complex initialization where each element needs a different value:

cobol
PERFORM VARYING WS-SUB FROM 1 BY 1
    UNTIL WS-SUB > 12
    MOVE WS-MONTH-NAMES(WS-SUB) TO WME-NAME(WS-SUB)
    MOVE WS-MONTH-DAYS-TABLE(WS-SUB) TO WME-DAYS(WS-SUB)
END-PERFORM.

SEARCH (Linear Search)

SEARCH scans a table sequentially from the current index position until it finds a match or exhausts the table:

cobol
01 WS-STATE-TABLE.
   05 WS-STATE OCCURS 50 TIMES
               INDEXED BY WS-STATE-IDX.
      10 WST-CODE    PIC X(2).
      10 WST-NAME    PIC X(20).
      10 WST-REGION  PIC X(10).

*> ... (table loaded from file at initialization)

LOOKUP-STATE.
    SET WS-STATE-IDX TO 1
    SEARCH WS-STATE
        AT END
            MOVE 'NOT FOUND' TO WS-RESULT-REGION
            SET WS-NOT-FOUND TO TRUE
        WHEN WST-CODE(WS-STATE-IDX) = WS-SEARCH-CODE
            MOVE WST-REGION(WS-STATE-IDX) TO WS-RESULT-REGION
            SET WS-FOUND TO TRUE
    END-SEARCH.

Key points about SEARCH:

  • Always SET index TO 1 before SEARCH (unless resuming a partial scan)
  • SEARCH increments the index automatically on each non-matching element
  • AT END executes if SEARCH reaches the end without finding a match
  • Multiple WHEN clauses are allowed — first match wins
  • SEARCH works on unsorted tables

SEARCH ALL (Binary Search)

SEARCH ALL requires the table to be declared with ASCENDING KEY or DESCENDING KEY, and the actual data must be sorted in that order:

cobol
01 WS-ZIP-CODE-TABLE.
   05 WS-ZIP-ENTRY OCCURS 1000 TIMES
                   ASCENDING KEY IS WZE-ZIP-CODE
                   INDEXED BY WS-ZIP-IDX.
      10 WZE-ZIP-CODE   PIC X(5).
      10 WZE-CITY       PIC X(30).
      10 WZE-STATE      PIC X(2).

LOOKUP-ZIP-CODE.
    SEARCH ALL WS-ZIP-ENTRY
        AT END
            MOVE 'UNKNOWN         ' TO WS-CITY
            MOVE '  '               TO WS-STATE
        WHEN WZE-ZIP-CODE(WS-ZIP-IDX) = WS-SEARCH-ZIP
            MOVE WZE-CITY(WS-ZIP-IDX)  TO WS-CITY
            MOVE WZE-STATE(WS-ZIP-IDX) TO WS-STATE
    END-SEARCH.

Key points about SEARCH ALL:

  • No need to SET the index before SEARCH ALL — it sets the index internally
  • Only one WHEN clause is allowed
  • The WHEN condition must compare the KEY field to a value
  • The table data must actually be sorted — SEARCH ALL does not sort it
  • For 1,000 elements: SEARCH checks up to 1,000; SEARCH ALL checks at most 10

Multi-Dimensional Tables

Tables can be nested up to 7 levels deep using nested OCCURS:

cobol
01 WS-SALES-TABLE.
   05 WS-REGION OCCURS 5 TIMES
                INDEXED BY WS-REG-IDX.
      10 WS-MONTH OCCURS 12 TIMES
                  INDEXED BY WS-MON-IDX.
         15 WSM-SALES-AMOUNT  PIC S9(11)V99 COMP-3.
         15 WSM-UNIT-COUNT    PIC 9(7) COMP.

Access requires both indexes or subscripts:

cobol
*> Subscript access:
ADD WS-TRANSACTION-AMOUNT
    TO WSM-SALES-AMOUNT(WS-CURRENT-REGION WS-CURRENT-MONTH).

*> Nested PERFORM with index:
PERFORM VARYING WS-REG-IDX FROM 1 BY 1
    UNTIL WS-REG-IDX > 5
    AFTER WS-MON-IDX FROM 1 BY 1
    UNTIL WS-MON-IDX > 12
        INITIALIZE WSM-SALES-AMOUNT(WS-REG-IDX WS-MON-IDX)
        INITIALIZE WSM-UNIT-COUNT(WS-REG-IDX WS-MON-IDX)
END-PERFORM.

Variable-Length Tables: OCCURS DEPENDING ON

OCCURS n TIMES DEPENDING ON data-name creates a table whose active size varies at runtime:

cobol
01 WS-ORDER-RECORD.
   05 OR-ORDER-ID          PIC X(10).
   05 OR-ITEM-COUNT        PIC 9(3) COMP.
   05 OR-ITEM-TABLE.
      10 OR-ITEM OCCURS 1 TIMES
                 UP TO 50 TIMES
                 DEPENDING ON OR-ITEM-COUNT.
         15 ORI-PRODUCT-ID  PIC X(10).
         15 ORI-QUANTITY    PIC 9(5) COMP.
         15 ORI-UNIT-PRICE  PIC S9(7)V99 COMP-3.

When you set OR-ITEM-COUNT to 3, only elements 1 through 3 are considered active. The record length used for file I/O is calculated as the fixed portion plus OR-ITEM-COUNT × element_size.

cobol
MOVE 3 TO OR-ITEM-COUNT
MOVE 'PROD-001  ' TO ORI-PRODUCT-ID(1)
MOVE 5 TO ORI-QUANTITY(1)
MOVE 29.99 TO ORI-UNIT-PRICE(1)
*> ... fill elements 2 and 3 ...
WRITE ORDER-RECORD-AREA.    *> writes variable-length record

Rules for DEPENDING ON:

  • The DEPENDING ON field must be in WORKING-STORAGE, not in the table itself
  • The minimum is always 1 (not 0)
  • The maximum is the value after OCCURS (or UP TO for clarity)
  • Moving or comparing a group that contains DEPENDING ON uses the current active length

Practical Pattern: Country Code Lookup Table

cobol
WORKING-STORAGE SECTION.
01 WS-CURRENCY-TABLE.
   05 WS-CCY-ENTRY OCCURS 30 TIMES
                   ASCENDING KEY IS WCCY-CODE
                   INDEXED BY WS-CCY-IDX.
      10 WCCY-CODE       PIC X(3).
      10 WCCY-NAME       PIC X(20).
      10 WCCY-SYMBOL     PIC X(3).
      10 WCCY-DECIMALS   PIC 9(1) COMP.

01 WS-CCY-COUNT          PIC 9(2) COMP VALUE 5.

PROCEDURE DIVISION.

LOAD-CURRENCY-TABLE.
    MOVE 'EUR' TO WCCY-CODE(1)
    MOVE 'EURO                ' TO WCCY-NAME(1)
    MOVE 'EUR' TO WCCY-SYMBOL(1)
    MOVE 2 TO WCCY-DECIMALS(1)

    MOVE 'GBP' TO WCCY-CODE(2)
    MOVE 'BRITISH POUND       ' TO WCCY-NAME(2)
    MOVE 'GBP' TO WCCY-SYMBOL(2)
    MOVE 2 TO WCCY-DECIMALS(2)

    MOVE 'JPY' TO WCCY-CODE(3)
    MOVE 'JAPANESE YEN        ' TO WCCY-NAME(3)
    MOVE 'JPY' TO WCCY-SYMBOL(3)
    MOVE 0 TO WCCY-DECIMALS(3)

    MOVE 'USD' TO WCCY-CODE(4)
    MOVE 'US DOLLAR           ' TO WCCY-NAME(4)
    MOVE 'USD' TO WCCY-SYMBOL(4)
    MOVE 2 TO WCCY-DECIMALS(4)

    MOVE 'ZAR' TO WCCY-CODE(5)
    MOVE 'SOUTH AFRICAN RAND  ' TO WCCY-NAME(5)
    MOVE 'ZAR' TO WCCY-SYMBOL(5)
    MOVE 2 TO WCCY-DECIMALS(5).

FIND-CURRENCY.
    SEARCH ALL WS-CCY-ENTRY
        AT END
            MOVE 'UNKNOWN CURRENCY' TO WS-ERROR-MSG
            SET WS-ERROR-FOUND TO TRUE
        WHEN WCCY-CODE(WS-CCY-IDX) = WS-INPUT-CCY-CODE
            MOVE WCCY-NAME(WS-CCY-IDX) TO WS-DISPLAY-NAME
            MOVE WCCY-DECIMALS(WS-CCY-IDX) TO WS-DECIMAL-PLACES
    END-SEARCH.

Next Steps

Table handling enables in-memory lookups and multi-dimensional data structures. The next batch processing topic is SORT and MERGE — sorting input files and merging sorted datasets using COBOL's built-in sort facility. See COBOL Sort and Merge, or return to the COBOL Mastery course.