【COBOL】OCCURS句完全ガイド|配列定義・INDEXED BY・SEARCH/SEARCH ALL・多次元・DEPENDING ON

COBOLのOCCURS句は、同じ構造を持つデータを繰り返し定義するための機能です。他の言語の「配列」に相当し、月別売上・商品マスタ・コード変換テーブルなど、業務プログラムのあらゆる場面で活用されます。

この記事では、OCCURS句の基本定義から、INDEXED BY句・SEARCH/SEARCH ALL文・DEPENDING ON句(可変長配列)・多次元配列まで、実務で必要な知識を体系的に解説します。ループ処理(PERFORM VARYING)の詳細はPERFORM文完全ガイドで扱っているため、本記事はDATA DIVISION側の定義と配列検索に焦点を当てます。

この記事でわかること

  • OCCURS句の基本構文と配置ルール・制約
  • 数値・文字・グループ項目の配列定義パターン
  • 多次元配列(2次元・3次元)の定義
  • DEPENDING ON句による可変長配列
  • INDEXED BY句の役割とSEARCH文(順次検索)
  • KEY IS句とSEARCH ALL文(二分探索)
  • コード変換テーブル・商品マスタの実践パターン
スポンサーリンク

OCCURS句とは

OCCURS句は、同じ構造を持つデータ項目を指定した個数だけ連続して定義するための記述です。定義した各要素は「添字(インデックス)」で識別します。

種類 構文 用途
静的配列 OCCURS n TIMES 要素数が固定の配列
可変長配列 OCCURS m TO n TIMES DEPENDING ON 変数 要素数を実行時に変動させる配列
インデックス付き配列 OCCURS n TIMES INDEXED BY インデックス名 SEARCH文で検索可能な配列
整列テーブル OCCURS n TIMES ASCENDING/DESCENDING KEY IS キー項目 INDEXED BY インデックス名 SEARCH ALL(二分探索)が使えるテーブル

基本構文と配置ルール

構文
レベル番号  データ名  OCCURS 要素数 TIMES
              [ASCENDING/DESCENDING KEY IS キー項目 ...]
              [INDEXED BY インデックス名 ...]
              PIC 句 または 従属項目の定義.

OCCURS句の記述位置についてのルールを押さえておきましょう。

レベル番号 使用可否 備考
01・77レベル 使用不可
02〜49レベル 使用可 通常の配列定義
66レベル(RENAMES) 使用不可
88レベル(条件名) 使用不可

01・77レベルにはOCCURS句を書けない
配列を定義したい場合は、01レベルのグループ項目の下に02〜49レベルでOCCURS句を書きます。

NG: 01レベルへのOCCURS / OK: 02レベルへ移す
* NG: 01レベルにOCCURSは書けない
01  WS-SCORE  PIC 9(3) OCCURS 10 TIMES.   *> コンパイルエラー

* OK: グループ項目の下に定義する
01  WS-SCORE-TABLE.
    05  WS-SCORE  PIC 9(3) OCCURS 10 TIMES.

1次元配列の定義パターン

数値配列

月別売上金額の配列(12要素)
WORKING-STORAGE SECTION.
01  WS-MONTHLY-SALES-TBL.
    05  WS-MONTHLY-SALES  PIC 9(9)V99  OCCURS 12 TIMES.

PROCEDURE DIVISION.
    *> 特定月の売上を参照(添字は1始まり)
    DISPLAY '1月: ' WS-MONTHLY-SALES(1)
    DISPLAY '3月: ' WS-MONTHLY-SALES(3)

    *> 合計を求めるループ
    MOVE 0 TO WS-TOTAL
    PERFORM VARYING WS-IDX FROM 1 BY 1 UNTIL WS-IDX > 12
        ADD WS-MONTHLY-SALES(WS-IDX) TO WS-TOTAL
    END-PERFORM.

文字配列

