4 JSONリレーショナル二面性ビューの使用

二面性ビューでサポートされるドキュメントまたはドキュメントの一部を挿入(作成)、更新、削除および問合せることができます。二面性ビューに関する情報をリストできます。

通常、ドキュメント中心のアプリケーションは、SQL/JSONファンクションまたはOracle Database API for MongoDBSimple Oracle Document Access (SODA)Oracle REST Data Services (ORDS)などのクライアントAPIを使用して、JSONドキュメントを直接操作します。分析、レポート、機械学習などのデータベース・アプリケーションおよび機能は、SQL、PL/SQL、JavaScriptまたはC (Oracle Call Interface)を使用して同じデータを操作できます。

また、SQLおよびその他のデータベース・コードは、他のリレーショナル・データと同様に、二面性ビューの基礎となるリレーショナル表のデータに直接動作することもできます。これには、変更操作が含まれています。基礎となる表のデータに対する変更は、二面性ビューによって提供されるドキュメントに自動的に反映されます。例4-3に、これを示します。

その逆も同様であるため、ドキュメントまたはドキュメントの基礎となるデータに対する動作では、他方にも自動的に影響を与えます。これには、二面性ビューによって提供されるJSONドキュメントとリレーショナル・データの間の二面性が反映されています。

ドキュメント・ビューの基礎となるに対する操作は、次のように、ビューでサポートされるドキュメントに自動的に影響します:

  • 二面性ビューのルート(上位レベル)表に行の挿入を実行すると、新しいドキュメントがビューに挿入されます。たとえば、driver表に行を挿入すると、ビューdriver_dvにドライバ・ドキュメントが挿入されます。

    ただし、表driverはドライバ・ドキュメントのデータの一部のみを提供するため、その表でサポートされているドキュメント・フィールドのみ、データが移入されるため、ドキュメントの他のフィールドは欠落しているか空です。

  • ルート表からの行の削除を実行すると、ビューから対応するドキュメントが削除されます。

  • ルート表の行を更新すると、対応するドキュメントが更新されます。

    行の挿入と同様に、その表データでサポートされるドキュメント・フィールドのみが更新され、他のフィールドは変更されません。

ノート:

JSONリレーショナル二面性ビューでサポートされるドキュメントまたはその基礎となる表データの更新は、そのデータの内容が変更されていない場合でも、データの一部の行が更新されたとSQLによってレポートされます。これは標準のSQL動作です。更新操作が成功すると、常に、ターゲットとする行が更新されたとレポートされます。これは、更新操作に付随するトリガーまたは行変換演算子が存在する可能性があり、それ自体がデータを変更できるという事実も示しています。

二面性ビュー自体に対する操作には、作成、削除、リスト、およびそれらに関するその他の情報のリストが含まれています。

  • 二面性ビューの作成の例は、「カーレースの例、二面性ビュー」を参照してください。

  • SQLコマンドDROP VIEWを使用して、任意のビューの削除と同様に既存の二面性ビューを削除できます。

    二面性ビューは独立していますが、通常は共有データがあるドキュメントが含まれています。たとえば、二面性ビューdriver_dvに影響を与えずに二面性ビューteam_dvを削除できます。ただし、二面性ビューは基礎となる表によって異なります。

    注意:

    使用できないビューがレンダリングされるため、二面性ビューの基礎となるは削除しないでください

  • 静的データ・ディクショナリ・ビューを使用して、既存の二面性ビューに関する情報を取得できます。「二面性ビューに関する情報の取得」を参照してください。

ノート:

特に明示的に示されていないかぎり、次のようになります。

  • ここに示す例は、決して相互に依存するものではありません。特に、それらの間には暗黙的な順序付けはありません。

  • ここでの二面性ビューを使用する例では、「カーレースの例、二面性ビュー」で定義したビューを使用します。これらのビューは、UNNESTを使用して定義されています(例2-5例2-7および例2-9)。

  • ここでの表を使用する例では、「カーレースの例、表」で定義した表を使用します。

関連項目:

4.1 二面性ビューへのドキュメント/データの挿入

JSONドキュメントを二面性ビューに直接挿入することも、二面性ビューの基礎となる表にデータを挿入することもできます。例は、これらの可能性を示しています。

ノート:

特に明示的に示されていないかぎり、次のようになります。

  • ここに示す例は、決して相互に依存するものではありません。特に、それらの間には暗黙的な順序付けはありません。

  • ここでの二面性ビューを使用する例では、「カーレースの例、二面性ビュー」で定義したビューを使用します。これらのビューは、UNNESTを使用して定義されています(例2-5例2-7および例2-9)。

  • ここでの表を使用する例では、「カーレースの例、表」で定義した表を使用します。

1つ以上の二面性ビューの基礎となるルート表にデータ(行)を挿入すると、各ビューでサポートされる新しいドキュメントが作成されます。その表に指定されたビューのフィールドのみがドキュメントに存在し、他のすべてのフィールドは存在しません。

たとえば、表raceに行を挿入すると、ビューrace_dv (ルート表として表raceを持つ)にドキュメントが挿入され、そのドキュメントにはレース固有のフィールドが含まれています。フィールドresultは、raceではなく、表driverおよびdriver_race_mapから導出されるため、欠落しています。

二面性ビューにドキュメントを挿入すると、そのフィールド値は、対応する表の列に必要なデータ型に自動的に変換されます。たとえば、値がサポートされているISO 8601の日時書式であるJSONフィールドは、対応する列の型がDATEの場合、SQLのDATE型の値に自動的に変換されます。一部のフィールドの型は、必要な列型に変換できない場合、エラーが発生します。

基礎となる表のJSON型の列に対応するフィールドの値では、このような型変換が行われません。テキストJSONドキュメントを挿入する場合は、キーワードEXTENDEDを指定したJSON型コンストラクタを拡張オブジェクトとともに使用して、Oracle固有の型のJSON言語スカラー値(dateなど)を指定できます。たとえば、{"$oracleDate" : "2022-03-27"}などのテキスト・フィールド値を使用して、JSON型の日付値を生成できます。(もちろん、同じ方法を使用して、テキスト・データを、基礎となる表の列に直接挿入するJSON型に変換できます。)

ヒント:

挿入するドキュメントが二面性ビューでサポートされている既存のドキュメントと似ているか、互換性があることを確認するには、ドキュメントの作成時にそれらのドキュメントをガイドとして記述するJSONスキーマを使用します。スキーマは、静的ディクショナリ・ビュー*_JSON_DUALITY_VIEWSの列JSON_SCHEMAから、またはPL/SQLファンクションDBMS_JSON_SCHEMA.describeを使用して取得できます。「二面性ビューに関する情報の取得」を参照してください。

あまり重要でないフィールドや、適切な値が不明なフィールドは省略できます。ただし、実行時エラーを回避するには、JSONスキーマの配列"required"に含まれるすべてのフィールドを含めることをお薦めします。

関連項目:

例4-1 主キー・フィールドを指定した二面性ビューへのJSONドキュメントの挿入 — SQLの使用

この例では、ビューteam_dvに3つのドキュメントを挿入し、ビューrace_dvに3つのドキュメントを挿入します。_idという名前の主キー・フィールドが明示的に指定されています。

ここでのレース・ドキュメントのフィールドdateの値は、ISO 8601の日時文字列です。フィールドdateに対応する表raceの列のデータ型がDATEであるため、これらは自動的にSQL DATE値に変換され、基礎となるrace表に挿入されます。

この例では、フィールド/列points (値0)およびpodium (値{})には、基本的なプレースホルダ値のみが指定されています。これらは、ビューとその表に最初に移入するのに使用されます。様々な種類のレースを定義しますが、実際のレース結果は記録しません。

個々のドライバのpointsフィールド/列の値はチーム・ドキュメント/表とドライバ・ドキュメント/表の間で共有されるため、1つの場所でこれらを更新すると、別の場所でも自動的に更新されます。これらの異なるビューでは、フィールド/列は同じ名前ですが、関連はありません。重要なのは、フィールド/列名間ではなく、二面性ビュー間の関係です。

挿入(および削除)と同様に、二面性ビューまたはその基礎となる表に対して更新を直接実行できます(例4-3を参照)。

カーレースの例は、カーレースの結果として動的に更新(置換)されるpointsおよびpodiumフィールドの値を示しています。この更新は、想定されるアプリケーション・ロジックの一部です。

また、アプリケーション・ロジックの一部として、特定のレースにおけるドライバのpositionが、そのドライバの累積されたpointsに関与することが想定されます(ドライバの位置が適切であればあるほど、ポイントが累積されます)。これもアプリケーション・コードで対処できます。あるいは、二面性ビューrace_dvまたはマッピング表driver_race_mapに対してBEFORE INSERT triggerなどを使用して対処できます(例4-15を参照)。

-- Insert team documents into TEAM_DV, providing primary-key field _id.
INSERT INTO team_dv VALUES ('{"_id"   : 301,
                              "name"   : "Red Bull",
                              "points" : 0,
                              "driver" : [ {"driverId" : 101,
                                            "name"     : "Max Verstappen",
                                            "points"   : 0},
                                           {"driverId" : 102,
                                            "name"     : "Sergio Perez",
                                            "points"   : 0} ]}');

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

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

-- Insert race documents into RACE_DV, providing primary-key field _id.
INSERT INTO race_dv VALUES ('{"_id"   : 201,
                              "name"   : "Bahrain Grand Prix",
                              "laps"   : 57,
                              "date"   : "2022-03-20T00:00:00",
                              "podium" : {}}');

INSERT INTO race_dv VALUES ('{"_id"   : 202,
                              "name"   : "Saudi Arabian Grand Prix",
                              "laps"   : 50,
                              "date"   : "2022-03-27T00:00:00",
                              "podium" : {}}');

INSERT INTO race_dv VALUES ('{"_id"   : 203,
                              "name"   : "Australian Grand Prix",
                              "laps"   : 58,
                              "date"   : "2022-04-09T00:00:00",
                              "podium" : {}}');

例4-2 主キー・フィールドを指定した二面性ビューへのJSONドキュメントの挿入 — RESTの使用

この例では、Oracle REST Data Services (ORDS)を使用して例4-1と同じことを実行します。簡潔にするために、1つのドキュメントのみを二面性ビューteam_dvに挿入し、1つのドキュメントをレース・ビューrace_dvに挿入します。二面性ビューの例を所有するデータベース・ユーザー(スキーマ)は、ここではユーザーJANUSとして表示されます。

ビューteam_dvにドキュメントを挿入します:

curl --request POST \
  --url http://localhost:8080/ords/janus/team_dv/ \
  --header 'Content-Type: application/json' \
  --data '{"_id"   : 302,
           "name"   : "Ferrari",
           "points" : 0,
           "driver" : [ {"driverId" : 103,
                         "name"     : "Charles Leclerc",
                         "points"   : 0},
                        {"driverId" : 104,
                         "name"     : "Carlos Sainz Jr",
                         "points"   : 0} ]}'

レスポンス:

201 Created

{"_id"      : 302,
 "_metadata" : {"etag" : "DD9401D853765859714A6B8176BFC564",
                "asof" : "0000000000000000"}, "name"      : "Ferrari",
 "points"    : 0,
 "driver"    : [ {"driverId" : 103,
                  "name"     : "Charles Leclerc",
                  "points"   : 0},
                 {"driverId" : 104,
                  "name"     : "Carlos Sainz Jr",
                  "points"   : 0}],
 "links"     : [ {"rel"  : "self",
                  "href" : "http://localhost:8080/ords/janus/team_dv/302"},
                 {"rel"  : "describedby",
                  "href" :
                   "http://localhost:8080/ords/janus/metadata-catalog/team_dv/item"},
                 {"rel"  : "collection",
                  "href" : "http://localhost:8080/ords/janus/team_dv/"} ]}

