こんにちは! 健史です。
バッチ処理で使われるコントロールブレイク、COBOLではマッチング同様 必須のロジックですが、COBOLに限らず必要なロジックで他の言語でも使われます。
そんなコントロールブレイクについて
・ポイントとか、あれば知りたい
・作成途中だけど、うまく動かなくて困っている
・作成して動いているけど、あれで良かったのかなぁ?
・ひな形や動いているプログラムを流用して作成しているけど、実はよくわかってない
・新入社員教育でやっているけど、難しくて理解できない、困っている
という方へ、コントロールブレイクのロジックを徹底解説します。
プログラムはVBAで紹介・検証していますが、ロジックはCOBOLはもちろん他の言語でも使えます。
PL/SQL版も作成しました。
ご活用頂ければと思います。
非コントールブレイク処理の認識から
"非コントロールブレイク処理"とは私が付けた処理名ですが、"非コントロールブレイク処理"でなくても何でもよいです。
要は「データの先頭から全件を順次読み込み処理する単純な処理」のことです。
例えば、先頭から読み込んだ売上ファイルの全売上金額を集計する場合です。
道路を走ることに例えると「とにかく、終点まで真っすぐに進む」イメージです。
フローチャート
ファイルの読み込み処理が2回ありますが、殆どの多くのプログラムで採用されており、プログラムの構造化の観点から採用されるロジックです。
ExcelVBAのプログラム
'変数定義 Dim ix1 As Long Dim ix2 As Long Dim wk_uri_kingaku As Long '売上ファイルのオープン ix1 = 0 '売上金額ファイルのオープン ix2 = 0 Sheets(2).Cells.Clear '初期値設定 wk_uri_kingaku = 0 '売上ファイルの読み込み ix1 = ix1 + 1 '売上ファイルが終わるまで繰り返す Do Until Sheets(1).Cells(ix1, 1) = "" wk_uri_kingaku = wk_uri_kingaku + Sheets(1).Cells(ix1, 2) ix1 = ix1 + 1 Loop '売上金額ファイルへの出力 ix2 = ix2 + 1 Sheets(2).Cells(ix2, 1) = wk_uri_kingaku
作成したデータは以下の通りです。
・入力データ
・処理後の出力データ
入力の[売上ファイル]をSheets(1)、出力の[売上金額ファイル]をSheets(2)としています。
売上ファイルのA列は日付、B列は売上金額です。商品コードなどは省略しています。
売上ファイルの終了はSheets(1)のA列が[''](NULL)です。
読み込みは、変数ix1を1ずつアップします。
セルA1であるSheets(1).Cells(1,1) = ''(NULL)の場合は1件もデータがないことになります。
売上金額ファイルは、最初にクリアした状態にする必要があるため、[Sheets(2).Cells.Clear]でクリアしています。
書き込みは、変数ix2を1ずつアップします。
既にオープンしているExcelシートを仮のファイルに見立てて処理していることから、
・読み書きするために必要な添え字[ix1][ix2]に初期値設定すること、及び、書き込むためのシートクリアをファイルオープン
・処理結果をそのまま画面に表示しおくため、ファイルのクローズ処理はない
です。
コントロールブレイク処理
2パターンのロジックを紹介します。処理結果は同じです。
日付順にソートされた売上ファイルを順次読み込み、日付別の売上金額を集計して売上金額ファイルに出力する処理です。
プログラム処理は、
・しかし日付が変わったら集計した金額を売上金額ファイルに出力する
です。
現場では「日付がブレイクしたら、集計した金額を売上金額ファイルに出力する」と使(い)います。
コントロールブレイクを訳すと「制御[コントロール]を中断[ブレイク]する」でしょうか。
道路を走ることに例えると「変化が生じたら進行を中断して寄り道して、寄り道が終わったら戻って元の道を進行する」イメージです。
ファイル読み込みが1回の場合 IF文型
IF文型とは、ブレイクキーの判断を[If]で記述する方法です。
CASE文などによる判定でも同じで、後述の「ループ型」と対比するため一律「IF文型」としています。
フローチャート その1
これは、赤枠で囲った繰り返し処理の中で「ファイルの読み込み処理を1回だけしかできない」パターンです。
繰り返し処理の中で、ファイルの読み込み処理を1回だけしかできない記述方法のプログラミング言語があるために紹介しました。
それは「PL/SQL」という言語で、「カーソルFOR LOOP文」を使用する場合です。(無論、COBOLなどでも1回のみ読み込み処理にできます)
「カーソルFOR LOOP文」を使用すると読み込み処理をコーディングする必要はないのですが、プログラミングで読み込み処理を制御できない記述方法で、「1件ごとにデータが読み込まれている」前提で処理内容だけを記述すれば良いのです。
PL/SQLには「読み込み処理をコーディングでき、意図するよう制御できる記述方法」もありますが、プログラム行数が多くなること、そのため標準化の観点から使わないルール、または奨励していないところがあります。
ちなみに上記の記述方法、ルール化や奨励していることが「良くないこと」などと思っていませんので、悪しからずです。
その場合のコントロールブレイクは、この処理ロジックが適切で、これ以上のベターはないでしょう。
本題の処理ロジックの補足説明です。
初期値でセットするのは、ブレイクキーの[ワーク.売上日付]だけで、金額を集計する[ワーク.売上金額]にはセットしません。
0をセットしても良いのですが、ループ処理のなかで0をセットしているので敢えて行う必要はありません。
繰り返し処理のなかで、最初の1件目の1回だけしか通らない線があります。
「ワーク.売上日付 = 0」のときに分岐するオレンジ色の線です。
初期値セットで[ワーク.売上日付]に0をセットしているのですが、これは初回だけ行うスイッチ機能を果たしています。
1件目のときもキーブレイク処理を行いますが、まだ売上金額を集計していないので売上金額ファイルを出力しないように制御する必要があります。
2件目以降は、0ではなく読み込んだ日付がセットされていて、該当日付をもつ売上金額がワーク.売上金額に蓄積されているのです。
重要なロジックが、メイン処理を終えた後にある「ワーク.売上日付の判断」です。
このロジックでは、メイン処理が終わった後にブレイク後の処理を行わないと最後のキーデータが処理されないのです。
これ、わかりますか?
この判断とブレイク後の処理を記述しないと、最後のデータがワークに残ったままで「無かったこと」になってしまうのです。
順不同でたいへん恐縮ですが、以下の[プログラム その1]の[処理後の出力データ]で、「20201204 40」のデータが出力されません。
一方で「キーブレイクを判断する項目が初期値のまま」ということは、データが1件もなかったことになります。
そのため、最後の判断が必要です。
スイッチとして[ワーク.売上日付]を使いましたが、以下のように[ワーク.スイッチ]を使っても同じことです。
「売上日付が'0'~'99999999'、数字の羅列の全パターンが想定される」場合には、スイッチを使います。
初期値に「1件目には絶対に来ない」と勝手に思い"99999999"を設定したとして、もし1件目に初期値のデータ"99999999"が来たらブレイクさせたいのにブレイクしなくなってしまいます。
どの数字の羅列を初期値にしても、初期値設定したデータが1件目に来ない可能性はありません、来る可能性はありますので。
プログラム その1
[ワーク.売上日付]をスイッチとして使う処理フローで作成したプログラムです。
(すでにチェック済みで「売上日付には"日付"データしかなく、日付以外のデータはない」前提の場合)
Dim ix1 As Long Dim ix2 As Long Dim wk_uri_hizuke, wk_uri_kingaku As Long '売上ファイルのオープン ix1 = 0 '売上金額ファイルのオープン ix2 = 0 Sheets(2).Cells.Clear '初期値クリア wk_uri_hizuke = 0 '売上ファイルの読み込み ix1 = ix1 + 1 '売上ファイルが終わるまで繰り返す Do Until Sheets(1).Cells(ix1, 1) = "" If Sheets(1).Cells(ix1, 1) <> wk_uri_hizuke Then 'ワーク.日付が0の場合は1件目のデータなので出力しない If wk_uri_hizuke <> 0 Then '売上金額ファイルへの出力 ix2 = ix2 + 1 Sheets(2).Cells(ix2, 1) = wk_uri_hizuke Sheets(2).Cells(ix2, 2) = wk_uri_kingaku End If '初期値設定 wk_uri_hizuke = Sheets(1).Cells(ix1, 1) wk_uri_kingaku = 0 End If 'キーブレイク関係なく、売上ファイルの金額を合計欄に加算するだけ wk_uri_kingaku = wk_uri_kingaku + Sheets(1).Cells(ix1, 2) '売上ファイルの読み込み ix1 = ix1 + 1 Loop '最後の日付の金額を出力していないのでワーク.日付を判断して出力 ' 日付が0の場合はデータが無かったことなので出力しない If wk_uri_hizuke <> 0 Then ix2 = ix2 + 1 Sheets(2).Cells(ix2, 1) = wk_uri_hizuke Sheets(2).Cells(ix2, 2) = wk_uri_kingaku End If
・入力データ
非コントロールブレイク処理と同じ
・処理後の出力データ
ファイル読み込みが複数回の場合 ループ型
ループ型は、ブレイクキーの判断を[Do Until]や[Do While]で記述する方法です。
COBOLでは、[PERFORM UNTIL]です。
フローチャート その2
「繰り返し処理の中で、ファイルの読み込み処理を複数回できる」パターンです。
[売上ファイル.売上金額をワーク.売上金額に加算]と[売上ファイルの読み込み]は繰り返し処理の中にある繰り返し処理です。
IF文型に比べ、シンプルになっています。
プログラム その2
Dim ix1 As Long Dim ix2 As Long Dim wk_uri_hizuke, wk_uri_kingaku As Long '売上ファイルのオープン ix1 = 0 '売上金額ファイルのオープン ix2 = 0 Sheets(2).Cells.Clear '売上ファイルの読み込み ix1 = ix1 + 1 '売上ファイルが終わるまで繰り返す Do Until Sheets(1).Cells(ix1, 1) = "" '初期値設定 wk_uri_hizuke = Sheets(1).Cells(ix1, 1) wk_uri_kingaku = 0 Do Until Sheets(1).Cells(ix1, 1) = "" Or _ Sheets(1).Cells(ix1, 1) <> wk_uri_hizuke 'キーブレイク関係なく、売上ファイルの金額を合計欄に加算するだけ wk_uri_kingaku = wk_uri_kingaku + Sheets(1).Cells(ix1, 2) '売上ファイルの読み込み ix1 = ix1 + 1 Loop '売上金額ファイルへの出力 ix2 = ix2 + 1 Sheets(2).Cells(ix2, 1) = wk_uri_hizuke Sheets(2).Cells(ix2, 2) = wk_uri_kingaku Loop
・入力データ
コントロールブレイク処理 その1と同じ
・処理後の出力データ
コントロールブレイク処理 その1と同じ
コントロールブレイク処理での留意点
初期値セット
必須の処理は以下で、キーブレイク処理の準備作業です。
・入力ファイルからのブレイクキーの退避
・ワーク上に定義した集計項目などのリセット、クリア
集計などのループ処理内
基本、非コントロールブレイク処理と同じです。
・入力ファイルの項目をワークの集計項目に累積計算処理など
・入力ファイル読み込み
「キーがブレイクしたら・・・」といった判断処理は、[IF文]や[Do Until文]で記述しているので、ループ処理内で意識する必要はありません。
ブレイク後の処理
間違えやすく、特に注意が必要な処理です。
・ワーク上の値を使うこと
ワーク.日付、ワーク.売上金額などの項目だけを使います。
・入力ファイルの項目を使うことはない
この処理で入力ファイルの項目を使うことはなく、使っていればミスであり、バグであり、正しく処理されていないでしょう。
「ブレイクした時の入力ファイル項目の値も出力する」といった記述があれば必要ですが、そのような要求仕様はまずないでしょう。
もしそのような要求仕様の場合に、無条件に入力ファイルの項目を移送(参照)すれば、最後のデータではファイルを読み終えた状態の入力ファイルを参照することになります。
条件とは「もしファイルが終了状態でなければ」で、「もしファイルが終了状態でなければ、入力ファイルの項目を移送(参照)する」です。
ちなみにCOBOLでは、ファイルを読み終えた状態のデータを参照すると異常終了します。異常終了ではなく警告終了するコンピュータ(コンパイラー)もあるかもしれません。
いずれにしてもファイルを読み終えた状態のデータを参照することは不適切で、コントロールブレイクの後処理で入力ファイルの項目を参照することは合理的ではありません。
コンロトールブレイクを理解していないと「入力ファイルの項目を使ってないけど間違ってないかな?」と不安・疑問に思うところです。
ファイル読み込みが1回の場合の留意点
上記でも書きましたが、「ファイル読み込みが1回の場合」にはメイン処理終了後、データの有無を確認し有ったならば、ブレイク後の処理が必要な場合があります。
今回の例では必要です。
不要なケースは「日付ごとの連番を1,2,3・・・とカウントアップし、出力ファイルにカウントアップした連番を付加して出力する」などです。
キーブレイク時の処理は「連番を付与する項目[ワーク.連番]をリセットする」だけで、非コントロールブレイク処理で「[ワーク.連番]をカウントアップし付加してファイル出力してしまう」からです。
すなわち、キーブレイクで処理する項目を非コントロールブレイク処理で使ってしまい、キーブレイク時には処理された値を使うことがないからです。
最後に
長くなりましたが最後まで目を通して頂き、おつかれさまでした!
「コントロールブレイク処理を理解できました!」でしょうか。
COBOLにてIF文型で標準化されておられる組織・担当の方、今からでもUNTIL型にすることを検討し変更されてはいかがでしょうか。
構造化できてIF文型よりは見やすく、新人の方にも説明しやすく分かりやすいと思います。
冒頭の「COBOLではマッチング同様 必須のロジック」についてです。
Oracleなどのデータベースを扱うSQLでは、キー毎に値を集計したり、番号を付番したりするコマンドがあり、短い行数でできます。
COBOLでは、キー毎に値を集計したり、番号を付番したりするコマンドがありません。
なので、プログラミングで作成します。
Oracleなどのデータベースでも、複数のキーブレイクがあったり、多くのファイルから項目を参照したりする複雑な処理を作成しなければならない場合があります。
その場合は、PL/SQLという言語でCOBOLのようにプログラムを作成したほうがわかりやすいですし、またSQLだけで実現することは困難です。
どんなに高級言語で処理が簡単にできるようになっても、プログラミングがなくなることはないでしょう。
コントロールブレイク、キーブレイクは、基本中の基本であり、プログラマが理解しておく必要があるロジックのひとつです。
上記プログラムをコピー&ペーストして試し、さらに応用し業務効率化の一助になればと思います。
参考:今回使用したデータ
・コピーしてペーストするときは、Excelシートのセル:A1で[右クリック]-[形式を選択して貼り付け(S)]から[貼り付ける形式(A)]に"テキスト"を選択します。
・実行にはシート2が必要ですので、ない場合には作成しておきます。
20201201 10 20201202 10 20201202 10 20201203 10 20201203 10 20201203 10 20201204 10 20201204 10 20201204 10 20201204 10
コントロールブレイク応用編の紹介
応用編のプログラムを作成しました。
コントロールブレイク処理の理解を深め、さらに考え方を広げる、レパートリーを増やすことにつながる内容と思っています。
PL/SQL版の紹介
PL/SQLで、ループ型のコントロールブレイク処理を作成しました。
コメント