エラーメッセージ配列(5要素)
WORKING-STORAGE SECTION.
01  WS-ERR-MSG-TBL.
    05  WS-ERR-MSG  PIC X(80)  OCCURS 5 TIMES.

PROCEDURE DIVISION.
    *> メッセージを設定
    MOVE '入力値が空です'           TO WS-ERR-MSG(1)
    MOVE '日付の形式が正しくありません' TO WS-ERR-MSG(2)
    MOVE '金額が0以下です'           TO WS-ERR-MSG(3)

    *> エラーコードに対応するメッセージを表示
    DISPLAY WS-ERR-MSG(WS-ERR-CODE).

グループ項目の配列(構造体の配列)

OCCURS句はグループ項目(複数フィールドを持つ複合型)にも適用できます。他の言語の「構造体の配列」に相当し、商品マスタや社員テーブルの定義に使います。

商品マスタテーブル(グループ項目の配列)
WORKING-STORAGE SECTION.
01  WS-ITEM-TABLE.
    05  WS-ITEM-ENTRY  OCCURS 100 TIMES.
        10  WS-ITEM-CODE   PIC X(6).       *> 商品コード
        10  WS-ITEM-NAME   PIC X(20).      *> 商品名
        10  WS-ITEM-PRICE  PIC 9(7)V99.    *> 単価
        10  WS-ITEM-STOCK  PIC 9(5).       *> 在庫数

01  WS-ITEM-COUNT  PIC 9(3) VALUE 0.       *> 有効件数
01  WS-IDX         PIC 9(3).

PROCEDURE DIVISION.
    *> 商品データをセット
    MOVE 'ITM001'     TO WS-ITEM-CODE(1)
    MOVE 'ノートPC'   TO WS-ITEM-NAME(1)
    MOVE 98000.00     TO WS-ITEM-PRICE(1)
    MOVE 50           TO WS-ITEM-STOCK(1)
    MOVE 1            TO WS-ITEM-COUNT

    *> 特定商品の在庫を更新
    SUBTRACT 1 FROM WS-ITEM-STOCK(1)

    *> 全商品一覧を表示
    PERFORM VARYING WS-IDX FROM 1 BY 1 UNTIL WS-IDX > WS-ITEM-COUNT
        DISPLAY WS-ITEM-CODE(WS-IDX) ' '
                WS-ITEM-NAME(WS-IDX) ' '
                WS-ITEM-PRICE(WS-IDX) '円 '
                WS-ITEM-STOCK(WS-IDX) '個'
    END-PERFORM.

多次元配列(2次元・3次元)

OCCURS句をネストすることで2次元・3次元の配列を定義できます。帳票の行列・年月日の組み合わせデータ・座席管理などに使います。

2次元配列

部門×月の売上テーブル(5部門×12か月)
WORKING-STORAGE SECTION.
01  WS-DEPT-SALES-TBL.
    05  WS-DEPT-ROW   OCCURS 5 TIMES.        *> 5部門
        10  WS-MONTH-SALES  PIC 9(9)V99
                             OCCURS 12 TIMES. *> 12か月

01  WS-DEPT-IDX   PIC 9(2).
01  WS-MONTH-IDX  PIC 9(2).
01  WS-TOTAL      PIC 9(12)V99 VALUE 0.

PROCEDURE DIVISION.
    *> 2次元配列への参照: (部門インデックス, 月インデックス)
    MOVE 1500000.00 TO WS-MONTH-SALES(1, 3)     *> 1部門3月に代入

    *> 全体合計を求める2重ループ
    PERFORM VARYING WS-DEPT-IDX FROM 1 BY 1
                    UNTIL WS-DEPT-IDX > 5
        PERFORM VARYING WS-MONTH-IDX FROM 1 BY 1
                        UNTIL WS-MONTH-IDX > 12
            ADD WS-MONTH-SALES(WS-DEPT-IDX, WS-MONTH-IDX)
                TO WS-TOTAL
        END-PERFORM
    END-PERFORM
    DISPLAY '年間総売上: ' WS-TOTAL.

