5.3 二面性ビューでのドキュメント/データの更新
JSONドキュメントを二面性ビューで直接更新することも、二面性ビューの基礎となる表のデータを更新することもできます。ドキュメントを完全に置き換えてドキュメントを更新することも、一部のフィールドのみを更新することもできます。例は、これらの可能性を示しています。
ノート:
特に明示的に示されていないかぎり、次のようになります。
-
ここに示す例は、決して相互に依存するものではありません。特に、それらの間には暗黙的な順序付けはありません。
-
ここでの二面性ビューを使用する例では、「二面性ビューの作成」で定義したビューを使用します。これらのビューは、
UNNEST
を使用して定義されています(例3-1、例3-3および例3-5)。 -
ここでの表を使用する例では、「カーレースの例、表」で定義した表を使用します。
ノート:
通常、更新には更新、挿入、および削除操作が含まれています。このトピックでは、1つ以上の既存のドキュメントまたは基礎となる表を変更する更新操作についてのみ説明します。挿入および削除の操作については、「二面性ビューへのドキュメント/データの挿入」および「二面性ビューからのドキュメント/データの削除」をそれぞれ参照してください。
二面性ビューの更新操作では、完全なドキュメントを更新(置換)したり、既存のオブジェクトの1つ以上のフィールドの値を更新できます。配列値フィールドの更新には、配列要素の挿入または削除を含めることができます。
更新操作では、二面性ビューによって明示的に定義されているオブジェクトのメンバー(フィールド/値ペア)を追加または削除できません。同じ理由で、更新では、ビュー定義が提供するもの以外のオブジェクトを追加または削除できません。
このような更新は、サポートするドキュメントの構造およびタイプを指定するビュー定義の変更を表します。このような変更を実行する必要がある場合は、ビューを再定義する必要があります。CREATE OR REPLACE JSON RELATIONAL DUALITY VIEW
を使用すると、実行できます。
一方、データ型JSON
の基礎となる列で定義されたJSON値は、デフォルトでは制約なしです。つまり、結果のJSONが整形式であるかぎり、あらゆる変更が可能です。基礎となる表のJSON
型の列に対応する値は、その列に適用されるJSONスキーマ(ある場合)によってのみ制約されます。
関連項目:
『Oracle Database JSON開発者ガイド』のJSONスキーマに関する項
1つ以上の二面性ビューの基礎となる表の行を更新すると、その表の行のデータに対応する(データから取得される)データを保持するすべての(任意の二面性ビューでサポートされる)ドキュメントが更新されます。(更新されたドキュメントの他のデータは変更されません。)
ノート:
JSONリレーショナル二面性ビューでサポートされるドキュメントまたはその基礎となる表データの更新は、そのデータの内容が変更されていない場合でも、データの一部の行が更新されたとSQLによってレポートされます。これは標準のSQL動作です。更新操作が成功すると、常に、ターゲットとする行が更新されたとレポートされます。これは、更新操作に付随するトリガーまたは行変換演算子が存在する可能性があり、それ自体がデータを変更できるという事実も示しています。
ノート:
一般に、JSON文字列からNVARCHAR2
、NCLOB
およびNCHAR
以外のタイプのSQL文字データを生成した場合、かつそのターゲット・データ型の文字セットがUnicodeベースでない場合、変換では、そのSQL型の文字セットでは表現できない文字に対して非可逆的文字セット変換が行われる可能性があります。
ヒント:
最初にデータベースから読み取ることなくドキュメントを更新しようとすると、欠落したフィールドまたは無効なフィールドによる書込みの損失や実行時エラーなど、いくつかの問題が発生する可能性があります。
更新する場合は、次のステップに従います:
-
データベースからドキュメントをフェッチします。
-
ドキュメントのローカル・コピーを変更します。
-
更新されたローカル・コピーをデータベースに保存します。
-
更新の試行(ステップ3)が、同時変更またはETAGの不一致のために失敗した場合は、ステップ1から3を繰り返します。
「二面性ビューでのオプティミスティックな同時実行性制御の使用」も参照してください。
例5-8 二面性ビューでのJSONドキュメント全体の更新 — SQLの使用
この例では、ドキュメント識別子(フィールド_id
)の値が201
である、二面性ビューrace_dv
内のレース・ドキュメントを置き換えます。(二面性ビューrace_dv
の対応する定義は、例3-5を参照してください。)
この例では、SQL操作UPDATE
を使用してこれを実行し、ビューの単一のJSON列(DATA
)の行を新しい値に設定します。SQL/JSONファンクションjson_value
およびOracle SQLファンクションjson_serialize
を使用して、更新操作の前後にドキュメントを選択してシリアライズ/整形出力し、変更を表示します。シリアライズの結果は、ここに部分的に表示されます。
新しい置換JSONドキュメントには、レースの結果が含まれ、結果にはレースdate
、podium
値(上位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,
"driverId" : 103,
"name" : "Charles Leclerc"},
{"driverRaceMapId" : 4,
"position" : 2,
"driverId" : 104,
"name" : "Carlos Sainz Jr"},
{"driverRaceMapId" : 9,
"position" : 3,
"driverId" : 106,
"name" : "Lewis Hamilton"},
{"driverRaceMapId" : 10,
"position" : 4,
"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;
例5-9 二面性ビューでのJSONドキュメント全体の更新 — RESTの使用
この例では、Oracle REST Data Services (ORDS)を使用して例5-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,
"driverId" : 103,
"name" : "Charles Leclerc"},
{"driverRaceMapId" : 4,
"position" : 2,
"driverId" : 104,
"name" : "Carlos Sainz Jr"},
{"driverRaceMapId" : 9,
"position" : 3,
"driverId" : 106,
"name" : "Lewis Hamilton"},
{"driverRaceMapId" : 10,
"position" : 4,
"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 (ORDS)を構成して、1秒のタイムアウトを指定してメタデータ・キャッシュを有効にしにます:
cache.metadata.enabled = true
cache.metadata.timeout = 1
『Oracle REST Data Servicesインストレーションおよび構成ガイド』の「REST対応SQLサービス設定の構成」を参照してください。
関連項目:
『Oracle REST Data Services開発者ガイド』のJSONリレーショナル二面性ビューのサポート
例5-10 二面性ビューでの一部のJSONドキュメントの更新
この例では、フィールドname
がLIKE
パターン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値の比較は実行されません。つまり、部分ドキュメント更新にオプティミスティックな同時実行性制御を使用することはできません。
例5-11 相互に関連するJSONドキュメントの更新 — SQLの使用
ドライバーCharles LeclercはチームFerrariに属しており、ドライバーGeorge RussellはチームMercedesに属しています。この例では、MercedesとFerrariのチーム・ドキュメントを更新することによって、2つのチーム間でこれらの2つのドライバを交換します。
ドライバ情報はチーム・ドキュメントとドライバ・ドキュメント間で共有されるため、これらの2つのドライバのドライバ・ドキュメントのフィールドteamID
は、チーム・ドキュメントが更新されると自動的に適切に更新されます。
または、許可されている場合は、2つのドライバのドライバ・ドキュメントを更新して、teamId
の値を変更できます。これにより、2つのチーム・ドキュメントが同時に更新されます。ただし、ビューdriver_dv
の定義では、表team
でサポートされているフィールドの変更は許可されません。これを実行しようとすると、例5-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%';
例5-12 相互に関連するJSONドキュメントの更新 — RESTの使用
この例では、Oracle REST Data Services (ORDS)を使用して例5-11と同じことを実行します。team_dv/303
とteam_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 (ORDS)を構成して、1秒のタイムアウトを指定してメタデータ・キャッシュを有効にしにます:
cache.metadata.enabled = true
cache.metadata.timeout = 1
『Oracle REST Data Servicesインストレーションおよび構成ガイド』の「REST対応SQLサービス設定の構成」を参照してください。
関連項目:
『Oracle REST Data Services開発者ガイド』のJSONリレーショナル二面性ビューのサポート
例5-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
を参照することに注意してください。
例5-14 エラーが発生する、許可されていない更新操作の試行 — RESTの使用
この例では、Oracle REST Data Services (ORDS)を使用して例5-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 (ORDS)を構成して、1秒のタイムアウトを指定してメタデータ・キャッシュを有効にしにます:
cache.metadata.enabled = true
cache.metadata.timeout = 1
『Oracle REST Data Servicesインストレーションおよび構成ガイド』の「REST対応SQLサービス設定の構成」を参照してください。
関連項目:
『Oracle REST Data Services開発者ガイド』のJSONリレーショナル二面性ビューのサポート
_________________________________________________________
- 二面性ビューを使用する場合のトリガーに関する考慮事項
二面性ビューの基礎となる表のデータを変更するトリガーは、問題になることがあります。それらを使用しないことをお薦めします。それらを使用する場合は、問題を回避するために、次の事柄を考慮する必要があります。
関連トピック
親トピック: JSONリレーショナル二面性ビューの使用
5.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変更と区別できません。
-
関連項目:
-
『Oracle Database PL/SQL言語リファレンス』のDMLトリガーに関する項
-
『Oracle Database PL/SQL言語リファレンス』の相関名および疑似レコードに関する項
-
二面性ビューの基になる表内の列を更新するためのトリガーの使用例については、https://github.com/oracle-samples/oracle-db-examples/blob/main/json-relational-duality/DualityViewTutorial.sql
親トピック: 二面性ビューでのドキュメント/データの更新