COBOL Table Processing

Table processing in COBOL means defining, loading, and accessing arrays (tables) of data using the OCCURS clause, subscripts, and indexes. Tables can be fixed-length or variable-length (OCCURS DEPENDING ON). You traverse them with PERFORM VARYING or search them with SEARCH (linear) or SEARCH ALL (binary). This page explains how to define tables, when to use subscripts versus indexes, how to use SEARCH and SEARCH ALL, and how to load and process tables safely.

Explain Like I'm Five: What Is a Table?

A table is like a row of boxes. Each box has the same shape (same fields: e.g. a number and a name). You say "how many boxes" (OCCURS 100 or 1 TO 500 DEPENDING ON a counter). To look at one box you say "the first," "the second," or "the tenth"—that number is the subscript or index. To find a box with a certain value you can walk along (SEARCH) or, if the boxes are in order, jump to the middle and then half again (SEARCH ALL). Table processing is the set of rules for creating these rows and reading or searching them.

Defining a Table with OCCURS

The OCCURS clause defines a repeating structure. You attach it to a group or elementary item and specify how many times it repeats. Fixed-length: OCCURS 100 TIMES means exactly 100 elements. Variable-length: OCCURS 1 TO 500 TIMES DEPENDING ON WS-COUNT means the number of valid elements is the current value of WS-COUNT (which must be between 1 and 500 when you use the table). The DEPENDING ON variable must be a numeric item defined outside the table (not inside the repeating group). Storage for variable-length tables is typically allocated for the maximum (500); at runtime only elements 1 through WS-COUNT are considered in use. If you reference an element beyond the current DEPENDING ON value, results are undefined or can cause abends.

cobol
1
2
3
4
5
6
7
8
9
10
11
12
13
01 RATE-TABLE. 05 RATE-ENTRY OCCURS 50 TIMES INDEXED BY RATE-IX. 10 RATE-CODE PIC X(4). 10 RATE-VALUE PIC 9(3)V99. 01 ORDER-TABLE. 05 ORDER-ENTRY OCCURS 1 TO 200 TIMES DEPENDING ON ORDER-COUNT INDEXED BY ORD-IX. 10 ORD-NUM PIC 9(6). 10 ORD-AMT PIC 9(7)V99. 01 ORDER-COUNT PIC 9(3) VALUE 0.

RATE-TABLE is fixed: always 50 entries. ORDER-TABLE has a maximum of 200 entries but only ORDER-COUNT entries are valid; you must set ORDER-COUNT when you load the table and keep it correct when adding or removing entries.

Subscripts Versus Indexes

A subscript is an ordinary numeric data item (e.g. PIC 9(3)) that you use to reference a table element: TABLE-ENTRY(I). You set it with MOVE or ADD (e.g. MOVE 1 TO I, then ADD 1 TO I in a loop). You are responsible for keeping it within bounds (1 to the table size or DEPENDING ON value). An index is a special variable created by INDEXED BY on the OCCURS clause. You set it with SET (e.g. SET IX TO 1). You cannot use MOVE or arithmetic on an index; use SET. Indexes are required for SEARCH and SEARCH ALL, and the compiler can generate more efficient code for index-based access. For simple sequential loops, subscripts are common; for search operations, use the index defined with INDEXED BY.

Accessing Table Elements

Reference an element by appending (subscript or index) in parentheses: TABLE-ENTRY(I) or TABLE-ENTRY(IX). For a group item, you can reference a subordinate: TABLE-ENTRY(I).FIELD-NAME. The subscript or index must be within the valid range (1 to OCCURS count or 1 to DEPENDING ON value). Out-of-range subscripts can cause protection exceptions or wrong data. When using DEPENDING ON, never reference an element at a position greater than the control variable. For variable-length tables, the control variable is often set when reading from a file (e.g. ADD 1 TO ORDER-COUNT after each successful read) and used as the upper bound in loops.

Sequential Traversal with PERFORM VARYING

To process every element from 1 to N, use PERFORM VARYING with a subscript: PERFORM VARYING I FROM 1 BY 1 UNTIL I > ORDER-COUNT ... END-PERFORM. Inside the loop, use TABLE-ENTRY(I). This keeps the subscript in bounds as long as ORDER-COUNT is correct. You can also use the index: SET IX TO 1, then PERFORM UNTIL IX > ORDER-COUNT ... SET IX UP BY 1 END-PERFORM. SET IX UP BY 1 advances the index. Using the index is consistent with SEARCH, where the index is left at the found position.