3次元配列

年×部門×月の3年分集計テーブル
WORKING-STORAGE SECTION.
01  WS-ANNUAL-TBL.
    05  WS-YEAR-ROW    OCCURS 3 TIMES.       *> 3年分
        10  WS-DEPT-ROW  OCCURS 5 TIMES.     *> 5部門
            15  WS-MON-AMT  PIC 9(9)V99
                              OCCURS 12 TIMES.*> 12か月

PROCEDURE DIVISION.
    *> 3次元参照: (年, 部門, 月)
    MOVE 2000000.00 TO WS-MON-AMT(1, 2, 6)  *> 1年目・2部門・6月

    *> PERFORM VARYING...AFTERで多重ループも可能(詳細はPERFOM文記事参照)
    PERFORM VARYING WS-YEAR-IDX  FROM 1 BY 1 UNTIL WS-YEAR-IDX  > 3
             AFTER  WS-DEPT-IDX  FROM 1 BY 1 UNTIL WS-DEPT-IDX  > 5
             AFTER  WS-MONTH-IDX FROM 1 BY 1 UNTIL WS-MONTH-IDX > 12
        ADD WS-MON-AMT(WS-YEAR-IDX, WS-DEPT-IDX, WS-MONTH-IDX)
            TO WS-GRAND-TOTAL
    END-PERFORM.

COBOLの添字はカンマ区切り(または空白区切り)で指定します。WS-MON-AMT(1, 2, 6)WS-MON-AMT(1 2 6) は同じ意味です。多次元ループの詳細はPERFORM文完全ガイド(VARYING…AFTER)を参照してください。

DEPENDING ON句(可変長配列)

OCCURS … DEPENDING ON句を使うと、要素数を実行時に変動させることができます。ファイルから読み込んだ件数に合わせて配列サイズを決めるパターンなどで使います。

構文: OCCURS m TO n TIMES DEPENDING ON 変数
01  WS-DATA-TABLE.
    05  WS-DATA-COUNT   PIC 9(3).                    *> 現在の要素数
    05  WS-DATA-ENTRY   OCCURS 1 TO 100 TIMES
                        DEPENDING ON WS-DATA-COUNT.  *> 1〜100の可変
        10  WS-DATA-KEY  PIC X(6).
        10  WS-DATA-VAL  PIC 9(7)V99.
可変長配列の実践例: ファイルから読み込んだ件数分だけ保持
WORKING-STORAGE SECTION.
01  WS-LOAD-TBL.
    05  WS-LOAD-COUNT   PIC 9(3) VALUE 0.
    05  WS-LOAD-ENTRY   OCCURS 1 TO 200 TIMES
                        DEPENDING ON WS-LOAD-COUNT.
        10  WS-PROD-CD   PIC X(6).
        10  WS-PROD-RATE PIC 9(3)V99.

PROCEDURE DIVISION.
    OPEN INPUT RATE-FILE
    PERFORM UNTIL WS-EOF OR WS-LOAD-COUNT >= 200
        READ RATE-FILE
            AT END MOVE 'Y' TO WS-EOF-FLAG
            NOT AT END
                ADD 1 TO WS-LOAD-COUNT
                MOVE RF-PROD-CD   TO WS-PROD-CD(WS-LOAD-COUNT)
                MOVE RF-PROD-RATE TO WS-PROD-RATE(WS-LOAD-COUNT)
        END-READ
    END-PERFORM
    CLOSE RATE-FILE
    DISPLAY WS-LOAD-COUNT '件をロードしました'.

DEPENDING ON変数の管理は厳密に
DEPENDING ON変数(現在の要素数)が実際のデータ件数と一致していないと、添字範囲外アクセスが発生します。要素を追加・削除するときは必ずDEPENDING ON変数も更新してください。また最大要素数(n)を超えた添字アクセスは実行時エラーです。

INDEXED BY句とSEARCH文(順次検索)