ビューrace_dvにドキュメントを挿入します:

curl --request POST \
  --url http://localhost:8080/ords/janus/race_dv/ \
  --header 'Content-Type: application/json' \
  --data '{"_id"   : 201,
           "name"   : "Bahrain Grand Prix",
           "laps"   : 57,
           "date"   : "2022-03-20T00:00:00",
           "podium" : {}}'

レスポンス:

201 Created
{"_id"      : 201,
 "_metadata" : {"etag" : "2E8DC09543DD25DC7D588FB9734D962B",
                "asof" : "0000000000000000"}, "name"      : "Bahrain Grand Prix",
 "laps"      : 57,
 "date"      : "2022-03-20T00:00:00",
 "podium"    : {},
 "result"    : [],
 "links"     : [ {"rel"  : "self",
                  "href" : "http://localhost:8080/ords/janus/race_dv/201"},
                 {"rel"  : "describedby",
                  "href" :
                   "http://localhost:8080/ords/janus/metadata-catalog/race_dv/item"},
                 {"rel"  : "collection",
                  "href" : "http://localhost:8080/ords/janus/race_dv/"} ]}

関連項目:

『Oracle REST Data Services開発者ガイド』JSONリレーショナル二面性ビューのサポート

例4-3 表へのJSONデータの挿入

この例では、JSONドキュメント二面性ビューに挿入する別の方法を示します。teamおよびraceにJSON データを挿入します。

挿入されたデータは、関連するドキュメントの一部(ビュー・タイプに固有の部分)にのみ対応しています。各表には、別の表が対象としていないデータのみの列があります(表は正規化されます)。

表データは正規化されるため、ビューでサポートされているドキュメントを含め、データが使用されているすべての場所に表行の挿入が反映されます。

ここで、例4-1のように、チームのポイントとレースの表彰者には、基本の(初期)値が与えられます。

INSERT INTO team VALUES (301, 'Red Bull', 0);
INSERT INTO team VALUES (302, 'Ferrari',  0);
   
INSERT INTO race
  VALUES (201, 'Bahrain Grand Prix',       57, DATE '2022-03-20', '{}');
INSERT INTO race
  VALUES (202, 'Saudi Arabian Grand Prix', 50, DATE '2022-03-27', '{}');
INSERT INTO race
  VALUES (203, 'Australian Grand Prix',    58, DATE '2022-04-09', '{}');

例4-4 主キー・フィールドの指定のない二面性ビューへのJSONドキュメントの挿入 — SQLの使用

この例では、主キー・フィールド(_id)を指定せずに、二面性ビューdriver_dvにドライバ・ドキュメントを挿入します。このフィールドの値は自動的に生成されます(基礎となる主キー列がINTEGER GENERATED BY DEFAULT ON NULL AS IDENTITYを使用して定義されているため)。次に、生成されたフィールド値を出力します。


-- Insert a driver document into DRIVER_DV, without providing a primary-key
--  field (_id).  The field is provided automatically, with a
--  generated, unique numeric value.
-- SQL/JSON function json_value is used to return the value into bind
--  variable DRIVERID.
VAR driverid NUMBER;
INSERT INTO driver_dv dv VALUES ('{"name"   : "Liam Lawson",
                                   "points" : 0,
                                   "teamId" : 301,
                                   "team" : "Red Bull",
                                   "race"   : []}')
  RETURNING json_value(DATA, '$._id') INTO :driverid;

SELECT json_serialize(data PRETTY) FROM driver_dv d
  WHERE d.DATA.name = 'Liam Lawson';

{"_id"      : 7,
 "_metadata" : {"etag" : "F9D9815DFF27879F61386CFD1622B065",
                "asof" : "00000000000C20CE"},
 "name"      : "Liam Lawson",
 "points"    : 0,
 "teamId"    : 301,
 "team"      : "Red Bull",
 "race"      : []}

例4-5 主キー・フィールドの指定のない二面性ビューへのJSONドキュメントの挿入 — RESTの使用

この例では、Oracle REST Data Services (ORDS)を使用して例4-4と同じことを実行します。二面性ビューの例を所有するデータベース・ユーザー(スキーマ)は、ここではユーザーJANUSとして表示されます。

curl --request POST \
  --url http://localhost:8080/ords/janus/driver_dv/ \
  --header 'Content-Type: application/json' \
  --data '{"name"   : "Liam Lawson",
           "points" : 0,
           "teamId" : 301,
           "team"   : "Red Bull",
           "race"   : []}'

レスポンス:

201 Created
{"_id"      : 7,
 "_metadata" : {"etag" : "F9EDDA58103C3A601CA3E0F49E1949C6",
                "asof" : "00000000000C20CE"},
 "name"      : "Liam Lawson",
 "points"    : 0,
 "teamId"    : 301,
 "team"      : "Red Bull",
 "race"      : [],
 "links"     :
  [ {"rel"  : "self",
     "href" : "http://localhost:8080/ords/janus/driver_dv/23"},
    {"rel"  : "describedby",
     "href" : "http://localhost:8080/ords/janus/metadata-catalog/driver_dv/item"},
    {"rel"  : "collection",
     "href" : "http://localhost:8080/ords/janus/driver_dv/"} ]}

関連項目:

『Oracle REST Data Services開発者ガイド』JSONリレーショナル二面性ビューのサポート

4.2 二面性ビューからのドキュメント/データの削除

JSONドキュメントを二面性ビューから直接削除することも、二面性ビューの基礎となる表からデータを削除することもできます。例は、これらの可能性を示しています。

ノート:

特に明示的に示されていないかぎり、次のようになります。

  • ここに示す例は、決して相互に依存するものではありません。特に、それらの間には暗黙的な順序付けはありません。

  • ここでの二面性ビューを使用する例では、「カーレースの例、二面性ビュー」で定義したビューを使用します。これらのビューは、UNNESTを使用して定義されています(例2-5例2-7および例2-9)。

  • ここでの表を使用する例では、「カーレースの例、表」で定義した表を使用します。

1つ以上の二面性ビューのルート(上位)表である表から行を削除すると、それらのビューからその行に対応するドキュメントが削除されます。

例4-6 二面性ビューRACE_DVからのJSONドキュメントの削除 — SQLの使用

この例では、レース二面性ビューrace_dvから_id脚注1202のレース・ドキュメントを削除します。(これは、レース名がSaudi Arabian GPのドキュメントの1つです。)

対応する行は、基礎となる表raceおよびdriver_race_mapから(各表から1行)削除されます。

ただし、race_dv定義表driverにはNODELETEの注釈が付けられているため、driver表から何も削除されません(ルール5の更新を参照。)二面性ビューrace_dvおよびdriver_dvの整形出力ドキュメントは、レース・ドキュメントの削除の影響を示しています。

SELECT json_serialize(DATA PRETTY) FROM race_dv;
SELECT json_serialize(DATA PRETTY) FROM driver_dv;

DELETE FROM race_dv dv WHERE dv.DATA."_id".numberOnly() = 202;

SELECT json_serialize(DATA PRETTY) FROM race_dv;
SELECT json_serialize(DATA PRETTY) FROM driver_dv;

削除の前後の問合せは、このレース・ドキュメントのみ削除されたことを示しています。ドライバ・ドキュメントは削除されませんでした:

{"_id"      : 202,
 "_metadata" : {"etag" : "7E056A845212BFDE19E0C0D0CD549EA0",
                "asof" : "00000000000C20B1"},
 "name"      : "Saudi Arabian Grand Prix",
 "laps"      : 50,
 "date"      : "2022-03-27T00:00:00",
 "podium"    : {},
 "result"    : []}

例4-7 二面性ビューRACE_DVからのJSONドキュメントの削除 — RESTの使用

この例では、Oracle REST Data Services (ORDS)を使用して例4-6と同じことを実行します。二面性ビューの例を所有するデータベース・ユーザー(スキーマ)は、ここではユーザーJANUSとして表示されます。

curl --request GET \
  --url http://localhost:8080/ords/janus/race_dv/
curl --request GET \
  --url http://localhost:8080/ords/janus/driver_dv/

curl --request DELETE \
  --url http://localhost:8080/ords/janus/race_dv/202

DELETEからのレスポンス:

200 OK
{"rowsDeleted" : 1}

二面性ビューrace_dvおよびdriver_dvのそれぞれに対してGETリクエストを使用すると、削除の前後両方に、このレース・ドキュメントのみ削除されたことが示されています。ドライバ・ドキュメントは削除されませんでした:

{"_id"      : 202,
 "_metadata" : {"etag" : "7E056A845212BFDE19E0C0D0CD549EA0",
                "asof" : "00000000000C20B1"},
 "name"      : "Saudi Arabian Grand Prix",
 "laps"      : 50,
 "date"      : "2022-03-27T00:00:00",
 "podium"    : {},
 "result"    : [],
 "links"     : [ {"rel"  : "self",
                  "href" : "http://localhost:8080/ords/janus/race_dv/202"} ]} ],

関連項目:

『Oracle REST Data Services開発者ガイド』JSONリレーショナル二面性ビューのサポート

4.3 二面性ビューでのドキュメント/データの更新

JSONドキュメントを二面性ビューで直接更新することも、二面性ビューの基礎となる表のデータを更新することもできます。ドキュメントを完全に置き換えてドキュメントを更新することも、一部のフィールドのみを更新することもできます。例は、これらの可能性を示しています。

ノート:

特に明示的に示されていないかぎり、次のようになります。

  • ここに示す例は、決して相互に依存するものではありません。特に、それらの間には暗黙的な順序付けはありません。

  • ここでの二面性ビューを使用する例では、「カーレースの例、二面性ビュー」で定義したビューを使用します。これらのビューは、UNNESTを使用して定義されています(例2-5例2-7および例2-9)。

  • ここでの表を使用する例では、「カーレースの例、表」で定義した表を使用します。

ノート:

通常、更新には更新、挿入、および削除操作が含まれています。このトピックでは、1つ以上の既存のドキュメントまたは基礎となる表を変更する更新操作についてのみ説明します。挿入および削除の操作については、「二面性ビューへのドキュメント/データの挿入」および「二面性ビューからのドキュメント/データの削除」をそれぞれ参照してください。

二面性ビューの更新操作では、完全なドキュメントを更新(置換)したり、既存のオブジェクトの1つ以上のフィールドの値を更新できます。配列値フィールドの更新には、配列要素の挿入または削除を含めることができます。

更新操作では、二面性ビューによって明示的に定義されているオブジェクトのメンバー(フィールド/値ペア)を追加または削除できません。同じ理由で、更新では、ビュー定義が提供するもの以外のオブジェクトを追加または削除できません。

このような更新は、サポートするドキュメントの構造およびタイプを指定するビュー定義の変更を表します。このような変更を実行する必要がある場合は、ビューを再定義する必要があります。CREATE OR REPLACE JSON RELATIONAL DUALITY VIEWを使用すると、実行できます。

一方、データ型JSONの基礎となる列で定義されたJSON値は、デフォルトでは制約なしです。つまり、結果のJSONが整形式であるかぎり、あらゆる変更が可能です。基礎となる表のJSON型の列に対応する値は、その列に適用されるJSONスキーマ(ある場合)によってのみ制約されます。

関連項目:

『Oracle Database JSON開発者ガイド』JSONスキーマに関する項

1つ以上の二面性ビューの基礎となる表の行を更新すると、その表の行のデータに対応する(データから取得される)データを保持するすべての(任意の二面性ビューでサポートされる)ドキュメントが更新されます。(更新されたドキュメントの他のデータは変更されません。)

ノート:

