COBOL 処理パターン　多段階集計

COBOLが活躍した時代はデータベース（SQL）が実用化される以前で、データの保存は基本的に順編成ファイルに行っていた。アクセス方法は順次の読み書きだけなので、データの抽出や集計を行うには、ファイルをソートし、ここで紹介するテクニックを駆使する必要があった。

これから紹介するサンプルプログラムをテンプレートとして利用すれば、順編成ファイルを扱うアプリケーションに大体対応できると思うので参考にして頂きたい。

多段階集計は特定のレコード項目をキーにして特定の項目（数値や金額など）を集計するテクニックである。キーの指定により集計の段階をコントロールできるのでこのような名前になっている。

### 機能仕様
入力ファイルは、日付と売り上げ金額から構成された売り上げレコードの集まりである。同じ日付の売り上げレコードは複数あるとする。データは日付の昇順でソートされている。
このファイルを読み込み、日ごと及び月ごとに金額をサマリーし、それぞれ一件のレコードして出力する。また総合計のレコードも作成する。

### 入出力構成図
<img class="lm100 tm10 bm0" src="http://mikomokaru.sakura.ne.jp/cobol/2/cob02_02.png" alt="[cob02_02.png]" width="300">

###プログラム構造図
処理パターンを理解する上での核心部分。ソースコードと比較しながら見ていけば記述ルールは理解していただけると思う。
<img class="lm100 tm10 bm0" src="http://mikomokaru.sakura.ne.jp/cobol/2/cob02_01.png" alt="[cob02_01.png]" width="500">