INDEXED BY句を付けると、配列に「インデックス型」の変数が紐付きます。インデックス型はPIC 9変数と異なり、バイトオフセットで内部管理されるため算術演算には使えませんが、SEARCH文(順次検索)を使うための必須条件です。

操作 構文 備考
宣言 INDEXED BY インデックス名 データ定義で宣言
初期化 SET インデックス名 TO 1 SETで1から始める(MOVE不可)
増減 SET インデックス名 UP BY 1 / DOWN BY 1 SETで増減(算術演算不可)
PIC 9変数との変換 SET 整数変数 TO インデックス名 相互変換にもSETを使う
INDEXED BY句の定義とSET文での操作
WORKING-STORAGE SECTION.
01  WS-CODE-TABLE.
    05  WS-CODE-ENTRY  OCCURS 50 TIMES INDEXED BY WS-CODE-IDX.
        10  WS-CODE     PIC X(4).
        10  WS-CODE-NAME PIC X(20).

01  WS-WORK-IDX   PIC 9(2).    *> 通常変数(添字として使用可)

PROCEDURE DIVISION.
    *> OK: SET文でインデックスを操作する
    SET WS-CODE-IDX TO 1         *> 先頭に移動
    SET WS-CODE-IDX UP BY 1      *> 次の要素へ
    SET WS-CODE-IDX DOWN BY 3    *> 3つ前へ

    *> インデックスと整数変数の相互変換
    SET WS-WORK-IDX TO WS-CODE-IDX   *> インデックス→整数
    SET WS-CODE-IDX TO WS-WORK-IDX   *> 整数→インデックス

    *> NG: 算術演算や直接MOVEはエラー
    *  ADD 1 TO WS-CODE-IDX           *> エラー
    *  MOVE 5 TO WS-CODE-IDX          *> エラー

SEARCH文(AT END・WHEN)

SEARCH文はOCCURS … INDEXED BYで定義した配列を順次検索します。テーブルの先頭から1要素ずつ条件を評価し、一致したところで処理を実行します。

SEARCH文の構文
SET インデックス名 TO 1
SEARCH テーブル名
    AT END
        *> 見つからなかったときの処理
    WHEN 条件式
        *> 条件に一致したときの処理
END-SEARCH
コード変換テーブルの検索(SEARCH文)
WORKING-STORAGE SECTION.
01  WS-DEPT-TABLE.
    05  WS-DEPT-ENTRY  OCCURS 20 TIMES INDEXED BY WS-DEPT-IDX.
        10  WS-DEPT-CODE  PIC X(4).
        10  WS-DEPT-NAME  PIC X(20).

01  WS-SEARCH-CODE   PIC X(4).
01  WS-FOUND-NAME    PIC X(20) VALUE SPACES.
01  WS-FOUND-FLG     PIC X(1) VALUE 'N'.

PROCEDURE DIVISION.
    *> テーブルにデータをロード(省略)
    MOVE 'D001' TO WS-DEPT-CODE(1)
    MOVE '営業部' TO WS-DEPT-NAME(1)
    MOVE 'D002' TO WS-DEPT-CODE(2)
    MOVE '開発部' TO WS-DEPT-NAME(2)
    *> ...(以下略)

    MOVE 'D002' TO WS-SEARCH-CODE

    *> SEARCH前に必ずSET TO 1でインデックスを初期化
    SET WS-DEPT-IDX TO 1
    SEARCH WS-DEPT-ENTRY
        AT END
            DISPLAY '該当する部門コードがありません: ' WS-SEARCH-CODE
            MOVE 'N' TO WS-FOUND-FLG
        WHEN WS-DEPT-CODE(WS-DEPT-IDX) = WS-SEARCH-CODE
            MOVE WS-DEPT-NAME(WS-DEPT-IDX) TO WS-FOUND-NAME
            MOVE 'Y' TO WS-FOUND-FLG
    END-SEARCH

    IF WS-FOUND-FLG = 'Y'
        DISPLAY '部門名: ' WS-FOUND-NAME
    END-IF.

