明日、金曜日がまた急遽休みになった。土日出勤が見込まれるために。まぁ、別に私は一向にかまわんッッのだが、週単位で移行がビハインドしてて大丈夫なのか・・・?
プライムの社風の違いもあるのかもしれないが、前のプロジェクトは開発フェーズだったこともあって時間外労働上等の雰囲気ムンムンだったのに対して、今の保守業務はしっかり休暇で業務時間調整が入る。自分の時間が確保できるのでいいのだが、昨年は残業代で稼いでいた部分もあるので新しい標準報酬月額が反映される秋までのラグには厳しいものがあるな・・・。ま、勉強にちゃんと時間を使ってベースを上げろと言うことやね!
移行作業に手がつけられないので、並行してるアドオン開発を進めていた。設計の参考にするため、動きが似ている既存のアドオンのソースコードを眺めていたのだが、ABAP初心者には分からない用語や文法が多すぎて、当たり前の処理も何やってるのか読み解くのにいちいち苦労する。
その中で、比較的簡単で面白く、割とABAPの基礎的なグラマーが詰まっていたアルゴリズムがあったので復習がてら書いていく。重複内容チェックのアルゴリズム。
まず前提として、得意先/品目ごとに設定した特別単価を格納しているテーブルAがあるとする。それぞれの特別単価は有効開始日と有効終了日を持っている。
このテーブルの有効終了日を、ファイル取り込みで一括更新する機能を作りたい。取り込むファイルの内容は得意先/品目ごとに新しい有効終了日を設定したもの。
で、この取り込みファイルだが、同じ得意先/品目の組み合わせが2つ以上存在していた場合は、テーブルA上の該当レコードは更新せずに、エラーとしてログに表示したい。どっちが正しい有効終了日がわからないので。
前置きが長くなったが、この取り込みファイルの重複をチェックするためのアルゴリズムのコーディング。変数や構造の定義とかは省くが、大まかにこんな感じ。
DATA (itab_dup_check) = itab_imp_data.
SORT itab_dup_check
BY kunnr
matnr.
DELETE ADJACENT DUPLICATES
FROM itab_dup_check
COMPARING kunnr
matnr.
LOOP AT itab_dup_check
ASSINGNING FIELD-SYMBOL(<ds_dup_check>).
CLEAR ln_dup_count.
LOOP AT itab_imp_data
TRANSPORTING NO FIELDS
WHERE kunnr = <sym_dup_check>-kunnr
AND matnr = <sym_dup_check>-matnr.
ln_dup_count = ln_dup_count + 1
ENDLOOP.
IF ln_dup_count > 1.
CLEAR type_dup_data.
type_dup_data-kunnr = <sym_dup_check>-kunnr.
type_dup_data-matnr = <sym_dup_check>-matnr.
APPEND type_dup_data TO itab_dup_data.
ENDIF.
ENDLOOP.
取込ファイルから取り込んだ内容は内部テーブルitab_imp_dataに格納済みとする。
まず、始めの
DATA (itab_dup_check) = itab_imp_data.
俺の場合ここから「?」って感じだった。まぁ、itab_dup_checkという新しい内部テーブル型変数を宣言して同時にitab_imp_dataの内容を代入してるんだな、というのはなんとなく理解できるんだけども、こんなインラインの文法ありなの?この括弧は何?
ネットで調べてみたら、ABAPがインライン型のデータ宣言に対応したのは比較的最近のことらしい。通りで会社から託された分厚い参考書をめくってもそれらしい記載がないわけだ・・・。ABAPはデータ型をガチガチに事前定義しておく必要があると思い込んでいたけど、こんな書き方もできたのね。
というわけで
DATA (変数) = 値
で、データのインライン宣言。
上のコードを古い(?)書き方に直すなら、
DATA itab_dup_check TYPE TABLE OF itab_imp_data.
itab_dup_check = itab_imp_data.
って感じかな。うーん、インラインもできると知れたのは良かったけど、やっぱり宣言文は一箇所にまとまってる方が見やすいので、積極的に使うものではなさそう。
肝心のコーディングの意味としては、先に触れた通り、ファイルから取り込んだデータをitab_dup_check(重複チェック用内部テーブル)に代入しているだけ。つまり、この段階では取り込んだ内容のコピーを作ったというイメージ。
次、
SORT itab_dup_check
BY kunnr
matnr.
DELETE ADJACENT DUPLICATES
FROM itab_dup_check
COMPARING kunnr
matnr.
これは文法知らなかったとしても単語で何やってるかはだいたい理解できるな。SORT〜は重複チェック用内部テーブルの内容を得意先/品目で昇順並び替えをしている。だが、なんのために・・・?
DELETE〜は同じく重複チェック用内部テーブルから得意先/品目の組み合わせで重複しているレコード(DUPLICATES)を削除(DELETE)している。ただし!ここで一番大事なのはADJACENT(隣接)という部分。その名の通り、この命令は隣接している重複行しか消してくれない。調べてみたんだけど、この隣接削除以外にテーブルの重複削除の命令文は見つからなかった。
ということはつまり、テーブルの重複削除をしたいときは、SORTしてからじゃないとダメ。この二つはお約束というか、セットで書かれるべき処理ってこと。なるほどねー。
次〜
LOOP AT itab_dup_check
ASSINGNING FIELD-SYMBOL(<sym_dup_check>).
LOOP AT itab_dup_check なので重複チェック用テーブルにデータがある限り以下の処理をループするよ〜という意味。その次のASSIGNING FIELD-SYMBOL(<aaa>)というオプションは、ABAPを勉強し始めてからよく見る。FIELD- SYMBOLというのは、変数の別名として使える容器のようなイメージ。
重複チェック用内部テーブル1行目を取り出し、シンボルに入れて処理。終わったら2行目を取り出しシンボルに入れて処理。以下テーブルの全行処理するまで繰り返し。つまり、今現在処理している行の内容を表すのが<sym_dup_check>というシンボル。
フィールドシンボルを使わなくても、作業用の新しい構造型を1つ定義すれば同様の動きは実現できるにはできる。
DATA type_dup_check TYPE itab_dup_check.
LOOP AT itab_dup_check INTO type_dup_check.
...処理
こんな感じで。コーディングの量自体はさほど変わらないが、これだと構造を一つ増える分、プログラムが消費するメモリ量が増大するので、パフォーマンス的によろしくないらしい。俺はメモリマネジメントのところはさっぱり詳しくないんだけど・・・。
OracleのときはCURSOR FOR LOOPが便利だったので、このフィールドシンボルに慣れるまで少し戸惑ったが、まぁ概念的なところは似通っている。
で、このループ内の処理部分
CLEAR ln_dup_count.
LOOP AT itab_imp_data
TRANSPORTING NO FIELDS
WHERE kunnr = <sym_dup_check>-kunnr
AND matnr = <sym_dup_check>-matnr.
ln_dup_count = ln_dup_count + 1
ENDLOOP.
IF ln_dup_count > 1.
CLEAR type_dup_data.
type_dup_data-kunnr = <sym_dup_check>-kunnr.
type_dup_data-matnr = <sym_dup_check>-matnr.
APPEND type_dup_data TO itab_dup_data.
ENDIF.
ln_dup_countというのは整数型の変数。CLEAR〜はループ毎に変数を初期化しているというだけなので特に難しいことなし。
その下のループ内ループ。LOOP AT itab_imp_dataなので、今度はファイル取込データのテーブルの行に依ってループしてるが、オプションのTRANSPORTING NO FIELDSとはなんぞや・・・?
調べてみたところの俺の解釈だけれども、「内部テーブルの行の内容自体は扱わないので持ち出さなくていいですよ」という意味のよう。つまり条件に合致する行がある限り処理をするけども、その処理に行が持っているデータの値は使わないので、フィールドシンボルとかあるいは作業用構造に割り当てはしないってこと。
具体的に見ていくと、検索条件になっているのは
WHERE kunnr = <sym_dup_check>-kunnr
AND matnr = <sym_dup_check>-matnr.
なので、ファイル取込データのテーブル内に、先のループ(大)でシンボルに格納されている行と得意先/品目の組み合わせが一致する行がある限り、
ln_dup_count = ln_dup_count + 1
という、ln_dup_countを1加算するという処理を繰り返す。
どういうことかというとつまり、取込データの内部テーブル内で同じ得意先/品目の組み合わせが重複していた場合、ループ(小)を2周以上することになるので、結果としてln_dup_countの値が1より大きくなるということ。あるべき値は1やね。
そういうわけで
IF ln_dup_count > 1.
CLEAR type_dup_data.
type_dup_data-kunnr = <sym_dup_check>-kunnr.
type_dup_data-matnr = <sym_dup_check>-matnr.
APPEND type_dup_data TO itab_dup_data.
ENDIF.
もしln_dup_countが1より大きかった場合は、ループ(大)で処理中の重複チェック用内部テーブルの行の得意先と品目の値を、別の構造(type_dup_data)に格納して、さらにその構造を重複データ格納用テーブル(itab_dup_data)に挿入する。ログでエラー内容を表示する時には、この重複データ格納用テーブルの内容を使うことになる。
ちなみに、APPEND命令とINSERT命令の違いは、INSERTがテーブルの任意の位置に行を挿入できるのに対して、APPENDは常にテーブルの一番最後の行へ付加されること。位置の特定がない分、APPENDの方が処理が早い。今回の場合、既にソートされた重複チェック用テーブルがループ(大)の依り代なので、APPENDでも重複データは得意先/品目順にちゃんと並ぶはず。そういう場合は迷わずAPPENDを使うべし。
と、かなり長くなってしまったが・・・以上のアルゴリズムが、ファイルの重複をチェックし、かつ該当のレコード内容をエラーログ用に格納するというところまでの実現方法。使ってる分にはあんまり意識しないけど、いざその設計方法考えてみるとなるほどな〜と感じることがよくある。
テーブル内容をいったんコピーして、重複チェック用のテーブルを成形するっていうところが個人的にアハ体験やったわ。俺だったら無理矢理1つのテーブルで実現しようとして、10回重複してる組合せがあったら、同じエラーログが10回出力されるような仕組みになって発狂してそう。