JSONリレーショナル二面性ビューでサポートされるドキュメントまたはその基礎となる表データの更新は、そのデータの内容が変更されていない場合でも、データの一部の行が更新されたとSQLによってレポートされます。これは標準のSQL動作です。更新操作が成功すると、常に、ターゲットとする行が更新されたとレポートされます。これは、更新操作に付随するトリガーまたは行変換演算子が存在する可能性があり、それ自体がデータを変更できるという事実も示しています。

ノート:

一般に、JSON文字列からNVARCHAR2NCLOBおよびNCHAR以外のタイプのSQL文字データを生成した場合、かつそのターゲット・データ型の文字セットがUnicodeベースでない場合、変換では、そのSQL型の文字セットでは表現できない文字に対して非可逆的文字セット変換が行われる可能性があります。

ヒント:

最初にデータベースから読み取ることなくドキュメントを更新しようとすると、欠落したフィールドまたは無効なフィールドによる書込みの損失や実行時エラーなど、いくつかの問題が発生する可能性があります。

更新する場合は、次のステップに従います:

  1. データベースからドキュメントをフェッチします。

  2. ドキュメントのローカル・コピーを変更します。

  3. 更新されたローカル・コピーをデータベースに保存します。

  4. 更新の試行(ステップ3)が、同時変更またはETAGの不一致のために失敗した場合は、ステップ1から3を繰り返します。

「二面性ビューでのオプティミスティックな同時実行性制御の使用」も参照してください。

例4-8 二面性ビューでのJSONドキュメント全体の更新 — SQLの使用

この例では、主キー・フィールド_idの値が201である二面性ビューrace_dvのレース・ドキュメントを置き換えます。SQL操作UPDATEを使用してこれを実行し、ビューの単一のJSON列(DATA)の行を新しい値に設定します。

SQL/JSONファンクションjson_valueおよびOracle SQLファンクションjson_serializeを使用して、更新操作の前後にドキュメントを選択してシリアライズ/整形出力し、変更を表示します。シリアライズの結果は、ここに部分的に表示されます。

新しい置換JSONドキュメントには、レースの結果が含まれ、結果にはレースdatepodium値(上位3位)、および各ドライバのresult値が含まれています。

SELECT json_serialize(DATA PRETTY)
  FROM race_dv WHERE json_value(DATA, '$._id.numberOnly()') = 201;

UPDATE race_dv
  SET DATA = ('{"_id"      : 201,
                "_metadata" : {"etag" : "2E8DC09543DD25DC7D588FB9734D962B"},
                "name"      : "Bahrain Grand Prix",
                "laps"      : 57,
                "date"      : "2022-03-20T00:00:00",
                "podium"    : {"winner"         : {"name" : "Charles Leclerc",
                                                   "time" : "01:37:33.584"},
                               "firstRunnerUp"  : {"name" : "Carlos Sainz Jr",
                                                   "time" : "01:37:39.182"},
                               "secondRunnerUp" : {"name" : "Lewis Hamilton",
                                                   "time" : "01:37:43.259"}},
                "result"    : [ {"driverRaceMapId" : 3,
                                 "position"        : 1,
                                 "driverInfo"      :
                                   {"driverId" : 103,
                                    "name"     : "Charles Leclerc"}},
                                {"driverRaceMapId" : 4,
                                 "position"        : 2,
                                 "driverInfo"      :
                                   {"driverId" : 104,
                                    "name"     : "Carlos Sainz Jr"}},
                                {"driverRaceMapId" : 9,
                                 "position"        : 3,
                                 "driverInfo"      :
                                   {"driverId" : 106,
                                   "name"      : "Lewis Hamilton"}},
                                {"driverRaceMapId" : 10,
                                 "position"        : 4,
                                 "driverInfo"      :
                                   {"driverId" : 105,
                                    "name"     : "George Russell"}} ]}')
    WHERE json_value(DATA, '$._id.numberOnly()') = 201;

COMMIT;

SELECT json_serialize(DATA PRETTY)
  FROM race_dv WHERE json_value(DATA, '$._id.numberOnly()') = 201;

例4-9 二面性ビューでのJSONドキュメント全体の更新 — RESTの使用

この例では、Oracle REST Data Services (ORDS)を使用して例4-8と同じことを実行します。二面性ビューの例を所有するデータベース・ユーザー(スキーマ)は、ここではユーザーJANUSとして表示されます。

curl --request PUT \
  --url http://localhost:8080/ords/janus/race_dv/201 \
  --header 'Content-Type: application/json' \
  --data '{"_id"      : 201,
           "_metadata" : {"etag":"2E8DC09543DD25DC7D588FB9734D962B"},
           "name"      : "Bahrain Grand Prix",
           "laps"      : 57,
           "date"      : "2022-03-20T00:00:00",
           "podium"    : {"winner"         : {"name" : "Charles Leclerc",
                          "time"           : "01:37:33.584"},
                          "firstRunnerUp"  : {"name" : "Carlos Sainz Jr",
                                              "time" : "01:37:39.182"},
                          "secondRunnerUp" : {"name" : "Lewis Hamilton",
                                              "time" : "01:37:43.259"}},
           "result"    : [ {"driverRaceMapId" : 3,
                            "position"        : 1,
                            "driverInfo"      : {"driverId" : 103,
                                                 "name"     : "Charles Leclerc"}},
                           {"driverRaceMapId" : 4,
                            "position"        : 2,
                            "driverInfo"      : {"driverId" : 104,
                                                 "name"     : "Carlos Sainz Jr"}},
                           {"driverRaceMapId" : 9,
                            "position"        : 3,
                            "driverInfo"      : {"driverId" : 106,
                                                 "name"     : "Lewis Hamilton"}},
                           {"driverRaceMapId" : 10,
                            "position"        : 4,
                            "driverInfo"      : {"driverId" : 105,
                                                 "name"     : "George Russell"}} ]}'

レスポンス:

200 OK
{"_id"      : 201,
 "name"      : "Bahrain Grand Prix",
 "laps"      : 57,
 "date"      : "2022-03-20T00:00:00",
 "podium"    : {"winner"         : {"name": "Charles Leclerc",
                                    "time": "01:37:33.584"},
                ...},
 "result"    : [ {"driverRaceMapId" : 3, ...} ],
 ...}

関連項目:

『Oracle REST Data Services開発者ガイド』JSONリレーショナル二面性ビューのサポート

例4-10 二面性ビューでの一部のJSONドキュメントの更新

この例では、フィールドnameLIKEパターンBahr%に一致する二面性ビューrace_dvレース・ドキュメントのフィールドnameの値を置き換えます。これを行うには、SQL操作UPDATEおよびOracle SQLファンクションjson_transformを使用します。新しい置換ドキュメントは、フィールドnameの値を除いて置換されたものと同じです。

ファンクションjson_transformの操作SETは、部分ドキュメント更新の実行に使用されます。

この例では、SQL/JSONファンクションjson_valueおよびOracle SQLファンクションjson_serializeを使用して、更新操作の前後にドキュメントを選択してシリアライズ/整形出力します。シリアライズの結果はここでは一部しか表示されておらず、カーレースの例では全体として、レース名と一致するドキュメントは1つのみです。


SELECT json_serialize(DATA PRETTY)
  FROM race_dv WHERE json_value(DATA, '$.name') LIKE 'Bahr%';

UPDATE race_dv dv
  SET DATA = json_transform(DATA, SET '$.name' = 'Blue Air Bahrain Grand Prix')
    WHERE dv.DATA.name LIKE 'Bahr%';
 
COMMIT;

SELECT json_serialize(DATA PRETTY)
  FROM race_dv WHERE json_value(DATA, '$.name') LIKE 'Bahr%';

既存のフィールドの値を置換すると、データ型JSONの基礎となる表の列に対応する、ビューrace_dvのフィールドpodiumなどのフィールドにも適用されることに注意してください。

ノート:

フィールドetagは部分ドキュメント更新の実行時に入力として渡されないため、このような場合、データベースではETAG値の比較は実行されません。つまり、部分ドキュメント更新にオプティミスティックな同時実行性制御を使用することはできません

例4-11 相互に関連するJSONドキュメントの更新 — SQLの使用

ドライバーCharles LeclercはチームFerrariに属しており、ドライバーGeorge RussellはチームMercedesに属しています。この例では、MercedesとFerrariのチーム・ドキュメントを更新することによって、2つのチーム間でこれらの2つのドライバを交換します。

ドライバ情報はチーム・ドキュメントとドライバ・ドキュメント間で共有されるため、これらの2つのドライバのドライバ・ドキュメントのフィールドteamIDは、チーム・ドキュメントが更新されると自動的に適切に更新されます。

または、許可されている場合は、2つのドライバのドライバ・ドキュメントを更新して、teamIdの値を変更できます。これにより、2つのチーム・ドキュメントが同時に更新されます。ただし、ビューdriver_dvの定義では、表teamでサポートされているフィールドの変更は許可されません。これを実行しようとすると、例4-13に示すようにエラーが発生します。

-- Update (replace) entire team documents for teams Mercedes and Ferrari,
-- to swap drivers Charles Leclerc and George Russell between the teams.
-- That is, redefine each team to include the new set of drivers. 
UPDATE team_dv dv
  SET DATA = ('{"_id"       : 303,
                "_metadata" : {"etag" : "039A7874ACEE6B6709E06E42E4DC6355"},
                "name"      : "Mercedes",
                "points"    : 40,
                "driver"    : [ {"driverId" : 106,
                                 "name"     : "Lewis Hamilton",
                                 "points"   : 15},
                                {"driverId" : 103,
                                 "name"     : "Charles Leclerc",
                                 "points"   : 25} ]}')
    WHERE dv.DATA.name LIKE 'Mercedes%';

UPDATE team_dv dv
  SET DATA = ('{"_id"       : 302,
                "_metadata" : {"etag" : "DA69DD103E8BAE95A0C09811B7EC9628"},
                "name"      : "Ferrari",
                "points"    : 30,
                "driver"    : [ {"driverId" : 105,
                                 "name"     : "George Russell",
                                 "points"   : 12},
                                {"driverId" : 104,
                                 "name"     : "Carlos Sainz Jr",
                                 "points"   : 18} ]}')
    WHERE dv.DATA.name LIKE 'Ferrari%';

COMMIT;

-- Show that the driver documents reflect the change of team
-- membership made by updating the team documents.
SELECT json_serialize(DATA PRETTY) FROM driver_dv dv
  WHERE dv.DATA.name LIKE 'Charles Leclerc%';

SELECT json_serialize(DATA PRETTY) FROM driver_dv dv
  WHERE dv.DATA.name LIKE 'George Russell%';

例4-12 相互に関連するJSONドキュメントの更新 — RESTの使用

この例では、Oracle REST Data Services (ORDS)を使用して例4-11と同じことを実行します。team_dv/303team_dv/302のそれぞれに対してPUT操作を実行することで、MercedesチームとFerrariチームを更新します。二面性ビューの例を所有するデータベース・ユーザー(スキーマ)は、ここではユーザーJANUSとして表示されます。

curl --request PUT \
  --url http://localhost:8080/ords/janus/team_dv/303 \
  --header 'Content-Type: application/json' \
  --data '{"_id"       : 303,
           "_metadata" : {"etag":"438EDE8A9BA06008C4DE9FA67FD856B4"},
           "name"      : "Mercedes",
           "points"    : 40,
           "driver"    : [ {"driverId" : 106,
                            "name"     : "Lewis Hamilton",
                            "points"   : 15},
                           {"driverId" : 103,
                            "name"     : "Charles Leclerc",
                            "points"   : 25} ]}'

GET操作を使用して、チーム・ドキュメントを更新することで行われたチーム・メンバーシップの変更がドライバ・ドキュメントに反映されていることを確認できます。このURLは、次のエンコードされたバージョンです:

  • http://localhost:8080/ords/janus/driver_dv/?q={"name":{"$eq":"Charles Leclerc"}}

  • http://localhost:8080/ords/janus/driver_dv/?q={"name":{"$eq":"George Russell"}}