SEARCH前に必ずSET TO 1
SEARCH文はインデックスの現在位置から検索を開始します。SET WS-IDX TO 1を書き忘れると、前回の検索で進んだインデックス位置から続きを検索してしまい、先頭のデータが見つからないバグが発生します。

KEY IS句とSEARCH ALL(二分探索)

OCCURS句にASCENDING KEY IS(または DESCENDING KEY IS)とINDEXED BYを組み合わせると、SEARCH ALL文(二分探索)が使えるようになります。大量のデータを高速検索する場合に有効で、線形検索(SEARCH)より大幅に高速です。

SEARCH vs SEARCH ALL の使い分け

  • SEARCH(順次検索): ソートされていないテーブルにも使える。要素数が少ない(〜100件程度)場合に向いている
  • SEARCH ALL(二分探索): テーブルが昇順または降順にソート済みであることが前提。数百〜数万件の大規模テーブルに向いている
SEARCH ALL用テーブルの定義(KEY IS句必須)
WORKING-STORAGE SECTION.
01  WS-PRODUCT-TABLE.
    05  WS-PROD-ENTRY  OCCURS 1000 TIMES
                       ASCENDING KEY IS WS-PROD-CODE
                       INDEXED BY WS-PROD-IDX.
        10  WS-PROD-CODE   PIC X(6).
        10  WS-PROD-NAME   PIC X(30).
        10  WS-PROD-PRICE  PIC 9(7)V99.
        10  WS-PROD-STOCK  PIC 9(5).
SEARCH ALL文の構文と使い方
*> SEARCH ALL は AT END と WHEN のみ使用可(SET TO 1は不要)
WORKING-STORAGE SECTION.
01  WS-SEARCH-CODE   PIC X(6).
01  WS-RESULT-PRICE  PIC 9(7)V99.
01  WS-FOUND-FLG     PIC X VALUE 'N'.

PROCEDURE DIVISION.
    MOVE 'ITM042' TO WS-SEARCH-CODE

    SEARCH ALL WS-PROD-ENTRY
        AT END
            DISPLAY '商品コードが見つかりません: ' WS-SEARCH-CODE
        WHEN WS-PROD-CODE(WS-PROD-IDX) = WS-SEARCH-CODE
            MOVE WS-PROD-PRICE(WS-PROD-IDX) TO WS-RESULT-PRICE
            MOVE 'Y' TO WS-FOUND-FLG
    END-SEARCH

    IF WS-FOUND-FLG = 'Y'
        DISPLAY '単価: ' WS-RESULT-PRICE
    END-IF.
比較項目 SEARCH(順次) SEARCH ALL(二分)
前提条件 不要 KEY IS句 + INDEXED BY + データのソート済みが必須
検索速度 O(n)(全件走査の可能性) O(log n)(二分探索)
インデックス初期化 SET TO 1 が必須 不要(自動で最適位置から開始)
WHEN条件 任意の条件式 等値比較(=)のみ。ASCENDING/DESCENDING KEYの項目のみ
複数条件 WHEN … WHEN … と複数書ける 1つの等値条件のみ

配列の初期化

OCCURS項目を含むグループ全体を初期化する方法をまとめます。

初期化の4パターン
WORKING-STORAGE SECTION.
01  WS-NUM-TABLE.
    05  WS-NUM  PIC 9(5)V99  OCCURS 100 TIMES.

01  WS-CHR-TABLE.
    05  WS-CHR  PIC X(10)    OCCURS 50 TIMES.

01  WS-IDX  PIC 9(3).

