本記事はOracle Cloud Infrastructure Advent Calendar 2025 シリーズ2 の11日目とJPOUG Advent Calendar 2025の11日目のダブルポストです。
はじめに
ある意味RDBMSの根本を無視した話なのですが、Oracle AI Databaseってこんな変なこともできますよというネタ的な意味も込めて紹介したいと思います。しかも方法が2つあります。本記事では片方のみ紹介し、もう片方は別記事に譲ります。
なお、どちらの方法も知名度の低い手法を使用しており難易度も高めです。
本記事の検証環境はATP26aiですが基本部分は9iくらいの古いデータベースでも動くと思われます。
本記事ではオブジェクト機能を使用してマルチレイアウトを実現する方法を紹介します。Oracle Databaseはバージョン8(1997年)くらいからオブジェクト・データベースの機能を持っています。そんなに古くから存在する機能なのにあまり使われていませんが… その後の本機能の進化によりオブジェクトの継承もできるようになり、この機能を使うことで最低1列だけ共通列が必要ですがマルチレイアウトのテーブルを作ることが可能です。以下実現方法を解説します。
作業環境はSQL*Plusを使用してください。SQL WorksheetやSQLclではオブジェクトを省略表示するので処理は通りますが目視でのデータ確認が困難です。
1. 共通部分のオブジェクトを作成
継承元となる親オブジェクトが必要なのでそれを作成します。メインフレームなどでよく利用されるマルチレイアウトも実際は共通項目が複数あったりしますし、RDBMS的な視点でも主キーが共通的に必要になってきますので、これが問題になるケースは少ないと思われます。ここでは1項目で構成されるオブジェクトを作成します。
create or replace type id_type as object (
id raw(16)
, final constructor function id_type(SELF IN OUT NOCOPY id_type) return self as result
, final constructor function id_type(SELF IN OUT NOCOPY id_type, p_uuid varchar2) return self as result
, final constructor function id_type(SELF IN OUT NOCOPY id_type, p_uuid raw) return self as result
) not final;
/
CREATE OR REPLACE TYPE BODY id_type
AS
constructor function id_type(SELF IN OUT NOCOPY id_type) return self as result
is
begin
select uuid() into self.id;
return;
end;
constructor function id_type(SELF IN OUT NOCOPY id_type, p_uuid varchar2) return self as result
is
begin
select uuid_to_raw(p_uuid) into self.id;
return;
exception
when others then
raise_application_error(-20000, 'UUID値検証エラー');
end;
constructor function id_type(SELF IN OUT NOCOPY id_type, p_uuid raw) return self as result
is
begin
select uuid_to_raw(raw_to_uuid(p_uuid)) into self.id;
return;
exception
when others then
raise_application_error(-20000, 'UUID値検証エラー');
end;
end;
/
今風にUUIDを意識した形になっています。Oracle Databaseのオブジェクト機能はユーザー定義のコンストラクタも書けるので、簡単ですがUUIDの自動生成や引数でUUIDを与えた場合のチェック機能なども実装してみました。余談ですがRU23.9でOracle DatabaseもUUIDの生成に対応していますのでそれらの機能を活用しています。SELECT文を使用してUUIDを代入しているのはこのバージョン(23.26)でのUUID関数はSQL関数としては対応していてもPL/SQL関数としては対応していないからです。23.9以前の環境の場合はUUID関連部分を自作してそれを利用する形に修正するか、コンストラクタ関連のコーディングを削除してください。
作成のポイントはCREATE TYPE文の最後にNOT FINAL句を加えている部分です。デフォルトはFINAL指定で、デフォルトのままでは継承ができません。
2. マルチレイアウトとなる部分のオブジェクトを作成
親オブジェクトを継承して個別レイアウト部分となるオブジェクトを作成します。
create or replace type rec1_type under id_type (
col001 number, col002 varchar2(100), col003 date
);
/
create or replace type rec2_type under id_type (
col101 number, col102 varchar2(100), col103 number, col104 varchar2(50)
);
今回は列名を重複しないようにしていますが別に重複しても問題ありません。列数も自由です。更なる継承が必要な場合はNOT FINAL句も付与してください。
3. 親オブジェクトのテーブルを作成
Oracle AI Databaseのオブジェクト型は列のデータ型として指定することもテーブル全体の型として指定することも可能です。今回は親オブジェクトであるID_TYPEをベースとしたオブジェクトのテーブルを作成します。これで準備は完了です。子オブジェクトはテーブルの作成には関わりません。
CREATE TABLE multi_layout OF id_type
(
CONSTRAINT pk_multi_layout primary key (id)
);
4. データを投入
ここでは子オブジェクトにそれぞれ2行ずつ、計4行のデータを挿入してみます。
INSERT INTO multi_layout VALUES (rec1_type(id_type().id, 1, 'AAA', to_date('20250101', 'YYYYMMDD')));
INSERT INTO multi_layout VALUES (rec1_type(id_type().id, 2, 'BBB', to_date('20251111', 'YYYYMMDD')));
INSERT INTO multi_layout VALUES (rec2_type(id_type().id, 3, 'CCC', 100, 'ZZZ'));
INSERT INTO multi_layout VALUES (rec2_type(id_type().id, 4, 'DDD', 200, 'YYY'));
commit;
オブジェクトのテーブルにはコンストラクタ経由でデータを挿入する必要があります。
このような形式をとることで異なる子オブジェクトを必要な数だけ作成すれば任意の種類のマルチレイアウトの実現が可能です。多重継承も可能なので孫以降のオブジェクトの作成も可能です。大多数の方がそうだと思われますがオブジェクト機能の経験のない方にはなかなか馴染みのない感じの書き方かもしれません…
5. データを検索
まずSELECT *してみます。
SQL> SELECT * FROM multi_layout;
ID
--------------------------------
4405E61B04EF4F67BFAF1308E6707FA0
8ECD53B0205A4F1BBF9C637135F0723E
A8F97B67A38C4FDCBF66B5EE6BF6E8C3
A9D6C4C08E1F4FFCBF86B7B96C1CD278
これだと親オブジェクトの列であるID列のみ表示されます。
全列表示させるためにはVALUE関数を使用します。
オブジェクトテーブルの操作は基本的にテーブル別名経由でのアクセスが必要です。
SELECT結果はオブジェクトの部分はコンストラクタ関数の形式で表示されます。
SQL> SELECT VALUE(m) FROM multi_layout m;
VALUE(M)(ID)
--------------------------------------------------------------------------------
REC1_TYPE('8ECD53B0205A4F1BBF9C637135F0723E', 1, 'AAA', '01-JAN-25')
REC1_TYPE('A8F97B67A38C4FDCBF66B5EE6BF6E8C3', 2, 'BBB', '11-NOV-25')
REC2_TYPE('A9D6C4C08E1F4FFCBF86B7B96C1CD278', 3, 'CCC', 100, 'ZZZ')
REC2_TYPE('4405E61B04EF4F67BFAF1308E6707FA0', 4, 'DDD', 200, 'YYY')
次いでrec1_typeの行だけを表示してみます。オブジェクト名部分を書き換えればrec2_typeの行だけを表示することも可能です。
SQL> SELECT VALUE(m) FROM multi_layout m
2 WHERE VALUE(m) IS OF (rec1_type);
VALUE(M)(ID)
--------------------------------------------------------------------------------
REC1_TYPE('8ECD53B0205A4F1BBF9C637135F0723E', 1, 'AAA', '01-JAN-25')
REC1_TYPE('A8F97B67A38C4FDCBF66B5EE6BF6E8C3', 2, 'BBB', '11-NOV-25')
次いで特定の列を各子オブジェクトから同じ列として取り出します。この例では同じデータ型の列で揃えていますが、元のデータ型が異なる場合はCASTしてデータ型を揃える必要があります。個別の列へアクセスするためにTREAT関数を使用する必要があるなどデータの取り出しがさらに難しくなっています。
SQL> SELECT m.id,
2 CASE
3 WHEN VALUE(m) IS OF (rec1_type) THEN TREAT(VALUE(m) AS rec1_type).col001
4 WHEN VALUE(m) IS OF (rec2_type) THEN TREAT(VALUE(m) AS rec2_type).col101
5 END col1
6 FROM multi_layout m;
ID COL1
-------------------------------- ----------
8ECD53B0205A4F1BBF9C637135F0723E 1
A8F97B67A38C4FDCBF66B5EE6BF6E8C3 2
A9D6C4C08E1F4FFCBF86B7B96C1CD278 3
4405E61B04EF4F67BFAF1308E6707FA0 4
最後に特定のレコード、列に更新をかけてみます。UPDATEの列指定の関係で例のようにインラインビューを経由させるか、あるいは子オブジェクト全体を更新する必要があります。なお、ID列のような親オブジェクトの列参照はTREAT関数を経由させる必要はありません。
UPDATE (
SELECT TREAT(VALUE(m) AS rec1_type) AS rec1
FROM multi_layout m
WHERE m.id = HEXTORAW('8ECD53B0205A4F1BBF9C637135F0723E')
) t
SET t.rec1.col001 = 98;
COMMIT;
以上、Oracle AI Databaseでマルチレイアウトのテーブルを作る方法のひとつを紹介しました。テーブルの作成はまだしもデータの操作はかなり難しいのが実情です。