SELECT(For All Entries, JOIN)
17.1 SELECT(For All Entries, JOIN)
ここでは、データベースのアクセス方法として以下を説明します。
・JOIN:DBテーブル同士を「キーで結合」して、必要な項目をまとめて取得する方法
・FOR ALL ENTRIES:検索用内部テーブルのキー群に一致するデータをまとめて取得する方法
SELECT(JOIN)
JOINは「結合」とも呼ばれ、一度のSELECT命令で、複数テーブルからレコードを抽出することが
可能です。
また、結合には内部結合と外部結合の2種類があります。
・内部結合
内部結合は、結合しているすべてのテーブルに存在するデータを抽出します。
抽出する際に、結合しているすべてのテーブルにデータが存在しない場合は、
抽出せずにスキップします。
・外部結合
外部結合は、基準となるテーブルにデータが存在する場合に抽出します。
内部結合と違い、結合しているすべてのテーブルにデータが存在しない場合も抽出します。
つまり、該当レコードが、スキップされずにすべて取得されます。
■ FROM句
結合なしのSELECT命令と同様に、FROM句には抽出先のデータベーステーブルを指定します。
■別名(AS)
AS は、SELECT文の中でテーブルに「別名(エイリアス)」を付ける書き方です。
JOINでは複数テーブルに同名の項目(例:MANDT など)が存在することが多いため、a~MANDT のように「どのテーブルの項目か」を明確にする目的で別名を付けます。
なお、別名は JOINのときだけでなく、単一テーブルのSELECTでも付けられます。
(将来JOINに発展する可能性がある場合や、可読性をそろえたい場合に使います)
この別名は、そのSELECT文の中でのみ有効で、SELECT文の外では使えない点に注意しましょう。
抽出元のデータベーステーブルは<DBテーブル1>で、<別名1>と記しますという意味です。
FROM sflight AS A のように書くと、以降そのSELECT文の中で A~carrid のように参照できます。
■ INNER JOIN(内部結合) /OUTER JOIN(外部結合)句
FROM句で指定したテーブルと結合させたいデータベーステーブルを指定することができます。
FROM句と同様に、データベーステーブルに任意の別名をつけることができます。
内部結合するデータベーステーブルは<DBテーブル2>で、<別名2>と記します、という意味です。
外部結合するデータベーステーブルは<DBテーブル2>で、<別名2>と記します、という意味です。
■ ON句
結合条件を指定します。ON句で指定した結合条件を満たしたレコードのみ抽出することができます。
条件の指定方法は、WHERE句とほぼ同様ですが、それぞれの項目名がどのデータベースの項目なのかを記述する必要があります。
データベーステーブル<DBテーブル1>の<項目>と、
データベーステーブル<DBテーブル2>の<項目>が等しいレコードを結合しなさい、という意味です。
■ WHERE句
WHERE句は抽出条件を指定します。ON句と同様に、条件を記述する際にそれぞれの項目名がどのデータベースの項目なのかを記述する必要があります。
■ SELECT INNER JOINの構文例:
SFLIGHT(フライト実績)とSPFLI(スケジュール)をCARRID/CONNIDで結合して一覧化する。
*内部結合 SELECT A~CARRID “航空会社コード A~CONNID “フライト接続番号 A~FLDATE “フライト日付 B~CITYFROM “到着都市 B~CITYTO “出発都市 FROM SFLIGHT AS A INNER JOIN SPFLI AS B ON A~CARRID = B~CARRID AND A~CONNID = B~CONNID INTO TABLE TAB_FLIGHT WHERE A~FLDATE > ‘20251101’.
この構文例の意味について、コードの記載順に説明していきます。
SELECT A~CARRID “航空会社コード A~CONNID “フライト接続番号 A~FLDATE “フライト日付 B~CITYFROM “到着都市 B~CITYTO “出発都市
ここでは、データベーステーブルから抽出する項目を指定しています。
抽出する項目は、
Aの項目:CARRID(航空会社コード)
Aの項目:CONNID(フライト接続番号)
Aの項目:FLDATE(フライト日付)
Bの項目:CITYFROM(到着都市)
Bの項目:CITYTO(出発都市)
となります。
FROM SFLIGHT AS A
FROM句には、抽出したいデータベーステーブル名を記述します。
データベーステーブル:SFLIGHT(フライト)から抽出し、別名をAと記します。
INNER JOIN SPFLI AS B
INNER JOIN句には、 結合させたいデータベーステーブル名を記述します。
データベーステーブル:SPFLI(フライトスケジュール)を内部結合し、別名をBと記します。
INNER JOINは「両方のテーブルに存在するキーだけ」を抽出します。
そのため、教材の例のように、片方(別名B)に存在しないキーは抽出対象外になります。
ON A~CARRID = B~CARRID AND A~CONNID = B~CONNID
ON句には、結合条件が書かれています。
Aの項目:CARRID(航空会社コード)が、Bの項目:CARRID(航空会社コード)と等しい、
かつ、Aの項目:CONNID(フライト接続番号)が、Bの項目:CONNID(フライト接続番号)に
等しいデータを結合します。
内部結合ですので、AとB両方のデータベーステーブルにデータが存在するレコードのみ結合されます。
INTO TABLE TAB_FLIGHT
INTO TABLE句には、取得したデータを格納する内部テーブルを指定します。
取得したデータは内部テーブル:TAB_FLIGHTに格納します。
WHERE A~FLDATE > ‘20251101’.
WHERE句では、抽出条件を指定しています。
Aの項目:FLDATE(フライト日付)が、2025年11月1日より後のデータが抽出対象になります。
■ 構文例の内部結合のイメージ
CARRID(航空会社コード)がLH、CONNID(フライト接続番号)が0400のレコードは、
別名:Bにレコードが存在しないため、抽出対象外となります。
■ SELECT LEFT OUTER JOINの構文例:
INNER JOIN と違い、「左側(A)」を残しつつ、結合先が無い場合は結合先項目を初期値にする。
*外部結合 SELECT A~CARRID “航空会社コード A~CONNID “フライト接続番号 A~FLDATE “フライト日付 B~CITYFROM “到着都市 B~CITYTO “出発都市 FROM SFLIGHT AS A LEFT OUTER JOIN SPFLI AS B ON A~CARRID = B~CARRID AND A~CONNID = B~CONNID INTO TABLE TAB_FLIGHT WHERE A~FLDATE > ‘20251101’.
先ほどの内部結合の構文例との違いは、青字部分の記載が、
LEFT OUTER JOIN(外部結合)か、INNER JOIN(内部結合)か、のみです。
この構文例の意味については、コードの違う以下の部分についてのみ説明します。
LEFT OUTER JOIN SPFLI AS B
LEFT OUTER JOIN句には、 結合させたいデータベーステーブル名を記述します。
データベーステーブル:SPFLI(フライトスケジュール)を外部結合し、別名をBと記します。
LEFT OUTER JOINは「左側(FROM側)の行を残す」結合です。
結合先(別名B)にレコードが無い場合、「B側の項目は初期値」になって出力されます。
INNER JOIN と「抽出対象が変わる」点に注目します。
■ 構文例の外部結合のイメージ
CARRID(航空会社コード)がLH、CONNID(フライト接続番号)が0400のデータは、
別名:Aには存在し、別名:Bに存在しませんが、外部結合では抽出対象となります。
別名:Bから取得できなかった項目:CITYFROM(出発都市)と、CITYTO(到着都市)は、
内部テーブル:TAB_FLIGHTでは、ブランクになります。
SELECT(FOR ALL ENTRIES)
FOR ALL ENTRIES命令では、指定した内部テーブルに含まれるレコードの項目の組み合わせを抽出対象として、一括検索することができます。
内部テーブルの値を1件ずつ抽出条件に指定しなくても、FOR ALL ENTRIESを使えば、
一括で抽出することができます。
<注意事項>
指定した検索用内部テーブルが0件の場合は、全件選択となってしまうため、
FOR ALL ENTRIES を使用する前には、必ずIF命令などを利用して、検索用内部テーブルの件数を確認しましょう。
■ データが読み込まれるまでの流れ
この例では、検索用内部テーブルを検索条件として、DBテーブルからデータを読み込み
取得用内部テーブルに格納しています。
すなわち、DBテーブルから
項目:Aの値が001、かつ、項目:Bの値がAのレコード、
および
項目:Aの値が003、かつ、項目:Bの値がCのレコード、
を読み込み、取得用内部テーブルに格納しています。
「サンプルコード」①
REPORT Z_SAMPLE171.
START-OF-SELECTION.
*(1) 生徒情報マスタ(ZTEST_TBL_001)から以下のデータを内部テーブルへ読み込みます。
SELECT *
FROM ZTEST_TBL_001 AS M
WHERE M~GRADE = 1
AND M~CLASS = 'A'
AND M~NUM <= 2
INTO TABLE @DATA(LT_INDEX).
"※重要:0件だと FOR ALL ENTRIES は全件抽出になるため、必ずガードする
IF LT_INDEX IS INITIAL.
WRITE: / '検索用内部テーブルが0件のため、抽出を行いません。'.
RETURN.
ENDIF.
*(2) (1)で用意した内部テーブルを検索用内部テーブルとして、
* 生徒別テスト得点情報テーブル(ZTEST_TBL_002)の読み込みを行います。
* 以下の生徒別テスト得点情報テーブルデータから、
* 検索用内部テーブルと検索キー項目(grade,class,num)が一致するデータを読み込みます。
SELECT *
FROM ZTEST_TBL_002 AS D
INTO TABLE @DATA(LT_DATA)
FOR ALL ENTRIES IN @LT_INDEX
WHERE D~GRADE = @LT_INDEX-GRADE
AND D~CLASS = @LT_INDEX-CLASS
AND D~NUM = @LT_INDEX-NUM.
*ヘッダの帳票出力
WRITE: 001(4) '学年',
010(2) '組',
020(8) '出席番号',
030(10) 'テスト名',
050(4) '国語',
055(4) '英語',
060(4) '数学'.
ULINE.
LOOP AT LT_DATA INTO DATA(LS_DATA).
WRITE: /001(1) LS_DATA-GRADE,
010(2) LS_DATA-CLASS,
020(2) LS_DATA-NUM,
030(10) LS_DATA-TESTNAME,
050(3) LS_DATA-JAPANESE,
055(3) LS_DATA-ENGLISH,
060(3) LS_DATA-MATH.
NEW-LINE.
ENDLOOP.
■ データが読み込まれるまでの流れ(イメージ)
■ データが読み込まれるまでの流れ(解説)
データベーステーブル:ZTEST_TBL_002(生徒別テスト得点情報テーブル)から、
項目:GRADE(学年)、CLASS(組)、NUM(出席番号)の値が、
検索用内部テーブル:T_INDEXの項目:GRADE(学年)、CLASS(組)、NUM(出席番号)の値と一致(赤枠部分)するデータを取得し、
取得用内部テーブル:T_DATAに格納します。(緑枠部分)
■ 出力結果
SELECT(FOR ALL ENTRIES – 新構文)
SELECT (FOR ALL ENTRIES)を新構文で記述すると以下の通りです。
新構文では、内部テーブルをJOINすることで検索用内部テーブルと値が一致するデータを取得します。
また、取得項目は明示的に記述し、内部テーブルには別名が必須です。
旧構文との大きな違いは、内部結合を用いるため
検索用内部テーブルが0件の場合でも自動で全件選択になることはありません。
「サンプルコード」②:FOR ALL ENTRIES 新構文=内部テーブルJOIN
EPORT ZCUSLIST1_INTERNAL_ALL_NEW.
START-OF-SELECTION.
*(1) 生徒情報マスタ(ZTEST_TBL_001)から以下のデータを内部テーブルへ読み込みます。
SELECT *
FROM ZTEST_TBL_001 AS M
WHERE M~GRADE = 1
AND M~CLASS = 'A'
AND M~NUM <= 2
INTO TABLE @DATA(LT_INDEX).
*(2) (1)で用意した内部テーブルを検索用内部テーブルとして、
* 生徒別テスト得点情報テーブル(ZTEST_TBL_002)の読み込みを行います。
* 以下の生徒別テスト得点情報テーブルデータから、
* 検索用内部テーブルと検索キー項目(grade,class,num)が一致するデータを読み込みます。
"新構文(内部結合)では、検索用が0件なら結果も0件(全件抽出にならない)
SELECT
D~GRADE,
D~CLASS,
D~NUM,
D~TESTNAME,
D~JAPANESE,
D~ENGLISH,
D~MATH
FROM ZTEST_TBL_002 AS D
INNER JOIN @LT_INDEX AS I
ON D~GRADE = I~GRADE
AND D~CLASS = I~CLASS
AND D~NUM = I~NUM
INTO TABLE @DATA(LT_DATA).
*ヘッダの帳票出力
WRITE: 001(4) '学年',
010(2) '組',
020(8) '出席番号',
030(10) 'テスト名',
050(4) '国語',
055(4) '英語',
060(4) '数学'
ULINE.
LOOP AT LT_DATA INTO DATA(LS_DATA).
WRITE: /001(1) LS_DATA-GRADE,
010(2) LS_DATA-CLASS,
020(2) LS_DATA-NUM,
030(10) LS_DATA-TESTNAME,
050(3) LS_DATA-JAPANESE,
055(3) LS_DATA-ENGLISH,
060(3) LS_DATA-MATH.
NEW-LINE.
ENDLOOP.
END-OF-SELECTION.
実行結果