2021年12月2日木曜日

RDFグラフの問合せにバインド変数を使用する

 先日、RDFグラフをAPEXで扱う記事を書いたのですが、基本的すぎて面白みがないと感じたので、RDFグラフの検索にバインド変数を使ってみました。アプリケーションの作成に当たっては、マニュアルRDFナレッジ・グラフ開発者ガイドに記載されている家系の情報を使っています。

以下のような動作をするアプリケーションを作成します。人の名前を入力すると、上の対話レポートにその人の子供を一覧し、下の対話モード・レポートには孫を一覧します。

アプリケーション開発に使用する環境は、以前の記事と同じです。データの準備やSQL の取り出しは手元で動かしているOracle Database 21c XEを使います。APEXアプリケーションはAutonomous Databaseに作成します。

両方の環境ともに、APEXDEVというスキーマで作業を行います。

Oracle Database 21c XEにロードしたグラフのデータは、DataPumpを使ってAutonomous Databaseへ移行します。


データの準備とSQLの取り出し


Oracle Database 21c XEに家系の情報をロードし、APEXアプリケーションに埋め込むSQLの取り出しを行います。

もしネットワークNET1が作成されていたら、削除しておきます。まっさらな環境で作業を行なうことで、エラーが発生する可能性を減らしておきます。

家系の情報をロードします。以下の作業を行なっています。

  1. SEM_APIS.CREATE_SEM_NETWORKを呼び出し、ネットワークNET1を作成。
  2. SEM_APIS.CRAETE_SEM_MODELを使用してモデルfamilyを作成。
  3. SEM_APIS.UPDATE_MODELを呼び出し、家系のスキーマを定義しデータを投入。
  4. SEM_APIS.CREATE_RULEBASEを呼び出し、grandParentOfのルール(parentOfのparentOf)を定義する。
  5. SEM_APIS.CREATE_ENTAILMENTを呼び出し、伴意(ルール索引)を作成する。
  6. SEM_MATCHを使った検索を実行し、動作の確認をする。

スクリプトを実行すると最後の検索結果が、以下のように表示されます。最初のSEM_MATCH関数を含むSELECT文は結果を返さないので、2つめのSELECT文の結果のみが表示されます。

GRANDFATHER

--------------------------------------------------------------------------------

GRANDCHILD

--------------------------------------------------------------------------------

<http://www.example.org/family/John>

<http://www.example.org/family/Cathy>


<http://www.example.org/family/John>

<http://www.example.org/family/Jack>


<http://www.example.org/family/John>

<http://www.example.org/family/Tom>


<http://www.example.org/family/John>

<http://www.example.org/family/Cindy>


RDFグラフの準備は完了したのでSQLを取り出します。

子供を検索するSQLを取り出します。
set serveroutput on
および
spool children.sql
などを最初に実行し、出力されるSQLを保存します。オプションにUSE_BIND_VAR=PLSQLを追加します。

最後に以下が出力されます。MarthaとSammyの子供が一覧されています。この部分は確認のための出力なので、SELECT文を取り出す際にファイルから削除します。

?s=<http://www.example.org/family/Martha>

|-->?c=<http://www.example.org/family/Cindy>

|-->?c=<http://www.example.org/family/Tom>


?s=<http://www.example.org/family/Sammy>

|-->?c=<http://www.example.org/family/Jack>

|-->?c=<http://www.example.org/family/Cathy>


出力されたSELECT文にはバインド変数のプレースホルダーとして:0:1が含まれます。これをAPEXで使用するページ・アイテム名に変更します。:0:P1_VID:1は:P1_TERMに置き換えます。


同様に孫を検索するSQLを取り出します。


最後に以下が出力されます。Johnの孫が一覧されています。SELECT文を取り出すにあたって削除します。

?s=<http://www.example.org/family/John>

|-->?c=<http://www.example.org/family/Cindy>

|-->?c=<http://www.example.org/family/Jack>

|-->?c=<http://www.example.org/family/Cathy>

