コードの分析およびテスト
データ型およびデータ型変換の正しい使用
文字項目の処理
ABAP SQL でのコードプッシュダウンの使用
内部テーブルのパフォーマンスの改善
権限チェックの実装
効果的なオブジェクト指向コードの設計
例外クラスの定義と処理
ABAP コードへの文書の追加

結合の実装

Objectives

After completing this lesson, you will be able to:
  • 結合を実装します。
  • 内部結合と外部結合を区別します。
  • ネストされた結合を実装します。

ジョインの基本と構文

結合の定義

2 つの SQL データソースを、両方のデータソースの列で構成される 1 つの結果セットに結合する必要がある場合があります。これは結合と呼ばれます。

たとえば、フライト接続を読み込む際に、フライト航空会社の詳細も読み込む必要がある場合があります。

結果セットの行は、結合タイプと、データソースの列間の結合条件によって決定されます。結合条件は、キーワード ON で始まり、データソースの列間の関係を定義します。

結合タイプは、対応するキーワードによって定義されます。ABAP SQL でサポートされている結合タイプは、INNER JOIN、LEFT OUTER JOIN、および RIGHT OUTER JOIN です。

これらの結合タイプのさまざまな結果については、まもなく説明します。ただし、最初に結合の ABAP SQL 構文を見てみましょう。

この例では、運送業者データ (運送業者 ID と運送業者名) と接続データ (接続 ID、出発空港、および到着空港) が 1 つの結果セットに結合されます。左側のデータソースはデータベーステーブル /DMO/CARRIER ですが、データベーステーブル /DMO/CONNECTION は右側のデータソースとして機能します。2 つのデータソース名間のキーワード INNER JOIN は、結合の種類を指定します。

ON の後の結合条件により、データベーステーブル /DMO/CARRIER の項目 CARRIER_ID の内容は、データベーステーブル /DMO/CONNECTION の項目 CARRIER_ID の内容と等しくなります。

結合条件や項目一覧、WHERE 句などでは、データソースの列は、データソース名と列名で完全に識別され、チルダ記号 ( ~ ) で区切られます。

注記

他の SQL ダイアレクトからは、ピリオドまたは完全停止記号 ( . ) を使用することができます。これは、ABAP SQL では別の符号に置き換える必要がありました。これは、ピリオド符号がすでに文の末尾を意味しているためです。

データソース名なしで列名を指定することができます。前提条件は、列名がいずれかのデータソースにのみ存在することです。

この例では、データベーステーブル /DMO/CARRIER にのみ列 NAME および CURRENCY_CODE が含まれていますが、CONNECTION_ID、AIRPORT_FROM_ID、および AIRPORT_TO_ID 列はデータベーステーブル /DMO/CONNECTION にのみ存在します。そのため、これらの列名は、先行する/DMO/CARRIER~ または /DMO/CONNECTION~ なしで一意です。

一方、どちらのテーブルにも列 CARRIER_ID が含まれています。そのため、この項目 /DMO/CARRIER~ または /DMO/CONNECTION~ は必須です。

データソース名のエイリアスを使用して、文を短くし、読みやすくすることをお奨めします。これは、データソース名の後にオプション AS を使用して行います。この例では、データベーステーブル /DMO/CARRIER にはエイリアス A が、データベーステーブル /DMO/CONNECTION にはエイリアス C が使用されています。

データソースにエイリアスがある場合は、元のデータソース名ではなくエイリアスを使用する必要があります。

自己結合の必須エイリアス

状況によっては、データソースのエイリアスは単なる構文の代替ではありません。これらは、同じデータソースを複数回含む文では必須になります。

例を見てみましょう。フライト接続 (/DMO/CONNECTION) を含むテーブルがあるとします。フランクフルト国際空港 (FRA) から到達できる目的地に関心があります。しかし、直行便ではなく、1回の乗り換えでフライトを探しています。

解決方法は、テーブル /DMO/CONNECTION とそれ自体の結合です。最初の結合条件では、2 番目の接続 (2 番目の区間) が、最初の接続 (1 番目の区間) が終了する空港から開始するように要求されます。AND 演算子を使用して最初の結合にリンクされた 2 番目の結合条件では、2 番目の接続が元の出発空港への直接復路フライトである組合せは除外されます。

