9.1 JSONリレーショナル二面性ビュー用のOracle GraphQLディレクティブ

GraphQLディレクティブは、GraphQLスキーマの追加情報または特定の動作を指定する注釈です。二面性ビューを定義するためのすべてのOracle GraphQLディレクティブは、GraphQLフィールドに適用されます。

GraphQLディレクティブは、接頭辞@が付いた名前であり、場合によっては引数が続きます。

二面性ビューを定義するためのOracle GraphQLには、次のディレクティブがあります:

  • ディレクティブ@flexは、JSON型の列を二面性ビュー用のフレックス列として指定します。このディレクティブの使用については、「フレックス列(基本より詳しい説明)」で説明します。

  • ディレクティブ@nestおよび@unnestは、二面性ビュー定義の中間オブジェクトのネストおよびネスト解除(フラット化)を指定します。これらは、それぞれSQLキーワードNESTおよびUNNESTに対応します。

    制限(順守されない場合はエラーが発生します):

    • ルート表の識別列(主キー列、アイデンティティ列、一意制約または一意索引がある列)に対応付けられているフィールドはネストできません

    • 別名を持つフィールドのネストは解除できません

    例9-1に、@nestの使用例を示します。@unnestを使用する例については、「GraphQLを使用したカーレース二面性ビューの作成」を参照してください。

  • ディレクティブ@linkは、列間の複数の外部キー・リンクを区別します。「Oracle GraphQLディレクティブ@link」を参照してください。

  • ディレクティブ@generatedにより、生成されるJSONフィールドを指定します。生成フィールドにより、二面性ビューによってサポートされているドキュメントが拡張されます。これらは、基になる個々の列にマップされないため、読取り専用です。

    ディレクティブ@generatedには、オプションの引数pathまたはsqlを、そのJSONフィールド値の計算に使用される値とともに指定します。pathの値は、SQL/JSONパス式です。sqlの値は、SQL式または問合せです。「生成フィールド、非表示フィールド」を参照してください。

  • ディレクティブ@hiddenにより、非表示のJSONフィールドを指定します。それは、二面性ビューによってサポートされているどのドキュメントにも表示されません。ディレクティブ@hiddenには引数を指定できません。「生成フィールド、非表示フィールド」を参照してください。

  • ディレクティブ@[no]update@[no]insertおよび@[no]deleteは、注釈を更新する二面性ビューとして機能します。これらは、SQL注釈キーワード[NO]UPDATE、[NO]INSERTおよび[NO]DELETEに対応しており、「注釈(NO)UPDATE、(NO)INSERT、(NO)DELETEによる更新操作の許可/禁止」で説明されています。

  • ディレクティブ@[no]checkは、オプティミスティックな同時実行性制御に対応する二面性ビューの部分を決定します。これらは、SQL注釈キーワード[NO]CHECKに対応しています。詳細は、「GraphQLを使用したカーレース二面性ビューの作成」を参照してください。

例9-1 ネストされたドライバ情報を使用した二面性ビューDRIVER_DV1の作成

この例では、二面性ビューdriver_dv1を作成しています。これは、例3-7でGraphQLを使用して定義され、例3-3でSQLを使用して定義されたビューdriver_dvと同じです。ただし、表driverの列のフィールドnameおよびpointsは、フィールドdriverInfoの値であるサブオブジェクトでネストされます。脚注1フィールドdriverInfoの指定は、ビューdriver_dv1の定義と元のビューdriver_dvの定義の違いのみです。

driver_dv1の対応するGraphQL定義およびSQL定義を示します。

CREATE JSON RELATIONAL DUALITY VIEW driver_dv1 AS
  driver
    {_id       : driver_id,
     driverInfo : driver @nest {team   : name,
                                points : points},
     team @unnest {teamId : team_id,
                   name   : name},
     race      : driver_race_map
                  [ {driverRaceMapId : driver_race_map_id,
                     race @unnest {raceId : race_id,
                                   name   : name},
                     finalPosition : position} ]};

次に対応するSQL定義を示します:

CREATE JSON RELATIONAL DUALITY VIEW driver_dv1 AS
  SELECT JSON {'_id'        : d.driver_id,
               'driverInfo' : {'name'   : d.name,
                               'points' : d.points},
               UNNEST
                 (SELECT JSON {'teamId' : t.team_id,
                               'team'   : t.name}
                    FROM team t
                    WHERE t.team_id = d.team_id),
               'race'     :
                 [ SELECT JSON {'driverRaceMapId' : drm.driver_race_map_id,
                                UNNEST
                                  (SELECT JSON {'raceId' : r.race_id,
                                                'name'   : r.name}
                                     FROM race r
                                     WHERE r.race_id = drm.race_id),
                                'finalPosition'   : drm.position}
                    FROM driver_race_map drm
                    WHERE drm.driver_id = d.driver_id ]}
    FROM driver d;

driverはビューのルート表であるため、デフォルトでフィールドはすべてビューにネストされず、GraphQLで@nestを使用してネストする必要があります。

(ルート以外の表のフィールドはデフォルトでネストされており、ネストを解除するには@unnest (SQLではキーワードUNNEST)を明示的に使用する必要があります。これは、チーム・フィールドteamIdname、およびレース・フィールドraceIdnameの場合です。)

_________________________________________________________

9.1.1 Oracle GraphQLディレクティブ@link

GraphQLディレクティブ@linkは、二面性ビューの基礎となる表内の列間の複数の外部キー・リンクを区別します。

ディレクティブ@linkにより、二面性ビューの基になる表の列間のリンク、つまり結合を指定します。通常、列は別の表のためのものですが、同じ表の列もリンクできます。この場合、外部キーは自己参照と呼ばれます。

一般に、外部キー・リンクを明示的に指定する必要がないという点は、二面性ビュー定義に対してGraphQLが示す、SQLより優れている利点です。このようなリンクは通常、基礎となる表依存関係グラフによって推測されるため、冗長性が低くなります。

GraphQLで外部キー・リンクを明示的に使用する必要があるのは、(1) 2つの表の間に複数の外部キー関係がある場合、または(2)表に同じ表を参照する外部キーがある場合、あるいはその両方の場合のみです。このような場合は、@linkディレクティブを使用して、特定のリンク(外部キーとリンクの方向)を指定します。

@linkディレクティブには、toまたはfromという名前の単一の引数が必要です。この引数では、値がネストされたオブジェクトである二面性ビュー・フィールドに対して、(1)ネストされたオブジェクトのフィールドを定義する列を持つ表の外部キー(to方向)を使用するか、(2)列がオブジェクトのフィールドのネスト/囲みを使用するかを定義する列を持つ表の外部キーを使用するか(from方向)を指定します。

toまたはfrom引数の値は文字列のGraphQLリストで、各文字列は単一の外部キー列(たとえば、to : ["FKCOL"])を指定します。複数の文字列のGraphQLリストは、複合外部キー(to : ["FKCOL1", "FKCOL2"]など)を表します。(GraphQLリストは、JSON配列に対応しています。カンマは、GraphQLではオプションです。)

表間の異なる外部キー関係を識別するための@linkディレクティブ

異なる表間の複数の外部キー関係を区別する@linkディレクティブの最初のユースケースは、二面性ビューteam_dv2およびdriver_dv2で示されます。

例9-2team_w_lead表定義には、列lead_driverからdriver表の列driver_idへの外部キー・リンクがあります。また、driver表定義には、列team_idからteam_w_lead表の主キー列team_idへの外部キー・リンクがあります。

図9-1の表依存関係グラフは、これら2つの依存関係を示しています。これは図3-1のグラフと同じですが、表team_w_leadの外部キー列lead_driverから表driverの主キー列driver_idへの追加リンクが含まれている点が異なります。

対応するチームの二面性ビューの定義は、例9-3および例9-4にあります。

図9-1 チーム・リーダーがいるカーレースの例、表依存関係グラフ

図9-1の説明が続きます
「図9-1 チーム・リーダーがいるカーレースの例、表依存関係グラフ」の説明

