13
5

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

JSONとRDBのいいとこ取り!JSONリレーショナル二面性ビューの基本と実践

Last updated at Posted at 2025-08-12

はじめに

Oracle Database 23aiから、「JSONリレーショナル二面性ビュー」というオブジェクトが作れるようになりました。本記事では、そのJSONリレーショナル二面性ビューの基本的な使い方について説明します。

本記事中の GitHub リンクは、すべて筆者個人が管理・公開しているリポジトリへのものです。掲載しているSQLやスクリプトは、記事内容の説明を目的として作成したものであり、自由に利用していただけます。

JSONリレーショナル二面性ビューとは

翻訳前の名称は「JSON Relational Duality View」で、リレーショナルデータベースの側面を持ちつつ、データをJSONのドキュメントとしても扱える「二面性」を持たせます。
JSONリレーショナル二面性ビューを使うことによって、以下のようなことが期待できます。

  • RESTful APIの実装コスト削減
    • JSON形式のデータをそのままOracle Databaseへ取り込めるため、アプリケーション側での変換操作が不要になります
    • 変換操作が不要となることから、O/Rマッパーの使用も廃止できます
  • 生成AIとの親和性
    • JSON形式のドキュメントは生成AIとの親和性が高く、既存データベースのデータを活用したRAG検索の実装時に、容易なベクトル化が可能です

以降、JSONリレーショナル二面性ビューは公式でも使用している略称「二面性ビュー」と記載します。

実行環境情報

項目 仕様/バージョン
ホストOS macOS Sequoia 15.5
Colima 0.8.1
Docker 28.1.1
Oracle Database 23ai Free 23.0.0.0.0

単一テーブルを参照する二面性ビュー

まずはシンプルな二面性ビューの検索やデータ操作を見ていきます。

テーブル作成

以下のDDLで生徒テーブルを作成します。

テーブルを作成したら、以下のINSERT文で初期データを作成します。

二面性ビュー作成

以下のDDLで二面性ビューを作成します。

JSON リレーショナル二面性ビューを作成する際、ルート要素(最上位の JSON オブジェクト)には必ず一意の識別子として _id フィールドが必要です。
この _id フィールドは、裏側のリレーショナルテーブルにおける主キーに対応し、ビューを通じて実行される SELECT・UPDATE・DELETE などの操作におけるデータ識別に使われます。
もし _id が存在しない場合、ビュー作成時にエラーとなるか、意図通りの更新が行えません。複数テーブルを統合するビューでも、必ずこの _id を一意に設定する必要があります。

検索

作成した二面性ビューに対し、まずは検索操作を試します。

全件検索

最初はシンプルに、全件検索してみましょう。

クエリ

結果
DATA
----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
{"_id":"3C3DCB1CEAEC06A3E063020011ACD2F1","first_name":"Adam","last_name":"Jones","major":"Computer Science","gpa":3.5,"_metadata":{"etag":"E73B0F8ADA21C8F12747AA7FD2019E7C","asof":"0000000000330AC0"}}
{"_id":"3C3DCB1CEAED06A3E063020011ACD2F1","first_name":"Barbara","last_name":"Harris","major":"Mathematics","gpa":3.8,"_metadata":{"etag":"32D000EA7499A43D6A5C8E9A9FA2DE97","asof":"0000000000330AC0"}}
{"_id":"3C3DCB1CEAEE06A3E063020011ACD2F1","first_name":"Charlie","last_name":"Smith","major":"Physics","gpa":3.2,"_metadata":{"etag":"A5E81015800426A33CEDD50E50D9CB3F","asof":"0000000000330AC0"}}

JSONでデータが出力されていることが確認できます。
アプリケーションからアクセスする際はこのままでもいいのですが、人の目でも読めるよう書式を整えたい場合は JSON_SERIALIZE 関数を利用します。また、複数行をまとめて1つの配列として取得したい場合は JSON_ARRAYAGG 関数を使用します。

JSON_SERIALIZE 関数により、下記の通り整形された結果が返されます。だいぶ見やすくなりました。

[
  {
    "_id" : "3C3DCB1CEAEC06A3E063020011ACD2F1",
    "_metadata" :
    {
      "etag" : "E73B0F8ADA21C8F12747AA7FD2019E7C",
      "asof" : "0000000000330B64"
    },
    "first_name" : "Adam",
    "last_name" : "Jones",
    "major" : "Computer Science",
    "gpa" : 3.5
  },
  {
    "_id" : "3C3DCB1CEAED06A3E063020011ACD2F1",
    "_metadata" :
    {
      "etag" : "32D000EA7499A43D6A5C8E9A9FA2DE97",
      "asof" : "0000000000330B64"
    },
    "first_name" : "Barbara",
    "last_name" : "Harris",
    "major" : "Mathematics",
    "gpa" : 3.8
  },
  {
    "_id" : "3C3DCB1CEAEE06A3E063020011ACD2F1",
    "_metadata" :
    {
      "etag" : "A5E81015800426A33CEDD50E50D9CB3F",
      "asof" : "0000000000330B64"
    },
    "first_name" : "Charlie",
    "last_name" : "Smith",
    "major" : "Physics",
    "gpa" : 3.2
  }
]

