7 生成フィールド、非表示フィールド

二面性ビューでは、JSONフィールドをリレーショナル列に直接マッピングするかわりに、SQL/JSONパス式、SQL式またはSQL問合せを使用してフィールドを生成できます。生成されたフィールド、および列にマップされたフィールドは非表示にできます。つまり、そのビューでサポートされているドキュメント内に表示されません。

生成フィールドの値の計算では、そのビューによって定義されている他のフィールドの値を使用できます(他の生成フィールドなど)。サポートされているドキュメント内でこれらのフィールドを非表示にするか表示するかは関係ありません。

式や問合せを使用してフィールド値を生成することは、インライン拡張とも呼ばれます。二面性ビューでサポートされているドキュメントが読み取られるときに、生成フィールドの追加によってそれが拡張されます。拡張の定義が二面性ビュー定義/作成コード(DDL)の一部であるという意味で、インラインと呼ばれます。

生成フィールドは読取り専用であり、ドキュメントが書き込まれるときには無視されます。それらに注釈(CHECKなど)を含めることはできません(それらは、フィールドetagの値の計算には影響しません)。

ノート:

同じ列を様々な二面性ビュー内の複数のフィールドにマッピングすると、それらのサポート対象ドキュメントでは、それらのフィールド内でその同じデータが共有されます。生成フィールドの使用により、別の方法で様々な二面性ビューの間でデータを共有できます。あるビュー内のフィールドの値が別のビュー内のフィールドとまったく同じである必要がない場合に、その値が他のフィールドの値によって決まるようにすることができます。

ある種類のドキュメント内のフィールドの値を、宣言的に、他の種類のドキュメント(いくつでも可)のフィールドの値に応じて定義できます。生成フィールドは読取り専用であるため、この種の共有は一方向です。

これは二面性ビューで宣言的な代替手段を提供するための別の方法であり、ビジネス・ロジックを、アプリケーション・コードで実装するのではなく、アプリケーション・データ自体の定義に組み込むことができます。

たとえば、例7-2を参照してください。そこでは、チーム・ドキュメントのpointsフィールドは、チームのドライバ用のドキュメントのpointsフィールドによってのみ定まります。つまり、チームのポイントは、ドライバのポイントの合計です。

ノート:

非表示フィールドの名前が、同じ表のフレックス列に格納されているフィールドの名前と競合する場合、二面性ビューでサポートされるドキュメントでは、その表に対応するJSONオブジェクトからのフィールドは存在しません

SQLでは、生成フィールドを、そのフィールド名とコロン(:)の直後にキーワードGENERATED、その後にキーワードUSINGおよび次のいずれかを続けて指定します:

  • キーワードPATHの後にSQL/JSONパス式が続く

  • SQL式

  • カッコで囲んだSQL問合せ: ()

GraphQLでは、ディレクティブ@generatedを使用して生成フィールドを指定し、それに、引数pathまたはsqlと、その値としてパス式(pathの場合)およびSQL式または問合せ(sqlの場合)を渡します。

パス式を指定した場合は、その式でターゲット指定(一致)したJSONデータを、その二面性ビューでサポートされているドキュメント内のどこにでも配置できます。つまり、パス式のスコープドキュメント全体です。

具体的に述べると、パス式では、生成されるドキュメント・フィールドを参照できるということです。ビュー作成コードでパス式が字句的に出現する前にフィールドの生成が定義されている場合は、生成フィールドを使用してターゲット・データを特定することもできます。

パス式で他のフィールド値を使用して値が計算される場合(通常は計算される)、これらの計算で使用されるフィールドは非表示にできます。したがって、パス式では非表示フィールドを参照できます。つまり、パス式のスコープは、フィールドが非表示になるの、生成されたドキュメントです。

SQL式または問合せを指定する場合は、それで、そのフィールドが属するJSONオブジェクトの基になる表の列(1)、(2)外部表の列、または(3)二面性ビューでサポートされているどのフィールドにもマップされていない列にあるSQLデータのみを参照する必要があります。