curl --request GET \
  --url 'http://localhost:8080/ords/janus/driver_dv/?q=%7B%22name%22%3A%7B%22%24eq%22%3A%22Charles%20Leclerc%22%7D%7D'

レスポンス:

200 OK
{"items" : [ {"_id"    : 103,
              "name"   : "Charles Leclerc",
              "points" : 25,
              "teamId" : 303,
              "team"   : "Mercedes",...} ],
 ...)
curl --request GET \
  --url 'http://localhost:8080/ords/janus/driver_dv/?q=%7B%22name%22%3A%7B%22%24eq%22%3A%22George%20Russell%22%7D%7D'

レスポンス:

200 OK
{"items" : [ {"_id"    : 105,
              "name"   : "George Russell",
              "points" : 12,
              "teamId" : 302,
              "team"   : "Ferrari",...} ],
 ...)

関連項目:

『Oracle REST Data Services開発者ガイド』JSONリレーショナル二面性ビューのサポート

例4-13 エラーが発生する、許可されていない更新操作の試行 — SQLの使用

この例では、二面性ビューdisで更新が許可され、エラーが発生するフィールドの更新を試みます。(許可されていない挿入および削除操作を試みたときに、同じような動作が発生します。)

この例では、ドライバCharles LeclercのチームをチームFerrariに変更してビューdriver_dvを使用します。これは、そのビューのこの部分の定義に違反します。これにより、基礎となる表がteamであるすべてのフィールドへの更新は許可されません。

(SELECT JSON {'_id' : t.team_id,
              'team'   : t.name WITH NOCHECK}
   FROM team t WITH NOINSERT NOUPDATE NODELETE
UPDATE driver_dv dv
  SET DATA = ('{"_id"       : 103,
                "_metadata" : {"etag" : "E3ACA7412C1D8F95D052CD7D6A3E90C9"},
                "name"      : "Charles Leclerc",
                "points"    : 25,
                "teamId"    : 303,
                "team"      : "Ferrari",
                "race"      : [ {"driverRaceMapId" : 3,
                                 "raceId"          : 201,
                                 "name"            : "Bahrain Grand Prix",
                                 "finalPosition"   : 1} ]}')
  WHERE dv.DATA._id = 103;
UPDATE driver_dv dv
*
ERROR at line 1:
ORA-40940: Cannot update field 'team' corresponding to column 'NAME' of table
'TEAM' in JSON Relational Duality View 'DRIVER_DV': Missing UPDATE annotation
or NOUPDATE annotation specified.

エラー・メッセージは、表TEAMの列NAMEを参照することに注意してください。

例4-14 エラーが発生する、許可されていない更新操作の試行 — RESTの使用

この例では、Oracle REST Data Services (ORDS)を使用して例4-13と同じことを実行します。二面性ビューの例を所有するデータベース・ユーザー(スキーマ)は、ここではユーザーJANUSとして表示されます。

curl --request PUT \
  --url http://localhost:8080/ords/janus/driver_dv/103 \
  --header 'Accept: application/json' \
  --header 'Content-Type: application/json' \
  --data '{"_id"       : 103,
           "_metadata" : {"etag":"F7D1270E63DDB44D81DA5C42B1516A00"},
           "name"      : "Charles Leclerc",
           "points"    : 25,
           "teamId"    : 303,
           "team"      : "Ferrari",
           "race"      : [ {"driverRaceMapId" : 3,
                            "raceId"          : 201,
                            "name"            : "Bahrain Grand Prix",
                            "finalPosition"   : 1} ]}'

レスポンス:

HTTP/1.1 412 Precondition Failed
{
       "code": "PredconditionFailed",
    "message": "Predcondition Failed",
       "type": "tag:oracle.com,2020:error/PredconditionFailed",
   "instance": "tag:oracle.com,2020:ecid/LVm-2DOIAFUkHzscNzznRg"
}

関連項目:

『Oracle REST Data Services開発者ガイド』JSONリレーショナル二面性ビューのサポート

例4-15 トリガーを使用したカーレースのポジションに基づいたドライバ・ポイントの更新

カーレース・アプリケーション・ロジックでは、レースの各ドライバについて累積されたpointsを、そのレースのドライバのpositionずつ動的に増分します。

アプリケーション・コードを使用してこのロジックを実装する代替の方法として、二面性ビューrace_dvまたはマッピング表driver_race_mapBEFORE INSERTトリガーなどを使用して、アプリケーション・データの定義の一部として定義します。この例では、後者を実行します。

driver_race_mapの各行は、表へのデータ挿入の直前に、トリガーが起動すると処理されます。行が処理されると、疑似レコードNEW (:NEWと呼ばれます)に行の新しい値が含まれています。たとえば、:NEW.positionは、特定のレースにおけるドライバのpositionの新しい値です。

CREATE OR REPLACE TRIGGER driver_race_map_trigger
  BEFORE INSERT ON driver_race_map
  FOR EACH ROW
  DECLARE
    v_points  INTEGER;
    v_team_id INTEGER;
BEGIN
  SELECT team_id INTO v_team_id FROM driver
    WHERE driver_id = :NEW.driver_id;
  IF    :NEW.position = 1 THEN
    v_points := 25;
  ELSIF :NEW.position = 2 THEN
    v_points := 18;
  ELSIF :NEW.position = 3 THEN
    v_points := 15;
  ELSIF :NEW.position = 4 THEN
    v_points := 12;
  ELSIF :NEW.position = 5 THEN
    v_points := 10;
  ELSIF :NEW.position = 6 THEN
    v_points := 8;
  ELSIF :NEW.position = 7 THEN
    v_points := 6;
  ELSIF :NEW.position = 8 THEN
    v_points := 4;
  ELSIF :NEW.position = 9 THEN
    v_points := 2;
  ELSIF :NEW.position = 10 THEN
    v_points := 1;
  ELSE
    v_points := 0;
  END IF;

  UPDATE driver SET points = points + v_points
    WHERE driver_id = :NEW.driver_id;
  UPDATE team   SET points = points + v_points
    WHERE team_id = v_team_id;
END;
/

関連項目:

4.3.1 二面性ビューを使用する場合のトリガーに関する考慮事項

二面性ビューの基礎となる表のデータを変更するトリガーは、問題になることがあります。問題を回避するためのガイドラインが示されています。原則として、トリガー本体では、二面性ビューのETAG値に関与する主キー列および列の値の変更を回避します。

二面性ビューの基礎となる表に作成するトリガーについて、Oracleでは次のことをお薦めします。これを実行しない場合、トリガーの作成時にエラーは発生しませんが、トリガーを起動するとエラーが発生する可能性があります。考慮する必要がある問題のあるケースが2つあります。(ここで<DML>の起動は、トリガーが起動されるDML文を指します。)

  • ケース1: トリガー本体が、相関名(pseudorecord) :NEWを使用して主キー列の値を変更します。たとえば、トリガー本体には:NEW.zipcode = 94065が含まれます。

    <DML>の起動によって列値がNULLに設定されないかぎり、これを実行しないでください主キー値は変更しないでください(NULL値以外)。

  • ケース2 (まれ): トリガー本体が、<DML>の起動によって更新される表と異なる表の列の値を変更し、その列が二面性ビュー(任意)のETAG値に関与します。

    次に例を示します。

    • <DML>の起動は、UPDATE emp SET zipcode = '94065' WHERE emp_id = '40295';です。

    • トリガー本体にはDML文UPDATE dept SET budget = 10000 WHERE dept_id = '592';が含まれます。

    • deptは一部の二面性ビューの基礎となり、列dept.budgetはその二面性ビューのETAG値に関与します。

    これは、このような列を更新すると、その列に対応するフィールドを含むドキュメントのETAG値が変更されるためです。これによって、同時実行性制御が妨げられます。この制御では、このような値を使用して同時変更を防止します。トリガーからのETAG変更は、別の同時セッションからのETAG変更と区別できません。

関連項目:

4.4 二面性ビューでのオプティミスティックな同時実行性制御の使用

他のセッションで同時に変更されていない場合にのみ、二面性ビューでのオプティミスティック/ロックフリーの同時実行性制御の使用、JSONドキュメントの記述、または更新のコミットを実行できます。

ドキュメント・レベルでのオプティミスティック同時実行性制御では、フィールド_metadataの値であるオブジェクト内のフィールドetagに埋込みETAG値が使用されます。

ノート:

特に明示的に示されていないかぎり、次のようになります。

  • ここに示す例は、決して相互に依存するものではありません。特に、それらの間には暗黙的な順序付けはありません。

  • ここでの二面性ビューを使用する例では、「カーレースの例、二面性ビュー」で定義したビューを使用します。これらのビューは、UNNESTを使用して定義されています(例2-5例2-7および例2-9)。

  • ここでの表を使用する例では、「カーレースの例、表」で定義した表を使用します。

ドキュメント中心のアプリケーションでは、オプティミスティックな同時実行性制御の使用により、更新が失われることを防止する場合があります。つまり、複数のデータベース・セッションがある場合はそれらで共通使用されているデータが変更されることで互いに妨げになるという問題への対処です。

ドキュメントのオプティミスティック同時実行性は、変更したドキュメントを保持(書込み)しようとすると、現在保持されているドキュメント・コンテンツが、必要な変更の適用先(ローカル)のコンテンツに対してチェックされるという考えに基づいています。つまり、コンテンツの現在の永続状態/バージョンは、最後の読取りとして永続コンテンツのアプリケーションのレコードと比較されます。

2つが異なる場合は、最後の読取りコンテンツが失効していることを意味します。次に、アプリケーションは最後に永続化されたコンテンツを取得し、それを変更の新しい開始点として使用し、新しく変更されたドキュメントを書き込もうとします。書込みは、アプリケーションによって最後に読み取られたコンテンツが、現在永続化されているコンテンツと同じ場合にのみ成功します。

この方法では、一般的に高いレベルの同時実行性を実現し、対話型アプリケーション(人間の待機時間なし)、切断されたモバイル・アプリケーション(古いドキュメントを使用した書込み試行は取り消されます)およびドキュメント・キャッシュ(古いキャッシュを使用した書込み試行は取り消されます)の利点がもたらされます。

同じデータに対する同時データベース操作の可能性が低いほど、オプティミスティックな同時実行性の効率が向上します。同じデータに多数の競合がある場合は、異なる同時実行性制御技術を使用する必要がある場合があります。

簡潔に述べると、オプティミスティックな同時実行性を実装するためにアプリケーション・コードで使用する手法は次のようになります。

  1. 変更するデータを読み取ります。その読取りから、そのデータの未変更の状態(その永続的な、最終コミット後の状態)のローカル表現を記録します。

  2. そのデータのローカル・コピーを変更します。

  3. 現在の永続状態が、記録されている状態と同じである場合のみ、変更されたデータを書き込みますます(永続化します)。

つまり、変更を永続化するまで、そのデータを未変更の状態のままにします。最後の読取り以降にそのデータが変更された場合は、再度、ステップ1から3を繰り返します

二重ビューでサポートされているJSONドキュメントの場合は、トップレベル・フィールド_metadataの値であるオブジェクト内のドキュメントのetagフィールドを確認することでこれを実行します。

フィールドetagのETAG値は、オプティミスティックな同時実行性制御のために確認するドキュメント・コンテンツを記録します。

デフォルトでは、ドキュメント・コンテンツ自体(ペイロード)のすべてが含まれます。フィールド_metadata (フィールドetagを含む値)はペイロードの一部ではなく、ETAG計算から常に除外されます。