例9-2 LEAD_DRIVER列を含む表TEAM_W_LEADの作成

この例では、表team_w_leadを作成します。これは、例2-4の表teamと同じです。ただし、表driverの列driver_idへの外部キーである追加の列lead_driverがある点が異なっています。

CREATE TABLE team_w_lead
  (team_id     INTEGER GENERATED BY DEFAULT ON NULL AS IDENTITY,
   name        VARCHAR2(255) NOT NULL UNIQUE,
   lead_driver INTEGER,
   points      INTEGER NOT NULL,
   CONSTRAINT team_pk PRIMARY KEY(team_id),
   CONSTRAINT lead_fk FOREIGN KEY(lead_driver) REFERENCES driver(driver_id));

driverには、team表の列team_idを参照する外部キー列team_idがあります。ここに示す例では、表driverの定義が例2-4と同じであるとします。ただし、外部キーは例2-4の表teamではなく、表team_w_leadを参照します。つまり、ここでは次のdriver表定義を使用します:


CREATE TABLE driver 
  (driver_id  INTEGER GENERATED BY DEFAULT ON NULL AS IDENTITY,
   name       VARCHAR2(255) NOT NULL UNIQUE,
   points     INTEGER NOT NULL,
   team_id    INTEGER,
   CONSTRAINT driver_pk PRIMARY KEY(driver_id),
   CONSTRAINT driver_fk FOREIGN KEY(team_id) REFERENCES team_w_lead(team_id));

team_w_leaddriverの間に2つの外部キー・リンクがあるため、これらの表を使用するチームおよびドライバの二面性ビューでは、例9-3および例9-4に示すように、ディレクティブ@linkを使用する必要があります。

例9-3 GraphQLディレクティブ@linkを示す、LEAD_DRIVERを使用した二面性ビューTEAM_DV2の作成

この例は例3-6と似ていますが、例9-2で定義されている表team_w_leadを使用しており、これには外部キー列lead_driverがあります。表team_w_leaddriverの間に2つの外部キー関係があるため、ディレクティブ@linkを使用して、使用する外部キーとその場所を指定する必要があります。

最上位レベルのJSONフィールドleadDriverの値は、表team_w_leadの外部キー列lead_driverによって提供されるドライバ・オブジェクトです。最上位フィールドdriverの値は、表driverの外部キー列team_idによって提供されるドライバ・オブジェクトのJSON配列です。

フィールドleadDriver@link引数は、fromを使用します。その値lead_driverが、外部にある/ネストするオブジェクトの基礎となる表team_w_leadの外部キー列であるためです。これは1対1の結合です。

フィールドdriver@link引数はtoを使用します。その値team_idが、内部にある/ネストされるオブジェクトの基礎となる表driver脚注2の外部キー列であるためです。これは1対多の結合です。

CREATE JSON RELATIONAL DUALITY VIEW team_dv2 AS
  team_w_lead
    {_id        : team_id,
     name       : name,
     points     : points,
     leadDriver : driver @link (from : ["LEAD_DRIVER"])
       {driverId : driver_id,
        name     : name,
        points   : points},
     driver     : driver @link (to : ["TEAM_ID"]) 
       [ {driverId : driver_id,
          name     : name,
          points   : points} ]};

例9-4 GraphQLディレクティブ@linkを示す、二面性ビューDRIVER_DV2の作成

この例は例3-7と似ていますが、例9-2で定義されている表team_w_leadを使用しており、これには外部キー列lead_driverがあります。表team_w_leaddriver脚注2の間に2つの外部キー関係があるため、ディレクティブ@linkを使用して、使用する外部キーとその場所を指定する必要があります。

フィールドteam@link引数はfromを使用します。その値team_idが、外部にある/ネストするオブジェクトの基礎となる表driverの外部キー列であるためです。

