ABAP

ABAPはコピペで書け!

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.

-ABAP