フィールドmetadataに加えて、ETAG計算から選択したペイロード・フィールドを除外できます。これは、同時実行性制御にとって変更が重要でないデータです。そのデータが最後にアプリケーションによって読み取られてから変更されても、更新操作は妨げられません。(リレーショナルな表現では、これは、ロックされている行内の特定の列をロックしないことに似ています。)

二面性ビュー定義のNOCHECK注釈で管理される列に対応するドキュメント・コンテンツは、そのビューでサポートされるドキュメントのETAG値の計算に関与しません。その他の内容はすべて計算に関与します。ETAG値は、CHECKとマークされた(暗黙的または明示的に)基礎となる表の列のみに基づきます。「注釈(NO)CHECKによるETAG計算のフィールドの包含/除外」を参照してください。

次に、フィールド_metadataとそのetagフィールド、ドキュメント・ペイロードが表示されたレース・ドキュメントの例を示します。ドキュメント・メタデータの詳細は、「カーレースの例、二面性ビュー」を参照してください。

{"_metadata" : {"etag" : "E43B9872FC26C6BB74922C74F7EF73DC",
                         "asof" : "00000000000C20BA"},
 "_id" : 201, "name" : "Bahrain Grand Prix", ...}

したがって、Oracle ETAGの同時実行性制御は値ベースまたはコンテンツベースです。競合する更新は、実際に、データの内容そのものを調べることで検出されます。

  • 読取り/取得操作によって自動的にフィールドetagが更新されます。これにより、CHECK可能なドキュメント・コンテンツの現在の永続状態がHTTP ETAGハッシュ値として記録されます。

  • ドキュメントのetag値が現在の永続(最終コミット後)データのその値と一致しない場合は、書込み/入力操作で自動的にドキュメントが拒否されます。つまり、そのデータが最後の読取り以降に変更されていた場合は、Oracle Databaseでエラーが発生します。そのため、アプリケーションで必要なのは、書込みエラーがあるかチェックして、ステップ1から3を繰り返すかどうかを判断することのみです。

図4-1に、そのプロセスを示します。

図4-1 オプティミスティック同時実行性制御プロセス

図4-1の説明が続きます
「図4-1 オプティミスティック同時実行性制御プロセス」の説明

実際の永続データ/内容に対する同時実行性の制御をベースにすると、ドキュメント・バージョン番号やタイムスタンプなどのロックまたはサロゲート情報を使用するよりも強力になり、信頼性が高くなります。

Oracle ETAGは値ベースであるため、様々なドキュメントのデータに対する更新が自動的に同期されます。また、ドキュメントの更新と基礎となる表への直接更新の間で同時実行性を自動的に確保します。ドキュメントAPIとSQLアプリケーションは、同じデータを同時に更新できます。

ステップ2 (ローカルで変更)とステップ3 (書込み)は実際に結合しています。更新操作の変更済ドキュメントを指定する場合、変更済ドキュメントのetagフィールドの値として読取り操作によって返されるETAG値を含めます。

データベース内のドキュメントの現在のコンテンツがetagフィールド値と異なる場合、試行された更新操作は失敗します。これは、最後の読取り以降にデータベース内のドキュメントが変更されたことを意味するためです。その操作に失敗した場合は、もう一度試してください。再度読み取って最新のETAG値を取得してから、フィールドetag内のETAG値を使用して更新を再試行します。

たとえば、次のように、Bahrain Grand Prix (_id=201)という名前のレースについて、2つの異なるデータベース・セッション(S1およびS2)が同じドキュメントを同時に更新するとします。

  • セッションS1は、例4-8または例4-9の更新を実行し、レース結果(フィールドlapsdatepodiumおよびresults)を書き込みます。

  • セッションS2は、例4-10の更新を実行し、レースの名前をBlue Air Bahrain Grand Prixに変更します。

各セッションは、更新操作にオプティミスティックな同時実行性を使用し、更新操作(ステップ2)が成功するまで次の2つのステップを繰り返し、変更をCOMMITすることにより、変更内容が最新のドキュメント・コンテンツであることを保証します。

  1. ドキュメントを読み取ります(選択します)。取得されたドキュメントのフィールドetagの値は、データベース内のドキュメントの現在の(CHECK可能な)コンテンツをエンコードします。

    例4-16および例4-17に、これを示します。

  2. 変更した内容を使用し、ステップ1で取得したフィールドetagを使用して、ドキュメントの更新を試みます。

    セッションS1の場合、更新操作は例4-8または例4-9です。セッションS2の場合、例4-10です。

ETAG値がドキュメントの現在の永続(最後にコミットされた)状態と一致しないため、更新操作に失敗すると、エラーが発生します。

このようなSQLからのエラーの例を次に示します:

UPDATE race_dv
*
ERROR at line 1:
ORA-42699: Cannot update JSON Relational Duality View 'RACE_DV': The ETAG of
document with ID 'FB03C2030200' in the database did not match the ETAG passed
in.

このようなRESTからのエラーの例を次に示します。If-Matchヘッダーに指定されたETAG値が、レース・ドキュメントのものと同じではありませんでした。

Response: 412 Precondition Failed

{"code"     : "PredconditionFailed",
 "message"  : "Predcondition Failed",
 "type"     : "tag:oracle.com,2020:error/PredconditionFailed",
 "instance" : "tag:oracle.com,2020:ecid/y2TAT5WW9pLZDNu1icwHKA"}

同じ基礎となる表データに対応する内容を持つ2つのドキュメントに対して複数の操作が同時に処理され、その内容がそのドキュメントのETAG計算に関与する場合、その操作の1つは成功する可能性があります。このため、同じ基礎となるデータを同時に変更しようとすると、必ずエラーが発生します。エラー・メッセージは、競合する操作が検出されたことを示し、可能な場合は、競合が検出されたドキュメント・フィールドを示します。

JSONリレーショナル二面性とは、ETAGをデータとともに使用して、SQLを使用したロックフリーな行の更新を実行することもできるということです。これを実行するには、ファンクションSYS_ROW_ETAGを使用して、表行の特定の列セットの現在の状態をETAGハッシュ値として取得します。

ファンクションSYS_ROW_ETAGは、行の指定された列の値のみを使用して行のETAG値を計算します。他のセッションが同時に更新しないようにする、すべての列の名前を渡します。これには、現在のセッションが更新する予定の列も含まれていますが、更新操作がアプリケーションに対して論理的に依存する値を含む列も含まれています。(引数として列をSYS_ROW_ETAGに渡す順序は関係ありません。)

この例では、S3およびS4という2つの異なるデータベース・セッションが、_idが201のレースに対して同じrace表データを同時に更新することを想定しています:

  • セッションS3は、列podiumを更新して、レースのpodium値を公開しようとします。

  • セッションS4は、列nameを更新し、レースの名前をBlue Air Bahrain Grand Prixに変更しようとします。

セッションは、オプティミスティックな同時実行性制御を使用して、干渉なしで特定の行を更新できます。そのため、(1)更新する行の現在のETAG値を取得し、(2)更新を試行し、そのETAG値を渡します。操作が失敗した場合、これらのステップを繰り返します。更新が成功するまで、新しいETAG値で再試行します(この時点で更新がコミットされます)。

例4-16 フィールドetagからのレース・ドキュメントの現在のETAG値の取得 — SQLの使用

この例では、_id 201のレースのドキュメントを選択します。ネイティブ・バイナリのJSON型データをテキストにシリアライズし、これを整形出力します。上位レベルのフィールド_metadataの値であるオブジェクトのフィールドetagのETAG値は、ドキュメントの現在のコンテンツをエンコードします。

そのetagフィールドと、更新操作に指定する変更済ドキュメントの値を使用します。

SELECT json_serialize(DATA PRETTY)
  FROM race_dv WHERE json_value(DATA, '$._id,numberOnly()') = 201;
JSON_SERIALIZE(DATAPRETTY)
--------------------------
{ 
  "_metadata" :
  { "etag" : "E43B9872FC26C6BB74922C74F7EF73DC",
    "asof" : "00000000000C20BA"
  },
  "_id" : 201,
  "name" : "Bahrain Grand Prix",
  "laps" : 57,
  "date" : "2022-03-20T00:00:00",
  "podium" :
  {
  },
  "result" :
  [
  ]
}
1 row selected.

例4-17 フィールドetagからのレース・ドキュメントの現在のETAG値の取得 — RESTの使用

この例では、Oracle REST Data Services (ORDS)を使用して例4-16と同じことを実行します。二面性ビューの例を所有するデータベース・ユーザー(スキーマ)は、ここではユーザーJANUSとして表示されます。

curl --request GET \
  --url http://localhost:8080/ords/janus/race_dv/201

Response:

{"_id"    : 201,
 "name"      : "Bahrain Grand Prix",
 "laps"      : 57,
 "date"      : "2022-03-20T00:00:00",
 ...
 "_metadata" : {"etag": "20F7D9F0C69AC5F959DCA819F9116848",
                "asof": "0000000000000000"},
 "links"     : [ {"rel": "self",
                  "href": "http://localhost:8080/ords/janus/race_dv/201"},
                 {"rel": "describedby",
                  "href": "http://localhost:8080/ords/janus/metadata-catalog/race_dv/item"},
                 {"rel": "collection",
                  "href": "http://localhost:8080/ords/janus/race_dv/"} ]}

例4-18 ファンクションSYS_ROW_ETAGを使用した表の同時更新の最適な制御

2つのデータベース・セッション(S3およびS4)は、表raceの同じ行(列race_idの値が201である行)を更新しようとします。

簡素化のために、ここではセッションS3に対してのみオプティミスティックな同時実行性制御を示します。セッションS4では、列nameの更新操作のみを示しています。

このシナリオでは、次のようになります。

  1. セッションS3は、列namerace_dateおよびpodiumをファンクションSYS_ROW_ETAGに渡し、(なんらかの理由で)列podiumの更新中に、S3で、他のセッションがnamerace_dateおよびpodiumのいずれの列も変更しないようにすることを想定しています。

  2. セッションS4は、列nameを更新し、その更新をコミットします。

  3. S3は、取得したETAG値を渡して、列podiumを更新しようとします。S4が同じ行の更新のため、この試行は失敗します。

  4. S3は、新しいETAG値を使用して行の更新を再試行します。この試行は成功し、S3が変更をコミットします。

-- S3 gets ETAG based on columns name, race_date, and podium.
SELECT SYS_ROW_ETAG(name, race_date, podium)
  FROM race WHERE race_id = 201;
SYS_ROW_ETAG(NAME,RACE_DATE,PODIUM)
-----------------------------------
201FC3BA2EA5E94AA7D44D958873039D
-- S4 successfully updates column name of the same row.
UPDATE race SET name = 'Blue Air Bahrain Grand Prix'
  WHERE race_id = 201;
1 row updated.
-- S3 unsuccessfully tries to update column podium.
--    It passes the ETAG value, to ensure it's OK to update.
UPDATE race SET podium = 
                '{"winner"         : {"name" : "Charles Leclerc",
                                      "time" : "01:37:33.584"},
                  "firstRunnerUp"  : {"name" : "Carlos Sainz Jr",
                                      "time" : "01:37:39.182"},
                  "secondRunnerUp" : {"name" : "Lewis Hamilton",
                                      "time" : "01:37:43.259"}}'
  WHERE race_id = 201
    AND SYS_ROW_ETAG(name, race_date, podium) =
          '201FC3BA2EA5E94AA7D44D958873039D';
0 rows updated.
-- S4 commits its update.
COMMIT;
Commit complete.
-- S3 gets a fresh ETAG value, and then tries again to update.
SELECT SYS_ROW_ETAG(name, race_date, podium)
  FROM race WHERE race_id = 201;