このような自己結合は、/DMO/CONNECTION の左用途のエイリアス名 a および /DMO/CONNECTION の右側の使用に対して b がないと機能しないことは明白です。異なるエイリアスがないと、結合条件でも、項目一覧でも WHERE 句でも、最初の接続と 2 番目の接続を区別できません。

注記

このような自己結合では、すべての列名が両方のデータソースに存在するため、各列名の前にエイリアス名が必要です。

試してみる: 結合構文

  1. インタフェース IF_OO_ADT_CLASSRUN を実装する新しいグローバルクラスを登録します。
  2. 以下のコードスニペットをメソッド if_oo_adt_classrun~main( ) の実装部分にコピーします。
    Code Snippet
    1234567891011121314151617181920
    SELECT FROM /dmo/carrier INNER JOIN /dmo/connection * SELECT FROM /dmo/carrier AS a INNER JOIN /dmo/connection AS c ON /dmo/carrier~carrier_id = /dmo/connection~carrier_id FIELDS /dmo/carrier~carrier_id, /dmo/carrier~name AS carrier_name, /dmo/connection~connection_id, /dmo/connection~airport_from_id, /dmo/connection~airport_to_id WHERE /dmo/carrier~currency_code = 'EUR' INTO TABLE @DATA(result). out->write( EXPORTING data = result name = 'RESULT' ).
  3. CTRL + F3 を選択してクラスを有効化し、F9 を選択してコンソールアプリとして実行します。
  4. コンソール出力を分析します。
  5. 最初の行 (キーワード SELECT を含む行) にカーソルを置き、CTRL + < を選択して、その行の最初の列に星記号 (*) を追加します。
  6. 次の行の先頭から星記号 (*) を削除します。テーブル名 /DMO/CARRIER がエイリアス名によって不明瞭になっていることを示す構文エラーを分析します。
  7. ON 条件で、テーブル名 /DMO/CARRIER をそのエイリアス名 A、テーブル名 /DMO/CONNECTION をそのエイリアス名 C に置き換えます。
  8. FIELDS 一覧および WHERE 句で、可能なすべての項目名の前にあるエイリアス名およびチルダ記号 (~) を削除します。これは、carrier_id 項目の前にのみ必須であることがわかります。
  9. 有効化して、再度実行します。

Join タイプ

内部結合、右外部結合、および左外部結合の詳細については、以下のビデオを視聴してください。

次の例は、左外部ジョインの結果を示します。結果セットで強調表示された 2 つのローは、内部ジョインにはありません。これらは、データベーステーブル /DMO/CONNECTION にフライト接続がない運送業者に対応します。

これらの行には、航空会社に関する情報 (左側のデータソース、テーブル /DMO/CARRIER からの情報) のみが含まれます。通常、右側のデータソース (CONNECTION_ID、AIRPORT_FROM_ID、および AIRPORT_TO_ID 列) からの情報を含む列には、初期値が入力されます。これらの値は、データベースによって返される Null 値ではなく、ABAP ランタイムによって入力されました。

注記

列 CONNECTION_ID のタイプは、ABAP 組込データ型 N に基づいています。この型の初期値は、一連の空白ではなく、一連のゼロです。