PROCEDURE DIVISION.
    *> パターン1: INITIALIZE で一括初期化(数値→0、文字→スペース)
    INITIALIZE WS-NUM-TABLE   *> 全要素を0にする
    INITIALIZE WS-CHR-TABLE   *> 全要素をスペースにする

    *> パターン2: MOVE ZEROS / SPACES でグループ全体をクリア
    MOVE ZEROS  TO WS-NUM-TABLE   *> 数値テーブル全体を0クリア
    MOVE SPACES TO WS-CHR-TABLE   *> 文字テーブル全体をスペースクリア

    *> パターン3: PERFORMでループして個別初期化
    PERFORM VARYING WS-IDX FROM 1 BY 1 UNTIL WS-IDX > 100
        MOVE 0 TO WS-NUM(WS-IDX)
    END-PERFORM

    *> パターン4: VALUE句で宣言時に初期値設定(静的のみ・DEPENDING ON不可)
    *  05 WS-FLAG OCCURS 10 TIMES VALUE 'N'.  *> 全要素'N'で初期化

初期化の推奨順序: 配列全体をまとめてクリアするなら INITIALIZE テーブル名 または MOVE ZEROS TO テーブル名 が最も簡潔です。要素ごとに異なる初期値を設定する場合はPERFORMループを使います。

実践パターン

コード変換テーブル(都道府県コード→名称)

コードから名称に変換するテーブルは業務プログラムの定番パターンです。件数が少ない(〜50件)場合はSEARCH、大量の場合はSEARCH ALLを使います。

都道府県コード変換テーブル(SEARCH ALL版)
WORKING-STORAGE SECTION.
01  WS-PREF-TABLE.
    05  WS-PREF-ENTRY  OCCURS 47 TIMES
                       ASCENDING KEY IS WS-PREF-CODE
                       INDEXED BY WS-PREF-IDX.
        10  WS-PREF-CODE  PIC 9(2).
        10  WS-PREF-NAME  PIC X(8).

01  WS-SEARCH-PREF    PIC 9(2).
01  WS-RESULT-NAME    PIC X(8) VALUE SPACES.

PROCEDURE DIVISION.
    *> テーブルをコード順(昇順)に設定する
    MOVE  1  TO WS-PREF-CODE(1)
    MOVE '北海道' TO WS-PREF-NAME(1)
    MOVE  2  TO WS-PREF-CODE(2)
    MOVE '青森県' TO WS-PREF-NAME(2)
    *> ... 47都道府県分設定(省略)

    *> 二分探索でコードから名称を取得
    MOVE 13 TO WS-SEARCH-PREF

    SEARCH ALL WS-PREF-ENTRY
        AT END
            MOVE '不明    ' TO WS-RESULT-NAME
        WHEN WS-PREF-CODE(WS-PREF-IDX) = WS-SEARCH-PREF
            MOVE WS-PREF-NAME(WS-PREF-IDX) TO WS-RESULT-NAME
    END-SEARCH

    DISPLAY '都道府県名: ' WS-RESULT-NAME.

累積集計テーブルへの書き込み

部門別・日別の売上を2次元配列に集計
WORKING-STORAGE SECTION.
01  WS-DAILY-TBL.
    05  WS-DAY-ROW   OCCURS 31 TIMES.    *> 31日分
        10  WS-DEPT-AMT  PIC 9(9)V99 OCCURS 5 TIMES.  *> 5部門

01  WS-DAY-IDX   PIC 9(2).
01  WS-DEPT-IDX  PIC 9(2).

PROCEDURE DIVISION.
    *> 全体を0クリア
    MOVE ZEROS TO WS-DAILY-TBL

    *> ファイルから読み込んだレコード(SR-DAY, SR-DEPT, SR-AMT)を集計
    PERFORM UNTIL WS-EOF
        ADD SR-AMT TO WS-DEPT-AMT(SR-DAY, SR-DEPT)

        READ SALES-FILE
            AT END MOVE 'Y' TO WS-EOF-FLAG
        END-READ
    END-PERFORM

    *> 日別・部門別に出力
    PERFORM VARYING WS-DAY-IDX FROM 1 BY 1 UNTIL WS-DAY-IDX > 31
        PERFORM VARYING WS-DEPT-IDX FROM 1 BY 1 UNTIL WS-DEPT-IDX > 5
            IF WS-DEPT-AMT(WS-DAY-IDX, WS-DEPT-IDX) > 0
                DISPLAY WS-DAY-IDX '日 部門' WS-DEPT-IDX
                        ': ' WS-DEPT-AMT(WS-DAY-IDX, WS-DEPT-IDX)
            END-IF
        END-PERFORM
    END-PERFORM.