検索結果には二面性ビュー作成時に設定していなかった ETAGASOF_metadata として出力されます。 ETAG とは端的に言えば、楽観的排他制御を行うための項目で、各フィールドのハッシュ値から求められる値です。後述「更新」のセクションで、使用法を説明します。また、ASOF は読み取り一貫性を保つための仕組みとして、内部で使用されます。詳細は以下のリンクを参考にしてください。

射影や選択

実際にシステムを構築する際は、一部のカラムのみ参照するクエリを書くことがほとんどかと思います。同様に、絞り込みも頻用しますので、二面性ビューに対し射影、選択するクエリを実装してみます。

クエリ

SELECT 節では、二面性ビューに JSON 関数を噛ませる必要があり、やや面倒です。
WHERE 節でも共通するポイントですが、JSONの構造から値を取得するためには JSON_VALUE 関数を使い、JSONのパスを記載する必要があるので、二面性ビューのJSON構造を理解した上でクエリを書く必要があります。

結果

射影と選択ができました。

DATA
--------------------------------------------------------------------
{"ex_first_name":"Adam","ex_last_name":"Jones","ex_gpa":"3.5"}
{"ex_first_name":"Barbara","ex_last_name":"Harris","ex_gpa":"3.8"}

挿入

続いて、データの登録を試します。

1件のデータ挿入

まずはシンプルに、1レコードだけデータを追加してみます。

クエリ

VALUES 節の中身を、そのままJSONで書いてしまいます。

挿入後の結果

二面性ビューに INSERT を行った結果、参照元の students テーブルへ値が反映されます。

ID                                   FIRST_NAME           LAST_NAME            MAJOR                       GPA
------------------------------------ -------------------- -------------------- -------------------- ----------
3C3DCB1CEAEC06A3E063020011ACD2F1     Adam                 Jones                Computer Science            3.5
3C3DCB1CEAED06A3E063020011ACD2F1     Barbara              Harris               Mathematics                 3.8
3C3DCB1CEAEE06A3E063020011ACD2F1     Charlie              Smith                Physics                     3.2
3C3DCB1CEAF006A3E063020011ACD2F1     David                Anderson             Computer Science            2.9

複数件のデータ挿入(実行不可)

Oracle Databaseでは VALUES 句を伴った INSERT の場合、一度に複数件のデータは挿入できません。この点は、二面性ビューでも同様の制約があります。

Oracle Databaseでは長らく VALUES 句を伴った INSERT に対し、1行しかレコードを挿入できませんでしたが、晴れて23aiから複数行を一括で挿入できるようになりました!

二面性ビューでは、JSONにおけるrootの要素に対し、1行しか挿入できない制約があります。

クエリ

結果
エラー・レポート -
ORA-40944: insert intoを実行できません(JSONリレーショナル二面性ビュー'DV_STUDENTS'): 入力JSONドキュメントが無効です。
JZN-00662: business object contains no root row

親子関係のあるテーブルを結合した二面性ビューでは、親テーブルのレコードと、子テーブルに対する複数レコードの挿入ができるようになっています。詳細は以下のリンクを参照してください。

更新

_metadataを使用したクエリ

更新前のデータは以下のとおりです。

{
  "_id" : "3C3DCB1CEAEC06A3E063020011ACD2F1",
  "_metadata" :
  {
    "etag" : "E73B0F8ADA21C8F12747AA7FD2019E7C",
    "asof" : "0000000000331160"
  },
  "first_name" : "Adam",
  "last_name" : "Jones",
  "major" : "Computer Science",
  "gpa" : 3.5
}

このデータに対し、以下のクエリで更新を行います。

結果

コンソールに以下メッセージが出力されます。

1行更新しました。

更新後のデータを再検索します。

{
  "_id" : "3C3DCB1CEAEC06A3E063020011ACD2F1",
  "_metadata" :
  {
    "etag" : "18D68D4255E5F6800FC158631C00A222",
    "asof" : "00000000003311C2"
  },
  "first_name" : "Adam",
  "last_name" : "Jones",
  "major" : "Computer Science",
  "gpa" : 4.2
}

ETAG の値が E73B0F8ADA21C8F12747AA7FD2019E7C から 18D68D4255E5F6800FC158631C00A222 に、ASOF の値が 0000000000331160 から 00000000003311C2 に更新されていることも確認できます。

ETAGの値を更新せずに再UPDATE

先ほどの UPDATE 文に対し、 ETAG の値を更新しないまま、再実行すると以下のエラーが発生します。