###COBOLソースコード
```
       IDENTIFICATION             DIVISION.
       PROGRAM-ID.                SUM001.

       ENVIRONMENT                DIVISION.
       INPUT-OUTPUT               SECTION.
       FILE-CONTROL.
      *入力ファイル 行順編成ファイル(テキストファイル)  
       SELECT  F1  ASSIGN  TO  "/Users/itohisao/Desktop/COBOL/F001.txt"
       ORGANIZATION IS LINE SEQUENTIAL
       ACCESS  MODE  IS  SEQUENTIAL  STATUS FST1.  
      *出力ファイル 行順編成ファイル(テキストファイル)  
       SELECT  F2  ASSIGN  TO  "/Users/itohisao/Desktop/COBOL/F002.txt"
       ORGANIZATION IS LINE SEQUENTIAL
       ACCESS  MODE  IS  SEQUENTIAL  STATUS FST2.  
       
       DATA                     DIVISION.
       FILE                     SECTION.
      * 入力ファイル
       FD  F1.
       01  F1R.
           05  YMD.           
               10  YYYY            PIC 9(04). 
               10  MM              PIC 9(02). 
               10  DD              PIC 9(02). 
           05  SALES               PIC 9(10).
      * 出力ファイル
       FD  F2.
       01  F2R                     PIC X(18).
       
       WORKING-STORAGE         SECTION.
       01  END-FLG                 PIC 9(01).
       01  FST1                    PIC X(02).
       01  FST2                    PIC X(02).
      *保存キー
       01  SAVE-KEY.           
           05  YYYY                PIC 9(4).          
           05  MM                  PIC 9(2). 
           05  DD                  PIC 9(2). 
      * 出力レコード                      
       01  SUB-TOTAL-RECORD.
           05  YMD.           
               10  YYYY            PIC 9(4). 
               10  MM              PIC 9(2). 
               10  MM-R            REDEFINES MM.
                   15 MM-CHAR      PIC X(02). 
               10  DD              PIC 9(2). 
               10  DD-R            REDEFINES DD.
                   15 DD-CHAR      PIC X(02). 
           05  SALES               PIC ZZZZZZZZZ9.
      * 合計                             
       01  TOTAL                   PIC 9(10).
      * 日次計
       01  SUB-TOTAL-DAY           PIC 9(10).
      * 月次計
       01  SUB-TOTAL-MONTH         PIC 9(10).

       PROCEDURE                DIVISION.
      ******************************************************************
      * 主処理 
      ****************************************************************** 
       PERFORM INIT-RTN
       PERFORM LOOP-RTN UNTIL END-FLG NOT = 0
       PERFORM END-RTN
       STOP RUN.
      ******************************************************************
      * 前処理 
      ******************************************************************
       INIT-RTN.
      *    初期化
           MOVE 0 TO END-FLG
           OPEN INPUT F1
           OPEN OUTPUT F2
           INITIALIZE TOTAL SUB-TOTAL-DAY SUB-TOTAL-MONTH
      *    1件目のレコードの入力
           PERFORM READ-RTN
      *    キーの保存
           MOVE YMD OF F1R TO SAVE-KEY.
       INIT-RTN-EX. 
           EXIT.
      ******************************************************************
      * 繰り返し処理 
      ******************************************************************
       LOOP-RTN.
      * 次のレコード・キーの比較   
           IF YMD OF F1R NOT = SAVE-KEY THEN
      *        キーブレーク・日次計出力
               PERFORM WRITE-DAY-RTN
               IF YYYY OF F1R NOT = YYYY OF SAVE-KEY OR
                  MM OF F1R NOT = MM OF SAVE-KEY THEN
      *            キーブレーク・月次計出力
                   PERFORM WRITE-MONTH-RTN
               END-IF   
      *        キーの保存
               MOVE YMD OF F1R TO SAVE-KEY   
           END-IF
      *    金額の加算
           ADD SALES OF F1R TO SUB-TOTAL-DAY SUB-TOTAL-MONTH TOTAL 
      *    レコードの入力
           PERFORM READ-RTN.
       LOOP-RTN-EX. 
           EXIT.
      ******************************************************************
      * 後処理 
      ******************************************************************
       END-RTN.
      *    日次計出力
           PERFORM WRITE-DAY-RTN
      *    月次計出力
           PERFORM WRITE-MONTH-RTN
      *    合計出力     
           MOVE YYYY OF SAVE-KEY TO YYYY OF SUB-TOTAL-RECORD
           MOVE "  " TO MM-CHAR OF SUB-TOTAL-RECORD
           MOVE "  " TO DD-CHAR OF SUB-TOTAL-RECORD
           MOVE TOTAL TO SALES OF SUB-TOTAL-RECORD 
           WRITE F2R FROM SUB-TOTAL-RECORD
      *    ファイルクローズ　
           CLOSE  F1 F2.
       END-RTN-EX.
           EXIT.
      ******************************************************************
      * 入力処理  
      ******************************************************************
       READ-RTN.
           READ  F1 
               AT END
                   ADD 1 TO END-FLG
                   MOVE HIGH-VALUE TO YMD OF F1R
           END-READ.
       READ-RTN-EX.
           EXIT.

      ******************************************************************
      * 日次出力処理  
      ******************************************************************
       WRITE-DAY-RTN.
      *    出力レコードの編集 
           INITIALIZE SUB-TOTAL-RECORD     
           MOVE SAVE-KEY TO YMD OF SUB-TOTAL-RECORD
           MOVE SUB-TOTAL-DAY TO SALES OF SUB-TOTAL-RECORD  
      *    ファイル出力     
           WRITE F2R FROM SUB-TOTAL-RECORD
      *    集計エリアのクリア     
           INITIALIZE SUB-TOTAL-DAY.
       WRITE-DAY-RTN-EX.
           EXIT.

      ******************************************************************
      * 月次出力処理  
      ******************************************************************
       WRITE-MONTH-RTN.
      *    出力レコードの編集 
           INITIALIZE SUB-TOTAL-RECORD     
           MOVE YYYY OF SAVE-KEY TO YYYY OF SUB-TOTAL-RECORD
           MOVE MM OF SAVE-KEY TO MM OF SUB-TOTAL-RECORD
           MOVE "  " TO DD-CHAR OF SUB-TOTAL-RECORD
           MOVE SUB-TOTAL-MONTH TO SALES OF SUB-TOTAL-RECORD 
      *    ファイル出力     
           WRITE F2R FROM SUB-TOTAL-RECORD
      *    集計エリアのクリア     
           INITIALIZE SUB-TOTAL-MONTH.
       WRITE-MONTH-RTN-EX.
           EXIT.
```

###処理前後のファイルの内容
<img class="lm100 tm10 bm0" src="http://mikomokaru.sakura.ne.jp/cobol/2/cob02_03.png" alt="[cob03_01.png]" width="500">


###もうひとつ別の方法
キーブレークの方法として、上記はひとつのループの中で、日次・月次それぞれキー判定を行い、出力をコントロールしている。