マスタテーブルの一括ロードと参照

消費税率テーブルを起動時にロード・本処理で参照
IDENTIFICATION DIVISION.
PROGRAM-ID. TAX-CALC.

DATA DIVISION.
WORKING-STORAGE SECTION.

* 消費税率テーブル(適用開始日昇順)
01  WS-TAX-TABLE.
    05  WS-TAX-ENTRY  OCCURS 1 TO 20 TIMES
                      DEPENDING ON WS-TAX-COUNT
                      ASCENDING KEY IS WS-TAX-FROM
                      INDEXED BY WS-TAX-IDX.
        10  WS-TAX-FROM   PIC 9(8).   *> 適用開始日 YYYYMMDD
        10  WS-TAX-RATE   PIC V99.    *> 税率(0.08, 0.10など)

01  WS-TAX-COUNT    PIC 9(2) VALUE 0.
01  WS-TODAY        PIC 9(8).
01  WS-PRICE        PIC 9(7)V99.
01  WS-TAX          PIC 9(7)V99.
01  WS-APPLIED-RATE PIC V99 VALUE 0.

PROCEDURE DIVISION.
    *> 税率テーブルの初期設定
    MOVE 19970401 TO WS-TAX-FROM(1)  MOVE 0.05 TO WS-TAX-RATE(1)
    MOVE 20140401 TO WS-TAX-FROM(2)  MOVE 0.08 TO WS-TAX-RATE(2)
    MOVE 20191001 TO WS-TAX-FROM(3)  MOVE 0.10 TO WS-TAX-RATE(3)
    MOVE 3 TO WS-TAX-COUNT

    *> 処理日の税率を逆順で探索(最新の適用開始日から)
    MOVE FUNCTION CURRENT-DATE(1:8) TO WS-TODAY

    PERFORM VARYING WS-TAX-IDX FROM WS-TAX-COUNT BY -1
                    UNTIL WS-TAX-IDX < 1
        IF WS-TAX-FROM(WS-TAX-IDX) <= WS-TODAY
            MOVE WS-TAX-RATE(WS-TAX-IDX) TO WS-APPLIED-RATE
            EXIT PERFORM
        END-IF
    END-PERFORM

    *> 税額計算
    MOVE 10000.00 TO WS-PRICE
    COMPUTE WS-TAX = WS-PRICE * WS-APPLIED-RATE
    DISPLAY '税額: ' WS-TAX.

よくある落とし穴と対策

① 添字の範囲外アクセス

症状: OCCURS 10 TIMESで定義した配列に対してWS-IDX = 11などの値でアクセスすると、実行時エラーまたは別の変数領域を破壊するバグが発生します。

対策: ループの終了条件を UNTIL WS-IDX > 要素数 と正確に書き、ループ外からのアクセス前にも添字の範囲チェックを行います。DEPENDING ON変数を使う場合は、要素追加前にカウントが上限を超えていないか確認してください。

② 01レベルにOCCURSを書いてコンパイルエラー

症状: 01 WS-TABLE PIC X(10) OCCURS 5 TIMES. のように01レベルに書くとコンパイルエラーになります。初心者によく見られるミスです。

対策: 01レベルはグループ項目の親として定義し、OCCURS句は必ず02〜49レベルに記述します。

③ SEARCH前にSET TO 1を書き忘れる

症状: SEARCH文(順次検索)はインデックスの現在値から検索を開始します。前回のSEARCHでインデックスが中途半端な位置のまま次の検索に入ると、先頭付近のデータが見つからないバグが発生します。