次のコマンドの開始中にエラーが発生しました : 行 1 -
UPDATE dv_students
SET data = ('{
    "_id": "3C3DCB1CEAEC06A3E063020011ACD2F1",
    "_metadata" : {
        "etag": "E73B0F8ADA21C8F12747AA7FD2019E7C"
    },
    "first_name": "Adam",
    "last_name": "Jones",
    "major": "Computer Science",
    "gpa": 4.2
}')
WHERE JSON_VALUE(data, '$._id') = '3C3DCB1CEAEC06A3E063020011ACD2F1'
エラー・レポート -
ORA-42699: updateを実行できません(JSONリレーショナル二面性ビュー'DV_STUDENTS'): データベースのID 'FB04334333444342314345414543303641334530363330323030313141434432463100'のドキュメントのETAGが、渡されたETAGと一致しませんでした。

公式ドキュメントの通り、楽観的排他制御が有効に機能していることがわかります。

_metadataを使用しないクエリ

更新前のデータ状態は以下のとおりです。

{
  "_id" : "3C3DCB1CEAEC06A3E063020011ACD2F1",
  "_metadata" :
  {
    "etag" : "18D68D4255E5F6800FC158631C00A222",
    "asof" : "00000000003311C2"
  },
  "first_name" : "Adam",
  "last_name" : "Jones",
  "major" : "Computer Science",
  "gpa" : 4.2
}

今度は、_metadata を抜いた下記クエリで UPDATE を行ってみます。

結果
1行更新しました。

_metadata がない場合でも、データの更新は行えました。更新後のデータは以下のとおりです。

{
  "_id" : "3C3DCB1CEAEC06A3E063020011ACD2F1",
  "_metadata" :
  {
    "etag" : "17BCDF72B34B13EE78ECA9880262C1EA",
    "asof" : "00000000003316AB"
  },
  "first_name" : "Adam",
  "last_name" : "Jones",
  "major" : "Computer Science",
  "gpa" : 4.5
}

削除

クエリ

DELETE文のクエリは、通常のクエリと大差ありません。WHERE句内の条件だけ、JSONを意識した書き方にするだけで問題ありません。

結果
1行削除されました。

もし楽観的排他制御を入れたいのであれば、WHERE条件句に ETAG の条件を指定すると良いでしょう。

複数のテーブルからなる二面性ビュー

最後に、複数のテーブルから構成される二面性ビューを作成してみます。今回は交差テーブルを利用した、多対多のモデルを使います。

テーブルの追加作成とデータ投入

以下のDDLでテーブルを作成します。

次に、追加したテーブルへ初期データを投入します。
student_id の値は適宜変更してください。

二面性ビュー作成

studentsenrollmentscourses のリレーションをJSON形式にする二面性ビューを以下のクエリで作成します。 enrollments テーブルが多対多のリレーションを持つ交差テーブルになります。

二面性ビュー作成時、 FROM 句内には2つ以上の表を記述することができません。すなわち、 JOIN 句が使えません。
リレーション構造を表したい場合、本実装例のように SELECT 句内で実装する必要があります。

検索

以下のクエリで、アダムさんの受講しているコースを検索します。

検索結果

アダムさんの受講しているコースが、以下のとおりJSON形式で取得できます。

{
  "_id" : "3C3DCB1CEAEC06A3E063020011ACD2F1",
  "_metadata" :
  {
    "etag" : "093CB67F4E6BFD8CDA99AAD0EA734B08",
    "asof" : "0000000000331C81"
  },
  "first_name" : "Adam",
  "last_name" : "Jones",
  "major" : "Computer Science",
  "gpa" : 4.5,
  "enrollments" :
  [
    {
      "student_id" : "3C3DCB1CEAEC06A3E063020011ACD2F1",
      "course_id" : "C001",
      "course" :
      {
        "_id" : "C001",
        "title" : "Introduction to Computer Science",
        "lecture_hall_id" : "L001"
      }
    },
    {
      "student_id" : "3C3DCB1CEAEC06A3E063020011ACD2F1",
      "course_id" : "C002",
      "course" :
      {
        "_id" : "C002",
        "title" : "Advanced Mathematics",
        "lecture_hall_id" : "L002"
      }
    },
    {
      "student_id" : "3C3DCB1CEAEC06A3E063020011ACD2F1",
      "course_id" : "C004",
      "course" :
      {
        "_id" : "C004",
        "title" : "Data Structures and Algorithms",
        "lecture_hall_id" : "L001"
      }
    }
  ]
}

最後に

二面性ビューは、普段JSONをよく扱うWebアプリケーションエンジニアなら、極めて低い学習コストで扱えるように思えます。
CRUD操作も容易なので、RDBMSのACID特性を保つ必要があるような、重要なデータを扱うシステムを開発する際は二面性ビューを積極的に使っていくことで、開発スピードを上げられると感じました。

13
5
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
13
5

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?