cobol
1
2
3
PERFORM VARYING I FROM 1 BY 1 UNTIL I > ORDER-COUNT DISPLAY ORD-NUM(I) ORD-AMT(I) END-PERFORM.

SEARCH: Linear Search

SEARCH performs a sequential search. You must SET the index to the starting position (usually 1) before SEARCH. The statement SEARCH table-name AT END imperative-statement WHEN condition imperative-statement END-SEARCH steps through the table. When a WHEN condition is true, the associated statements run and the SEARCH ends. If no WHEN is true before the end of the table, AT END runs. After SEARCH, the index is at the position where the condition was true, or past the last element if AT END ran. You can have multiple WHEN clauses; the first match wins. Use SEARCH when the table is small or not sorted.

cobol
1
2
3
4
5
6
7
SET RATE-IX TO 1 SEARCH RATE-ENTRY AT END DISPLAY 'Rate not found' WHEN RATE-CODE(RATE-IX) = WS-SEARCH-CODE MOVE RATE-VALUE(RATE-IX) TO WS-RATE END-SEARCH.

SEARCH ALL: Binary Search

SEARCH ALL does a binary search. The table must be sorted in ascending order by the key you use in the WHEN clause. You do not SET the index before SEARCH ALL; the statement manages the index internally. Syntax: SEARCH ALL table-name AT END ... WHEN key-name (index) = value ... END-SEARCH. The WHEN must use an equality comparison with the search key. SEARCH ALL is much faster than SEARCH for large tables (O(log n) vs O(n)) but requires the table to be in order. If the table is not sorted, the result is undefined.

SEARCH vs SEARCH ALL
AspectSEARCHSEARCH ALL
AlgorithmLinear (sequential).Binary (half-interval).
Table orderAny order.Must be sorted ascending by search key.
IndexSET index to 1 (or start) before SEARCH.No need to SET; SEARCH ALL sets it.
PerformanceO(n).O(log n) for large tables.

Loading a Table from a File

Common pattern: initialize the count to 0, open the file, read in a loop. For each record, add 1 to the count, check that the count does not exceed the maximum OCCURS, then MOVE the record to the table element at that subscript (e.g. MOVE record TO TABLE-ENTRY(WS-COUNT)). At end of file, exit the loop. The count now holds the number of loaded elements; use it as the upper bound for loops and as the DEPENDING ON value for variable-length tables. If the file can have more records than the table size, stop when the count reaches the maximum to avoid overflow.

cobol
1
2
3
4
5
6
7
8
MOVE 0 TO ORDER-COUNT PERFORM UNTIL EOF OR ORDER-COUNT >= 200 READ ORDER-FILE AT END SET EOF TO TRUE NOT AT END ADD 1 TO ORDER-COUNT MOVE ORD-REC TO ORDER-ENTRY(ORDER-COUNT) END-READ END-PERFORM.

Step-by-Step: Defining and Using a Fixed Table

  1. In the DATA DIVISION, define the table with OCCURS n TIMES and INDEXED BY index-name. Define the elementary or group items under it.
  2. To scan the table, use PERFORM VARYING subscript FROM 1 BY 1 UNTIL subscript > n. Use table-element(subscript) inside the loop.
  3. To search, SET index TO 1, then SEARCH table-element AT END ... WHEN key(index) = value ... END-SEARCH. Use the index after SEARCH to access the found element.

Step-by-Step: Variable-Length Table with DEPENDING ON

  1. Define the table with OCCURS 1 TO max TIMES DEPENDING ON control-var INDEXED BY index-name. Define control-var as a numeric item outside the table.
  2. When loading, set control-var to 0, then for each item add 1 to control-var and MOVE the item to table-element(control-var). Do not exceed max.
  3. When processing, use control-var as the upper bound: PERFORM VARYING I FROM 1 BY 1 UNTIL I > control-var. Never reference table-element(k) for k > control-var.

Best Practices

Test Your Knowledge

Test Your Knowledge

1. To use SEARCH ALL, the table must be:

  • Unsorted
  • Sorted in ascending order by the key used in WHEN
  • Sorted in descending order
  • Empty

2. The variable in DEPENDING ON must:

  • Be inside the table
  • Be a numeric item outside the table, with value in the OCCURS range when the table is used
  • Be alphanumeric
  • Equal zero

3. To access the third element of TABLE-ENTRY you can:

  • MOVE 3 TO TABLE-ENTRY
  • Use TABLE-ENTRY(3) with a subscript or index of 3
  • Use TABLE-ENTRY(2) because indexes are 0-based
  • Use SEARCH only