SYS_ROW_ETAG(NAME,RACE_DATE,PODIUM)
-----------------------------------
E847D5225C7F7024A25A0B53A275642A
UPDATE race SET podium = 
                '{"winner"         : {"name" : "Charles Leclerc",
                                      "time" : "01:37:33.584"},
                  "firstRunnerUp"  : {"name" : "Carlos Sainz Jr",
                                      "time" : "01:37:39.182"},
                  "secondRunnerUp" : {"name" : "Lewis Hamilton",
                                      "time" : "01:37:43.259"}}'
  WHERE race_id = 201
    AND SYS_ROW_ETAG(name, race_date, podium) =
          'E847D5225C7F7024A25A0B53A275642A';
1 row updated.
COMMIT;
Commit complete.

-- The data now reflects S4's name update and S3's podium update.
SELECT name, race_date, podium FROM race WHERE race_id = 201;
NAME   RACE_DATE   PODIUM
-------------------------
Blue Air Bahrain Grand Prix
20-MAR-22
{"winner":{"name":"Charles Leclerc","time":"01:37:33.584"},"firstRunnerUp":{"nam
e":"Carlos Sainz Jr","time":"01:37:39.182"},"secondRunnerUp":{"name":"Lewis Hami
lton","time":"01:37:43.259"}}

1 row selected.

関連項目:

『Oracle REST Data Services開発者ガイド』JSONリレーショナル二面性ビューのサポート

4.4.1 二面性ビューのトランザクションの使用

JSONドキュメントで連続する複数の更新(DML)操作に対するオプティミスティックな同時実行性制御を実現するために、二面性ビューに固有の特別な種類のトランザクションを使用できます。一連の更新をコミットするのは、他のセッションが同じドキュメントを同時に変更していない場合のみです。

「二面性ビューでのオプティミスティックな同時実行性制御の使用」では、ドキュメントのETAG値を使用して、単一の更新(DML)操作に対する同時実行性を最適に制御する方法について説明します。

ただし、複数の更新をユニットとしてまとめて実行する場合、更新済ドキュメントの未変更部分を更新間で、別のセッションで変更しない、つまりコミットする前に、変更しないようにするにはどうすればよいでしょうか。

これを実行する1つの方法として、複数の更新操作の間、1つ以上の二面性ビューで1つ以上のドキュメントをロックします。これを実行するには、ビューからJSON型の列DATAの対応する行をSELECTしてFOR UPDATEを実行します。例4-19に、これを示します。ただし、これを実行すると、基礎となる表のそれぞれがロックされ、負荷がかかる場合があります。

かわりに、二面性ビュー固有の特別な種類のトランザクションを使用して、二面性ビュー・ドキュメントに対して複数の更新操作を最適に実行できます。実際は、ドキュメント(ビューのDATA列の行)が完全にロックされているが、ロックされていないように見えます。ロックは、変更される基礎となる表の行に対してのみ実行され、変更されていない行はトランザクション全体でロックされていないままになります。変更は、ドキュメントが同時に変更されていない場合にのみコミットされます。

もう1つの同時セッションで更新間でドキュメントを変更できますが、トランザクションがコミットされる前にそれが発生した場合はコミット操作が失敗し、その場合は再試行します。

二面性ビューのトランザクションでは、読取りが繰り返し行われます。トランザクション実行中のすべての読取りは、トランザクションの開始時に取得されるデータのスナップショットに対して実行されます。

トランザクション内で、更新操作の前に、更新する予定の各ドキュメントが、データベース内の現在保持されている値に関して最新であることを確認します。この検証は、ドキュメントの登録と呼ばれます。ドキュメントを登録すると、ドキュメントを読み取って取得したETAG値が最新であることが検証されます。この検証に失敗した場合は、トランザクションをロールバックしてやりなおします。

二面性ビューに対して複数操作トランザクションを実行するには、パッケージDBMS_JSON_DUALITYから次のプロシージャを使用してPL/SQLコードを使用します:

  • begin_transaction - トランザクションを開始します。これにより、データベースの状態の"スナップショット"が効率的に取得されます。トランザクションのすべての更新操作は、このスナップショットに基づきます。

  • register - 最後の読取りとしてドキュメントのETAG値が、トランザクションの開始時にデータベース内のドキュメントのETAG値と一致することを確認します。そうでない場合は、エラーが発生します。つまり、ドキュメントの更新時に使用するETAG値がトランザクションの開始時点で正しいことを確認してください。

    最後にドキュメントを読み取り、そのETAG値をトランザクションの開始前に取得した場合、その値はトランザクションに対して必ずしも有効ではありません。コミット操作は、トランザクションの開始前に発生した可能性のある変更をチェックできません。トランザクションの開始前にドキュメントを最後に読み取った場合は、registerをコールして、ドキュメントに使用するETAG値が最初から有効であることを確認します。

    プロシージャregisterは、オブジェクト識別子(OID)を使用してチェックするドキュメントを識別します。これは、二面性ビューの非表示列OBJECT_RESIDを問い合せることで取得できます。ドキュメントを読み取ってETAG値を取得するかわりに、二面性ビューの非表示列OBJECT_ETAGを問い合せることができます。

  • commit_transaction - 複数の更新トランザクションをコミットします。ETAG値を比較して、更新用に指定されたドキュメントをデータベースの現在の状態に対して検証します。更新のために発行されたドキュメントのETAGが、トランザクション中に同時セッションによって変更された場合は、エラーが発生します。

プロシージャは、begin_transactionregistercommit_transactionの順にコールします。begin_transactionをコールした直後にregisterをコールします。

全体的なアプローチは、1つの更新操作で使用するのと同じですが、複数の操作にわたって拡張されます。データベース内のドキュメントを最適に変更し、一部の同時操作が妨げられた場合は、最初からやりなおして新しいトランザクションを再試行します。

  1. トランザクション中にいずれかが失敗した場合(エラーが発生した場合)、これをロールバック(ROLLBACK)し、新しいトランザクションを開始してbegin_transactionを再度コールします。

    特に、ドキュメント登録が失敗した場合やトランザクションのコミットに失敗した場合は、新しいトランザクションでやりなおす必要があります。

  2. 新しいトランザクションの開始時に、ドキュメントを再度読み、トランザクションの開始時のデータベース状態のETAG値を取得して、registerを再度コールします。

エラーがなくなるまでステップ1と2を繰り返します。

例4-19 更新のための二面性ビュー・ドキュメントのロック

この例では、二面性ビューteam_dvの生成されたJSON型のDATA列のMercedesおよびFerrariチーム行を、現在のセッションの次のCOMMITまでロックします。

FOR UPDATE句は、列DATAの行全体をロックします。これは、チーム・ドキュメント全体をロックすることを意味します。これは、同様に、基礎となる各表の関連行をロックすることを意味します。

SELECT DATA FROM team_dv dv
  WHERE dv.DATA.name LIKE 'Mercedes%'
  FOR UPDATE;

SELECT DATA FROM team_dv dv
  WHERE dv.DATA.name LIKE 'Ferrari%'
  FOR UPDATE;

関連項目:

例4-20 二面性ビューのトランザクションを使用した2つのドキュメントの適切な同時更新

この例では、二面性ビューのトランザクションでのオプティミスティックな同時実行性を使用して、チームMercedesおよびFerrariの二面性ビューteam_dvのドキュメントを更新します。ここでは、2チーム間でドライバのCharles LeclercとGeorge Russellを入れ替えます。トランザクション後、チーム・ドキュメント(二面性ビューteam_dvでサポート)とドライバ・ドキュメント(二面性ビューdriver_dvでサポート)の両方がドライバの入替えを反映します。

ドキュメントを読み取って、ドキュメント識別子(非表示の列OBJECT_RESID)および現在のETAG値を取得します。ETAG値は、取得したドキュメントでメタデータ・フィールドETAGの値としてここで取得されますが、かわりに非表示列OBJECT_ETAGを選択することも可能です。

SELECT OBJECT_RESID, DATA FROM team_dv dv
  WHERE dv.DATA.name LIKE 'Mercedes%';
OBJECT_RESID
------------
DATA
----
FB03C2040400
{"_id" : 303,
 "_metadata":
  {"etag" : "039A7874ACEE6B6709E06E42E4DC6355",
   "asof" : "00000000001BE239"},
 "name" : "Mercedes",
 ...}
SELECT OBJECT_RESID, DATA FROM team_dv dv
  WHERE dv.DATA.name LIKE 'Ferrari%';
OBJECT_RESID
------------
DATA
----
FB03C2040300
{"_id" : 303,
 "_metadata":
  {"etag" : "C5DD30F04DA1A6A390BFAB12B7D4F700",
   "asof" : "00000000001BE239"},
 "name" : "Ferrari",
 ...}

複数の更新トランザクションを開始してから、更新する各ドキュメントを登録し、前回読み取ってから変更されていないことを確認します。前述で読み取ったドキュメントIDおよびETAGの値は、プロシージャregisterに渡されます。

ETAGが最新でない場合、他のセッションで読取りとトランザクション開始の間にドキュメントが更新されたため、ROLLBACKが必要になり、その後begin_transactionでやりなおします(ここには示されていません)。

BEGIN
  DBMS_JSON_DUALITY.begin_transaction();
  DBMS_JSON_DUALITY.register('team_dv',
                             hextoraw('FB03C2040400'),
                             hextoraw('039A7874ACEE6B6709E06E42E4DC6355'));
  DBMS_JSON_DUALITY.register('team_dv',
                             hextoraw('FB03C2040300'),
                             hextoraw('C5DD30F04DA1A6A390BFAB12B7D4F700'));

更新(DML)操作を実行します: ドライバを入れ替えたドキュメントに元のドキュメントを置き換えます。

  UPDATE team_dv dv
    SET DATA = ('{"_id" : 303,
                  "name"   : "Mercedes",
                  "points" : 40,
                  "driver" : [ {"driverId" : 106,
                                "name"     : "Lewis Hamilton",
                                "points"   : 15},
                               {"driverId" : 103,
                                "name"     : "Charles Leclerc",
                                "points"   : 25} ]}')
      WHERE dv.DATA.name LIKE 'Mercedes%';

  UPDATE team_dv dv
    SET DATA = ('{"_id" : 302,
                  "name"   : "Ferrari",
                  "points" : 30,
                  "driver" : [ {"driverId" : 105,
                                "name"     : "George Russell",
                                "points"   : 12},
                               {"driverId" : 104,
                                "name"     : "Carlos Sainz Jr",
                                "points"   : 18} ]}')
      WHERE dv.DATA.name LIKE 'Ferrari%';

トランザクションをコミットします。

  DBMS_JSON_DUALITY.commit_transaction();
END;

4.5 JSONドキュメントのシステム変更番号(SCN)の使用

システム変更番号(SCN)は、内部の論理的なデータベース・タイムスタンプです。メタデータ・フィールドasofは、データベースからドキュメントが取得された時点のSCNを記録します。SCNを使用すると、他のデータの読取り時の一貫性を確保できます。

SCNは、トランザクションのACID (原子性、一貫性、分離および永続性)プロパティに準拠する必要があるデータベース内で発生するイベントを順序付けます。

例4-21 ドキュメントのフェッチ時に記録されたSCNの取得

この例では、_id値201で識別されるレース・ドキュメントのシリアライズした表現であるレースの二面性ビューrace_dvからフェッチします。脚注2 SCNはフィールド_metadataの値であり、フィールドasofの値であるオブジェクト内にあります。ドキュメントがフェッチされた時点を記録します。

SELECT json_serialize(DATA PRETTY) FROM race_dv rdv
  WHERE rdv.DATA."_id" = 201;

結果:

JSON_SERIALIZE(DATAPRETTY)
--------------------------
{"_id"       : 201,
 "_metadata" :
  {
    "etag" : "F6906A8F7A131C127FAEF32CA43AF97A",
    "asof" : "00000000000C4175"
  },
 "name"      : "Blue Air Bahrain Grand Prix",
 "laps"      : 57,
 "date"      : "2022-03-20T00:00:00",
 "podium"    : {...},
 "result"    : [ {...} ]
}

1 row selected.

例4-22 別のレース・ドキュメントが取得された時点でのレース・ドキュメントの取得

この例では、レース・ドキュメント201のSCNに対応する状態にあるraceIdの値が203で識別されるレース・ドキュメントをフェッチします(例4-21を参照)。

SELECT json_serialize(DATA PRETTY) FROM race_dv
  AS OF SCN to_number('00000000000C4175', 'XXXXXXXXXXXXXXXX')
  WHERE json_value(DATA, '$._id') = 203;

結果:

JSON_SERIALIZE(DATAPRETTY)
--------------------------
{"_id"       : 203,
 "_metadata" :
  {
    "etag" : "EA6E1194C012970CA07116EE1EF167E8",
    "asof" : "00000000000C4175"
  },
  
 "name"      : "Australian Grand Prix",
 "laps"      : 58,
 "date"      : "2022-04-09T00:00:00",
 "podium"    : {...},
 "result"    : [ {...} ]
}

1 row selected.

関連項目:

4.6 二面性ビューのドキュメントに対する操作の最適化

二面性ビューでサポートされているドキュメントに対する操作(具体的には、問合せ)は、自動的に、基になる表データに対する操作としてリライトされます。この最適化には、索引の活用が含まれています。基になるデータの型はすべて認識されているため、暗黙的な実行時型変換は、通常は回避できます。

二面性ビューを問い合せる(つまり、それでサポートされているJSONドキュメントを問い合せる)ことは、JSONデータ型のDATAという名前の単一列がある表またはビューを問い合せることに似ています。(二面性ビューの非表示列OBJECT_ETAGおよびOBJECT_RESIDを問い合せることもできます。「カーレースの例、二面性ビュー」を参照してください。)

フィルタ述語(SQL/JSON条件json_existsを使用)またはSELECTリスト(SQL/JSONファンクションjson_valueを使用)でJSONドキュメントの値を使用する問合せの場合、基礎となるリレーショナル・データからの中間JSONオブジェクト(JSON型の列DATAの場合)の作成は負荷がかかり、不要です。可能な場合は、基礎となる列に格納されているデータに直接アクセスするために、このような問合せが最適化(自動的にリライト)されます。

このドキュメント作成の回避により、パフォーマンスが大幅に向上します。問合せは、JSONドキュメントではなく表データに対して効率的に行われます。ドキュメントは、実際に問合せ結果に必要な場合にのみ作成されます。

ただし、次のような理由で、一部の問合せをリライトすることはできません:

  • 問合せパス式には子孫パス・ステップ(..)が含まれ、再帰的に、直前のステップに一致するオブジェクトまたは配列(前のステップがない場合はコンテキスト項目)に適用されます。

  • 問合せのフィルタ式は、一部の配列要素にのみ適用され、すべて([*])には適用されません。たとえば、[3]は4番目の配列要素にのみ適用され、[last]は最後の要素にのみ適用されます。

  • 問合せパス式には、否定されたフィルタ式が含まれています。『Oracle Database JSON開発者ガイド』パス式での否定を参照してください。

JSON_EXPRESSION_CHECKは、簡単な誤字を指摘する場合にも役立ちます。SQL/JSONパス式またはドット表記法構文でJSONフィールド名の不一致を検出してレポートします。

パラメータJSON_EXPRESSION_CHECKは、(1)データベース初期化ファイル(init.ora)、(2) ALTER SESSIONまたはALTER SYSTEM文、または(3) SQL問合せヒント(/*+ opt_param('json_expression_check', 'on') */)を使用して設定できます。Oracle DatabaseリファレンスJSON_EXPRESSION_CHECKを参照してください。

場合によっては、コードで明示的に型変換がコールされることがあり、それにより、リライトでの最適化によって最適にならず、不要な実行時オーバーヘッドが発生する可能性があります。これは、たとえば、SQL/JSONファンクションjson_valueの場合に起こる可能性があります。デフォルトでは、そのSQLの戻り型はVARCHAR2です。たとえば、その値が、基になる表列(型はNUMBER)に使用される予定の場合は、不要な実行時型変換が発生する可能性があります。

このため、最良のパフォーマンスにするには、一般的ガイドラインとして、ドキュメント・フィールド値に実行時型変換が必要ないことを示すRETURNING句または型変換SQL/JSON項目メソッドを使用することをお薦めします。その型として、対応付けられた基になる列で使用されているのと同じ型を指定します。

たとえば、レース・ドキュメント内のフィールド_idは、基になるrace表内の列race_idに対応付けられており、その列のSQL型はNUMBERです。このため、json_valueを使用してフィールド_idを選択またはテストするときに、NUMBER値が返されるようにする必要があります。

次の2つの問合せの2番目は、通常は、1番目よりもパフォーマンスが高くなります。これは、1番目の問合せではjson_valueからVARCHAR2値が戻されて、実行時にそれがNUMBER値およびDATE値に変換されるためです。2つ目の例では、型変換SQL/JSON項目メソッドnumberOnly()RETURNING DATE句を使用して、使用するSQL型がNUMBERおよびDATEであることを問合せコンパイラに示します。(型変換項目メソッドを使用することは、対応するRETURNING型を使用することと同じです。)

SELECT json_value(DATA, '$.laps'),
       json_value(DATA, '$.date')
  FROM race_dv
  WHERE json_value(DATA, '$._id') = 201;
SELECT json_value(DATA, '$.laps.numberOnly()'),
       json_value(DATA, '$.date' RETURNING DATE)
  FROM race_dv
  WHERE json_value(DATA, '$._id.numberOnly()') = 201;

同じ一般的ガイドラインが、単純ドット表記法構文の使用にも当てはまります。自動最適化は、通常は、ドット表記法の構文がWHERE句で使用されている場合に実行されます: ドット表記法の式で対象となったデータは、その対象データの比較先の値の型に型キャストされます。ただし、問合せコンパイル時に、関連する型を推測できないことがあります。たとえば、比較する値が、実行時まで型が不明なSQL/JSON変数($aなど)から取得される場合です。関連する項目メソッドを追加して、想定される入力が問合せコンパイル時に明確になるようにします。

次の2つの問合せの2番目は、ガイドラインに従っています。これは通常は、1番目よりもパフォーマンスが高くなります。その理由は、SELECT句およびORDER BY句で項目メソッドnumberOnly()およびdateOnly()が使用され、適切なデータ型が指定されているためです。脚注3

SELECT t.DATA.laps, t.DATA."date"
  FROM race_dv t
  WHERE t.DATA."_id" = 201
  ORDER BY t.DATA."date";
SELECT t.DATA.laps.numberOnly(), t.DATA."date".dateOnly()
  FROM race_dv t
  WHERE t.DATA."_id".numberOnly() = 201
  ORDER BY t.DATA."date".dateOnly();

関連項目:

4.7 二面性ビューに関する情報の取得

静的データ・ディクショナリ・ビューを使用して、二面性ビュー、その基礎となる表、列およびキー列リンクに関する情報を取得できます。二面性ビューのJSONスキーマの説明を取得することもできます。これには、サポートされるJSONドキュメントの構造およびJSON言語タイプの説明が含まれています。

JSON二面性ビューの静的ディクショナリ・ビュー

既存の二面性ビューに関する情報を取得するには、静的データ・ディクショナリ・ビューDBA_JSON_DUALITY_VIEWSUSER_JSON_DUALITY_VIEWSおよびALL_JSON_DUALITY_VIEWSを確認します。脚注4これらの各ディクショナリ・ビューには、各二面性ビューについて次のものが含まれています:

  • ビュー名および所有者

  • ルート表名および所有者

  • JSON型の列の名前

  • 挿入、削除および更新の各操作をビューで許可するかどうか

  • ビューが読取り専用かどうか

  • ビューが有効かどうか

  • JSON列を記述するJSONスキーマ

ディクショナリ・ビューDBA_JSON_DUALITY_VIEW_TABSUSER_JSON_DUALITY_VIEW_TABSおよびALL_JSON_DUALITY_VIEW_TABSを使用して、二面性ビューの基礎となるをリストできます。これらの各ディクショナリ・ビューには、二面性ビューについて次のものが含まれています:

  • ビュー名および所有者

  • 表名および所有者

  • 挿入、削除および更新の各操作を表で許可するかどうか

  • 表が読取り専用かどうか

  • 表にフレックス列があるかどうか

  • 表がビューのルート表かどうか

  • 二面性ビューの表を識別する番号

  • ビュー内の親表を識別する番号

  • 表とその親表との関係: 親内でネストされているか、外部結合または内部結合のターゲットであるか

ディクショナリ・ビューDBA_JSON_DUALITY_VIEW_TAB_COLSUSER_JSON_DUALITY_VIEW_TAB_COLSおよびALL_JSON_DUALITY_VIEW_TAB_COLSを使用して、二面性ビューの基礎となる表のをリストできます。これらの各ディクショナリ・ビューには、ビューと表の名前と所有者、その表がルート表かどうか、そのビュー内の表を識別する番号、および表内の各列に関する次の情報が含まれています:

  • 列名、データ型および最大文字数(文字データ型の場合)

  • JSONキー名

  • 挿入、削除および更新の各操作を列で許可するかどうか

  • 列が読取り専用かどうか

  • 列がフレックス列かどうか

  • 主キー指定での列の位置(該当する場合)

  • ETAG指定内の列の位置(該当する場合)

ディクショナリ・ビューDBA_JSON_DUALITY_VIEW_LINKSUSER_JSON_DUALITY_VIEW_LINKSおよびALL_JSON_DUALITY_VIEW_LINKSを使用して、二面性ビューに関連付けられたリンクをリストできます。主キーまたは一意キーから外部キーへのリンクか、その逆のリンクです。これらの各ディクショナリ・ビューには、リンクごとに次のものが含まれています:

  • ビューの名前と所有者

  • リンクの親表の名前と所有者

  • リンクの子表の名前と所有者

  • リンクの開始列と終了列の名前

  • リンクの結合タイプ

  • リンクに関連付けられたJSONキーの名前

関連項目:

『Oracle Databaseリファレンス』静的データ・ディクショナリ・ビューに関する項

JSONリレーショナル二面性ビューのJSONの説明

JSONスキーマでは、JSONデータの構造およびJSON言語タイプを指定します。これは、JSONドキュメントの既存のセットのサマリーの説明として機能するか、JSONドキュメントのセットで想定される内容または許可される内容の指定として機能します。以前のユースケースは、JSONデータ・ガイドから取得したスキーマのユースケースです。後者のユースケースには、二面性ビューでサポートされるドキュメントを説明するJSONスキーマのケースが含まれています。

PL/SQLファンクションDBMS_JSON_SCHEMA.describeを使用して、二面性ビューでサポートされるJSONドキュメントを説明するJSONスキーマを取得できます。(このドキュメントは、静的ディクショナリ・ビューDBA_JSON_DUALITY_VIEWSUSER_JSON_DUALITY_VIEWSおよびALL_JSON_DUALITY_VIEWSの列JSON_SCHEMAでも使用できます。「JSON二面性ビューの静的ディクショナリ・ビュー」を参照してください。)

このJSONスキーマには、次の3種類の情報が含まれています:

  1. ドキュメントをサポートする二面性ビューに関する情報

    これには、ビューを所有するデータベース・スキーマ(ユーザー) (フィールドdbObject)およびビューで許可される操作(フィールドdbObjectProperties)が含まれます。

  2. 二面性ビューの基礎となるに関する情報。

    これには、ドメイン名(フィールドdbDomain)、主キー(フィールドdbPrimaryKey)、外部キー(フィールドdbForeignKey)、フレックス列が存在するかどうか(フィールドadditionalProperties)、列データ型の制限(文字列の場合はフィールドmaxLength、数値の場合はフィールドsqlPrecisionなど)が含まれます。

  3. ドキュメントの許可された構造およびJSON言語のタイプに関する情報

    この情報を使用して、ビューに追加または変更するデータを検証できます。これは上位レベルのスキーマフィールドpropertiesの値として使用でき、独自の権限でJSONスキーマとして使用できます。

例4-23では、DBMS_JSON_SCHEMA.describeを使用して、カーレースの例の各二面性ビュー(driver_dvrace_dvおよびteam_dv)を説明します。

例4-23 DBMS_JSON_SCHEMA.DESCRIBEを使用した二面性ビューを記述するJSONスキーマの表示

この例では、各カーレースの二面性ビューについて、ビューでサポートされるJSONドキュメントを記述するJSONスキーマを示します。

上位レベルのJSONスキーマ・フィールドpropertiesの値は、ビューに追加またはビューで変更されるデータを検証するために使用できるJSONスキーマです。もう1つの上位レベルのプロパティでは、ドキュメントをサポートする二面性ビューについて記述します。

各ビューを作成して所有するデータベース・スキーマ/ユーザーは、ここでプレースホルダ値とともに表示されます(イタリックで表示されます)。これはフィールドdbObjectの値に反映されます。二面性ビューの場合、これはビュー所有者のデータベース・スキーマ名で修飾されるビュー名です。たとえば、データベース・ユーザー/スキーマteam_dv_ownerが二面性ビューteam_dvを作成した場合、そのビューのフィールドdbObjectの値はteam_dv_owner.team_dvになります。

(もちろん、同じデータベース・ユーザー/スキーマがこれらの二面性ビューを作成し、所有できます。ただし、その必要はありません。)

配列フィールドdbObjectPropertiesは、二面性ビュー自体で許可される操作を指定します。

  • insertableは、ビューにドキュメントを挿入できることを意味します。

  • updatableは、ビュー内の既存のドキュメントを更新できることを意味します。

  • deletableは、既存のドキュメントをビューから削除できることを意味します。

  • checkは、各ドキュメント内の少なくとも1つのフィールドがCHECKとマークされ、ETAGの計算に関与することを意味します。

フィールドtypeは、標準のJSON言語非スカラー型(objectまたはarray)を指定します。両方のフィールドtypeおよびextendedTypeは、スカラーJSON言語型の指定に使用します。

ネイティブ・バイナリJSONデータ(OSON形式)は、SQLデータ型に対応していてJSON標準には含まれていないスカラー型(dateなど)を追加することでJSON言語を拡張します。これらのOracle固有のスカラー型は、常にextendedTypeで指定されます。

フィールドitemsは、配列値の要素タイプを指定します。サポートされているドキュメント内の各JSONオブジェクトのフィールドは、そのオブジェクトのスキーマ・フィールドpropertiesの下にリストされます。すべてのドキュメント・フィールドは下線が付いています

(JSONスキーマを作成するために必要なものは、ファンクションDBMS_JSON_SCHEMA.describeのみです。ここでの使用方法は、キーワードPRETTYを渡すために、SQL/JSONファンクションjson_serializeでラップされています。これにより、整形出力されます。)

-- Duality View TEAM_DV
SELECT json_serialize(DBMS_JSON_SCHEMA.describe('TEAM_DV') PRETTY)
  AS team_dv_json_schema;
TEAM_DV_JSON_SCHEMA
-------------------
{"title"                : "TEAM_DV",
 "dbObject"             : "TEAM_DV_OWNER.TEAM_DV",
 "dbObjectType"         : "dualityView",
 "dbObjectProperties"   : [ "insertable", "updatable", "deletable", "check" ],
 "type"                 : "object",
 "properties"           : {"_id"          :
                           {"extendedType"      : "number",
                            "sqlScale"          : 0,
                            "generated"         : true,
                            "dbFieldProperties" : [ "check" ]},
                           "_metadata"    : {"etag" : {"extendedType" : "string",
                                                       "maxLength"    : 200},
                                             "asof" : {"extendedType" : "string",
                                                       "maxLength"    : 20}},
                           "dbPrimaryKey" : [ "_id" ],
                           "name"         : {"extendedType"      : "string",
                                             "maxLength"         : 255,
                                             "dbFieldProperties" : [ "update",
                                                                     "check" ]},
                           "points"       : {"extendedType"      : "number",
                                             "sqlScale"          : 0,
                                             "dbFieldProperties" : [ "update",
                                                                     "check" ]},
                           "driver"       :
                           {"type"  : "array",
                            "items" :
                            {"type"                 : "object",
                             "properties"           :
                             {"dbPrimaryKey" : [ "driverId" ],
                              "name          :
                              {"extendedType"      : "string",
                               "maxLength"         : 255,
                               "dbFieldProperties" : [ "update", "check" ]},
                              "points"       :
                              {"extendedType"      : "number",
                               "sqlScale"          : 0,
                               "dbFieldProperties" : [ "update" ]},
                              "driverId"     : {"extendedType"      : "number",
                                                "sqlScale"          : 0,
                                                "generated"         : true,
                                                "dbFieldProperties" : [ "check" ]}},
                             "required"             : [ "name",
                                                        "points",
                                                        "driverId" ],
                             "additionalProperties" : false}}},
 "required"             : [ "name", "points", "_id" ],
 "additionalProperties" : false}

1 row selected.
-- Duality View DRIVER_DV
SELECT json_serialize(DBMS_JSON_SCHEMA.describe('DRIVER_DV') PRETTY)
  AS driver_dv_json_schema;
DRIVER_DV_JSON_SCHEMA
---------------------
{"title"                : "DRIVER_DV",
 "dbObject"             : "DRIVER_DV_OWNER.DRIVER_DV",
 "dbObjectType"         : "dualityView",
 "dbObjectProperties"   : [ "insertable", "updatable", "deletable", "check" ],
 "type"                 : "object",
 "properties"           : {"_id"          : {"extendedType"      : "number",
                                             "sqlScale"          : 0,
                                             "generated"         : true,
                                             "dbFieldProperties" : [ "check" ]},
                           "_metadata"    : {"etag" : {"extendedType" : "string",
                                                       "maxLength"    : 200},
                                             "asof" : {"extendedType" : "string",
                                                       "maxLength"    : 20}},
                           "dbPrimaryKey" : [ "_id" ],
                           "name"         : {"extendedType"      : "string",
                                             "maxLength"         : 255,
                                             "dbFieldProperties" : [ "update", "check" ]},
                           "points"       : {"extendedType"      : "number",
                                             "sqlScale"          : 0,
                                             "dbFieldProperties" : [ "update", "check" ]},
                           "team"         : {"extendedType"  : "string",
                                             "maxLength"     : 255},
                           "teamId"       : {"extendedType"      : "number",
                                             "sqlScale"          : 0,
                                             "generated"         : true,
                                             "dbFieldProperties" : [ "check" ]},
                           "race"         : {"type"  : "array",
                                             "items" :
                                             {"type"                 : "object",
                                              "properties"           :
                                              {"dbPrimaryKey"    : [ "driverRaceMapId" ],
                                               "finalPosition"   :
                                               {"extendedType"      : [ "number",
                                                                        "null" ],
                                                "sqlScale"          : 0,
                                                "dbFieldProperties" : [ "update",
                                                                        "check" ]},
                                               "driverRaceMapId" :
                                               {"extendedType"      : "number",
                                                "sqlScale"          : 0,
                                                "generated"         : true,
                                                "dbFieldProperties" : [ "check" ]},
                                               "name"            :
                                               {"extendedType"      : "string",
                                                "maxLength"         : 255,
                                                "dbFieldProperties" : [ "check" ]},
                                               "raceId"          :
                                               {"extendedType"      : "number",
                                                "sqlScale"          : 0,
                                                "generated"         : true,
                                                "dbFieldProperties" : [ "check" ] }},
                                              "required"             :
                                              [ "driverRaceMapId", "name", "raceId" ],
                                              "additionalProperties" : false}}},
 "required"             : [ "name", "points", "_id", "team", "teamId" ],
 "additionalProperties" : false}
1 row selected.
-- Duality View RACE_DV
SELECT json_serialize(DBMS_JSON_SCHEMA.describe('RACE_DV') PRETTY)
  AS race_dv_json_schema;
RACE_DV_JSON_SCHEMA
-------------------
{"title"              : "RACE_DV",
 "dbObject"           : "RACE_DV_OWNER.RACE_DV",
 "dbObjectType"       : "dualityView",
 "dbObjectProperties" : [ "insertable", "updatable", "deletable", "check" ],
 "type"               : "object",
 "properties"         : {"_id"       : {"extendedType"       : "number",
                                        "extendedType"      : "number",
                                        "sqlScale"          : 0,
                                        "generated"         : true,
                                        "dbFieldProperties" : [ "check" ]},
                         "_metadata" : {"etag" : {"extendedType" : "string",
                                                  "maxLength"    : 200},
                                        "asof" : {"extendedType" : "string",
                                                  "maxLength"    : 20}},
                         "dbPrimaryKey" : [ "_id" ],
                         "laps"      : {"extendedType"      : "number",
                                        "sqlScale"          : 0,
                                        "dbFieldProperties" : [ "check" ]},
                         "name"      : {"extendedType"      : "string",
                                        "maxLength"         : 255,
                                        "dbFieldProperties" : [ "update", "check" ]},
                         "podium"    : {"dbFieldProperties" : [ "update" ]},
                         "date"      : {"extendedType"      : "date",
                                        "dbFieldProperties" : [ "update", "check" ]},
                         "result"    : {"type"  : "array",
                                        "items" :
                                        {"type"                 : "object",
                                         "properties"           :
                                         {"dbPrimaryKey"    : [ "driverRaceMapId" ],
                                          "position"        :
                                          {"extendedType"      : "number",
                                           "sqlScale"          : 0,
                                           "dbFieldProperties" : [ "update",
                                                                   "check" ]},
                                          "driverRaceMapId" :
                                          {"extendedType"      : "number",
                                           "sqlScale"          : 0,
                                           "generated"         : true,
                                           "dbFieldProperties" : [ "check" ]},
                                          "name"            :
                                          {"extendedType"      : "string",
                                           "maxLength"         : 255,
                                           "dbFieldProperties" : [ "update",
                                                                   "check" ]},
                                          "driverId"        :
                                          {"extendedType"      : "number",
                                           "sqlScale"          : 0,
                                           "generated"         : true,
                                           "dbFieldProperties" : [ "check" ]}},
                                         "required"             : [ "driverRaceMapId",
                                                                    "name",
                                                                    "driverId" ],
                                         "additionalProperties" : false}}},
 "required"             : [ "laps", "name", "_id" ],
 "additionalProperties" : false}
1 row selected.

関連項目:



脚注一覧

脚注1: この例では、SQLの単純なドット表記法を使用します。_idはSQL/JSONパス式内にないので、アンダースコア文字(_)のために、二重引用符文字(")で囲む必要があります。
脚注2: この例では、SQLの単純なドット表記法を使用します。_idはSQL/JSONパス式内にないので、アンダースコア文字(_)のために、二重引用符文字(")で囲む必要があります。
脚注3: この例では、SQLの単純なドット表記法を使用します。_idはSQL/JSONパス式内にないので、アンダースコア文字(_)のために、二重引用符文字(")で囲む必要があります。
脚注4: PL/SQLファンクションDBMS_JSON_SCHEMA.describeを使用して二面性ビューの説明を取得することもできます。