Searching and Sorting Tables
|
This content was generated with the assistance of AI and should be verified against the official documentation before being relied on in production. The reference material consulted while preparing these pages is listed in the section bibliography. |
This page is written against ISO/IEC 1989:2023 (edition 3, January 2023; an upcoming 4th edition is in ballot), where SEARCH and SEARCH ALL are part of the table-handling facility inherited from COBOL 85 and the table form of SORT was added by COBOL 2002; against IBM Enterprise COBOL for z/OS 6.5 (PTFs through September 2026); and against GnuCOBOL 3.2 (28 July 2023, the latest release), with which every program on it was compiled and run on 2026-10-20 with cobc -x -Wall (and again with -std=ibm, which printed nothing different). The programs that are deliberately wrong say so, and the compiler messages shown are the ones cobc (GnuCOBOL) 3.2.0 printed.
COBOL has no built-in dictionary or list type: a lookup table is an OCCURS item in WORKING-STORAGE that the program fills (from a VALUE and REDEFINES, or from a file) and then walks with a statement made for the purpose. This page covers the three statements that do that work: the serial SEARCH, the binary SEARCH ALL, and the SORT statement in its table form, which puts a table into the order that SEARCH ALL needs. The table definitions themselves (OCCURS, INDEXED BY, SET, subscripts versus indexes, OCCURS DEPENDING ON) are explained on Tables and OCCURS; the file-based SORT and MERGE statements are on SORT and MERGE. The examples use the Circulation public-library scenario: the book master BOOKMAST and the member master MEMBMAST are loaded into tables, and the day’s checkouts from LOANTRAN are searched by member and book.
Where the book is outdated: Taulli’s Modern Mainframe Development (2022, chapter 6) introduces OCCURS, INDEXED BY, SEARCH and SEARCH ALL at survey level, and for sorting it shows only a DFSORT job in JCL. It does not show the COBOL SORT statement at all, so neither the file form nor the table form (format 2) that this page documents. Its examples were written for GnuCOBOL through OpenCobolIDE, an unmaintained IDE (see Getting Started).
The two search statements at a glance
SEARCH (format 1) |
SEARCH ALL (format 2) |
|
|---|---|---|
Method |
Serial: tests the table elements one after another from the current index value |
Binary: halves the remaining range at every test |
Order of the table |
Any; the order is irrelevant |
The data must really be in the order that the |
Starting point |
Whatever the search index holds when the statement starts, so the program must |
None; the whole table is searched and the index need not be initialized |
|
Any number of |
Exactly one |
Table definition |
|
|
Serial search: SEARCH
Syntax
SEARCH table-name
[VARYING index-name-or-identifier]
[AT END imperative-statement]
WHEN condition imperative-statement-or-NEXT SENTENCE
[WHEN condition imperative-statement-or-NEXT SENTENCE] ...
[END-SEARCH]
table-name is the item that carries the OCCURS clause; it is written without a subscript and without reference modification. The index it uses is the first (or only) index named in its INDEXED BY phrase, called the search index. The statement works like this:
-
If the search index is already greater than the highest occurrence, the search ends at once and the
AT ENDphrase runs. -
Otherwise the
WHENconditions are evaluated in the order in which they are written. The first one that is true ends the search, its statements run and control passes to the end of theSEARCH. The search index then addresses the element that satisfied the condition. -
If no condition is true, the search index moves to the next element and step 2 repeats.
-
If the end of the table is reached without a hit, the search ends and the
AT ENDphrase runs. Without anAT ENDphrase, control just passes to the next statement.
The search index is the only index that SEARCH changes (plus the VARYING object, below). That has three consequences that every program must respect:
-
Initialize the index first. The search starts at the index’s current value, so
SET table-index TO 1before each search from the top. Enterprise COBOL requires the index to hold a valid value even if the program never uses it, and in a multidimensional table every superordinate index has to be set as well. -
The index is left where the search stopped. After a hit it addresses the matching element, which is exactly what the
WHENstatements need. After a miss it is past the end of the table, so a following search without aSETfinds nothing at all. -
Only one dimension per statement. To search a two-dimensional table the program executes a
SEARCHfor the inner table once per outer element (shown later), writing the indexes of all dimensions in theWHENcondition.
For a table defined with OCCURS ... DEPENDING ON, the search stops at the current value of the object of the DEPENDING ON phrase, not at the maximum, so that object must contain the real number of elements before the search (this is checked by the first example). END-SEARCH closes the statement, which is what allows a SEARCH to sit inside an IF or another conditional statement. NEXT SENTENCE in a WHEN phrase transfers control to the statement after the next period, not after END-SEARCH, so in new code use CONTINUE instead. Index data items cannot be used as subscripts, and the table’s ASCENDING KEY or DESCENDING KEY is ignored by the serial search.
A first serial search
The program loads the book master into a table whose size follows BOOK-COUNT, then looks a book up by ISBN. It uses VARYING with a plain numeric item so the entry number can be shown, and ends with a search that has two WHEN phrases.
IDENTIFICATION DIVISION.
PROGRAM-ID. SERIAL1.
* Serial SEARCH over the book table loaded from BOOKMAST.
ENVIRONMENT DIVISION.
INPUT-OUTPUT SECTION.
FILE-CONTROL.
SELECT BOOKMAST ASSIGN TO "BOOKMAST"
ORGANIZATION IS LINE SEQUENTIAL.
DATA DIVISION.
FILE SECTION.
FD BOOKMAST.
COPY BOOKREC.
WORKING-STORAGE SECTION.
01 EOF-FLAG PIC X VALUE "N".
88 AT-EOF VALUE "Y".
01 BOOK-COUNT PIC 9(3) VALUE ZERO.
01 BOOK-TABLE.
05 BOOK-ENTRY OCCURS 1 TO 100 TIMES
DEPENDING ON BOOK-COUNT
INDEXED BY BK-IX.
10 TB-ID PIC 9(6).
10 TB-ISBN PIC X(13).
10 TB-TITLE PIC X(30).
10 TB-COPIES PIC 9(3).
10 TB-STATUS PIC X.
01 WANTED-ISBN PIC X(13).
01 POSITION-NO PIC 9(3).
PROCEDURE DIVISION.
MAIN-PARA.
PERFORM LOAD-TABLE
MOVE "9780000000028" TO WANTED-ISBN
PERFORM FIND-BOOK
MOVE "9780000000099" TO WANTED-ISBN
PERFORM FIND-BOOK
PERFORM FIND-FIRST-WITHDRAWN
STOP RUN.
LOAD-TABLE.
OPEN INPUT BOOKMAST
PERFORM UNTIL AT-EOF
READ BOOKMAST
AT END SET AT-EOF TO TRUE
NOT AT END
ADD 1 TO BOOK-COUNT
MOVE BOOK-ID TO TB-ID (BOOK-COUNT)
MOVE BOOK-ISBN TO TB-ISBN (BOOK-COUNT)
MOVE BOOK-TITLE TO TB-TITLE (BOOK-COUNT)
MOVE BOOK-COPIES TO TB-COPIES (BOOK-COUNT)
MOVE BOOK-STATUS TO TB-STATUS (BOOK-COUNT)
END-READ
END-PERFORM
CLOSE BOOKMAST.
FIND-BOOK.
SET BK-IX TO 1
MOVE 1 TO POSITION-NO
SEARCH BOOK-ENTRY VARYING POSITION-NO
AT END
DISPLAY WANTED-ISBN " not in the catalogue"
WHEN TB-ISBN (BK-IX) = WANTED-ISBN
DISPLAY WANTED-ISBN " found at entry " POSITION-NO
": " TB-TITLE (BK-IX)
END-SEARCH.
FIND-FIRST-WITHDRAWN.
SET BK-IX TO 1
SEARCH BOOK-ENTRY
WHEN TB-STATUS (BK-IX) = "W"
DISPLAY "First withdrawn book: " TB-ID (BK-IX)
WHEN TB-COPIES (BK-IX) = ZERO
DISPLAY "First book without copies: " TB-ID (BK-IX)
END-SEARCH.
It compiled with no warnings and printed:
9780000000028 found at entry 002: COBOL BASICS
9780000000099 not in the catalogue
First withdrawn book: 000004
-
The first search found the ISBN in entry 2.
POSITION-NOstarted at 1 and was increased by 1 every time the search index moved, so it holds the entry number when the search stops. -
The second ISBN is not in the table, and the
AT ENDphrase ran after the four loaded entries. The table can hold 100 entries, but the search never looked at entries 5 to 100 becauseBOOK-COUNTis 4. -
The last search has two
WHENphrases. The first element for which either one is true ends the search, and the phrases are tried in order for each element: book 4 is withdrawn, and no book has zero copies, so the firstWHENfired on entry 4. A search for the first element that satisfies any of several conditions is a singleSEARCHwith severalWHENphrases, not several searches.
VARYING takes either an index-name or a data item. With an integer data item (as here) the item grows by 1 whenever the search index advances. With an index data item it grows by the same amount as the search index. With an index-name of another table, one SEARCH steps two tables in parallel, and the search is driven by the table’s own index. An integer VARYING item is therefore the simplest way to obtain a plain entry number without a SET.
The index keeps its value
This program shows the most frequent mistake, a second search that starts where the first one stopped, and then a legitimate use of the same behaviour: finding every match by resuming one entry after each hit. It displays the entry number after SET entry-no TO index, because an index-name is not an ordinary numeric item (GnuCOBOL, if asked to DISPLAY an index, prints it as +000000003).
IDENTIFICATION DIVISION.
PROGRAM-ID. SERIAL2.
* The search index keeps its value between SEARCH statements.
DATA DIVISION.
WORKING-STORAGE SECTION.
01 CAT-VALUES.
05 FILLER PIC X(9) VALUE "000001A02".
05 FILLER PIC X(9) VALUE "000002A00".
05 FILLER PIC X(9) VALUE "000003A05".
05 FILLER PIC X(9) VALUE "000004W01".
05 FILLER PIC X(9) VALUE "000005A07".
01 CAT-TABLE REDEFINES CAT-VALUES.
05 CAT-ENTRY OCCURS 5 TIMES INDEXED BY CAT-IX.
10 CAT-ID PIC 9(6).
10 CAT-STATUS PIC X.
10 CAT-COPIES PIC 99.
01 WANTED-ID PIC 9(6).
01 ENTRY-NO PIC 9.
PROCEDURE DIVISION.
MAIN-PARA.
DISPLAY "-- wrong: the second SEARCH is not preceded by SET"
SET CAT-IX TO 1
MOVE 3 TO WANTED-ID
PERFORM FIND-ID
MOVE 1 TO WANTED-ID
PERFORM FIND-ID
DISPLAY "-- right: SET CAT-IX TO 1 before every search"
MOVE 3 TO WANTED-ID
SET CAT-IX TO 1
PERFORM FIND-ID
MOVE 1 TO WANTED-ID
SET CAT-IX TO 1
PERFORM FIND-ID
DISPLAY "-- every active book with copies, resuming the"
DISPLAY " search one entry after each hit"
SET CAT-IX TO 1
PERFORM UNTIL CAT-IX > 5
SEARCH CAT-ENTRY
AT END
SET CAT-IX TO 6
WHEN CAT-STATUS (CAT-IX) = "A"
AND CAT-COPIES (CAT-IX) > 0
DISPLAY " lendable: " CAT-ID (CAT-IX)
SET CAT-IX UP BY 1
END-SEARCH
END-PERFORM
STOP RUN.
FIND-ID.
SEARCH CAT-ENTRY
AT END
DISPLAY WANTED-ID " not found"
WHEN CAT-ID (CAT-IX) = WANTED-ID
SET ENTRY-NO TO CAT-IX
DISPLAY WANTED-ID " found, entry " ENTRY-NO
END-SEARCH.
-- wrong: the second SEARCH is not preceded by SET
000003 found, entry 3
000001 not found
-- right: SET CAT-IX TO 1 before every search
000003 found, entry 3
000001 found, entry 1
-- every active book with copies, resuming the
search one entry after each hit
lendable: 000001
lendable: 000003
lendable: 000005
Book 1 is in the table, yet the first block reports it as missing: the preceding search for book 3 left the index on entry 3, and book 1 sits before it. Compiled with -Wall, nothing warns about this, because the program is valid; the repair is the SET before each search, as in the second block. The last block resumes after each hit with SET CAT-IX UP BY 1 (the loop ends when the index passes 5, and AT END forces it to 6). Resuming is how COBOL visits all the matches of a non-unique key.
Searching a table of tables
SEARCH works on one dimension at a time. For the stock of each book on each shelf of three branches, the outer index selects the branch and the inner SEARCH goes over the shelves of that branch.
IDENTIFICATION DIVISION.
PROGRAM-ID. TWODIM.
* SEARCH works on one dimension at a time: set the outer index
* with SET and search the inner table.
DATA DIVISION.
WORKING-STORAGE SECTION.
01 STOCK-VALUES.
05 FILLER PIC X(15) VALUE "010022000330004".
05 FILLER PIC X(15) VALUE "050011000215003".
05 FILLER PIC X(15) VALUE "010012000740009".
01 STOCK-TABLE REDEFINES STOCK-VALUES.
05 BRANCH OCCURS 3 TIMES INDEXED BY BR-IX.
10 SHELF OCCURS 3 TIMES
ASCENDING KEY IS SHELF-BOOK
INDEXED BY SH-IX.
15 SHELF-BOOK PIC 99.
15 SHELF-COPIES PIC 999.
01 WANTED-BOOK PIC 99.
01 BRANCH-NO PIC 9.
PROCEDURE DIVISION.
MAIN-PARA.
MOVE 20 TO WANTED-BOOK
PERFORM VARYING BR-IX FROM 1 BY 1 UNTIL BR-IX > 3
SET SH-IX TO 1
SEARCH SHELF
AT END
CONTINUE
WHEN SHELF-BOOK (BR-IX SH-IX) = WANTED-BOOK
SET BRANCH-NO TO BR-IX
DISPLAY "Book " WANTED-BOOK " at branch "
BRANCH-NO ": " SHELF-COPIES (BR-IX SH-IX)
" copies"
END-SEARCH
END-PERFORM
STOP RUN.
Source: Enterprise COBOL for z/OS 6.5 Language Reference: SEARCH statement (multidimensional serial search)
Book 20 at branch 1: 003 copies
Book 20 at branch 3: 007 copies
The table named after SEARCH is the inner one (SHELF) with no subscript. Its outer index is set before the statement (here by PERFORM VARYING), the inner one is set to 1, and the WHEN condition names both indexes. Branch 2 holds no book 20, and the search ended there silently because the AT END phrase only has CONTINUE.
Binary search: SEARCH ALL
Syntax and eligibility
SEARCH ALL table-name
[AT END imperative-statement]
WHEN key-item = value-or-expression [AND key-item = value-or-expression] ...
imperative-statement-or-NEXT SENTENCE
[END-SEARCH]
A SEARCH ALL splits the range in two at every step, keeping the half in which the wanted key can lie, so the number of tests grows with the logarithm of the table size: a table of 5,000 members needs at most 13 comparisons, and one of 32,768 entries about 15 (the GnuCOBOL Programmer’s Guide gives the same figure for 32,768), where a serial search tests 2,500 elements on average for 5,000. For a table the program searches many times, the saving is large.
To be eligible, the table’s OCCURS clause must contain both an INDEXED BY phrase and an ASCENDING KEY or DESCENDING KEY phrase. The index used is always the one of the first INDEXED BY name, and it needs no SET. The results are predictable only if these conditions hold, and the compiler checks only the first:
-
The table has a
KEYclause and an index. A violation is a compile-time error. -
The data really are in key order. The
KEY ISclause declares an order; it does not create it. If the table is not in that order, no message appears at compile time or at run time, and the search simply gives wrong answers. -
The key values are unique (the combination of all the keys when there are several). With duplicates, the search can stop on any of the equal elements.
The WHEN condition has strict rules, because the binary search must be able to decide which half to discard:
-
Each test compares a key item (subscripted with the table’s first index) for equality (
=,EQUAL TO) with an identifier, a literal or an arithmetic expression, or it is a condition-name with a single value that belongs to a key item.ANDjoins the tests;ORand the relational operators other than equality are not allowed. -
The compared value must not itself be a key item or an item subscripted by the search index.
-
When a key is used in the condition, all the keys that precede it in the table’s
KEYphrase must be used too. -
Floating-point key items are not allowed in Enterprise COBOL.
-
Without a hit, control passes to the
AT ENDphrase, or to the next statement when it is missing. The final value of the index is then unpredictable. With a hit, the index addresses the matching element.
A member lookup
The member master is read in key order (its records are in MEMBER-ID sequence on file, which is what makes the table order right), loaded into a table that declares the key, and searched with SEARCH ALL.
IDENTIFICATION DIVISION.
PROGRAM-ID. BINARY1.
* SEARCH ALL over the member table, ascending key MEMBER-ID.
ENVIRONMENT DIVISION.
INPUT-OUTPUT SECTION.
FILE-CONTROL.
SELECT MEMBMAST ASSIGN TO "MEMBMAST"
ORGANIZATION IS LINE SEQUENTIAL.
DATA DIVISION.
FILE SECTION.
FD MEMBMAST.
COPY MEMBREC.
WORKING-STORAGE SECTION.
01 EOF-FLAG PIC X VALUE "N".
88 AT-EOF VALUE "Y".
01 MEMBER-COUNT PIC 9(4) VALUE ZERO.
01 MEMBER-TABLE.
05 MEMBER-ENTRY OCCURS 1 TO 5000 TIMES
DEPENDING ON MEMBER-COUNT
ASCENDING KEY IS TM-ID
INDEXED BY MB-IX.
10 TM-ID PIC 9(6).
10 TM-NAME PIC X(30).
10 TM-FINES PIC S9(5)V99.
10 TM-STATUS PIC X.
88 TM-SUSPENDED VALUE "S".
01 WANTED-ID PIC 9(6).
01 FINE-EDIT PIC ZZ,ZZ9.99.
PROCEDURE DIVISION.
MAIN-PARA.
PERFORM LOAD-TABLE
MOVE 101 TO WANTED-ID
PERFORM FIND-MEMBER
MOVE 102 TO WANTED-ID
PERFORM FIND-MEMBER
MOVE 103 TO WANTED-ID
PERFORM FIND-MEMBER
STOP RUN.
LOAD-TABLE.
OPEN INPUT MEMBMAST
PERFORM UNTIL AT-EOF
READ MEMBMAST
AT END SET AT-EOF TO TRUE
NOT AT END
ADD 1 TO MEMBER-COUNT
MOVE MEMBER-ID TO TM-ID (MEMBER-COUNT)
MOVE MEMBER-NAME TO TM-NAME (MEMBER-COUNT)
MOVE MEMBER-FINES TO TM-FINES (MEMBER-COUNT)
MOVE MEMBER-STATUS TO TM-STATUS (MEMBER-COUNT)
END-READ
END-PERFORM
CLOSE MEMBMAST.
FIND-MEMBER.
SEARCH ALL MEMBER-ENTRY
AT END
DISPLAY "Member " WANTED-ID " is not registered"
WHEN TM-ID (MB-IX) = WANTED-ID
MOVE TM-FINES (MB-IX) TO FINE-EDIT
DISPLAY "Member " WANTED-ID " " TM-NAME (MB-IX)
" fines " FINE-EDIT
IF TM-SUSPENDED (MB-IX)
DISPLAY " suspended: no new loans"
END-IF
END-SEARCH.
Member 000101 GRACE HOPPER fines 12.50
Member 000102 ALAN TURING fines 50.00
suspended: no new loans
Member 000103 is not registered
There is no SET before the search. TM-SUSPENDED is a condition-name of the table’s element, so the IF after the hit tests the matching element through the index that SEARCH ALL left behind. Member 103 is absent, and the AT END phrase reports it.
The order is the programmer’s responsibility
The first block of the output of the sort example in the next section searches a table that declares ASCENDING KEY IS SH-ID but whose entries are in the order 4, 1, 5, 2, 6, 3. The compiler cannot see the actual order of the data, so it raises nothing, and three of the six books that are in the table are reported as missing. The table SORT puts the entries in key order, after which every search finds its entry.
Composite keys and the two orders
A table may have several keys, listed in order of significance, and the WHEN condition then names every one of them joined with AND. The next program loads the checkouts of the day, sorts them by member and then by book, and searches by both keys. It also searches a table declared with DESCENDING KEY, used for fine bands.
IDENTIFICATION DIVISION.
PROGRAM-ID. LOANKEY.
* Table SORT on two keys, SEARCH ALL on both, and a fine band
* lookup that needs a serial SEARCH on a DESCENDING-ordered table.
ENVIRONMENT DIVISION.
INPUT-OUTPUT SECTION.
FILE-CONTROL.
SELECT LOANTRAN ASSIGN TO "LOANTRAN"
ORGANIZATION IS LINE SEQUENTIAL.
DATA DIVISION.
FILE SECTION.
FD LOANTRAN.
COPY LOANREC.
WORKING-STORAGE SECTION.
01 EOF-FLAG PIC X VALUE "N".
88 AT-EOF VALUE "Y".
01 OPEN-COUNT PIC 9(3) VALUE ZERO.
01 OPEN-LOANS.
05 OPEN-LOAN OCCURS 1 TO 200 TIMES
DEPENDING ON OPEN-COUNT
ASCENDING KEY IS OL-MEMBER OL-BOOK
INDEXED BY OL-IX.
10 OL-MEMBER PIC 9(6).
10 OL-BOOK PIC 9(6).
10 OL-DATE PIC 9(8).
01 WANTED-MEMBER PIC 9(6).
01 WANTED-BOOK PIC 9(6).
01 DAYS-LATE PIC 9(3).
01 FINE-VALUES.
05 FILLER PIC X(7) VALUE "0300100".
05 FILLER PIC X(7) VALUE "0140050".
05 FILLER PIC X(7) VALUE "0070025".
05 FILLER PIC X(7) VALUE "0010010".
01 FINE-BANDS REDEFINES FINE-VALUES.
05 FINE-BAND OCCURS 4 TIMES
DESCENDING KEY IS FB-FROM
INDEXED BY FB-IX.
10 FB-FROM PIC 9(3).
10 FB-RATE PIC 99V99.
01 RATE-EDIT PIC Z9.99.
PROCEDURE DIVISION.
MAIN-PARA.
PERFORM LOAD-OPEN-LOANS
SORT OPEN-LOAN ON ASCENDING KEY OL-MEMBER OL-BOOK
MOVE 101 TO WANTED-MEMBER
MOVE 2 TO WANTED-BOOK
PERFORM FIND-LOAN
MOVE 101 TO WANTED-MEMBER
MOVE 1 TO WANTED-BOOK
PERFORM FIND-LOAN
MOVE 5 TO DAYS-LATE
PERFORM FIND-RATE
MOVE 20 TO DAYS-LATE
PERFORM FIND-RATE
MOVE ZERO TO DAYS-LATE
PERFORM FIND-RATE
STOP RUN.
LOAD-OPEN-LOANS.
OPEN INPUT LOANTRAN
PERFORM UNTIL AT-EOF
READ LOANTRAN
AT END SET AT-EOF TO TRUE
NOT AT END
IF LOAN-CHECKOUT
ADD 1 TO OPEN-COUNT
MOVE LOAN-MEMBER-ID TO OL-MEMBER (OPEN-COUNT)
MOVE LOAN-BOOK-ID TO OL-BOOK (OPEN-COUNT)
MOVE LOAN-DATE TO OL-DATE (OPEN-COUNT)
END-IF
END-READ
END-PERFORM
CLOSE LOANTRAN.
FIND-LOAN.
SEARCH ALL OPEN-LOAN
AT END
DISPLAY "No open loan " WANTED-MEMBER "/" WANTED-BOOK
WHEN OL-MEMBER (OL-IX) = WANTED-MEMBER
AND OL-BOOK (OL-IX) = WANTED-BOOK
DISPLAY "Open loan " WANTED-MEMBER "/" WANTED-BOOK
" since " OL-DATE (OL-IX)
END-SEARCH.
FIND-RATE.
SET FB-IX TO 1
SEARCH FINE-BAND
AT END
DISPLAY DAYS-LATE " days late: no fine"
WHEN DAYS-LATE >= FB-FROM (FB-IX)
MOVE FB-RATE (FB-IX) TO RATE-EDIT
DISPLAY DAYS-LATE " days late: band from "
FB-FROM (FB-IX) " rate " RATE-EDIT
END-SEARCH.
Open loan 000101/000002 since 20261020
No open loan 000101/000001
005 days late: band from 001 rate 0.10
020 days late: band from 014 rate 0.50
000 days late: no fine
The two-key SEARCH ALL finds member 101 with book 2 and reports that member 101 holds no book 1 (member 101 appears only with book 2 in the file). In file order the checkouts of LOANTRAN already happen to be sorted by member, but the table SORT makes the program correct for any input order.
The fine-band lookup shows what SEARCH ALL cannot do. A fine is charged by the band that the days late fall in, so the test is a range (>= the lower bound) and not an equality. A binary search cannot be used, and the table is declared with a DESCENDING KEY only to document the order; the lookup is a serial SEARCH on a table sorted from the highest threshold downwards, so the first band whose threshold is not above the days late is the right one. Five days late is below the 7-day threshold and pays the 1-day band, 20 days pays the 14-day band, and zero days matches no band, so AT END means no fine. A range lookup in a table sorted by threshold is therefore the second legitimate use of the serial search on a sorted table.
What the compiler rejects
The rules above are partly enforced by GnuCOBOL 3.2. The messages below come from one-statement programs, each compiled with cobc -x -Wall, over a table E with ASCENDING KEY IS E-ID and INDEXED BY IX, unless the text says otherwise.
An inequality in the WHEN condition (also OR, which gives the same message):
SEARCH ALL E
WHEN E-ID (IX) >= WANTED-ID
DISPLAY "hit"
END-SEARCH
d1.cob: in paragraph 'MAIN-PARA':
d1.cob:13: error: invalid SEARCH ALL condition
13 > WHEN E-ID (IX) >= WANTED-ID
A table with no KEY phrase (the table has INDEXED BY only):
d3.cob: in paragraph 'MAIN-PARA':
d3.cob:11: error: SEARCH ALL requires KEY phrase
11 > SEARCH ALL E
A table with a KEY phrase but without INDEXED BY:
d4.cob: in paragraph 'MAIN-PARA':
d4.cob:11: error: 'E' not indexed
11 > SEARCH ALL E
A WHEN that tests an item that is not a key (E-COPIES (IX) = 3):
d5.cob: in paragraph 'MAIN-PARA':
d5.cob:13: error: SEARCH ALL requires comparision of KEY field
13 > WHEN E-COPIES (IX) = 3
The wording is GnuCOBOL’s (the misspelling of "comparision" is the compiler’s own). Enterprise COBOL reports such errors with its own messages, which this page does not reproduce because they could not be run here.
The table form of SORT
Syntax
SORT table-name
[ON {ASCENDING | DESCENDING} KEY key-item ...] ...
[WITH DUPLICATES IN ORDER]
[COLLATING SEQUENCE IS alphabet-name]
This is format 2 of SORT, added by COBOL 2002. It sorts the elements of an OCCURS table in place, in storage, with no sort work files, no SD and no INPUT PROCEDURE/OUTPUT PROCEDURE; it can appear anywhere in the procedure division, even in a program compiled with the THREAD option, where the file form is not supported. It is meant for tables that fit in storage.
-
table-namehas anOCCURSclause. It may be qualified, and it may be subscripted, but the rightmost subscript must be omitted or written asALL; that is how one row of a two-dimensional table is sorted. The number of elements sorted follows theOCCURSrules, so a table withOCCURS DEPENDING ONsorts only its current elements. -
Each
key-itemis the table element itself or an item inside it that is not repeated by anotherOCCURSand is not variably located. The keys are listed from the most to the least significant, andASCENDING/DESCENDINGapply to all the keys that follow until the other word appears (the program above sorts byDESCENDING KEY SH-COPIESand thenASCENDING KEY SH-TITLEas two phrases). Enterprise COBOL allows alphanumeric, numeric, floating-point, national and edited keys; DBCS, pointer and object keys, and dynamic-length items, are not allowed. -
If the
KEYphrases are omitted, the keys declared in the table’s ownOCCURSclause are used; if the statement names keys, they override the table’s. The key phrase can be omitted only when the table declares keys. -
WITH DUPLICATES IN ORDERmakes the sort stable: elements with equal keys stay in their original relative order. Without it, their order is undefined in the standard and in Enterprise COBOL. -
COLLATING SEQUENCE IS alphabet-namechooses the collating sequence for alphabetic, alphanumeric, alphanumeric-edited, external floating-point and numeric-edited keys, using an alphabet fromSPECIAL-NAMES. Without it, Enterprise COBOL compares character keys in the EBCDIC sequence unless aPROGRAM COLLATING SEQUENCEis given. On GnuCOBOL the program collating sequence, which is native (ASCII) unless set otherwise, applies, so a table sorted on a PC may come out in a different order than on z/OS when keys mix letters, digits and spaces (see NATIONAL, UTF-8 and Code Pages for the EBCDIC/ASCII difference). -
The
SORT-RETURNspecial register and the other sort special registers do not apply to the table form.
Sorting a table, then searching it
The program below declares a six-entry shelf table, searches it before sorting (the incorrect case), sorts it with ON ASCENDING KEY SH-ID, searches again, and then sorts twice more on other keys to show the order of keys and the treatment of ties.
IDENTIFICATION DIVISION.
PROGRAM-ID. TABSORT.
* An unsorted table, SEARCH ALL on it, then the table SORT.
DATA DIVISION.
WORKING-STORAGE SECTION.
01 SHELF-VALUES.
05 FILLER PIC X(20) VALUE "000004OLD LEDGERS 01".
05 FILLER PIC X(20) VALUE "000001DATA STRUCT 03".
05 FILLER PIC X(20) VALUE "000005COBOL TABLES03".
05 FILLER PIC X(20) VALUE "000002COBOL BASICS02".
05 FILLER PIC X(20) VALUE "000006BATCH JOBS 03".
05 FILLER PIC X(20) VALUE "000003BATCH PROC 02".
01 SHELF-TABLE REDEFINES SHELF-VALUES.
05 SHELF-ENTRY OCCURS 6 TIMES
ASCENDING KEY IS SH-ID
INDEXED BY SH-IX.
10 SH-ID PIC 9(6).
10 SH-TITLE PIC X(12).
10 SH-COPIES PIC 99.
01 WANTED-ID PIC 9(6).
01 I PIC 9.
PROCEDURE DIVISION.
MAIN-PARA.
DISPLAY "-- unsorted table, SEARCH ALL"
PERFORM VARYING WANTED-ID FROM 1 BY 1 UNTIL WANTED-ID > 6
PERFORM FIND-ID
END-PERFORM
SORT SHELF-ENTRY ON ASCENDING KEY SH-ID
DISPLAY "-- after SORT ON ASCENDING KEY SH-ID"
PERFORM VARYING WANTED-ID FROM 1 BY 1 UNTIL WANTED-ID > 6
PERFORM FIND-ID
END-PERFORM
DISPLAY "-- SORT ON DESCENDING KEY SH-COPIES, SH-TITLE"
SORT SHELF-ENTRY ON DESCENDING KEY SH-COPIES
ON ASCENDING KEY SH-TITLE
PERFORM SHOW-TABLE
DISPLAY "-- SORT ON ASCENDING KEY SH-COPIES (ties)"
SORT SHELF-ENTRY ON ASCENDING KEY SH-COPIES
WITH DUPLICATES IN ORDER
PERFORM SHOW-TABLE
STOP RUN.
FIND-ID.
SEARCH ALL SHELF-ENTRY
AT END
DISPLAY " " WANTED-ID " not found"
WHEN SH-ID (SH-IX) = WANTED-ID
DISPLAY " " WANTED-ID " found: " SH-TITLE (SH-IX)
END-SEARCH.
SHOW-TABLE.
PERFORM VARYING I FROM 1 BY 1 UNTIL I > 6
DISPLAY " " SH-ID (I) " " SH-COPIES (I) " "
SH-TITLE (I)
END-PERFORM.
-- unsorted table, SEARCH ALL
000001 not found
000002 not found
000003 not found
000004 found: OLD LEDGERS
000005 found: COBOL TABLES
000006 found: BATCH JOBS
-- after SORT ON ASCENDING KEY SH-ID
000001 found: DATA STRUCT
000002 found: COBOL BASICS
000003 found: BATCH PROC
000004 found: OLD LEDGERS
000005 found: COBOL TABLES
000006 found: BATCH JOBS
-- SORT ON DESCENDING KEY SH-COPIES, SH-TITLE
000006 03 BATCH JOBS
000005 03 COBOL TABLES
000001 03 DATA STRUCT
000003 02 BATCH PROC
000002 02 COBOL BASICS
000004 01 OLD LEDGERS
-- SORT ON ASCENDING KEY SH-COPIES (ties)
000004 01 OLD LEDGERS
000003 02 BATCH PROC
000002 02 COBOL BASICS
000006 03 BATCH JOBS
000005 03 COBOL TABLES
000001 03 DATA STRUCT
-
In the unsorted table the order is 4, 1, 5, 2, 6, 3. The binary search found 4, 5 and 6 and reported 1, 2 and 3 as missing, and no diagnostic was issued, so the
KEY ISclause should be backed by aSORT(or by a file that is already in order) whenever the data come from elsewhere. -
After
SORT ... ON ASCENDING KEY SH-IDall six searches succeed. -
DESCENDING KEY SH-COPIESfollowed byASCENDING KEY SH-TITLEputs the books with most copies first and orders equal counts alphabetically by title. -
The last sort used
WITH DUPLICATES IN ORDERonSH-COPIESalone. The books with 2 copies stayed in the order they had before (3 then 2), and so did those with 3 copies (6, 5, 1). The GnuCOBOL guide says the phrase is syntactically recognized but that GnuCOBOL always behaves as if it were given; this run kept ties in their original order, and the program asks for it explicitly so that the result does not depend on the compiler.
A table that declares its keys can be sorted without naming them again. The tiny program below sorts a three-entry table with SORT E, taking the key from the table definition, and then overrides it with ON DESCENDING KEY:
IDENTIFICATION DIVISION.
PROGRAM-ID. D9.
DATA DIVISION.
WORKING-STORAGE SECTION.
01 T-VALUES.
05 FILLER PIC X(2) VALUE "3C".
05 FILLER PIC X(2) VALUE "1A".
05 FILLER PIC X(2) VALUE "2B".
01 T REDEFINES T-VALUES.
05 E OCCURS 3 TIMES ASCENDING KEY IS E-ID.
10 E-ID PIC 9.
10 E-X PIC X.
01 I PIC 9.
PROCEDURE DIVISION.
MAIN-PARA.
SORT E
PERFORM VARYING I FROM 1 BY 1 UNTIL I > 3
DISPLAY E-ID (I) E-X (I)
END-PERFORM
SORT E ON DESCENDING KEY E-ID
PERFORM VARYING I FROM 1 BY 1 UNTIL I > 3
DISPLAY E-ID (I) E-X (I)
END-PERFORM
STOP RUN.
1A
2B
3C
3C
2B
1A
The manual of GnuCOBOL says that a table sort without keys "is unsupported and rejected by the compiler". In GnuCOBOL 3.2.0 that is true only if the table also lacks a KEY phrase in its OCCURS clause; with one, SORT E compiles and sorts, as the standard and Enterprise COBOL allow. When neither exists, the message is:
d8.cob: in paragraph 'MAIN-PARA':
d8.cob:11: error: table SORT requires KEY phrase
11 > STOP RUN.
Choosing a search: serial, binary or keyed file
| Situation | Choose | Why |
|---|---|---|
A few dozen entries, a code table that is searched a few times |
Serial |
The loop is simple, the table needs no order, and the cost is not measurable. |
Thousands of entries, many lookups per run (for example every |
Table |
At most 13 tests per lookup for 5,000 members. The sort cost is paid once, and it is the only way to guarantee the order that |
Conditions that are not plain equality: a fine band, a date range, "first element that is withdrawn or has no copies" |
Serial |
A binary search only decides on equality of unique keys. For ranges, order the table so that the first match is the wanted one. |
The key is not unique and every match is wanted |
Serial |
|
A very large master, shared among programs, updated while it is read, or needing a key that survives the run |
A keyed file (indexed organization on GnuCOBOL, VSAM KSDS on z/OS) or a Db2 table |
The table lives and dies with the program, and its capacity is fixed at compile time. See Indexed Files and VSAM; Db2 is on Embedded SQL and Db2. |
The table is the result of a merge with a sorted input (a balance-line update) |
Neither: read both files in key order |
A sequential match of two sorted files reads each record once, the usual shape of the nightly Circulation update. |
Three further rules of thumb follow from the statements above:
-
A table is a copy. A program that loads
BOOKMASTinto a table and updates the table must write the changes back, and it works with a snapshot if another job changes the file. -
The capacity of the table is part of the program. An
OCCURS ... DEPENDING ONtable holds up to the maximum written in the clause; loading more entries than that overwrites storage after the table unless the program checks the count, or is compiled with the subscript checking of the compiler (SSRANGEon Enterprise COBOL,-debugon GnuCOBOL), as described on Tables and OCCURS. COBOL 2014 dynamic-capacity tables (OCCURS DYNAMIC) remove the fixed limit where the compiler implements them; this page’s examples do not rely on them. -
Sorting a table costs about as much as sorting the same records in a file, so a table is sorted once at load time. Do not sort inside a loop that searches.
Standard, Enterprise COBOL and GnuCOBOL
| Feature | ISO/IEC 1989:2023 | Enterprise COBOL 6.5 | GnuCOBOL 3.2 |
|---|---|---|---|
|
Yes (already in COBOL 85) |
Yes |
Yes |
Several |
One |
One |
One |
Table |
Yes (COBOL 2002) |
Yes; the release that introduced it was not verified for this page |
Yes |
|
Yes |
Yes |
Yes (the manual’s statement that keys are required is outdated, shown above) |
|
Yes |
Yes |
Accepted; ties were kept in order in this test |
|
Yes |
Yes, EBCDIC by default |
Yes, native sequence by default |
|
Not applicable |
Not applicable |
Not tested |
Common mistakes
-
No
SETbefore a serialSEARCH: it starts where the last one stopped, as the second example showed. -
A
KEYclause on unsorted data: no diagnostic, wrong answers;SORTthe table or load it in order. -
Duplicate keys under
SEARCH ALL: any one of the equal elements may be found; use a serial search resumed after each hit. -
A range or
ORin aSEARCH ALL: rejected with "invalid SEARCH ALL condition"; use a serialSEARCH. -
SEARCHon a table that was loaded only partly: withOCCURS ... DEPENDING ON, update the object (BOOK-COUNT) while loading, otherwise the search covers more or fewer elements than were loaded. -
NEXT SENTENCEin aWHENphrase: it jumps to the next period, not toEND-SEARCH; writeCONTINUE. -
A table sorted on a PC and searched on the mainframe (or the reverse) with mixed-case or alphanumeric keys: the ASCII and EBCDIC collating sequences order letters, digits and spaces differently, so a
SEARCH ALLcan fail on data sorted under the other sequence. Sort on the machine that searches, or name the sequence withCOLLATING SEQUENCE.
References
-
Enterprise COBOL for z/OS 6.5 Language Reference: SEARCH statement
-
Enterprise COBOL for z/OS 6.5 Language Reference: SORT statement
-
GnuCOBOL 3.2 Programmer’s Guide (the sections SEARCH, SEARCH ALL and Table SORT)
-
Tom Taulli, Modern Mainframe Development, O’Reilly Media, 2022 (chapter 6, consulted for the topics only)