つまり、SQL式または問合せのスコープは、そのSQL式または問合せ自体、およびそれを字句的に含む問合せです。副問合せ内の表の列は参照可能ではありません。生成されるJSONデータに関しては、そのスコープは、生成フィールドが属するJSONオブジェクト、およびそのオブジェクトを含むJSONデータです。

たとえば、例7-1では、生成フィールドonPodiumは、表driver_race_map (これは、フィールドonPodiumが属するJSONオブジェクトの基になる)の列positionを参照するSQL式を使用して定義されています。

1つ以上の式または問合せで非表示フィールドの値を使用して、他のフィールドの値を計算できます(それら自体はサポート対象ドキュメント内で非表示になっていても表示されていてもかまいません)。フィールドを非表示にすることを指定するには、それにマップされている列名の後にキーワードHIDDENを使用するか、そのフィールドを生成するGENERATED USING句を使用します。

例7-1 SQL問合せおよびSQL式を使用して生成されるフィールド

この例では、二面性ビューrace_dv_sql_genを定義します。この定義は、例3-5でのビューrace_dvの場合と同じですが、さらに次の2つの生成フィールドがあります:

  • fastestTime — レースの最速時間。SQL問合せによるフィールド生成を使用します。

  • onPodium — 指定されたドライバのレース結果でそのドライバが表彰台に上がるかどうか。SQL式によるフィールド生成を使用します。

fastestTimeの値は、表彰台に上がるドライバのレース時間にSQL集計関数minを適用することで計算されます。これらは、レース表のJSON型列podiumのオブジェクト・フィールドwinnerのフィールドtime (podium.winner.time)から取得されます。

onPodiumの値は、表driver_race_mapの列positionの値から計算されます。その列値が12または3の場合、フィールドonPodiumの値は"YES"であり、それ以外の場合は"NO"です。このロジックは、SQLのCASE式を評価することで実現されます。

GraphQL:

CREATE JSON RELATIONAL DUALITY VIEW race_dv_sql_gen AS
  race
    {_id         : race_id
     name        : name
     laps        : laps @NOUPDATE
     podium      : podium @NOCHECK
     fastestTime @generated (sql : "SELECT min(rt.podium.winner.time) FROM race rt")
     result      : driver_race_map @insert @update @delete @link (to : ["RACE_ID"])
       {driverRaceMapId : driver_race_map_id
        onPodium        @generated (sql : "(CASE WHEN position BETWEEN 1 AND 3
                                                   THEN 'YES'
                                                   ELSE 'NO'
                                            END)")
        driver @unnest @update @noinsert @nodelete
          {driverId : driver_id
           name     : name}}};

(この定義では、GraphQLディレクティブ@linkと引数toを使用して、フィールドresultの値であるネストされたオブジェクトに対して、表driver_race_mapの外部キー列race_idを使用するように指定しています。これは、表raceの主キー列race_idにリンクしています。「Oracle GraphQLディレクティブ@link」を参照してください。)

SQL:

CREATE JSON RELATIONAL DUALITY VIEW race_dv_sql_gen AS
  SELECT JSON {'_id'         : r.race_id,
               'name'        : r.name,
               'laps'        : r.laps WITH NOUPDATE,
               'date'        : r.race_date,
               'podium'      : r.podium WITH NOCHECK,
               'fastestTime' : GENERATED USING
                                (SELECT min(rt.podium.winner.time) FROM race rt),
               'result'      :
                 [ SELECT JSON {'driverRaceMapId' : drm.driver_race_map_id,
                                'position'        : drm.position,
                                'onPodium'        : GENERATED USING
                                                      (CASE WHEN position BETWEEN 1 AND 3
                                                              THEN 'YES'
                                                              ELSE 'NO'
                                                       END),
                                UNNEST (SELECT JSON {'driverId' : d.driver_id,
                                                     'name'     : d.name}
                                          FROM driver d WITH NOINSERT UPDATE NODELETE
                                          WHERE d.driver_id = drm.driver_id)}
                     FROM driver_race_map drm WITH INSERT UPDATE DELETE
                     WHERE drm.race_id = r.race_id ]}
    FROM race r WITH INSERT UPDATE DELETE;