対策: SERACHの直前に必ず SET インデックス名 TO 1 を記述して先頭から検索を開始します。SEARCH ALL(二分探索)の場合は不要です。

④ SEARCH ALLでデータがソートされていない

症状: SEARCH ALLはデータがKEY ISで指定したキーの昇順(または降順)にソートされていることを前提とします。ソートされていない状態でSEARCH ALLを実行すると、見つかるべきデータが「見つからない(AT END)」という誤った結果になります。

対策: SEARCH ALLを使うテーブルはキー順にデータをロードするか、ロード後にSORT処理をかけます。ソートが保証できない場合はSEARCH(順次検索)を使います。

⑤ インデックス型変数に算術演算を行う

症状: INDEXED BYで定義したインデックス変数にADD 1やMOVEを直接行おうとするとコンパイルエラーになります。インデックス型はバイトオフセットで管理されており、通常の整数変数と内部表現が異なります。

対策: インデックスの操作はすべて SET インデックス名 TO 値SET インデックス名 UP BY 1SET インデックス名 DOWN BY 1 を使います。整数変数との相互変換も SET 整数変数 TO インデックス名 で行います。

よくある質問

QOCCURS句の配列はどこから始まりますか?
ACOBOLの配列の添字は1始まりです。OCCURS 10 TIMESで定義した場合、有効な添字は1〜10です。0からアクセスしようとするとエラーになります。他の言語(C・Java・Python)の0始まりに慣れていると間違えやすいので注意してください。
QINDEXED BYとPIC 9変数の添字はどちらを使えばよいですか?
ASEARCH文・SEARCH ALL文を使う場合はINDEXED BYが必須です。PERFORM VARYINGのループカウンタとしてのみ使う場合はPIC 9変数で十分です。ただし1つの配列にINDEXED BYとPIC 9の両方を使い分けることもできます(SEARCH用にINDEXED BY、ループ用にPIC 9)。
QOCCURS句の要素数に変数を使えますか?
A静的なOCCURS n TIMESのnには定数しか指定できません。実行時に要素数を変えたい場合はOCCURS m TO n TIMES DEPENDING ON変数を使います。ただし上限(n)は定数で宣言時に確定しておく必要があります。
QOCCURS句にVALUE句で初期値を設定できますか?
AOCCURS項目にVALUE句を書けるかどうかはコンパイラによって異なります。GnuCOBOLでは一部許容されますが、IBM COBOLでは通常エラーとなります。移植性を保つためINITIALIZEやMOVEで初期化するのが安全です。
QSEARCH ALLで複数の条件を組み合わせて検索できますか?
ASEARCH ALLのWHEN句はKEY ISで指定したキー項目に対する等値比較(=)のみです。複数条件の組み合わせや範囲検索にはSEARCH ALLは使えません。その場合はSEARCH(順次検索)またはPERFOM VARYINGでループして条件を判定します。

まとめ

機能 構文 特徴
静的配列 OCCURS n TIMES 要素数固定・通常の配列
可変長配列 OCCURS m TO n TIMES DEPENDING ON 変数 実行時に要素数を変動させる
インデックス付き OCCURS n TIMES INDEXED BY 名前 SEARCH文(順次検索)が使える
整列テーブル ASCENDING/DESCENDING KEY IS キー + INDEXED BY SEARCH ALL(二分探索)が使える
2次元配列 OCCURS句をネスト (行, 列)の2添字でアクセス
初期化 INITIALIZE / MOVE ZEROS・SPACES グループ全体を一括クリア

OCCURS句はCOBOLの配列機能の核です。INDEXED BY・SEARCH・SEARCH ALLを使いこなすことで、大規模テーブルの高速検索から固定長ファイルの処理まで幅広い業務要件に対応できます。

ループ処理の詳細はPERFORM文完全ガイドを、同じメモリ領域の多重定義はREDEFINES句完全ガイドも合わせてご確認ください。