XX年ぶりにコーディングをした。
ほとんど忘れてたので、いろいろ試行錯誤した軌跡を残しておく。
初心者はこれらのパーツをパクれば、大体のことはできると思う。
ただし、プログラミングをしたことある人向けね。
注:ABAPでは配列が使えない。これ機種転換時(注1)の注意事項。
いわゆるCOBOL系なので、三次元(以上)の配列の概念はなく、2次元配列=DBの概念しかない(2次元好きには朗報?)。
注1:某提督なら問題ないという説もある。
注2:オレ氏はCOBOL、C、JAVA等が使えるので、機種転換は簡単だが、やはりABAPで配列が使えないのが若干モヤる。
→配列だったらサクッと書けるのに、ABAPでは書けないのがダルい。
→例えば帳票系レポートででセルというかマスというかの値を入れ替えたり(場所移動)する操作。
注3:ちなみに帳票系といえば、オレ氏が新入社員のころから何十年とかわらないの七不思議の1つ、レイアウト問題。
たとえば請求書で割引の要素が多すぎて横幅が足りない問題。これ、印刷用には「割引」でまとめて1列でええやん。
で、気になる人はエクセル(非定型フォーマット)にPWつけて配布するとか、Web上でスクロールできる表を見せるとか。
○○別XX別集計表も一緒で、ローデータをエクセルで渡すから、後はご自由にPIVOT組んでよね。
PIVOTわからない人用にに2,3個、定型でPIVOT作った状態で渡す。
今どきPIVOT知らない人は、事務仕事はもう辞め。歯を食いしばってPIVOT覚えるとか昭和か!だし、好きな仕事しようぜ。
注4:ちなみに一番大事なのは「PIVOTでできる以上のことに価値があると思えないのでやりません」という、嫌われる勇気だったりする。
これについては、タイパとかコスパとかを大事にする若者達が正しい。
他に、消費税の1円のズレを合わせるのに、会議で何人も集まって、何十万(時給換算)つかってますとか。
定義系
定数
CONSTANTS:
C_ABC TYPE CHAR03 VALUE 'ABC',
C_ABC TYPE C LENGTH 3 VALUE 'ABC'.
→TYPEのあと、データエレメント or 直で型指定+長さで指定する。
構造とか(ガワの定義)
請求伝票タイプ(FKART)の例でいろいろと説明。
レンジテーブル(用の構造。以下略)
DATA: BEGIN OF G_TYP_FKART,
SIGN TYPE BAPISIGN,
OPTION TYPE BAPIOPTION,
LOW TYPE FKART,
HIGH TYPE FKART,
END OF G_TYP_FKART,
G_TYP_RNG_FKART TYPE RANGE OF FKART,
BEGIN~ENDの構造の話は省略する&DATA句も以下省略する。
で、いきなりレンジテーブル用の構造の話なんだけど。
SE16Nとかで、検索画面からの検索条件の指定すると思うけど、検索条件の内部表現がレンジテーブルね。
使い方としては、固定値テーブルから特定の条件値リストを取得してレンジテーブル化し、Selectするときに使用したりする。
ちなみにSelect用の固定値テーブルは、単一指定を複数並べる風に書くので、SIGNは”I"、OPTIONは”EQ”を指定する。
そして、LOWに固定値Tから読んだ値をセットする。※ちなみにHIGHは初期値のままで良い。
普通のテーブル
レンジTBLだと、G_TYP_RNG_FKART TYPE RANGE OF FKART
になるが、
普通TBLの場合、G_TYP_TAB_FKART TYPE STANDARD TABLE OF G_TYP_FKART
となる。
ちなみに構造自体も必要な項目を並べたフツーの見た目になる。
BEGIN OF G_TYP_FKART,
FKART TYPE FKART,
VBELN TYPE VBELN,
END OF G_TYP_FKART,
要はレンジテーブルに使われる、SIGNとかOPTIONとかLOWとかHIGHはでてこないって話。
インデックス付(ソートテーブル)
通常テーブルとは検索(READ)速度がダンチなので、LOOP内でREADするときなんかは必ずこちらの書式で書く。
G_TYP_TAB_FKART TYPE STANDARD TABLE OF G_TYP_FKART,
WITH NON-UNIQUE SORTED KEY NS01 COMPONENTS FKART
WITH NON-UNIQUE SORTED KEY NS01 COMPONENTS FKART VBELN.
通常テーブルの定義にWITHから始まる1文追加するだけ。必要に応じて2行とか3行とか追加しても良い。
注)とりあえず2行目だけ=検索項目全部入りだけ書けば、一部のキーだけ(例えばVBELNだけ)指定した場合も速いとか?(わからんけど)
上記では念のため、READ時に使うキーの組み合わせをそれぞれ(FKARTとFKART&VBELNの2つ)指定している。
注2)NON-UNIQUE指定なので、キー値がテーブル内で重複しても問題ない。
注3)速度面について。
インデックスをつけると、検索時間が100~1000倍速くなる。基本的なチューニングはこれだけやればOK(超簡単).
後からチューニングは面倒なので、目安としてLOOP中で3桁回以上READする内部TBL(itab)は最初から全部ソートキーをつけておくと良い。
(面倒じゃなければ全部につけてもいいけど、オレ氏はコスパ良くないのでやらん)
変数定義(実体化とか具現化っていうとか言わんとか)
DATA: L_LINES TYPE INT4.
基本はTYPEでDE指定。C LENGTH 3 とかでもOK。
VBRK-FKARTのようにテーブル名+項目名もOK.(DEが通らない時は次点でこっち)
DATA: L_WA_FKART TYPE G_TYP_FKART.
構造/ワークエリアの定義は上記の通り、TYPEの後に構造(BEGIN OF ~END OFのやつ)を指定する。
DATA: itab_FKART TYPE G_TYP_TAB_FKART.
内部テーブル定義はテーブル構造をTYPEの後に指定。(普通のテーブル定義のセクションを参照)
FIELD-SYMBOLS: <FS_FKART> TYPE G_TYP_FKART.
フィールドシンボル=参照渡し用のポインタというかなんというか。
TYPEの後に構造を指定する。
選択画面の項目
PARAMETERS:
P_FKART TYPE FKART OBLIGATORY.
SELECT-OPTIONS:
S_FKART TYPE FKART NO INTERBALS.
単一指定なら前者、複数指定なら後者。
必須はOBLIGATORY。範囲指定不可の場合、NO INTERBALS。
ちなみに範囲指定:「10/1」~「10/31」みたいな指定のこと。
10月*( 具体的には"202410*" )みたいに1項目のワイルドカード(*) 指定で同じことができるので、範囲指定はあまり使わない。
選択テキストの編集はSE38>テキストエレメント>選択テキストタブ。
データ抽出
抽出準備系:固定値テーブル読み出し→レンジT作成
SELECTする前準備として、固定値テーブル読み出し→レンジT作成の件。
SELECT FROM 固定値T
FIELDS VALUE1 AS LOW
WHERE CODE = "0001"
INTO CORRESPONDING FIELDS OF TABLE @ITAB_RNG_FKART.
注:LOWに固定値テーブル値をセットする。
LOOP AT ITAB_RNG_FKART ASSIGNING <FS_FKART>
<FS_FKART>-SIGN = "I"
<FS_FKART>-OPTION = "EQ"
ENDLOOP.
注:あとはお作法的に、SIGNとOPTIONに固定値をセット。
抽出実行
SELECT FROM VBRK AS 1
INNER JOIN VBRP AS 2
ON 1~VBELN = 2~VBELN
FIELDS
1~VBELN
WHERE
1~FKART IN @ITAB_RNG_FKART
INTO CORRESPONDING FIELDS OF TABLE @L_TAB_FKART.
1.@マークはリテラルに着ける。
リテラルが何?という人は、文法エラーの表示に従って@マークを付ければOK.
3.1~VBELN AS DENPYOBANGO のところ補足。
外部連携用のテーブルが旧システム準拠で日本語ローマ字の項目名がついている場合がある。
そんなとき、抽出元の項目名に対して別名をつけるときにASを使う。
上記の例だと、抽出元のVBELN項目を格納先テーブルのDENPYOBANGO項目にASで紐づけている。
編集
ソート:SORT L_TAB_FKART BY VBELN ASCENDING FKART DESCENDING.
項目+昇順or降順をならべればOK。
重複削除:DELETE ADJACENT DUPLICATES FROM L_TAB_FKART COMPARING VBELN FKART.
VBRKとVBRPを連結後に、ヘッダ情報だけ残したい場合、上記文で重複削除する。
重複削除時の注意点:削除前に必ず削除キーのソートが必要。
単純ループ:
LOOP AT itab ASSIGNING <FS>
”何かの処理”
ENDLOOP
条件指定ループ:
LOOP TABLE itab WHERE
FKART = 'Z999'
ASSIGNING <FS>.
ENDLOOP.
グループループ:
LOOP AT itab_親 ASSIGNING <FS_親> GROUP BY (FKART = <FS_親-FKART>).
LOOP AT GROUP <FS_親> ASSIGINING <FS_子>
READ TABLE L_TAB_伝票タイプマスタ TRANSPORTING NO FIELDS
WITH KEY FKART = <FS_親>-FKART.
IF SY=SUBRC = 0.
<FS_子>-DEL_FLG = 1.
ENDIF.
ENDLOOP.
ENDLOOP.
DELETE itab_親 where FLG_DEL = 1.
補足:固定値テーブルに伝票タイプ(FKART)があるか?をREADでチェック。
その後、該当伝票に対して削除フラグつける。
さらに、そのあと該当行を削除するという流れ。
補足2:削除フラグを付ける別のやり方としてレンジテーブル作って、LOOPでWHERE使う手もある。
インデックス(ソートキー)指定ループ:
LOOP AT G_TAB_FKART ASSIGINIG <FS_FKART>
USING KEY NS01 WHERE FKART = 'Z999'.
ENDLOOP.
内部テーブルリード:
READ TABLE itab.
内部テーブルリード (インデックス/ソートキー付):
READ TABLE itab
WITH TABLE KEY NS01 COMPONENTS
FKART = 'Z999' INTO G_REC_FKART_MST
内部テーブルループ&挿入:
LOOP AT G_TAB_FKART INTO G_REC_FKART.
MOVE-CORRESPONDING G_REC_FKART TO L_REC_FKART2.
APPEND L_REC_FKART2 TO L_TAB_FKART2.
ENDLOOP.
ちなみに全く項目が同じなら、
L_REC_FKART2 = L_REC_FKART.
のように一発で挿入できる。
レコード数チェック&サブルーチンからのEXIT:
DESCRIBE TABLE itab LINES l_lines. "レコード数チェック
CHECK l_lines <> 0. "チェックNGならサブルーチンからExit(Formから抜ける)。
外部テーブル読込(ALL ENTRES):
SELECT FROM vbrk
FIELDS
VBELN,
FKART,
NETWR
FOR ALL ENTRIES IN @L_TAB_VBRP
" 前段でVBRPを抜いて、抜いた伝票のヘッダ情報だけを取る場合。1TBLしか指定不可。2TBL以上指定なら別途レンジテーブルを作る。
WHERE L_TAB_VBRP-VBELN
INTO CORRESPONDING FIELDS OF TABLE itab_vbrk.
ちなみに昔はSELECTの後、項目名を先に書いて、そのあとFROMテーブル名という、直感的に逆(子→親)の指定だった。
今はFIELDS句が追加され、直感的な順番(親→子)で指定できるようになったらしい。
(ループとセットで)AT関連:
個人的にはATEND、ATNEW、AT FIRSTの順に使う。
とはいえ、簡単なデータ階層のテーブルならGROUP使った方がいい。
というのも、AT系は制約があるから雑に使うと動作不良を起こすから(ハマって調査に時間喰うので)
AT制約(というか罠?):
その1。AT xxx ~ENDATの中で項目値を使う場合。
ATとFSの同時使用不可なので、INTOで実値を構造(ワークエリア)に入れるんだが、ATで指定した項目値以外は”****”になって使えない。(リアルに使えんのよ)
その2。(コレ完全に罠)。
ATはLOOP内で使うんだが、LOOPのitabがたとえばこんな内容だとする。
シリアルNO(SEQNO)
請求タイプ(FKART)
請求番号(VBELN)
で、AT NEW FKARTとかやるわけだが、請求タイプが変わった時だけATの中に入るとおもうじゃんか。
だけど、もしFKARTの前にある項目=つまりシリアルNOの値が変わる場合、ATの中に入っちゃう。
この例だと、普通シリアルNoって連番だから、1行毎に毎回ATに入っちゃうってわけ。
これがあるので、AT使う時は、必ず大きいグループから小さいグループにitab内の項目を並べるよう定義しないとダメなんよね。
なので、保守を考えるなら、デカい=項目数の多いトランitabなんかにはATは使わない方がいい。
まぁデカいというか、厳密には項目数はいくらあってもいいけど、キー項目が伝票#と明細#の2個くらいならまぁいい。
そうじゃなくて、組織階層やら属性やらのキーになりえる項目が5個6個とある場合はATじゃなくて、GROUP使ったほうがいい。
ちなみにだけど、ATだろうが、GROUPだろうが、LOOPはやたらネスト深くしないで、面倒でも分割してLOOPを複数書いてくれ。
深いネストは読み手を選ぶからさ。もう読むの大変なわけよ。オサーンには辛いのよ。
1粒だけど高い、高級キャビアのような、ソースが短い=高級なものはお腹いっぱいで胃もたれしそうになるわけで。
ソースは長くてもいい。コンビニおにぎりみたいな一般的な読みやすいコードを書いてほしい。
やたらサブルーチンが深いソースも同じ話で、一目でわからないから困るんだよね。
かといって、全部フラットにされたらそれはそれで困るんだが(どっちやねん)
サブルーチン(Form)
Form F_get_vbrk USING L_TAB_vbeln type G_TYP_TAB_vbeln
CHANGING L_TAB_vbrk type G_TYP_TAB_VBRK.
USINGは値渡し、CHANGINGは参照渡し。
要はUSINGはForm内で値を変えても呼び出し元の値は変わらない。
CHANGINGは文字通り、呼び出し元の値も変わるってこと。
内部&外部変換
伝票番号等の単純変換だが、昔とちがって数式でできるようになった。
l_vbeln_in = '0000012345'
l_vbeln_out = |{ l_vbeln_in alpha = OUT }|.
※l_vbeln_outの中身は' 12345'
注1:||はおまじないみたいなもので、とりあえず一番外側に書いておく。
注2:変数は{}でかこむ。
ソース:
L_OUTPUT = | Date: { SY-DATUM } Time: { SY-UZEIT } |.
出力イメージ
Date: 20241231 Time: 12:00:00
注:あくまでたぶん・・・的な、イメージ的なやつねこれ。本当にこう出力されるかは保証できんから。
注2:項目の間は自動でスペースが入る(かどうかわからん)
注3(というか感想):大外に||を書いておけば、定数をクオートで囲わなくていい点もGood.
クオートそのものを出力したいなら、そのまま書けばよい。
ソース:
L_OUTPUT = | Date: '{ SY-DATUM } '|.
出力イメージ
Date: '20241231'
注:直感的になってGood。
内外変換通貨項目編
ABAPの場合、通貨項目はちょっと特殊で上記のような数式では対応できない。
なのでロジックを書いておく。(一応、標準のFMもあるが、いろいろと改造したい人もいるかも?なので)
Form CR2C USING CR_VAL TYPE WERTV8
CHANGING T_VAL TYPE CHAR length 20.
DATA:
TXT1 TYPE C,
L_INT TYPE P LENGTH 16.
IF CR_VAL = '0'.
Clear T_VAL.
ELSE.
L_INT = TRANC( CR_VAL * 100 ) ”円貨専用のため100倍で固定&整数部分のみ抽出。外貨兼用の場合は各自改造して。
T_VAL = L_INT.
SEARCH T_VAL FOR '-'.
IF SY-SUBRC = 0 AND SY-FDPOS <> 0.
SPLIT T_VAL AT '-' INTO T_VAL TXT1.
CONDENSE T_VAL.
CONCATENATE '-' T_VAL INTO T_VAL
ELSE.
CONDENSE T_VAL.
ENDIF.
ENDFORM.