同じことを行うために、キーごとにループを回す方法もある。次の例では全体のループの中に、外側に月次で集計するループ、内側に日次で集計するループを保持し、それぞれのループの中で集計・出力を行なっている。

ひとつ注意することは、キーブレーク用のキーの保存を日次・月次で別に行うこと。そうしないとファイルの終了の判定ができなくなる。
両者、特に一長一短があると思えないので、どちらで行っても構わないと思う。強いて言えば、前者はファイル読み込み終了後に最後に残った集計結果を後処理で出力する必要があり、共通の出力処理が別の場所でもう１回発生することがやや欠点か？（個人的には上記の方法に馴染んでいたので最初に紹介しただけのこと）

###プログラム構造図
<img class="lm100 tm10 bm0" src="http://mikomokaru.sakura.ne.jp/cobol/2/cob02_04.png" alt="[cob02_04.png]" width="500">

###COBOLソースコード
```
       IDENTIFICATION             DIVISION.
       PROGRAM-ID.                TEST001.
       ENVIRONMENT                DIVISION.
       INPUT-OUTPUT               SECTION.
       FILE-CONTROL.
      * 入力ファイル 行順編成ファイル(テキストファイル)   
       SELECT  F1  ASSIGN  TO  "/Users/itohisao/Desktop/COBOL/F001.txt"
       ORGANIZATION IS LINE SEQUENTIAL
       ACCESS  MODE  IS  SEQUENTIAL  STATUS FST1.  
      * 出力ファイル 行順編成ファイル(テキストファイル)  
       SELECT  F2  ASSIGN  TO  "/Users/itohisao/Desktop/COBOL/F002.txt"
       ORGANIZATION IS LINE SEQUENTIAL
       ACCESS  MODE  IS  SEQUENTIAL  STATUS FST2.  
       
       DATA                     DIVISION.
       FILE                     SECTION.
      * 入力ファイル
       FD  F1.
       01  F1R.
           05  YMD.           
               10  YYYY            PIC 9(4). 
               10  MM              PIC 9(2). 
               10  DD              PIC 9(2). 
           05  SALES               PIC 9(10).
      * 出力ファイル
       FD  F2.
       01  F2R                  PIC X(18).
       
       WORKING-STORAGE         SECTION.
       01  END-FLG                 PIC 9(01).
       01  FST1                    PIC X(02).
       01  FST2                    PIC X(02).
      * 保存キー（年月日） 
       01  SAVE-KEY-1              PIC 9(8).
      * 保存キー（年月）
       01  SAVE-KEY-2.
           05  YYYY                PIC 9(4). 
           05  MM                  PIC 9(2).
      * 保存キー（年）
       01 SAVE-YYYY                PIC 9(04).     
      * 出力レコード                      
       01  SUB-TOTAL-RECORD.
           05  YMD.           
               10  YYYY            PIC 9(4). 
               10  MM              PIC 9(2).
               10  MM-R            REDEFINES MM.
                   15 MM-CHAR      PIC X(02).  
               10  DD              PIC 9(2).
               10  DD-R            REDEFINES DD.
                   15 DD-CHAR      PIC X(02). 
           05  SALES               PIC ZZZZZZZZZ9.
      * 合計                             
       01  TOTAL                   PIC 9(10).
      * 日次計 
       01  SUB-TOTAL-DAY           PIC 9(10).
      * 月次計
       01  SUB-TOTAL-MONTH         PIC 9(10). 

       PROCEDURE                DIVISION.
      ******************************************************************
      * 主処理 
      ****************************************************************** 
       PERFORM INIT-RTN
       PERFORM LOOP-RTN UNTIL END-FLG NOT = 0
       PERFORM END-RTN
       STOP RUN.
      ******************************************************************
      * 前処理 
      ******************************************************************
       INIT-RTN.
      *    初期化 
           MOVE 0 TO END-FLG
           OPEN INPUT F1
           OPEN OUTPUT F2
           INITIALIZE TOTAL SUB-TOTAL-DAY SUB-TOTAL-MONTH
      * 1件目のレコード
           PERFORM READ-RTN
      *    キーの保存１ 年月日     
           MOVE YMD OF F1R TO SAVE-KEY-1.
      *    キーの保存２ 年月     
           MOVE YYYY OF F1R TO YYYY OF SAVE-KEY-2.
           MOVE MM OF F1R TO MM OF SAVE-KEY-2.
      *    年の保存     
           MOVE YYYY OF F1R TO SAVE-YYYY.
       INIT-RTN-EX. 
           EXIT.
      ******************************************************************
      * 繰り返し処理 
      ******************************************************************
       LOOP-RTN.
      *    月次繰り返し処理 ここから--- 
           PERFORM UNTIL YYYY OF F1R NOT = YYYY OF SAVE-KEY-2 OR 
                         MM OF F1R NOT = MM OF SAVE-KEY-2 
      *        日次繰り返し処理 ここから---
               PERFORM UNTIL YMD OF F1R NOT = SAVE-KEY-1
      *            金額の加算
                   ADD SALES OF F1R TO TOTAL SUB-TOTAL-DAY 
                                             SUB-TOTAL-MONTH
      *            レコードの入力             
                   PERFORM READ-RTN
               END-PERFORM
      *        日次繰り返し処理 ここまで---         
      *        日次出力・出力レコードの編集
               INITIALIZE SUB-TOTAL-RECORD     
               MOVE SAVE-KEY-1 TO YMD OF SUB-TOTAL-RECORD
               MOVE SUB-TOTAL-DAY TO SALES OF SUB-TOTAL-RECORD  
      *        ファイル出力     
               WRITE F2R FROM SUB-TOTAL-RECORD
      *        集計エリアのクリア    
               INITIALIZE SUB-TOTAL-DAY
      *        キーの保存(年月日)     
               MOVE YMD OF F1R TO SAVE-KEY-1
           END-PERFORM
      *    月次繰り返し処理 ここまで--- 
      *    月次計出力・出力レコードの編集 
           INITIALIZE SUB-TOTAL-RECORD     
           MOVE YYYY OF SAVE-KEY-2 TO YYYY OF SUB-TOTAL-RECORD
           MOVE MM OF SAVE-KEY-2 TO MM OF SUB-TOTAL-RECORD
           MOVE "  " TO DD-CHAR OF SUB-TOTAL-RECORD
           MOVE SUB-TOTAL-MONTH TO SALES OF SUB-TOTAL-RECORD 
      *    ファイル出力     
           WRITE F2R FROM SUB-TOTAL-RECORD
      *    集計エリアのクリア    
           INITIALIZE SUB-TOTAL-MONTH
      *   キーの保存(年月)     
           MOVE YYYY OF F1R TO YYYY OF SAVE-KEY-2
           MOVE MM OF F1R TO MM OF SAVE-KEY-2.      
       LOOP-RTN-EX. 
           EXIT.
      ******************************************************************
      * 後処理 
      ******************************************************************
       END-RTN.
      *    合計出力     
           INITIALIZE SUB-TOTAL-RECORD     
           MOVE SAVE-YYYY TO YYYY OF SUB-TOTAL-RECORD
           MOVE "  " TO MM-CHAR OF SUB-TOTAL-RECORD
           MOVE "  " TO DD-CHAR OF SUB-TOTAL-RECORD
           MOVE TOTAL TO SALES OF SUB-TOTAL-RECORD 
           WRITE F2R FROM SUB-TOTAL-RECORD
      *    ファイルクローズ     
           CLOSE  F1  F2.
       END-RTN-EX.
           EXIT.
      ******************************************************************
      * 入力処理  
      ******************************************************************
       READ-RTN.
           READ  F1 
               AT END
                   ADD 1 TO END-FLG
                   MOVE HIGH-VALUE TO YMD OF F1R
           END-READ.
       READ-RTN-EX.
           EXIT.
```

関連記事
[COBOL 処理パターン　マッチング](https://qiita.com/mikomokaru-jpn/items/ec75da8319cf642c33f5)
[COBOL 処理パターン　テーブル展開](https://qiita.com/mikomokaru-jpn/items/ffb340cb0d647fc56597)
[COBOL 処理パターン　外部サブプログラム呼ぶ](https://qiita.com/mikomokaru-jpn/items/967036b9bd3a11af601f)
[COBOL 処理パターン　C言語で作成した外部サブプログラムを呼ぶ](https://qiita.com/mikomokaru-jpn/items/85bb6a3b15dfe845b3e4)