例7-2 SQL/JSONパス式を使用して生成されるフィールド

この例では、二面性ビューteam_dv_path_genを定義します。この定義は、チームのポイントがteam表に格納されない点を除き、例3-1でのビューteam_dvの場合と同じです。それらは、チーム内のドライバのポイントを合計することで計算されます。

SQL/JSONパス式$.driver.points.sum()によってこれを実現します。これは、集計項目メソッドsum()を表driverの列points内の値に適用します。

GraphQL:

CREATE JSON RELATIONAL DUALITY VIEW team_dv_path_gen AS
  team @insert @update @delete
    {_id    : team_id
     name   : name
     points @generated (path : "$.driver.points.sum()")
     driver @insert @update @link (to : ["TEAM_ID"])
       {driverId : driver_id
        name     : name
        points   : points @nocheck}};

SQL:

CREATE JSON RELATIONAL DUALITY VIEW team_dv_path_gen AS
  SELECT JSON {'_id'    : t.team_id,
               'name'   : t.name,
               'points' : GENERATED USING PATH '$.driver.points.sum()',
               'driver' :
                 [ SELECT JSON {'driverId' : d.driver_id,
                                'name'     : d.name,
                                'points'   : d.points WITH NOCHECK}
                     FROM driver d WITH INSERT UPDATE
                     WHERE d.team_id = t.team_id ]}
    FROM team t WITH INSERT UPDATE DELETE;

以前は、このドキュメントでは、ドライバのpointsフィールドとチームのpointsフィールドの両方がアプリケーション・コードによって更新されることを想定していました。しかしながら、チームのpointsは、ドライバのpointsの値によってのみ定まります。このロジック(機能依存)をチームの二面性ビュー自体に統合し、それを宣言的に表すこと(チームのポイント = そのチームのドライバのポイントの合計)が理にかなっています。

ノート:

生成フィールドは読取り専用です。つまり、チーム・ドキュメントの最上位フィールドpointsが生成される場合は、挿入または更新するチーム・ドキュメントのpointsフィールド(最上位レベル)は無視されます。そのかわり、これらのチーム・フィールド値は、挿入または更新されたドキュメントのpoints値から計算されます。このような更新の例は、例5-11および例5-19を参照してください。

例7-3 非表示フィールドを使用して生成されるフィールド

この例では、従業員表empを使用して二面性ビューemp_dv_genを定義します。

  • emp.wageおよびemp.tipsを使用して、非表示フィールドwageおよびtipsをそれぞれ定義します。

  • emp.wageおよびemp.tipsの値を合計するSQL式を使用して、フィールドtotalCompを生成します。

  • フィールドtipsおよびwageの値を比較するSQL/JSONパス式を使用して、ブール・フィールドhighTipsを生成します。

CREATE TABLE emp(empno NUMBER PRIMARY KEY,
                 first VARCHAR2(100),
                 last  VARCHAR2(100),
                 wage  NUMBER,
                 tips  NUMBER);

INSERT INTO emp VALUES (1, 'Jane', 'Doe', 1000, 2000);

GraphQL:

CREATE JSON RELATIONAL DUALITY VIEW emp_dv_gen AS
  emp
    {_id       : empno
     wage      : wage @hidden
     tips      : tips @hidden
     totalComp @generated (sql  : "wage + tips")
     highTips  @generated (path : "$.tips > $.wage")};

SQL:

CREATE JSON RELATIONAL DUALITY VIEW emp_dv_gen AS
  SELECT JSON {'_id'       : EMPNO,
               'wage'      : e.wage HIDDEN,
               'tips'      : e.tips HIDDEN,
               'totalComp' : GENERATED USING (e.wage + e.tips),
               'highTips'  : GENERATED USING PATH '$.tips > $.wage'}
    FROM emp e;

SELECT data FROM emp_dv_gen;

問合せ結果(わかりやすくするためフォーマット出力しています):

{"_id"       : 1,
 "totalComp" : 3000, 
 "highTips"  : true, 
 "_metadata" : {"etag" : "B8CA77231CA578A6137788C83BC0F410",
                "asof" : "000025B864BC59AB"}}