試してみる: 内部結合と外部結合

  1. インタフェース IF_OO_ADT_CLASSRUN を実装する新しいグローバルクラスを登録します。
  2. 以下のコードスニペットをメソッド if_oo_adt_classrun~main( ) の実装部分にコピーします。
    Code Snippet
    1234567891011121314151617181920212223242526
    SELECT FROM /dmo/Agency AS a INNER JOIN /dmo/customer AS c * LEFT OUTER JOIN /dmo/customer AS c * RIGHT OUTER JOIN /dmo/customer AS c ON a~city = c~city FIELDS agency_id, name AS Agency_name, a~city AS agency_city, c~city AS customer_city, customer_id, last_name AS customer_name WHERE ( c~customer_id < '000010' OR c~customer_id IS NULL ) AND ( a~agency_id < '070010' OR a~agency_id IS NULL ) INTO TABLE @DATA(result_Join). out->write( EXPORTING data = result_join name = 'RESULT_JOIN' ).
  3. CTRL + F3 を選択してクラスを有効化し、F9 を選択してコンソールアプリとして実行します。
  4. コンソール出力を分析します。

    注記

    このコード例では、同じ都市に住所を持つ顧客と旅行代理店のペアが取得されます。WHERE 句を使用して、結果セットを小さくします。そのため、INNER JOIN では、得意先 '000010' および旅行代理店 '070010' のみが検索されます。WHERE 句は、外部ジョインにも有効な形式で記述されます。条件 <field_name> IS NULL は、値 NULL を含む結果をデータベースから除外しないために必要です。
  5. SELECT 文の FROM 句で、INNER JOINLEFT OUTER JOIN に置き換え、再度有効化します。または、INNER JOIN を使用して行をコメントアウトし、LEFT OUTER JOIN を使用して行のコメントを解除することもできます。
  6. を再度実行し、コンソール出力を最初の出力と比較します。
  7. SELECT 文の FROM 句で、LEFT OUTER JOINRIGHT OUTER JOIN に置き換え、再度有効化します。または、LEFT OUTER JOIN を使用して行をコメントアウトし、RIGHT OUTER JOIN を使用して行のコメントを解除することもできます。
  8. を再度実行し、コンソール出力を最初の 2 つの出力と比較します。

ネストされた結合

複数のデータソースの結合

ABAP SQL では、3 つ以上のデータソースを同時に結合することができます。最初に、2 つのデータソースを結合します。つまり、先ほど説明したフライト航空会社とフライト接続に参加します。

出発空港の名称と市区町村 (AIRPORT_FROM_ID) を読み込むためにデータベーステーブル /DMO/AIRPORT を追加するとします。

3 つのデータソースを結合するには、最初に既存の結合を括弧で囲みます。この最初の結合は、次の結合の左データソースとして機能します。結合キーワード (INNER JOIN、LEFT OUTER JOIN、RIGHT OUTER JOIN)、右データソース (/DMO/AIRPORT)、エイリアス (AS apf)、および追加結合の結合条件 (ON c~airport_from_id = apf~airport_id) を追加します。

次に、データベーステーブル /DMO/AIRPORT を再度追加して、到着空港の名称と市区町村 (AIRPORT_TO_ID) も読み込むとします。

この 4 つのデータソースの結合を実装するために、3 つのデータソースの結合に対して行った処理を繰り返します。3 つのデータソースの既存の結合を別の括弧のペアで囲み、結合キーワード、右データソース、および結合条件を追加します。

注記

相互にリンクできるデータソースの最大数に達するまで、これを繰り返すことができます。現在、1 つの SELECT 文で最大 50 のデータソース (49 結合) を使用できます。

結合の順序

前の例では、次のデータソースが常に次の結合の右側に追加されました。左側のデータソースは、データベーステーブルまたは結合のいずれかでした。3 つの結合すべての正しいデータソースは、データベーステーブルでした。

括弧を使用すると、結合を別の結合の正しいデータソースとして使用することもできます。下の例では、 /DMO/CONNECTION と /DMO/AIRPORT (AS apf) の結合から開始します。これを括弧で囲み、/DMO/AIRPORT (AS apt) との結合の左データソースとして使用します。/DMO/CARRIER を追加するには、既存の結合を別の括弧のペアで囲みます。次に、左データソース (/DMO/CARRIER) を追加し、開括弧の前に結合キーワードを追加し、閉じ括弧の後に ON 条件を追加します。

注記

この例では、2 つの結果が同一です。ただし、データベースによる結合の評価順序によって結果が異なる場合もあります。

括弧の重要性

構文の観点からは、括弧は必要ありません。ただし、読みやすくするために、ネストされた結合では括弧を省略しないことを強くお奨めします。これについては、以下の例で説明します。

SELECT 文には 4 つのデータベーステーブルの結合が含まれていますが、括弧は使用されません。表面的に見ると、 /DMO/CARRIER と /DMO/CONNECTION の結合が最初に評価されると考えられます。これは、これらのテーブルが FROM 句に最初に表示されるためです。

しかし、この結論は間違っている!