CREATE JSON RELATIONAL DUALITY VIEW driver_dv2 AS
  driver
    {_id       : driver_id
     name      : name
     points    : points
     team_w_lead
       @link (from: ["TEAM_ID"])
       @unnest
       {teamId : team_id,
        team   : name}
     race      : driver_race_map
                   [ {driverRaceMapId : driver_race_map_id,
                      race @unnest
                        {raceId       : race_id,
                         name         : name}
                      finalPosition   : position} ]};

同じ表を参照する外部キー関係を識別するための@linkディレクティブ

特定の表からそれ自体への自己参照外部キーを識別する@linkディレクティブの2番目のユースケースは、二面性ビューteam_dv3driver_dv3およびdriver_manager_dvで示されます。脚注3

例9-5driver_w_mgr表定義には、同じ表driver_w_mgrの列manager_idから列driver_idへの外部キー・リンクがあります。脚注4

図9-2の表依存関係グラフは、この自己参照表依存関係を示しています。これは、図3-1のグラフの簡易バージョン(race表またはdriver_race mapマッピング表なし)ですが、表driver_w_mgrの外部キー列manager_idから同じ表の主キー列driver_idへの追加リンクが含まれています。

図9-2 ドライバの自己参照があるカーレースの例、表依存関係グラフ

図9-2の説明が続きます
「図9-2 ドライバの自己参照があるカーレースの例、表依存関係グラフ」の説明

team_dv3およびdriver_dv3二面性ビューの定義は、それぞれ例9-6および例9-7にあります。@linkの使用に関して、元のカーレース・ビュー(team_dvおよびdriver_dv)との顕著な違いは次のとおりです:

  • ビューteam_dv3の配列driverの情報は、各ドライバのマネージャをフィールドmanagerIdで識別します。

  • ビューdriver_dv3では、ドライバのマネージャの識別子がフィールドbossに含まれています。

ここでは、3番目の二面性ビューdriver_manager_dvにドライバとしてのマネージャの情報(フィールドnameおよびpoints)が含まれ、マネージャにレポートするドライバの情報(配列reports)が含まれています。この定義は、例9-8にあります。

例9-5 列MANAGER_IDを含む表DRIVER_W_MGRの作成

この例では、表driver_w_mgrを作成します。これは、例2-4の表driverと同じです。ただし、同じ表(driver_w_mgr)の列driver_idへの外部キーである追加の列manager_idがある点が異なっています。

CREATE TABLE driver_w_mgr
  (driver_id  INTEGER GENERATED BY DEFAULT ON NULL AS IDENTITY,
   name       VARCHAR2(255) NOT NULL UNIQUE,
   points     INTEGER NOT NULL,
   team_id    INTEGER,
   manager_id INTEGER,
   CONSTRAINT driver_pk  PRIMARY KEY(driver_id),
   CONSTRAINT driver_fk1 FOREIGN KEY(manager_id) REFERENCES driver_w_mgr(driver_id),
   CONSTRAINT driver_fk2 FOREIGN KEY(team_id) REFERENCES team(team_id));

外部キー列manager_idは同じ表driver_w_mgrを参照するため、この表を使用するドライバの二面性ビュー(driver_dv3)およびマネージャの二面性ビュー(driver_manager_dv)は、それぞれ例9-7および例9-8に示すように、ディレクティブ@linkを使用する必要があります。

例9-6 二面性ビューTEAM_DV3の作成(マネージャ付きドライバ)

二面性ビューteam_dv3の定義は、例3-6の二面性ビューteam_dvの定義と同じです。ただし、表driverではなく、表driver_w_mgrを使用し、配列driverのドライバ情報には、フィールドmanagerIdが含まれ、その値は、(表driver_w_mgrの列manager_idからの)ドライバのマネージャの識別子である点が異なっています。

CREATE JSON RELATIONAL DUALITY VIEW team_dv3 AS
  team @insert @update @delete
    {_id : team_id,
     name   : name,
     points : points,
     driver : driver_w_mgr @insert @update
       [ {driverId : driver_id,
          name      : name,
          managerId : manager_id,
          points    : points @nocheck} ]};

これは、ビューの同等のSQL定義です:

CREATE OR REPLACE JSON RELATIONAL DUALITY VIEW team_dv3 AS
  SELECT JSON {'_id'    : t.team_id,
               'name'   : t.name,
               'points' : t.points,
               'driver' :
                 [ SELECT JSON {'driverId'  : d.driver_id,
                                'name'      : d.name,
                                'managerId' : d.manager_id,
                                'points'    : d.points WITH NOCHECK}
                     FROM driver_w_mgr d WITH INSERT UPDATE
                     WHERE d.team_id = t.team_id ]}
    FROM team t WITH INSERT UPDATE DELETE;

3つのチーム・ドキュメントがビューteam_dv3に挿入されます。配列driverの各ドライバ・オブジェクトには、managerIdフィールドがあり、その値はドライバのマネージャの識別子であるか、ドライバにマネージャがいない(ドライバがマネージャである)ことを示すnullのいずれかです。このユースケースでは、チームのすべてのドライバに同じマネージャ(同様にチームに所属)がいます。


INSERT INTO team_dv3 VALUES ('{"_id"    : 301,
                               "name"   : "Red Bull",
                               "points" : 0,
                               "driver" : [ {"driverId"  : 101,
                                             "name"      : "Max Verstappen",
                                             "managerId" : null,
                                             "points"    : 0},
                                            {"driverId"  : 102,
                                             "name"      : "Sergio Perez",
                                             "managerId" : 101,
                                             "points"    : 0} ]}');

INSERT INTO team_dv3 VALUES ('{"_id"    : 302,
                               "name"   : "Ferrari",
                               "points" : 0,
                               "driver" : [ {"driverId"  : 103,
                                             "name"      : "Charles Leclerc",
                                             "managerId" : null,
                                             "points"    : 0},
                                            {"driverId"  : 104,
                                             "name"      : "Carlos Sainz Jr",
                                             "managerId" : 103,
                                             "points"    : 0} ]}');

INSERT INTO team_dv3 VALUES ('{"_id"    : 303,
                               "name"   : "Mercedes",
                               "points" : 0,
                               "driver" : [ {"driverId"  : 105,
                                             "name"      : "George Russell",
                                             "managerId" : null,
                                             "points"    : 0},
                                            {"driverId"  : 106,
                                             "name"      : "Lewis Hamilton",
                                             "managerId" : 105,
                                             "points"    : 0},
                                            {"driverId"  : 107,
                                             "name"      : "Liam Lawson",
                                             "managerId" : 105,
                                             "points"    : 0} ]}');

例9-7 二面性ビューDRIVER_DV3の作成(マネージャ付きドライバ)

この例は、例3-7で定義されているビューの簡易バージョンです。チームもドライバのためのレース情報も含まれません。かわりに、ドライバのマネージャの識別子がフィールドbossに含まれます。

例9-5に定義されている表driver_w_mgrを使用して、外部キー列manager_idを使用してそのマネージャ情報を取得します。その外部キー関係は同じ表driver_w_mgrを参照するため、ディレクティブ@linkを使用して外部キーを指定する必要があります。

フィールドboss@link引数はfromを使用します。その値["MANAGER_ID"]が、外部にある/ネストするオブジェクトの基礎となる表driver_w_mgrの外部キー列であるためです。これは1対1の結合です。

CREATE JSON RELATIONAL DUALITY VIEW driver_dv3 AS
  driver_w_mgr @insert @update @delete
    {_id    : driver_id,
     name   : name,
     points : points @nocheck,
     boss   : driver_w_mgr @link (from : ["MANAGER_ID"])
       {driverId : driver_id,
        name     : name}};

これはビューの同等のSQL定義で、結合を明示的にします:

CREATE OR REPLACE JSON RELATIONAL DUALITY VIEW driver_dv3 AS
  SELECT JSON {'_id'     : d1.driver_id,
               'name'    : d1.name,
               'points'  : d1.points WITH NOCHECK,
               'boss'    : (SELECT JSON {'driverId' : d2.driver_id,
                                         'name'     : d2.name,
                                         'points'   : d2.points WITH NOCHECK}
                              FROM driver_w_mgr d2
                              WHERE d1.manager_id = d2.driver_id)}
    FROM driver_w_mgr d1 WITH INSERT UPDATE DELETE;

