COBOL Module 6 – Tables: OCCURS, SEARCH and SORT
SEARCH ALL, table SORT and OCCURS DEPENDING ON
Search COBOL tables with SEARCH and SEARCH ALL, keep them in key order with the table SORT, and size variable-length tables with OCCURS DEPENDING ON safely.
What you will learn
- Search tables with SEARCH and SEARCH ALL and state the precondition of each
- Sort a table in memory with the table SORT statement
- Use OCCURS DEPENDING ON for variable-length tables and validate its counter
- Compare the cost of serial and binary searches from measured runs
Before you start
What you’ll learn · Before you start
What you will learn
- Search tables with SEARCH and SEARCH ALL and state the precondition of each
- Sort a table in memory with the table SORT statement
- Use OCCURS DEPENDING ON for variable-length tables and validate its counter
- Compare the cost of serial and binary searches from measured runs
Before you start
On this page
Two slabs appended to the end of a rate table were enough to make SEARCH ALL report 104 and 162 as not found, although both codes were in the table, and nothing raised an error. A binary search is fast because it trusts the table to be in key order, and that trust has to be earned before every search.
Below you will search tables with SEARCH and SEARCH ALL and meet the condition each one depends on, put a table in order with the table SORT, measure what each search costs as a table grows, and size a table to its data with OCCURS DEPENDING ON without trusting a count that came from outside.
SEARCH reads the table from where its index stands
A serial SEARCH tests its WHEN condition entry by entry and moves the table’s index on after each miss. IBM’s reference says it runs “beginning at the current setting of the search index”, and that before a serial search “you must set the value” of that index. When the index passes the last entry, AT END runs. This rate table holds eight slabs in ascending code order, with a teaching rate for each:
IDENTIFICATION DIVISION.
PROGRAM-ID. SEARCH1.
* A rate table of eight slabs in ascending code order, searched
* with SEARCH, once with a stale index, and with SEARCH ALL.
* The rates are a teaching example.
DATA DIVISION.
WORKING-STORAGE SECTION.
01 RATE-VALUES.
05 FILLER PIC X(7) VALUE "1040650".
05 FILLER PIC X(7) VALUE "1170675".
05 FILLER PIC X(7) VALUE "1250700".
05 FILLER PIC X(7) VALUE "1330710".
05 FILLER PIC X(7) VALUE "1480725".
05 FILLER PIC X(7) VALUE "1560740".
05 FILLER PIC X(7) VALUE "1620750".
05 FILLER PIC X(7) VALUE "1790775".
01 RATE-TABLE REDEFINES RATE-VALUES.
05 RATE-ENTRY OCCURS 8 TIMES
ASCENDING KEY IS RT-CODE
INDEXED BY RT-IDX.
10 RT-CODE PIC 9(3).
10 RT-RATE PIC 9V999.
01 WANTED PIC 9(3).
01 ENTRY-NO PIC 99.
PROCEDURE DIVISION.
MOVE 162 TO WANTED
SET RT-IDX TO 1
PERFORM SERIAL-FIND
MOVE 117 TO WANTED
PERFORM SERIAL-FIND
SET RT-IDX TO 1
PERFORM SERIAL-FIND
MOVE 162 TO WANTED
PERFORM BINARY-FIND
MOVE 140 TO WANTED
PERFORM BINARY-FIND
STOP RUN.
SERIAL-FIND.
SEARCH RATE-ENTRY
AT END
DISPLAY "SEARCH " WANTED ": not found"
WHEN RT-CODE(RT-IDX) = WANTED
SET ENTRY-NO TO RT-IDX
DISPLAY "SEARCH " WANTED ": entry " ENTRY-NO
", rate " RT-RATE(RT-IDX)
END-SEARCH.
BINARY-FIND.
SEARCH ALL RATE-ENTRY
AT END
DISPLAY "SEARCH ALL " WANTED ": not found"
WHEN RT-CODE(RT-IDX) = WANTED
SET ENTRY-NO TO RT-IDX
DISPLAY "SEARCH ALL " WANTED ": entry " ENTRY-NO
", rate " RT-RATE(RT-IDX)
END-SEARCH. from session import files, run
# A serial SEARCH from entry 1, again from where the index was left, and after a SET; then SEARCH ALL for a code that
# is in the table and one that is not.
files("search.cob")
run("""
cobc -x search.cob && ./search
""") Output
$ cobc -x search.cob && ./search SEARCH 162: entry 07, rate 0.750 SEARCH 117: not found SEARCH 117: entry 02, rate 0.675 SEARCH ALL 162: entry 07, rate 0.750 SEARCH ALL 140: not found
Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 search_session.py
The first SEARCH started at entry 1 and found 162 at entry 7. Step through it: each step is one entry the search tests, the way SEARCH moves RT-IDX through the table:
- being compared or looked at
- in its final place
- visited or ruled out
Pseudo-code
-
for i from 0 to n − 1: -
if a[i] = target: return i -
return "not found"
Look for 162 by checking each value from the left until it turns up or the array ends.
When it finishes: Found 162 at index 6.
Try your own input
The figure numbers its positions from 0, as most programming languages do, so its position 6 is entry 7 of the COBOL table. The second search, for 117, printed not found, although 117 is entry 2. It ran without a SET, so it started where the first search had left RT-IDX, at entry 7:
The drawing shows the whole scene; its 6 steps are listed below.
Every step as text
- Where the index stands. The first SEARCH found 162 at entry 7 and left RT-IDX there. Nothing set it back, and IBM's reference says a serial SEARCH begins at the current setting of the search index.
- Entry 7. The search for 117 starts at entry 7. Its code is 162, not 117, so SEARCH moves the index on to entry 8.
- Entry 8. Entry 8 holds 179, not 117 either. The index moves past the last entry.
- Not found. The index is past the highest occurrence, so the search ends and AT END prints 117 not found, although 117 is entry 2 of the table.
- The reset. SET RT-IDX TO 1 puts the index on the first entry again, so the next SEARCH reads the table from the start.
- Found. Entry 1 holds 104, then entry 2 holds 117, so the WHEN is true and the session printed entry 02, rate 0.675.
A SEARCH without SET
Wrong: SEARCH RATE-ENTRY in a paragraph that is performed for many codes, with SET RT-IDX TO 1 only once at the start of the program. Right: SET RT-IDX TO 1 immediately before every serial SEARCH, in the same paragraph.
SEARCH ALL halves a table that is in key order
SEARCH ALL is a binary search: A search that compares the key with the middle entry of the range that can still hold it and keeps one half, so n entries need at most the whole part of log2(n) plus 1 probes. It needs the entries in key order.. It needs the table described with ASCENDING KEY: The phrase of an OCCURS clause that names the field a table is kept in order by, smallest first. SEARCH ALL relies on that order, and the table SORT can produce it. or DESCENDING KEY and INDEXED BY, sets the index itself, and looks at the middle of the range that can still hold the key, so each probe rules out half of what is left. IBM’s reference adds the condition: the results are “predictable only when” the data “is ordered in ASCENDING KEY or DESCENDING KEY order”. In search.cob, SEARCH ALL found 162 at entry 7 and reported 140, which is not in the table, as not found. Step through its probes on the same eight codes:
- being compared or looked at
- in its final place
- visited or ruled out
Pseudo-code
-
lo ← 0; hi ← n − 1 -
while lo ≤ hi: -
mid ← lo + ⌊(hi − lo) / 2⌋ -
if a[mid] = target: return mid -
if a[mid] < target: lo ← mid + 1 -
else: hi ← mid − 1 -
return "not found"
Look for 162. The invariant: if 162 is in the array, it is somewhere in a[lo..hi]. At the start that range is the whole array, positions 0 to 7.
When it finishes: Found 162 at index 6.
Try your own input
The key order is a promise the program makes, and nothing checks it. unsorted.cob keeps the same eight slabs but has two of them appended out of order, 162 and 104 after 179, as an update that adds new slabs at the end might leave them:
IDENTIFICATION DIVISION.
PROGRAM-ID. UNSORTED.
* The rate table with two slabs appended out of order, as an
* update might leave it. SEARCH ALL assumes ascending codes.
DATA DIVISION.
WORKING-STORAGE SECTION.
01 RATE-VALUES.
05 FILLER PIC X(7) VALUE "1170675".
05 FILLER PIC X(7) VALUE "1250700".
05 FILLER PIC X(7) VALUE "1330710".
05 FILLER PIC X(7) VALUE "1480725".
05 FILLER PIC X(7) VALUE "1560740".
05 FILLER PIC X(7) VALUE "1790775".
05 FILLER PIC X(7) VALUE "1620750".
05 FILLER PIC X(7) VALUE "1040650".
01 RATE-TABLE REDEFINES RATE-VALUES.
05 RATE-ENTRY OCCURS 8 TIMES
ASCENDING KEY IS RT-CODE
INDEXED BY RT-IDX.
10 RT-CODE PIC 9(3).
10 RT-RATE PIC 9V999.
01 WANTED PIC 9(3).
PROCEDURE DIVISION.
MOVE 104 TO WANTED
PERFORM BINARY-FIND
MOVE 162 TO WANTED
PERFORM BINARY-FIND
MOVE 133 TO WANTED
PERFORM BINARY-FIND
SORT RATE-ENTRY ON ASCENDING KEY RT-CODE
DISPLAY "after SORT, entry 1 is " RT-CODE(1)
" and entry 8 is " RT-CODE(8)
MOVE 104 TO WANTED
PERFORM BINARY-FIND
MOVE 162 TO WANTED
PERFORM BINARY-FIND
STOP RUN.
BINARY-FIND.
SEARCH ALL RATE-ENTRY
AT END
DISPLAY "SEARCH ALL " WANTED ": not found"
WHEN RT-CODE(RT-IDX) = WANTED
DISPLAY "SEARCH ALL " WANTED ": rate "
RT-RATE(RT-IDX)
END-SEARCH. from session import files, run
# SEARCH ALL on a table whose last two entries are out of order, then the same searches after a table SORT.
files("unsorted.cob")
run("""
cobc -x unsorted.cob && ./unsorted
""") Output
$ cobc -x unsorted.cob && ./unsorted SEARCH ALL 104: not found SEARCH ALL 162: not found SEARCH ALL 133: rate 0.710 after SORT, entry 1 is 104 and entry 8 is 179 SEARCH ALL 104: rate 0.650 SEARCH ALL 162: rate 0.750
Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 unsorted_session.py
SEARCH ALL reported 104 and 162 as not found, while 133, in the part that was still in order, was found. The binary search ruled out the half where each misplaced code really was, because by key order it could not have been there.
Key idea
SEARCH ALL is correct only on a table in the order its KEY clause declares; on any other table it can miss a key that is present, without an error.
Checkpoint
Check: Serial SEARCH and its index · SEARCH ALL and key order
SORT puts a table in key order, in place
The second half of unsorted.cob shows the fix. SORT RATE-ENTRY ON ASCENDING KEY RT-CODE is the table form of the SORT statement. IBM’s reference says it “sorts table elements according to specified table keys”, and that the sorted elements “are placed in the same table”. After it, entry 1 was 104 and entry 8 was 179, and SEARCH ALL found both codes it had missed.
When the SORT names no key, the KEY phrase of the table’s OCCURS clause decides, so the sort and the searches cannot disagree about the order. Where codes can be equal, WITH DUPLICATES keeps equal entries in their original order; IBM’s reference says that without it, their relative order “is undefined”.
Sort once, after loading
Sort a table once, right after it is loaded or changed, and before the first SEARCH ALL, rather than before every search. A load from a file that is supposed to be in order can also check the order as it stores each entry, and fail when a code is smaller than the one before it.
What the two searches cost, measured
A serial search of a table of n entries tests every entry before it can report a miss, and about half of them, on average, for a key that is there. A binary search halves the range at every probe, so it needs at most ⌊log₂ n⌋ + 1 probes: 10 for 1,000 entries, 13 for 5,000 and 15 for 20,000. Step the chart through n doubling, with the lesson’s sizes among the steps, and switch the target between a missing key and every key that is present:
- Linear search, measured (circles)
- Binary search, measured (squares)
- Derived count (dashed): n for linear search; ⌊log₂ n⌋ + 1 for binary search
- The size in focus (ringed)
n = 1: linear search checks 1 value and binary search makes 1 probe. The dashed lines are the derived counts: n and ⌊log₂ n⌋ + 1.
From n = 1 to 20,000, searching for a value larger than every one, the counts grew: linear search from 1 to 20,000 values checked and binary search from 1 to 15 probes. Each matched the derived count at every size: n for linear search and ⌊log₂ n⌋ + 1 for binary search.
Every measured count as a table
| n | Linear search: values checked | Binary search: probes |
|---|---|---|
| 1 | 1 | 1 |
| 2 | 2 | 2 |
| 4 | 4 | 3 |
| 8 | 8 | 4 |
| 16 | 16 | 5 |
| 32 | 32 | 6 |
| 64 | 64 | 7 |
| 128 | 128 | 8 |
| 256 | 256 | 9 |
| 512 | 512 | 10 |
| 1,000 | 1,000 | 10 |
| 1,024 | 1,024 | 11 |
| 2,048 | 2,048 | 12 |
| 4,096 | 4,096 | 13 |
| 5,000 | 5,000 | 13 |
| 8,192 | 8,192 | 14 |
| 16,384 | 16,384 | 15 |
| 20,000 | 20,000 | 15 |
Counts are not times, so this program measures both searches. It makes 200,000 lookups, about half of them for keys that are not in the table, in tables of 1,000, 5,000 and 20,000 keys, built with cobc -O2:
IDENTIFICATION DIVISION.
PROGRAM-ID. SEARCHBENCH.
* 200,000 lookups, half of them for keys that are not in the
* table, with SEARCH or SEARCH ALL. The table holds N keys,
* 2, 4, 6 and so on, N from the command line, at most 20000.
DATA DIVISION.
WORKING-STORAGE SECTION.
01 TABLE-SIZE PIC 9(5) VALUE 1.
01 KEY-TABLE.
05 KEY-ENTRY OCCURS 1 TO 20000 TIMES
DEPENDING ON TABLE-SIZE
ASCENDING KEY IS K-VALUE
INDEXED BY K-IDX.
10 K-VALUE PIC 9(6).
01 ARGS PIC X(20).
01 MODE-ARG PIC X(6).
01 SIZE-ARG PIC X(6).
01 I PIC 9(7) COMP-5.
01 WANTED PIC 9(6).
01 FOUND-COUNT PIC 9(6) VALUE 0.
01 MISS-COUNT PIC 9(6) VALUE 0.
PROCEDURE DIVISION.
ACCEPT ARGS FROM COMMAND-LINE
UNSTRING ARGS DELIMITED BY SPACE INTO MODE-ARG SIZE-ARG
END-UNSTRING
COMPUTE TABLE-SIZE = FUNCTION NUMVAL(SIZE-ARG)
PERFORM VARYING I FROM 1 BY 1 UNTIL I > TABLE-SIZE
COMPUTE K-VALUE(I) = I * 2
END-PERFORM
PERFORM VARYING I FROM 1 BY 1 UNTIL I > 200000
COMPUTE WANTED = FUNCTION MOD(I, 2 * TABLE-SIZE)
IF MODE-ARG = "serial"
SET K-IDX TO 1
SEARCH KEY-ENTRY
AT END ADD 1 TO MISS-COUNT
WHEN K-VALUE(K-IDX) = WANTED
ADD 1 TO FOUND-COUNT
END-SEARCH
ELSE
SEARCH ALL KEY-ENTRY
AT END ADD 1 TO MISS-COUNT
WHEN K-VALUE(K-IDX) = WANTED
ADD 1 TO FOUND-COUNT
END-SEARCH
END-IF
END-PERFORM
DISPLAY MODE-ARG " " TABLE-SIZE " keys: found " FOUND-COUNT
", not found " MISS-COUNT
STOP RUN. from session import files, run
# The same 200,000 lookups with SEARCH and with SEARCH ALL in tables of 1000, 5000 and 20000 keys, built with -O2.
# Bash's time reports the user CPU time of each run.
files("searchbench.cob")
run("""
cobc -x -O2 searchbench.cob
TIMEFORMAT=' user %2U s'; for n in 1000 5000 20000; do time ./searchbench serial $n; time ./searchbench binary $n; done
""") Output
$ cobc -x -O2 searchbench.cob $ TIMEFORMAT=' user %2U s'; for n in 1000 5000 20000; do time ./searchbench serial $n; time ./searchbench binary $n; done serial 01000 keys: found 099900, not found 100100 user 0.15 s binary 01000 keys: found 099900, not found 100100 user 0.08 s serial 05000 keys: found 099980, not found 100020 user 0.50 s binary 05000 keys: found 099980, not found 100020 user 0.08 s serial 20000 keys: found 099995, not found 100005 user 1.85 s binary 20000 keys: found 099995, not found 100005 user 0.09 s
This output changes from run to run: CPU times depend on the computer and change a little from run to run; the found and not-found counts do not
Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 searchbench_session.py
Both searches found the same keys at every size, which checks that they agree. On the arm64 Mac that ran them, under macOS 26, the serial searches took more time in step with the table, roughly three to four times as much each time it grew, while SEARCH ALL stayed near the same small figure at all three sizes. The ratio of the times is far smaller than the ratio of the counts on the chart, because each run also spends time on its loop, the MOD and the counters, which are the same for both searches and are most of what a binary search costs here.
Key idea
A serial SEARCH costs time in proportion to the table, a binary SEARCH ALL hardly any more as the table grows, so a large table that is searched often belongs in key order with SEARCH ALL.
Checkpoint
Check: The table SORT · What each search costs
OCCURS DEPENDING ON makes a table as long as its counter
A statement record that holds as many amounts as the account had transactions does not need room for the largest case in every record. OCCURS 1 TO 10 TIMES DEPENDING ON TXN-COUNT describes such a variable-length table: A table declared with OCCURS min TO max TIMES DEPENDING ON a counter. The counter's current value is the number of entries, so it sets the length of the table and of its record.: IBM’s reference says the counter is “the data item whose current value represents the current number of occurrences”, and the record’s length follows it:
IDENTIFICATION DIVISION.
PROGRAM-ID. ODO.
* A statement record with a variable number of amounts: up to
* 10, as many as TXN-COUNT says. Its length follows the count.
DATA DIVISION.
WORKING-STORAGE SECTION.
01 TXN-COUNT PIC 99.
01 STATEMENT-REC.
05 ACCT-NO PIC X(6) VALUE "AC0417".
05 TXN-AMT PIC 9(5) OCCURS 1 TO 10 TIMES
DEPENDING ON TXN-COUNT.
01 REC-LEN PIC 999.
PROCEDURE DIVISION.
MOVE 2 TO TXN-COUNT
PERFORM SHOW-LENGTH
MOVE 3 TO TXN-COUNT
PERFORM SHOW-LENGTH
MOVE 11 TO TXN-COUNT
PERFORM SHOW-LENGTH
PERFORM CHECK-COUNT
STOP RUN.
SHOW-LENGTH.
COMPUTE REC-LEN = FUNCTION LENGTH(STATEMENT-REC)
DISPLAY "TXN-COUNT " TXN-COUNT ": record of " REC-LEN
" bytes".
CHECK-COUNT.
IF TXN-COUNT < 1 OR TXN-COUNT > 10
DISPLAY "TXN-COUNT " TXN-COUNT ": rejected, the table"
" holds 1 to 10"
END-IF. The session also runs odocheck.cob, which the next section explains:
from session import files, run
# The record's length for three counts, built with -debug, then a write past the count in three builds: normal, -debug,
# and -debug with the IBM dialect.
files("odo.cob", "odocheck.cob")
run("""
cobc -x -debug odo.cob && ./odo
cobc -x odocheck.cob && ./odocheck
!cobc -x -debug -o odocheck-debug odocheck.cob && ./odocheck-debug
cobc -x -debug -std=ibm -o odocheck-ibm odocheck.cob && ./odocheck-ibm
""") Output
$ cobc -x -debug odo.cob && ./odo TXN-COUNT 02: record of 016 bytes TXN-COUNT 03: record of 021 bytes TXN-COUNT 11: record of 061 bytes TXN-COUNT 11: rejected, the table holds 1 to 10 $ cobc -x odocheck.cob && ./odocheck wrote TXN-AMT(3) while TXN-COUNT is 2 $ cobc -x -debug -o odocheck-debug odocheck.cob && ./odocheck-debug libcob: odocheck.cob:15: error: subscript of 'TXN-AMT' out of bounds: 3 note: current maximum subscript for 'TXN-AMT': 2 Last statement of "ODOCHECK" was MOVE at line 15 of odocheck.cob ENTRY ODOCHECK at odocheck.cob:13 Started by ./odocheck-debug [exit status 1] $ cobc -x -debug -std=ibm -o odocheck-ibm odocheck.cob && ./odocheck-ibm wrote TXN-AMT(3) while TXN-COUNT is 2
Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 odo_session.py
The record was 16 bytes with 2 amounts and 21 with 3: 6 bytes of account and 5 for each amount. Step through the three counts:
The drawing shows the whole scene; its 5 steps are listed below.
Every step as text
- Count 2. With TXN-COUNT = 2 the record is ACCT-NO and two amounts, 6 + 2 x 5 = 16 bytes, which is what FUNCTION LENGTH printed.
- Count 3. One more amount makes 21 bytes. Nothing moves in storage; the record simply ends 5 bytes later, because its length is worked out from the count each time it is used.
- The largest record. OCCURS 1 TO 10 TIMES reserves room for ten amounts, so the area is 6 + 10 x 5 = 56 bytes. Any count from 1 to 10 fits.
- Count 11. With TXN-COUNT = 11, FUNCTION LENGTH printed 61 bytes, 5 more than the area holds, and even the -debug build gave no error. IBM's reference calls the behaviour undefined when the count is outside the range of the OCCURS.
- The check. CHECK-COUNT tested the count against 1 to 10 and rejected 11 before the record was used. A count read from input needs that test every time.
A count of 11 gave 61 bytes, 5 more than the 56 bytes the area can hold, and this program was built with -debug, which said nothing about it. IBM’s reference is plain about such a value: “the behavior is undefined if the value of the object is outside of the range”. CHECK-COUNT tested the count against 1 to 10 and rejected 11, which is the only check that ran.
Key idea
The counter of an OCCURS DEPENDING ON decides how long the table is, so test a count that comes from input against the OCCURS limits before anything uses the record.
Checkpoint
Check: OCCURS DEPENDING ON and its counter
A write past the count is caught only in some builds
odocheck.cob writes TXN-AMT(3) while TXN-COUNT is 2, inside the area but past the current count. The normal build let
it happen. The -debug build stopped with subscript of 'TXN-AMT' out of bounds: 3 and the note
current maximum subscript for 'TXN-AMT': 2. The same -debug build with -std=ibm let it happen again.
GnuCOBOL 3.2’s NEWS explains the difference. The full check “which checks against the ODO item is still used in the default dialect, but several dialects now only check against the maximum only”: under the IBM dialect, any subscript up to 10 passes. A test build in the IBM dialect therefore needs the program’s own checks of the count as much as a production build does.
Items after a variable table
GnuCOBOL 3.2 also moved -fodoslide into the dialect settings: the NEWS says it “is now implied with -std=ibm/mvs/bs2000”. It decides where items that follow a variable table are placed, so programs with such records that are built with those dialects may need to be compiled again, or built with -fno-odoslide to keep the old layout.
Checkpoint
Check: Checks of a write past the count
Practise: search, sort and size tables
The questions below come from the searches, sorts and counts of this lesson’s programs. Decide each answer from the code and the table’s order, then compare it with the session:
Practice that adapts to you
Practise this lesson
17 questions about 6 ideas of this lesson. Each answer is explained at once, and what you get right is kept in this browser.
Then build a lookup step where GnuCOBOL is installed:
A rate lookup that refuses an unsorted table
Load the eight slabs of search.cob from a file, check while loading that each code is greater than the one before it, and fail with a message when one is not. Then look up a list of codes with SEARCH ALL. Run it once with the slabs in order and once with 104 moved to the end, as in unsorted.cob, and compare the two runs.
Interview questions
Warm-up (fresher to 2–5 years): what must be true of a table before SEARCH ALL can be used on it?
Its OCCURS clause must declare ASCENDING KEY or DESCENDING KEY and INDEXED BY, and its entries must actually be in that order when the search runs. SEARCH ALL is a binary search: it looks at the middle of the range that can still hold the key and rules out the other half, so an entry out of order can sit in a half it has already ruled out. In the lesson, two slabs appended after 179 made SEARCH ALL report 104 and 162 as not found, without an error, while a code in the sorted part was still found. Sorting the table with SORT … ON ASCENDING KEY after loading it, or checking the order while loading, makes the search correct again. Unlike a serial SEARCH, SEARCH ALL sets its index itself.
Two harder questions follow: a rate lookup that started to miss codes, and a count that came from a damaged record. Decide your first step before you open each one:
Model answers open with any pass: ₹79 for 1 week ($2.99), one payment that never renews.
-
Mid to senior
After a change that lets operators add new rate slabs, a nightly job charges the default rate to some accounts whose slab is in the rate table. The lookup uses SEARCH ALL, and the slabs are loaded from a file. What do you suspect, how do you confirm it, and how do you make the lookup safe?
-
Mid to senior
An input record carries a count of up to 10 amounts, which the program uses as the counter of an OCCURS DEPENDING ON table. One damaged record arrives with a count of 37, and the output for the records after it is garbled. Explain what happened and how you would harden the program.
Organisations named on this page
IBM and the GnuCOBOL project are cited here as sources and have no connection with MySmartCoPilot. SEARCH, SORT and OCCURS DEPENDING ON are quoted from IBM’s documentation and GnuCOBOL’s NEWS, and the outputs and times are runs of GnuCOBOL 3.2.
Flashcards · 8 cards
Recall it, then check
Say the answer to yourself before you look: recalling it is what makes it stick.
-
Where does a serial SEARCH start?
At the current setting of the table's index. SET it to 1 just before every serial SEARCH: a search left where the previous one stopped reported a present code as not found.
-
What does a table need before SEARCH ALL can be used on it?
ASCENDING KEY or DESCENDING KEY and INDEXED BY on its OCCURS, and entries that really are in that order. SEARCH ALL sets its index itself.
-
What happens when SEARCH ALL runs on a table that is out of key order?
It can miss keys that are there, with no error: the binary search rules out the half where a misplaced key sits.
-
How do you put a table into key order in place?
Use the table form of SORT on the entries, with RT-CODE as the ascending key, once after loading. WITH DUPLICATES keeps entries with equal keys in their original order.
-
At most how many probes does a binary search need for 5,000 entries?
13, from the bound ⌊log₂ n⌋ + 1. A serial search tests every one of the 5,000 entries before it can report a miss.
-
What does OCCURS 1 TO 10 TIMES DEPENDING ON TXN-COUNT do to a record's length?
The record is as long as its count: 6 bytes of account plus 5 for each amount gave 16 bytes for 2 amounts and 21 for 3.
-
What if the DEPENDING ON count lies outside the OCCURS range?
IBM calls the behaviour undefined: a count of 11 gave 61 bytes in an area of 56, and -debug said nothing. Test any count that comes from input against the limits.
-
Does a GnuCOBOL -debug build catch a subscript past the current DEPENDING ON count?
In the default dialect, yes. Under -std=ibm it checks against the maximum only, so writing entry 3 while the count is 2 goes through.
Key takeaways
- A serial SEARCH starts at the index’s current setting, so SET the index to 1 before each one; the search for 117 that skipped this step reported a code that was there as not found.
- SEARCH ALL is a binary search that is correct only on a table in its declared key order; two misplaced slabs made it miss 104 and 162 without an error.
- The table SORT puts the entries in key order in place, after which SEARCH ALL found every code.
- A miss costs a serial search every entry and a binary search at most ⌊log₂ n⌋ + 1 probes, and the measured serial times grew with the table while SEARCH ALL’s hardly changed.
- OCCURS DEPENDING ON makes the record as long as its count, 16 bytes for 2 amounts and 21 for 3, and a count outside the OCCURS range is undefined, so it is validated; GnuCOBOL’s IBM dialect checks subscripts only against the maximum.
Terms in this lesson
- ASCENDING KEY
- The phrase of an OCCURS clause that names the field a table is kept in order by, smallest first. SEARCH ALL relies on that order, and the table SORT can produce it.
- binary search
- A search that compares the key with the middle entry of the range that can still hold it and keeps one half, so n entries need at most the whole part of log2(n) plus 1 probes. It needs the entries in key order.
- variable-length table
- A table declared with OCCURS min TO max TIMES DEPENDING ON a counter. The counter's current value is the number of entries, so it sets the length of the table and of its record.
References
- Enterprise COBOL for z/OS 6.5: SEARCH statement (IBM)
- Enterprise COBOL for z/OS 6.5: SEARCH statement, serial search (IBM)
- Enterprise COBOL for z/OS 6.5: SEARCH statement, binary search (IBM)
- Enterprise COBOL for z/OS 6.5: SORT statement (IBM)
- Enterprise COBOL for z/OS 6.5: OCCURS DEPENDING ON clause (IBM)
- GnuCOBOL 3.2 NEWS (GnuCOBOL project (GNU))
Report a problem with this lesson
Kept only in this browser. Your Learn progress