Tables and the OCCURS clause
Suppose you wanted to store your monthly sales figures for the year. You could define 12 fields, one for each month, like this:
05 MONTHLY-SALES-1 PIC S9(5)V99. 05 MONTHLY-SALES-2 PIC S9(5)V99. 05 MONTHLY-SALES-3 PIC S9(5)V99. ... 05 MONTHLY-SALES-11 PIC S9(5)V99. 05 MONTHLY-SALES-12 PIC S9(5)V99.
But there’s an easier way in COBOL. You can specify the field once and declare that it repeats 12 times. You do this with the OCCURS clause, like this:
05 MONTHLY-SALES OCCURS 12 TIMES PIC S9(5)V99.
(By now you should also know this can be written on two lines like this):
05 MONTHLY-SALES OCCURS 12 TIMES PIC S9(5)V99.
This specifies 12 fields, all of which have the same PIC, and is called a table (also called an array). The individual fields are referenced in COBOL by using subscripts, such as “MONTHLY-SALES(1)”. This table occupies 84 bytes in the record (12 * (5+2)). (The sign is embedded, not separate, and the decimal is implied.).
1. The OCCURS can also be at the group level
And this is the most useful application of OCCURS. For example, all 25 line items on an invoice (75 fields) could be held in this group:
05 LINE-ITEMS OCCURS 25 TIMES. 10 QUANTITY PIC 9999. 10 DESCRIPTION PIC X(30). 10 UNIT-PRICE PIC S9(5)V99.
Notice the OCCURS is listed at the group level, so the entire group occurs 25 times. The order of the data in the file is as-if you had specified multiple groups, like this:
05 LINE-ITEMS-1. 10 QUANTITY PIC 9999. 10 DESCRIPTION PIC X(30). 10 UNIT-PRICE PIC S9(5)V99. 05 LINE-ITEMS-2. 10 QUANTITY PIC 9999. 10 DESCRIPTION PIC X(30). 10 UNIT-PRICE PIC S9(5)V99. ... 05 LINE-ITEMS-25. 10 QUANTITY PIC 9999. 10 DESCRIPTION PIC X(30). 10 UNIT-PRICE PIC S9(5)V99.
2. There can be nested occurs – an occurs within an occurs. In the next example, suppose we stock ten products andwe want to keep a record of the monthly sales of each product for the past 12 months. We could do just that with this table:
01 INVENTORY-RECORD. 05 INVENTORY-ITEM OCCURS 10 TIMES. 10 MONTHLY-SALES OCCURS 12 TIMES PIC 999.
In this case, “INVENTORY-ITEM” is a group composed only of “MONTHLY-SALES”, which occurs 12 times for each occurrence of an inventory item. This gives an array (table) of 10 * 12 fields. The only information in this record are the 120 monthly sales figures — 12 months for each of 10 items.
We could also have a description for each item. The description would go under the 05 level INVENTORY-ITEM group, at the 10 level, the same as the monthly sales. Further, we could track, say, the sale price of each item for each month. A record which will do these things is:
01 INVENTORY-RECORD. 05 INVENTORY-ITEM OCCURS 10 TIMES. 10 ITEM-DESCRIPTION PIC X(30). 10 MONTHLY-SALES OCCURS 12 TIMES. 15 QUANTITY-SOLD PIC 999. 15 UNIT-PRICE PIC 9(5)V99.
Notice we have made MONTHLY-SALES a group, which now contains two fields, and the whole group repeats 12 times for each instance of INVENTORY-ITEM. This short layout has 250 fields: two fields (QUANTITY-SOLD and UNIT-PRICE) that repeat 12 times for each inventory item, times 10 items, plus the ITEM-DESCRIPTION field for each of the 10 items. Fields and groups can be nested several levels deep, and it’s possible to have thousands of fields in a layout only a couple pages long.
3. Occurs Depending On
One really great feature of COBOL tables, and a really nasty one to convert to other languages, is the “OCCURS DEPENDING ON”. This is an OCCURS, like above, but the number of times it occurs in a particular record can vary (between some limits). The number of times it actually occurs in any particular record will be given by a value in another field of that record. This creates records that vary in size from record to record.
The OCCURS-DEPENDING-ON can include many subordinate fields and groups, all of which occur multiple times. Further, most compilers allow one or more (fixed) OCCURS to be nested within an OCCURS-DEPENDING-ON, and some compilers allow multiple OCCURS-DEPENDING-ON to be nested, or to occur in succession. This can get pretty involved, so we will only give one simple example, that of a patient’s medical treatment-history record .
01 PATIENT-TREATMENTS. 05 PATIENT-NAME PIC X(30). 05 PATIENT-SS-NUMBER PIC 9(9). 05 NUMBER-OF-TREATMENTS PIC 99 COMP-3. 05 TREATMENT-HISTORY OCCURS 0 TO 50 TIMES DEPENDING ON NUMBER-OF-TREATMENTS INDEXED BY TREATMENT-POINTER. 10 TREATMENT-DATE. 15 TREATMENT-DAY PIC 99. 15 TREATMENT-MONTH PIC 99. 15 TREATMENT-YEAR PIC 9(4). 10 TREATING-PHYSICIAN PIC X(30). 10 TREATMENT-CODE PIC 99.
Here are the significant points of this record:
- The name of the record is “PATIENT-TREATMENTS”.
- The first three fields “PATIENT-NAME”, “PATIENT-SS-NUMBER”, and “NUMBER-OF-TREATMENTS” occur in the fixed portion of every record. This fixed portion is the same for every record.
- The TREATMENT-HISTORY group is the variable portion of the record. It can occur from 0 to 50 times.
- “”NUMBER-OF-TREATMENTS” is a number from 0 to 50 that tells us how many times the group TREATMENT-HISTORY occurs in this record.
- The value in NUMBER-OF-TREATMENTS is stored in a comp-3 packed format. This is very common. Also very common is comp or binary format. All of these are binary data formats.
- TREATMENT-HISTORY is a group that is comprised of all the lower level fields beneath it. (Down to the next 05 level, or the end of the record).
- All the fields and groups within TREATMENT-HISTORY occur between 0 and 50 times.
- Because 0 is a valid number of occurrences, it is possible the variable portion of the record is not present.
- The “INDEXED BY TREATMENT-POINTER” clause may or may not be present. If present it tells the compiler the name of the variable (TREATMENT-POINTER) to use as the index into the array. If you don’t understand this, you can safely ignore the “indexed by…” clause, unless you are programming in COBOL.
- TREATMENT-DATE is a group that is comprised of the day, month, and year fields beneath it.
- These records vary in size from 41 to 2041 bytes, and would be stored in some type of variable length file.