括弧がない場合、評価の順序は、データソースの順序ではなく、ON 条件の順序によって決定されます。詳しく調べると、テーブル /DMO/CARRIER (エイリアス a) と /DMO/CONNECTION (エイリアス c) を相互にリンクする ON 条件が FROM 句の最後に配置されていることがわかります。したがって、この結合は最後に評価されます。

ヒント

このような混乱を避けるため、ネストされた結合では必ず括弧を使用してください。

ネストされた結合の実装

この演習問題では、ネストした結合を実装して、フライト接続と空港詳細を同じ SELECT 命令で読み込みます。

テンプレート:

  • /LRN/CL_S4D401_TCS_STRING_PROC (グローバルクラス)

ソリューション:

  • /LRN/CL_S4D401_DBS_NESTED_JOIN (グローバルクラス)

タスク 1: テンプレートのコピー (オプション)

テンプレートクラス /LRN/CL_S4D401_TCS_STRING_PROC をコピーします。前の演習問題を完了した場合は、このタスクをスキップし、クラス ZCL_##_SOLUTION の編集を続行することができます。

ステップ

  1. クラス /LRN/CL_S4D401_TCS_STRING_PROC を独自のパッケージのクラスにコピーします (推奨名: ZCL_##_SOLUTION、## はグループ番号)。

    1. Project Explorer で、クラス /LRN/CL_S4D401_TCS_STRING_PROC を右クリックしてコンテキストメニューを開きます。

    2. コンテキストメニューから Duplicate.... を選択します。

    3. Package 項目にパッケージの名称を入力します。Name 項目に、ZCL_##_SOLUTION (## はグループ番号) と入力します。

    4. Next を選択します。

    5. 移送依頼を確認し、Finish を選択します。

  2. コピーを有効化します。

    1. Ctrl + F3 を押してクラスを有効化します。

タスク 2: 結合の実装

ローカルクラス LCL_PASSENGER_FLIGHT のメソッド CLASS_CONSTRUCTOR の実装を調整します。データベーステーブル /LRN/CONNECTION から読み込む SELECT 文を拡張します。2 つの結合を追加して、出発空港と到着空港のタイムゾーンを読み込みます。

ステップ

  1. ローカルクラス LCL_PASSENGER_FLIGHT のメソッド CLASS_CONSTRUCTOR の実装にナビゲートします。

    1. たとえば、エディタで Local Types タブを開き、METHOD class_constructor を検索することができます。

    2. または、左側のアウトラインビューで ZCL_##_SOLUTIONLCL_PASSENGER_FLIGHT を展開し、CLASS_CONSTRUCTOR を選択することもできます。

  2. データベーステーブル /LRN/CONNECTION から読み込む SELECT 文を見つけて、データソースのエイリアスを追加します (推奨エイリアス名: c)。

    1. コードを以下のように調整します。

      ABAP
      12345
      SELECT FROM /lrn/connection AS c FIELDS carrier_id, connection_id, airport_from_id, airport_to_id, departure_time, arrival_time INTO TABLE @connections_buffer.
  3. データベーステーブル /LRN/AIRPORT から出発空港の詳細を読み込む適切な JOIN タイプの結合を追加します。2 番目のデータソースのエイリアスを追加します (推奨エイリアス名: f)。

    1. コードを以下のように調整します。

      ABAP
      123456
      SELECT FROM lrn/connection AS c LEFT OUTER JOIN /lrn/airport AS f FIELDS carrier_id, connection_id, airport_from_id, airport_to_id, departure_time, arrival_time INTO TABLE @connections_buffer.
  4. 結合条件を追加します。

    1. コードを以下のように調整します。

      ABAP
      1234567
      SELECT FROM /lrn/connection AS c LEFT OUTER JOIN /lrn/airport AS f ON c~airport_from_id = f~airport_id FIELDS carrier_id, connection_id, airport_from_id, airport_to_id, departure_time, arrival_time INTO TABLE @connections_buffer.
  5. 到着空港の詳細を読み込む類似の結合を追加し、3 番目のデータソースに別のエイリアスを追加します (推奨エイリアス名: t)。

    1. コードを以下のように調整します。

      ABAP
      123456789
      SELECT FROM /lrn/connection AS c LEFT OUTER JOIN /lrn/airport AS f ON c~airport_from_id = f~airport_id LEFT OUTER JOIN /lrn/airport AS t ON c~airport_to_id = t~airport_id FIELDS carrier_id, connection_id, airport_from_id, airport_to_id, departure_time, arrival_time INTO TABLE @connections_buffer.
  6. 出発空港のタイムゾーンおよび到着空港のタイムゾーンを項目一覧に追加します。適切なエイリアスを指定します (推奨エイリアス名: timzone_from および timzone_to)。

    1. コードを以下のように調整します。

      ABAP
      1234567891011
      SELECT FROM /lrn/connection AS c LEFT OUTER JOIN /lrn/airport AS f ON c~airport_from_id = f~airport_id LEFT OUTER JOIN /lrn/airport AS t ON c~airport_to_id = t~airport_id FIELDS carrier_id, connection_id, airport_from_id, airport_to_id, departure_time, arrival_time, f~timzone AS timzone_from, t~timzone AS timzone_to INTO TABLE @connections_buffer.
  7. 内部テーブル connections_buffer の行データ型にナビゲートし、タイムゾーンに 2 つのコンポーネントを追加します。SELECT 命令の項目一覧でエイリアス名として使用したものと同じ名称を使用します。

    注意

    構成品目期間の前に新規構成品目を追加してください。期間コンポーネントが SELECT 文に入力されていないため、これは重要です。または、SELECT 命令の INTO 句で CORRESPONDING FIELDS OF オプションを使用します。
    1. 構造データ型 st_connections_buffer の定義を見つけます。

    2. コードを以下のように調整します。

      ABAP
      123456789101112
      TYPES: BEGIN OF st_connections_buffer, carrier_id TYPE /dmo/carrier_id, connection_id TYPE /dmo/connection_id, airport_from_id TYPE /dmo/airport_from_id, airport_to_id TYPE /dmo/airport_to_id, departure_time TYPE /dmo/flight_departure_time, arrival_time TYPE /dmo/flight_departure_time, timzone_from TYPE timezone, timzone_to TYPE timezone, duration TYPE i, END OF st_connections_buffer.

タスク 3: 冗長な SELECT の削除

ローカルクラス LCL_PASSENGER_FLIGHT のメソッド CLASS_CONSTRUCTOR の実装で、フライト期間の計算で内部テーブル connections_buffer のタイムゾーン情報を使用します。次に、データベーステーブル /LRN/AIRPORT への冗長読込アクセスを削除します。

ステップ

  1. ローカルクラス LCL_PASSENGER_FLIGHT のメソッド CLASS_CONSTRUCTOR の実装に戻ります。

    1. 前と同じ方法でこのステップを実行します。

  2. 最初の CONVERT 命令の TIME ZONE オプションで、ローカル内部テーブル apairts への読込アクセスを構造接続のコンポーネント timzone_from へのアクセスに置き換えます。

    1. 最初の CONVERT 命令を以下のように調整します。

      ABAP
      12345
      CONVERT DATE today TIME connection-departure_time * TIME ZONE airports[ airport_id = connection-airport_from_id ]-timzone TIME ZONE connection-timzone_from INTO UTCLONG DATA(departure_utclong).
  3. コンポーネント timzone_to を使用して、2 番目の CONVERT 命令を同様の方法で調整します。

    1. 2 番目の CONVERT 命令を以下のように調整します。

      ABAP
      12345
      CONVERT DATE today TIME connection-arrival_time * TIME ZONE airports[ airport_id = connection-airport_to_id ]-timzone TIME ZONE connection-timzone_to INTO UTCLONG DATA(arrival_utclong).
  4. 上にスクロールし、データベーステーブル /LRN/AIRPORT から読み込む SELECT 命令のすべてのコード行の前にコメント記号を追加します。

    1. メソッド CLASS_CONSTRUCTOR の実装の先頭まで上にスクロールします。

    2. データベーステーブル /LRN/AIRPORT から読み込む完全な SELECT 文を選択し、Ctrl + < を押します。

    3. コードは以下のようになります。

      ABAP
      1234
      * SELECT * FROM /lrn/airport * FIELDS airport_id, timzone * INTO TABLE @DATA(airports).
  5. グローバルクラスをコンソールアプリとして有効化してテストします。

    1. Ctrl + F3 を押します。

    2. F9 を押します。