この問合せでは、ドライバ106 (Lewis Hamilton)のドキュメントが選択されます。

SELECT json_serialize(DATA PRETTY)
  FROM driver_dv3 v WHERE v.data."_id" = 106;

ドライバLewis HamiltonにはマネージャGeorge Russellがいることを示しています。ドライバとボスの関係は1対1です。

JSON_SERIALIZE(DATAPRETTY)
--------------------------
{
  "_id" : 106,
  "_metadata" :
  {
    "etag" : "998443C3E7762F0EB88CB90899E3ECD1",
    "asof" : "0000000000000000"
  },
  "name" : "Lewis Hamilton",
  "points" : 0,
  "boss" :
  {
    "driverId" : 105,
    "name" : "George Russell",
    "points" : 0
  }
}

1 row selected.

例9-8 二面性ビューDRIVER_MANAGER_DVの作成

この二面性ビューは、他のドライバを管理するドライバに関する情報を提供します。フィールド_idnameおよびpointsには、マネージャに関する情報が含まれます。フィールドreportsは、マネージャにレポートするドライバの配列(ID、名前およびポイント)です。

フィールドreports@link引数はtoを使用します。その値["MANAGER_ID"]が、外部にある/ネストするオブジェクトの基礎となる表driver_manager_dvの外部キー列であるためです。これは1対多の結合です。

CREATE JSON RELATIONAL DUALITY VIEW driver_manager_dv AS
  driver_w_mgr @insert @update @delete
    {_id     : driver_id,
     name    : name,
     points  : points  @nocheck,
     reports : driver_w_mgr @link (to : ["MANAGER_ID"])
       [ {driverId : driver_id,
          name     : name,
          points   : points @nocheck} ]};

これはビューの同等のSQL定義で、結合を明示的にします:

CREATE OR REPLACE JSON RELATIONAL DUALITY VIEW driver_manager_dv AS
  SELECT JSON {'_id'     : d1.driver_id,
               'name'    : d1.name,
               'points'  : d1.points WITH NOCHECK,
               'reports' : [ SELECT JSON {'driverId' : d2.driver_id,
                                           'name'     : d2.name,
                                           'points'   : d2.points WITH NOCHECK}
                                FROM driver_w_mgr d2
                                WHERE d1.driver_id = d2.manager_id ]}
    FROM driver_w_mgr d1 WITH INSERT UPDATE DELETE;

この問合せでは、ドライバ(マネージャ) 105 (George Russell)のドキュメントが選択されます。

SELECT json_serialize(DATA PRETTY)
  FROM driver_manager_dv v WHERE v.data."_id" = 105;

マネージャGeorge Russellには、彼にレポートする2人のドライバLewis HamiltonとLiam Lawsonがいることを示しています。マネージャとレポートの関係は1対多です。

JSON_SERIALIZE(DATAPRETTY)
--------------------------
{
  "_id" : 105,
  "_metadata" :
  {
    "etag" : "7D91177F7213E086ADD149C2193182FD",
    "asof" : "0000000000000000"
  },
  "name" : "George Russell",
  "points" : 0,
  "reports" :
  [
    {
      "driverId" : 106,
      "name" : "Lewis Hamilton",
      "points" : 0
    },
    {
      "driverId" : 107,
      "name" : "Liam Lawson",
      "points" : 0
    }
  ]
}

1 row selected.


脚注一覧

脚注1: 更新およびETAGチェックの注釈はここでは示していません。
脚注2: 例9-2で示した表driverの定義を想定しています。
脚注3: このユースケースを示すためにここで使用されているデータは架空のものです。
脚注4: レースカーのドライバのマネージャがドライバでもあるというユースケースは、現実にはありえないことである可能性があります。ただし、表からそれ自体への外部キー・リンクを識別する機能は、間違いなく役に立ちます。