|-->?c=<http://www.example.org/family/Tom>


出力されたSELECT文のバインド変数の:0:P1_VID:1:P1_TERMに置き換えます。


これで対話モード・レポートのソースとなるSELECT文の準備ができました。


データの移行


Oracle Database 21c XEからAutonomous Databaseでデータを移行します。

expdpの実行をユーザーAPEXDEVで行なうため、APEXDEVに必要な権限を与えます。SYS AS SYSDBAで接続し、以下を実行します。ディレクトリの作成権限をAPEXDEVに与えます。

SQL> grant create any directory to apexdev;


Grant succeeded.


SQL> 


ユーザーAPEXDEVで接続し、ディレクトリdump_dirを作成します。

SQL> create directory dump_dir as '/home/oracle';


Directory created.


SQL> 


expdpを実行します。ダンプ・ファイルは/home/oracle/rdf.dmpとして出力します。

expdp apexdev/********@localhost/xepdb1 directory=dump_dir dumpfile=rdf.dmp version=19

$ expdp apexdev/********@localhost/xepdb1 directory=dump_dir dumpfile=rdf.dmp version=19


Export: Release 21.0.0.0.0 - Production on Thu Dec 2 14:50:38 2021

Version 21.3.0.0.0


Copyright (c) 1982, 2021, Oracle and/or its affiliates.  All rights reserved.


Connected to: Oracle Database 21c Express Edition Release 21.0.0.0.0 - Production

Starting "APEXDEV"."SYS_EXPORT_SCHEMA_01":  apexdev/********@localhost/xepdb1 directory=dump_dir dumpfile=rdf.dmp version=19 

Processing object type SCHEMA_EXPORT/TABLE/TABLE_DATA

Processing object type SCHEMA_EXPORT/TABLE/INDEX/STATISTICS/INDEX_STATISTICS

Processing object type SCHEMA_EXPORT/TABLE/INDEX/STATISTICS/FUNCTIONAL_INDEX/INDEX_STATISTICS

Processing object type SCHEMA_EXPORT/TABLE/STATISTICS/TABLE_STATISTICS

Processing object type SCHEMA_EXPORT/STATISTICS/MARKER

Processing object type SCHEMA_EXPORT/PRE_SCHEMA/PROCACT_SCHEMA

Processing object type SCHEMA_EXPORT/SEQUENCE/SEQUENCE

Processing object type SCHEMA_EXPORT/TABLE/TABLE

Processing object type SCHEMA_EXPORT/TABLE/GRANT/OWNER_GRANT/OBJECT_GRANT

Processing object type SCHEMA_EXPORT/TABLE/COMMENT

Processing object type SCHEMA_EXPORT/PROCEDURE/PROCEDURE

Processing object type SCHEMA_EXPORT/PROCEDURE/GRANT/OWNER_GRANT/OBJECT_GRANT

Processing object type SCHEMA_EXPORT/PROCEDURE/ALTER_PROCEDURE

Processing object type SCHEMA_EXPORT/VIEW/VIEW

Processing object type SCHEMA_EXPORT/VIEW/GRANT/OWNER_GRANT/OBJECT_GRANT

Processing object type SCHEMA_EXPORT/TABLE/INDEX/INDEX

Processing object type SCHEMA_EXPORT/TABLE/INDEX/FUNCTIONAL_INDEX/INDEX

Processing object type SCHEMA_EXPORT/TABLE/CONSTRAINT/CONSTRAINT

Processing object type SCHEMA_EXPORT/TABLE/CONSTRAINT/REF_CONSTRAINT

Processing object type SCHEMA_EXPORT/VIEW/TRIGGER

. . exported "APEXDEV"."NET1#RDF_LINK$":"MODEL_2"        23.95 KB     219 rows

. . exported "APEXDEV"."NET1#RDF_VALUE$"                 23.67 KB      94 rows

. . exported "APEXDEV"."NET1#RDF_RULEBASE$"              6.585 KB       9 rows

. . exported "APEXDEV"."NET1#RDF_CLIQUE$":"MODEL_0"          0 KB       0 rows

. . exported "APEXDEV"."NET1#RDF_COLLISION$"             5.070 KB       1 rows

. . exported "APEXDEV"."NET1#RDF_CRS_URI$"               293.2 KB    5688 rows

. . exported "APEXDEV"."NET1#RDF_DELTA$":"MODEL_0"           0 KB       0 rows

. . exported "APEXDEV"."NET1#RDF_GRANT_INFO$"                0 KB       0 rows

. . exported "APEXDEV"."NET1#RDF_HIST$"                  7.328 KB       1 rows

. . exported "APEXDEV"."NET1#RDF_LINK$":"MODEL_0"            0 KB       0 rows

. . exported "APEXDEV"."NET1#RDF_LINK$":"MODEL_1"        12.67 KB      29 rows

. . exported "APEXDEV"."NET1#RDF_MODEL$_TBL"             8.109 KB       1 rows

. . exported "APEXDEV"."NET1#RDF_MODEL_INTERNAL$"        8.093 KB       1 rows

. . exported "APEXDEV"."NET1#RDF_NAMESPACE$"                 0 KB       0 rows

. . exported "APEXDEV"."NET1#RDF_NETWORK_INDEX_INTERNAL$"  14.77 KB      12 rows

. . exported "APEXDEV"."NET1#RDF_PARAMETER"              6.492 KB       2 rows

. . exported "APEXDEV"."NET1#RDF_PRECOMP$"               6.828 KB       1 rows

. . exported "APEXDEV"."NET1#RDF_PRECOMP_DEP$"           5.968 KB       3 rows

. . exported "APEXDEV"."NET1#RDF_PRED_STATS$":"MODEL_0"      0 KB       0 rows

. . exported "APEXDEV"."NET1#RDF_PRED_STATS$":"MODEL_1"      0 KB       0 rows

. . exported "APEXDEV"."NET1#RDF_RI_SHAD_2$"                 0 KB       0 rows

. . exported "APEXDEV"."NET1#RDF_RULE$"                  19.73 KB      20 rows

. . exported "APEXDEV"."NET1#RDF_SYSTEM_EVENT$"              0 KB       0 rows

. . exported "APEXDEV"."NET1#RDF_TERM_STATS$":"MODEL_0"      0 KB       0 rows

. . exported "APEXDEV"."NET1#RDF_TERM_STATS$":"MODEL_1"      0 KB       0 rows

. . exported "APEXDEV"."NET1#RDF_TS$"                        0 KB       0 rows

. . exported "APEXDEV"."NET1#RENAMED_APPTAB_RDF_MODEL_ID_1"      0 KB       0 rows

. . exported "APEXDEV"."NET1#SEM_INDEXTYPE_METADATA$"        0 KB       0 rows

Master table "APEXDEV"."SYS_EXPORT_SCHEMA_01" successfully loaded/unloaded

******************************************************************************

Dump file set for APEXDEV.SYS_EXPORT_SCHEMA_01 is:

  /home/oracle/rdf.dmp

Job "APEXDEV"."SYS_EXPORT_SCHEMA_01" successfully completed at Thu Dec 2 14:51:29 2021 elapsed 0 00:00:49


$ 


出力されたダンプ・ファイルをオブジェクト・ストレージにアップロードします。

適当なバケットを開き、アップロードをクリックします。


ドロワーが開くので、ファイルとしてrdf.dmpを選択しアップロードをクリックします。ファイルのアップロードが完了したら、ドロワーを閉じます


オブジェクトとしてアップロードしたrdf.dmpが一覧されます。右端のハンバーガー・メニューを開き、事前承認済リクエストの作成を実行します。


有効期限を短期間(1日など)に設定し直し、事前承認済リクエストの作成を実行します。


事前承認済リクエストのURLが表示されるので、コピーしてどこかに保存しておきます。Autonomous Database側からは、このURLよりダンプ・ファイルを取得します。


URLのコピーができたら、ダイアログを閉じます。

出力されたダンプ・ファイルをインポートします。

最初にAutonomous Database側でもグラフのデータを削除しておきます。

SQLワークショプSQLコマンドより、SEM_APIS.DROP_SEM_NETWORKを実行します。これはOracle Database 21c XEで実行したコマンドと全く同じです。


Autonomous Databaseのデータベース・アクションに、管理者であるADMINで接続します。開発SQLを開きます。

オブジェクト・ストレージにあるダンプ・ファイルをディレクトリDATA_PUMP_DIRへダウンロードします。DBMS_CLOUD.GET_OBJECTを呼び出します。object_uriとして、先ほどコピーした事前承認済のURLを指定します。
-- 入れ替える場合は、すでにあるファイルを削除する。
begin
    dbms_cloud.delete_file(
        directory_name => 'DATA_PUMP_DIR'
        , file_name => 'rdf.dmp');
end;
/

begin
    dbms_cloud.get_object(
        object_uri => 'https://objectstorage.ap-tokyo-1.oraclecloud.com/p/事前承認済のURL/o/rdf.dmp',
        directory_name => 'DATA_PUMP_DIR'
    );
end;

DataPump APIを使って、インポート処理を行います。以前に記載したDataPump APIの紹介記事をもとに、以下のスクリプトを実行します。




実行結果は以下になりました。いくつかエラーや警告が出ていますが、これから行なう作業には影響はありませんでした。
Master table "ADMIN"."IMP_APEXDEV" successfully loaded/unloaded
Starting "ADMIN"."IMP_APEXDEV":  
Processing object type SCHEMA_EXPORT/PRE_SCHEMA/PROCACT_SCHEMA
Processing object type SCHEMA_EXPORT/SEQUENCE/SEQUENCE
Processing object type SCHEMA_EXPORT/TABLE/TABLE
Table "APEXDEV"."NET1#RENAMED_APPTAB_RDF_MODEL_ID_1" exists. All dependent
metadata and data will be skipped due to table_exists_action of skip
Processing object type SCHEMA_EXPORT/TABLE/TABLE_DATA
. . imported "APEXDEV"."NET1#RDF_LINK$":"MODEL_2"        23.95 KB     219 rows
. . imported "APEXDEV"."NET1#RDF_VALUE$"                 23.67 KB      94 rows
*** Job percent done = 32
. . imported "APEXDEV"."NET1#RDF_RULEBASE$"              6.585 KB       9 rows
. . imported "APEXDEV"."NET1#RDF_CLIQUE$":"MODEL_0"          0 KB       0 rows
. . imported "APEXDEV"."NET1#RDF_COLLISION$"             5.070 KB       1 rows
*** Job percent done = 99
. . imported "APEXDEV"."NET1#RDF_CRS_URI$"               293.2 KB    5688 rows
. . imported "APEXDEV"."NET1#RDF_DELTA$":"MODEL_0"           0 KB       0 rows
. . imported "APEXDEV"."NET1#RDF_GRANT_INFO$"                0 KB       0 rows
. . imported "APEXDEV"."NET1#RDF_HIST$"                  7.328 KB       1 rows
. . imported "APEXDEV"."NET1#RDF_LINK$":"MODEL_0"            0 KB       0 rows
. . imported "APEXDEV"."NET1#RDF_LINK$":"MODEL_1"        12.67 KB      29 rows
. . imported "APEXDEV"."NET1#RDF_MODEL$_TBL"             8.109 KB       1 rows
. . imported "APEXDEV"."NET1#RDF_MODEL_INTERNAL$"        8.093 KB       1 rows
. . imported "APEXDEV"."NET1#RDF_NAMESPACE$"                 0 KB       0 rows
. . imported "APEXDEV"."NET1#RDF_NETWORK_INDEX_INTERNAL$"  14.77 KB      12 rows
. . imported "APEXDEV"."NET1#RDF_PARAMETER"              6.492 KB       2 rows
. . imported "APEXDEV"."NET1#RDF_PRECOMP$"               6.828 KB       1 rows
. . imported "APEXDEV"."NET1#RDF_PRECOMP_DEP$"           5.968 KB       3 rows
. . imported "APEXDEV"."NET1#RDF_PRED_STATS$":"MODEL_0"      0 KB       0 rows
. . imported "APEXDEV"."NET1#RDF_PRED_STATS$":"MODEL_1"      0 KB       0 rows
. . imported "APEXDEV"."NET1#RDF_RI_SHAD_2$"                 0 KB       0 rows
. . imported "APEXDEV"."NET1#RDF_RULE$"                  19.73 KB      20 rows
. . imported "APEXDEV"."NET1#RDF_SYSTEM_EVENT$"              0 KB       0 rows
. . imported "APEXDEV"."NET1#RDF_TERM_STATS$":"MODEL_0"      0 KB       0 rows
. . imported "APEXDEV"."NET1#RDF_TERM_STATS$":"MODEL_1"      0 KB       0 rows
. . imported "APEXDEV"."NET1#RDF_TS$"                        0 KB       0 rows
. . imported "APEXDEV"."NET1#SEM_INDEXTYPE_METADATA$"        0 KB       0 rows
Processing object type SCHEMA_EXPORT/TABLE/GRANT/OWNER_GRANT/OBJECT_GRANT
Processing object type SCHEMA_EXPORT/PROCEDURE/PROCEDURE
ORA-31684: Object type PROCEDURE:"APEXDEV"."NET1#SDO_RDF_PROC_DR_TRUNC_1"
already exists

Processing object type SCHEMA_EXPORT/PROCEDURE/GRANT/OWNER_GRANT/OBJECT_GRANT
Processing object type SCHEMA_EXPORT/PROCEDURE/ALTER_PROCEDURE
ORA-39111: Dependent object type
ALTER_PROCEDURE:"APEXDEV"."NET1#SDO_RDF_PROC_DR_TRUNC_1" skipped, base object
type PROCEDURE:"APEXDEV"."NET1#SDO_RDF_PROC_DR_TRUNC_1" already exists

Processing object type SCHEMA_EXPORT/VIEW/VIEW
ORA-31684: Object type VIEW:"APEXDEV"."FAMILY_RDF_DATA" already exists

Processing object type SCHEMA_EXPORT/VIEW/GRANT/OWNER_GRANT/OBJECT_GRANT
Processing object type SCHEMA_EXPORT/TABLE/INDEX/INDEX
Processing object type SCHEMA_EXPORT/TABLE/INDEX/FUNCTIONAL_INDEX/INDEX
Processing object type SCHEMA_EXPORT/TABLE/CONSTRAINT/CONSTRAINT
Processing object type SCHEMA_EXPORT/TABLE/INDEX/STATISTICS/INDEX_STATISTICS
Processing object type
SCHEMA_EXPORT/TABLE/INDEX/STATISTICS/FUNCTIONAL_INDEX/INDEX_STATISTICS
Processing object type SCHEMA_EXPORT/TABLE/CONSTRAINT/REF_CONSTRAINT
Processing object type SCHEMA_EXPORT/VIEW/TRIGGER
Processing object type SCHEMA_EXPORT/TABLE/STATISTICS/TABLE_STATISTICS
Processing object type SCHEMA_EXPORT/STATISTICS/MARKER
ORA-39082: Object type TRIGGER:"APEXDEV"."NET1#SDO_RDF_TRIG_AVIEW_1" created
with compilation warnings

*** Job percent done = 100
Job "ADMIN"."IMP_APEXDEV" completed with 4 error(s) at Thu Dec 2 06:54:12 2021
elapsed 0 00:00:25
Job has completed
Final job state = COMPLETED


PL/SQL procedure successfully completed.

Elapsed: 00:00:31.075

以上でRDFのデータのインポートが完了しました。


APEXアプリケーションの作成


人の名前を与えて、その人の子供と孫を一覧するAPEXアプリケーションを作成します。

アプリケーション作成ウィザードを実行し、空のアプリケーションを作成します。名前バインド変数を使ったRDF問合せとします。アプリケーションの作成をクリックします。


アプリケーションが作成されたら、ページ・デザイナホーム・ページを開きます。

リージョンの作成を行います。識別名前Childrenタイプ対話モード・レポートを選択します。ソース位置ローカル・データベースタイプSQL問合せを選択します。SQL問合せには、Oracle Database 21c XEで生成した子供を一覧するSQLを設定します。


ページ・アイテムの作成を行います。識別名前P1_PERSONタイプテキスト・フィールドとします。ラベル人名とします。


RDFグラフを検索するSELECT文に与える値を保持するページ・アイテムP1_VIDP1_TERMを作成します。これらの値はP1_PERSONから導出されます。タイプ非表示にし、設置保護された値OFFにします。


対話モード・レポートChildrenを選択し、送信するページ・アイテムとしてP1_VIDP1_TERMを指定します。


もう一つ対話モード・レポートのリージョンを作成します。識別名前Grandchildrenタイプ対話モード・レポートとし、SQL問合せとして、孫を一覧するSQLを設定します。送信するページ・アイテムとしてP1_VIDP1_TERMを指定します。


ページ・アイテムP1_PERSONに値が設定されたら、それを元にP1_VIDとP1_TERMを設定する動的アクションを作成します。

ページ・アイテムP1_PERSON上で動的アイテムの作成を実行します。識別名前検索条件の導出とします。タイミングはデフォルトで、イベント変更選択タイプアイテムアイテムP1_PERSONになります。P1_PERSONが変更されたときに、アクションが実行されます。


TRUEアクション識別アクションとして、サーバー側のコードを実行を選びます。設定言語としてPL/SQLを選択し、PL/SQLコードとして以下を記述します。


送信するアイテムとしてP1_PERSON戻すアイテムとしてP1_VIDP1_TERMを指定します。


バインド変数に与える値がP1_VID、P1_TERMに設定されたので、対話モード・レポートのリージョンChildren、Grandchildrenをリフレッシュします。

TRUEアクションを作成し、識別アクションリフレッシュ影響を受ける要素タイプリージョンリージョンとしてChildrenを選択します。もうひとつ同じTRUEアクションを作成し、そちらはリージョンとしてGrandchildrenを選択します。


以上でアプリケーションは完成です。実行すると最初のGIF画面のように動作します。

今回作成したアプリケーションのエクスポートを以下に置きました。
https://github.com/ujnak/apexapps/blob/master/exports/rdf-query-with-bind-variable.sql

Oracle RDF Graph Serverを実装すると、RDFを扱う一連の操作をREST API経由で呼び出すことができるようです。Autonomous Database単体では実行できなかったSPARQLも実行できます。Oracle RDF Graph Serverについてのマニュアルの記載はこちらです。SPARQL問合せキャッシュを参照すると、変換されたSQLにもアクセスできそうです。

グラフとして扱うことが自然なデータであれば、グラフとして扱う方がローコードのスタイルに合うように思います。幸いオラクル・データベースではグラフを扱うことができます。とはいえ、グラフの勉強は必要になります。

以上になります。

Oracle APEXのアプリケーション作成の参考になれば幸いです。

追記
  1. DataPumpを使ったエクスポート/インポートを行う際に、ローカルのデータベースとAutonomous Databaseでバージョンを合わせていませんでした。ローカルが21c、Autonomous Databaseは19cです。しかし、19cのExpress Editionはリリースされていないので、21cか18cかの選択になります。ライセンスを購入している場合は19cを使うと良いかと思います。MDSYS.SDO_RDFパッケージに含まれるプロシージャに違いがあるようです。
  2. やはりDataPumpでのデータ移行は強引なので(当然、サポートもされない)、Autonomous Databaseではユーザーがルールベースを追加することはできない前提でRDFナレッジ・グラフを使うのが妥当と言えます。