9 ユーザー定義の推論と問合せ

RDFセマンティク・グラフの拡張アーキテクチャによって、ユーザー定義の機能を追加できるようになりました。

ノート:

共有デプロイメントでAutonomous Databaseインスタンスを使用している場合、ユーザー定義の推論機能および問合せ機能には、Oracle JVMの有効化が必要です。Autonomous DatabaseインスタンスでOracle JVMを有効にするには、共有ExadataインフラストラクチャでのOracle Autonomous Databaseの使用Oracle Javaの使用を参照してください。

Oracle Database 12cリリース1 (12.1)で有効になった機能は次のとおりです。

  • 推論の拡張機能アーキテクチャによって、すでに提供されている推論のサポートに加えて、ユーザー定義の推論が使用可能になります。

  • 問合せの拡張アーキテクチャにより、SPARQL問合せで使用されるユーザー定義の関数と集計を追加できます。これはいずれも、SEM_MATCH関数およびSupport for Apache Jenaを介して実現されます。

ノート:

この章で説明する機能は、上級ユーザーを対象としています。「RDFセマンティク・グラフの概要」「OWLの概要」で説明される主要な概念と技術について、十分に理解していることが前提です。

9.1 ユーザー定義の推論

RDFセマンティク・グラフの推論の拡張アーキテクチャによって、すでに提供されている推論のサポートに加えて、ユーザー定義の推論が使用可能になります。

9.1.1 ユーザー定義の推論によって解決される問題と得られる利点

Oracle Database 12cリリース(12.1)よりも前のOracle Database推論エンジンでは、OWL 2 RL、RDFS、SKOS、SNOMED (コアEL)およびユーザー定義ルールに対するネイティブ・サポートが提供され、広範なアプリケーションと要件に対応していました。一方で、新しいRDFリソースを作成してルール推論プロセスに追加することができないという制限がありました。

Oracle Database 12c リリース(12.1)よりも前の機能と制限の例として、次の簡単な推論ルールを考えてみます。

?C   rdfs:subClassOf  ?D .
?x    rdf:type  ?C  .  ==>  ?x   rdf:type  ?D

前述のルールでは、インスタンスxのサブクラスCは、インスタンスCのスーパークラスDになる、としています。ルールに対応する部分は、2つの変数 ?x?Dを示しています。ただし、これらの変数はルールの前件部分に存在している必要があり、さらにこれらのRDFリソースはナレッジ・ベース内に存在している必要があります。いいかえると、たとえばJohnGraduateStudentとして存在しており、その公理によってGraduateStudentクラスはStudentクラスのサブクラスであると指定されている場合にのみ、JohnStudentであると導出されます。

もう1つの制限の例は、Oracle Database 12cリリース(12.1)よりも前の推論関数では、推論プロセスの新しいRDFリソースとしてフルネームを生成するために姓と名を組み合せることがサポートされていなかったことです。この要件は、具体的には、次のようなルールとして取得されます。

?x   :firstName  ?fn
?x   :lastName   ?ln  ==>  ?x  :fullName  concatenate(?fn ?ln)

Oracle Database 12cリリース1 (12.1)では、RDFセマンティク・グラフ推論拡張アーキテクチャによって、推論プロセスでユーザーが独自の推論拡張関数を実装し、これをネイティブの推論プロセスに統合できるようになりました。このアーキテクチャにより、次のことが実現されています。

  • 新しいRDFリソースの生成を必要とするルールをサポートします。

    たとえば、文字列または他の文字列操作の連結、数学的な計算およびWebサービス・コールアウトがあります。

  • 特定の既存のルールをカスタマイズされた最適化を使用して実装することができます。

    ネイティブOWL推論エンジンには、多数のルールのための最適化があり、これらのルールは様々な大規模なオントロジには効率的に動作しますが、試されてないいくつかの新しいオントロジについては、特定の推論コンポーネントをカスタマイズした最適化の方が効果的に動作する場合があります。この場合は、SEM_APIS.CREATE_ENTAILMENTコールの特定の推論コンポーネントを無効にし、 新しい最適化を実装する、カスタマイズした推論拡張機能関数を指定することができます(inf_ext_user_func_nameパラメータを使用)。

  • 洗練された推論機能を使用して推論エンジンを拡張することができます。

    たとえば、地理空間推論、時間間隔推論、テキスト解析機能のネイティブのデータベース推論プロセスへの統合があります。

9.1.2 ユーザー定義の推論のAPIサポート

ユーザー定義の推論のための主要なアプリケーション・プログラミング・インタフェース(API)は、SEM_APIS.CREATE_ENTAILMENTプロシージャの特に最後のパラメータです。

inf_ext_user_func_name  IN VARCHAR2 DEFAULT NULL

inf_ext_user_func_nameパラメータを指定すると、使用したい特定のロジックを実装する、1つ以上のユーザー定義の推論関数が指定されます。

9.1.2.1 ユーザー定義の推論関数の要件

SEM_APIS.CREATE_ENTAILMENTプロシージャへのコールで、inf_ext_user_func_nameパラメータに指定される各ユーザー定義の推論関数は、次のとおりである必要があります。

  • 名前は文字列SEM_INF_で始まります。

  • 起動者の権限ではなく定義者の権限で作成されます。(定義者権限と起動者権限については、『Oracle Databaseセキュリティ・ガイド』を参照してください。)

ユーザー定義の推論関数の形式は、次のSEM_INF_EXAMPLEという架空の関数の例に示すような形式です。

create or replace function sem_inf_example(
    src_tab_view         in  varchar2,
    resource_id_map_view in  varchar2,
    output_tab           in  varchar2,
    action               in  varchar2,
    num_calls            in  number,
    tplInferredLastRound in  number,
    options              in  varchar2 default null,
    optimization_flag    out number,
    diag_message         out varchar2
    )
return boolean
as
  pragma autonomous_transaction;
begin
  if (action = SDO_SEM_INFERENCE.INF_EXT_ACTION_START) then
    <... preparation work ...>  
  end if;
  if (action = SDO_SEM_INFERENCE.INF_EXT_ACTION_RUN) then
    <... actual inference logic ...>
    commit;
  end if;
  if (action = SDO_SEM_INFERENCE.INF_EXT_ACTION_END) then
    <... clean up ...> 
  end if;
return true;  -- succeed
end;
/
grant execute on sem_inf_example to MDSYS;

ユーザー定義関数の形式で、optimization_flag出力パラメータは、数値に対応する1つ以上のOracle定義の名前を指定できます。次の1つ以上を指定できます。

  • SDO_SEM_INFERENCE.INF_EXT_OPT_FLAG_NONEは、推論エンジンが拡張関数の最適化を有効にしないことを示します。(optimization_flagパラメータが設定されていないとき、これが推論エンジンのデフォルトの動作です。)

  • SDO_SEM_INFERENCE.INF_EXT_OPT_FLAG_ALL_IDSは、拡張関数によって推論されたすべてのトリプル/クワッドがリソースIDのみを使用することを示します。つまり、output_tab表にはリソースID(列gidsidpidおよびoid)のみが含まれ、語彙の値(列gspおよびoはすべてのNULLです)は含まれません。この最適化フラグを有効化すると、推論エンジンは、リソースID参照をスキップすることができます。

  • SDO_SEM_INFERENCE.INF_EXT_OPT_FLAG_NEWDATA_ONLYは、拡張関数によって推論されるすべてのトリプル/クワッドが新規であり、src_tab_view内に存在しないことを示します。この最適化フラグを有効化すると、推論エンジンでoutput_tab表とsrc_tab_viewとの間の重複チェックをスキップすることができます。src_tab_viewには、拡張関数から推論されたトリプル/クワッドと、前回の推論ラウンドからのトリプル/クワッドが含まれることに注意してください。

  • SDO_SEM_INFERENCE.INF_EXT_OPT_FLAG_UNIQDATA_ONLYは、拡張関数によって推論されるすべてのトリプル/クワッドが一意であり、output_tab表内に存在しないことを示します。この最適化フラグを有効化すると、推論エンジンでoutput_tab表内の重複チェックをスキップすることができます(たとえば拡張関数で2回推論された同じトリプルをチェックする必要がありません)。拡張関数の各推論ラウンドの最初のoutput_tab表は空であり、データの一意性を保つ必要があるのは現在の推論ラウンドでのみであることに注意してください。

  • SDO_SEM_INFERENCE.INF_EXT_OPT_FLAG_IGNORE_NULLは、主語、述語または目的語のリソースがNULLのとき、推論されたトリプルまたはクワッドが推論エンジンで無視されることを示します。推論エンジンは、output_tab表で両方の列がNULLのとき、リソースをNULLとみなします (たとえば、s列とsid列が両方ともNULLの場合、主語はNULL)。この最適化フラグを有効化すると、推論エンジンでoutput_tab表の無効なトリプル/クワッドをスキップすることができます。推論エンジンは、NULLのグラフ列(ggid)をデフォルトのグラフとして解釈することに注意してください。

optimization_flag出力パラメータに複数の値を指定するには、プラス記号(+)を使用して値を連結します。たとえば、次のようにします。

optimization_flag := SDO_SEM_INFERENCE.INF_EXT_OPT_FLAG_ALL_IDS +
                     SDO_SEM_INFERENCE.INF_EXT_OPT_FLAG_NEWDATA_ONLY +
                     SDO_SEM_INFERENCE.INF_EXT_OPT_FLAG_UNIQDATA_ONLY;

optimization_flag出力パラメータの使用の詳細は、「例3: パフォーマンスの最適化」を参照してください。

9.1.3 ユーザー定義の推論拡張関数の例

次の例に、ユーザー定義の推論拡張関数を使用して伴意を作成する方法を示します。

最初の3つの例では、モデルEMPLOYEESが存在し、(Turtle形式で表示された)次のセマンティク・データが含まれていることを前提としています。

:John   :firstName  "John"  ;
        :lastName   "Smith" .
 
:Mary   :firstName  "Mary"  ;
        :lastName   "Smith" ;
        :name       "Mary Smith" .
 
:Alice  :firstName  "Alice" .
 
:Bob    :firstName  "Bob" ;
        :lastName   "Billow" .

ユーザー定義の推論拡張ファンクションの作成に関する要件とガイドラインについては、「ユーザー定義推論のAPIサポート」を参照してください。

9.1.3.1 例1: 静的トリプルの追加

ユーザー定義の推論の拡張関数で新しいデータを推論する最も基本的な方法は、静的データの追加です。静的データは、モデルに既存のデータに依存しません。このことは、ユーザー定義の推論の拡張関数にとって一般的な例でありませんが、伴意へのトリプルの追加に関する基本を示しています。既存のオントロジを拡張するための準備フェーズ(つまりaction='START')では、静的データの挿入の方が、より一般的に実行されます。

次のユーザー定義の推論拡張関数(sem_inf_static)は、伴意に3つの静的トリプルを追加します。

-- this user-defined rule adds static triples
create or replace function sem_inf_static(
    src_tab_view         in  varchar2,
    resource_id_map_view in  varchar2,
    output_tab           in  varchar2,
    action               in  varchar2,
    num_calls            in  number,
    tplInferredLastRound in  number,
    options              in  varchar2 default null,
    optimization_flag    out number,
    diag_message         out varchar2
    )
return boolean
as
  query varchar2(4000);
  pragma autonomous_transaction;
begin
  if (action = 'RUN') then
    -- generic query we use to insert triples
    query := 
      'insert /*+ parallel append */ into ' || output_tab || 
      ' ( s,  p,  o) VALUES ' ||
      ' (:1, :2, :3) ';
 
    -- execute the query with different values
    execute immediate query using 
      '<http://example.org/S1>', '<http://example.org/P2>', '"O1"';
 
    execute immediate query using
      '<http://example.org/S2>', '<http://example.org/P2>', '"2"^^xsd:int';
 
    -- duplicate quad
    execute immediate query using
      '<http://example.org/S2>', '<http://example.org/P2>', '"2"^^xsd:int';
 
    execute immediate query using
      '<http://example.org/S3>', '<http://example.org/P3>', '"3.0"^^xsd:double';
 
    -- commit our changes
    commit;
  end if;
 
  -- return true to indicate success
  return true;
end sem_inf_static;
/
show errors;

sem_inf_static関数は、SQL挿入問合せを実行(挿入のターゲット表としてoutput_tabを使用)することで、新しいデータを挿入します。output_tab表には、現在のコール(num_callsパラメータを参照)中にsem_inf_static関数によって追加されるトリプルのみが含まれることになります。推論エンジンは、ユーザー定義の推論拡張関数を常に3回以上(アクション・パラメータ('START''RUN'および'END')で取り得る値ごとに1回)コールします。sem_inf_staticは準備またはクリーンアップを実行する必要がないため、関数ではRUNフェーズでデータが追加されるのみです。拡張関数では、現在の推論ラウンド中に推論されるデータに応じて、RUNフェーズの間に複数回コールできます。

sem_inf_static関数では、既存のトリプルに対する(重複トリプルを防ぐための)チェックは行いませんが、結果の伴意に重複トリプルを生成することはありません。重複は推論エンジンによってoutput_tab表(拡張関数によって挿入されるデータ)からフィルタ処理された後、最終的な伴意(モデルまたは他の推論データ)からもフィルタ処理されます。適切な最適化フラグを設定する(optimization_flagパラメータを使用する)ことで、この便利な機能は使用されず、パフォーマンスが向上します。(最適化フラグの詳細は、「例3: パフォーマンスの最適化」を参照してください。)

output_tabの表定義にはグラフ名の列が表示されていますが、グローバル推論( SEM_APIS.CREATE_ENTAILMENTのデフォルトの動作)と名前付きグラフ・グローバル推論(NGGI)を実行すると、推論エンジンは、拡張関数によって追加されるトリプルですべてのグラフ名を無視して、オーバーライドします。ユーザー定義の拡張ファンクションで、特定の名前付きグラフにトリプルを追加するには、NGLI (名前付きグラフ・ローカル推論)を使用します。NGLIでは、すべてのトリプルが名前付きグラフに属している必要があります(output_tabgid列とg列の両方がNULLではないことが必要です)。

MDSYSユーザーには、sem_inf_static関数を推論に使用するため、この実行権限が必要です。次の例は、sem_inf_static関数に適切な権限を付与し、(OWLPRIME推論ロジックとともに)関数を使用して伴意を作成する方法を示しています。

-- grant appropriate privileges
grant execute on sem_inf_static to mdsys;
 
-- create the entailment
begin
  sem_apis.create_entailment( 
    'EMPLOYEES_INF'
  , sem_models('EMPLOYEES')
  , sem_rulebases('OWLPRIME')
  , passes => SEM_APIS.REACH_CLOSURE
  , inf_ext_user_func_name => 'sem_inf_static' 
);
end;
/

次の例は、新しい伴意データを示しています。

-- formatting
column s format a23;
column p format a23;
column o format a23;
set linesize 100;
 
-- show results
select s, p, o from table(SEM_MATCH(
    'select ?s ?p ?o where { ?s ?p ?o } order by ?s ?p ?o'
  , sem_models('EMPLOYEES')
  , sem_rulebases('OWLPRIME')
  , null, null, null
  , 'INF_ONLY=T'));

前述の問合せは、sem_inf_staticによって追加される3つの一意の静的トリプルを重複なしで戻します。

S                      P                      O
---------------------- ---------------------- -----------------------
http://example.org/S1  http://example.org/P2  O1
http://example.org/S2  http://example.org/P2  2
http://example.org/S3  http://example.org/P3  3E0
9.1.3.2 例2: 動的トリプルの追加

静的データの追加は有用ですが、通常は準備(action='START')フェーズの間に実行されます。動的データを追加するには、モデル内の既存のデータを参照し、その既存のデータに基づいて新しいデータを生成します。これは、ユーザー定義の推論拡張関数の最も一般的なケースです。

次のユーザー定義の推論拡張関数(sem_inf_dynamic)は、従業員のフルネームを表す新しいトリプルを作成するために、姓と名を連結します。

-- this user-defined rule adds static triples
create or replace function sem_inf_dynamic(
    src_tab_view         in  varchar2,
    resource_id_map_view in  varchar2,
    output_tab           in  varchar2,
    action               in  varchar2,
    num_calls            in  number,
    tplInferredLastRound in  number,
    options              in  varchar2 default null,
    optimization_flag    out number,
    diag_message         out varchar2
    )
return boolean
as
  firstNamePropertyId number;
  lastNamePropertyId  number;
  fullNamePropertyId  number;
 
  sqlStmt    varchar2(4000);
  insertStmt varchar2(4000);
  pragma autonomous_transaction;
begin
  if (action = 'RUN') then
    -- retrieve ID of resource that already exists in the data (will
    -- throw exception if resource does not exist). These will improve
    -- performance of our SQL queries. 
    firstNamePropertyId := sdo_sem_inference.oracle_orardf_res2vid('http://example.org/firstName');
    lastNamePropertyId  := sdo_sem_inference.oracle_orardf_res2vid('http://example.org/lastName');
    fullNamePropertyId  := sdo_sem_inference.oracle_orardf_res2vid('http://example.org/name');
 
    -- SQL query to find all employees and their first and last names
    sqlStmt :=
      'select ids1.sid employeeId,
              values1.value_name firstName,
              values2.value_name lastName
       from   ' || resource_id_map_view || ' values1,
              ' || resource_id_map_view || ' values2,
              ' || src_tab_view || '         ids1,
              ' || src_tab_view || '         ids2
       where  ids1.sid = ids2.sid 
         AND  ids1.pid = ' || to_char(firstNamePropertyId,'TM9') || ' 
         AND  ids2.pid = ' || to_char(lastNamePropertyId,'TM9')  || ' 
         AND  ids1.oid = values1.value_id 
         AND  ids2.oid = values2.value_id 
       /* below ensures we have NEWDATA (a no duplicate optimization flag) */
         AND  not exists
               (select 1 
                from   ' || src_tab_view || ' 
                where  sid = ids1.sid AND 
                       pid = ' || to_char(fullNamePropertyId,'TM9') || ')';
 
    -- create the insert statement that concatenates the first and
    -- last names from our sqlStmt into a new triple.
    insertStmt :=
      'insert /*+ parallel append */ 
       into ' || output_tab || ' (sid, pid, o) 
       select employeeId, ' || to_char(fullNamePropertyId,'TM9') || ', ''"'' || firstName || '' '' || lastName ||  ''"''
       from   (' || sqlStmt || ')';
 
    -- execute the insert statement
    execute immediate insertStmt;
 
    -- commit our changes
    commit;
 
    -- set our optimization flags indicating we already checked for
    -- duplicates in the model (src_tab_view)
    optimization_flag := SDO_SEM_INFERENCE.INF_EXT_OPT_FLAG_NEWDATA_ONLY;
  end if;
 
  -- return true to indicate success
  return true;
end sem_inf_dynamic;
/
show errors;

sem_inf_dynamic関数は、2つの主要なステップを使用して新しいデータを挿入します。最初に関数は、既存のデータからすべての姓と名を収集するSQL問合せを構築します。このSQL問合せをsqlStmt変数に格納します。次に関数は、収集する姓と名を使用して新しいトリプルを挿入し、従業員ごとにフルネームを生成します。このSQL問合せをinsertStmt変数に格納します。副問合せでINSERTを実行しているため、insertStmt問合せにはsqlStmt問合せが含まれることに注意してください。

sqlStmt問合せは、リソース・ビュー(resource_id_map_view)と既存のデータ・ビュー(src_tab_view)の2つの主なビュー全体で結合を実行します。既存のデータ・ビューにはすべての既存のトリプルが含まれますが、それらのトリプルの値は、字句の値ではなく数値IDを使用して格納されます。sqlStmt問合せは従業員の姓と名の字句の値を抽出する必要があるため、リソース・ビューで2回結合を実行します(1回は姓でもう1回は名)。

sqlStmt問合せには、パフォーマンスの向上に役立てるためのPARALLEL SQLヒントが含まれます。均等なハードウェア構成でのパラレル実行は、パフォーマンスを大幅に向上させることができます。(詳細は、「例3: パフォーマンスの最適化」を参照してください。)

insertStmt問合せでは、既存のデータ・ビュー(src_tab_view)にすでに存在するトリプルが追加されることのないように、重複チェックも実行します。関数は、このチェックをINF_EXT_OPT_FLAG_NEWDATA_ONLY最適化フラグを有効にすることで実行したことを示しています。拡張関数内部でチェックを実行すると、推論の全体的なパフォーマンスが向上します。既存のデータ・ビューには、sem_inf_dynamic関数によって現在追加されている新しいトリプルが含まれないため、output_tab表内にはまだ重複が存在している場合があることに注意してください。sem_inf_dynamic関数でoutput_tab表の重複を詳細にチェックする場合、INF_EXT_OPT_FLAG_UNIQUEDATA_ONLY最適化フラグを有効にすることもできます。

両方のSQL問合せは、結合と挿入を実行するためにRDFリソースの数値IDを使用します。字句の値ではなくIDを使用すると、問合せのパフォーマンスが向上します。sem_inf_dynamic関数では、使用する予定の字句の値のIDを調べることによるパフォーマンスの利点を活用します。この場合関数は、名、姓およびフルネーム・プロパティを表す3つのURIを調べます。sem_inf_dynamic関数が、すべての新しいトリプルを完全にIDとして挿入した場合、INF_EXT_OPT_FLAG_ALL_IDS最適化フラグを有効にできます。ただし、この場合、新しいトリプルにはそれぞれ単一の新しい字句値(従業員のフルネーム)が含まれます。

sem_inf_dynamic関数で伴意を作成するには、次のように、MDSYSユーザーに実行権限を付与し、SEM_APIS.CREATE_ENTAILMENTプロシージャに関数名を渡します。

-- grant appropriate privileges
grant execute on sem_inf_dynamic to mdsys;
 
-- create the entailment
begin
  sem_apis.create_entailment( 
    'EMPLOYEES_INF'
  , sem_models('EMPLOYEES')
  , sem_rulebases('OWLPRIME')
  , passes => SEM_APIS.REACH_CLOSURE
  , inf_ext_user_func_name => 'sem_inf_dynamic' 
);
end;
/

伴意には、sem_inf_dynamicによって追加される次の2つの新しいトリプルを含める必要があります。

S                        P                        O
------------------------ ------------------------ -----------------------
http://example.org/Bob   http://example.org/name  Bob Billow
http://example.org/John  http://example.org/name  John Smith

前述の例で、sem_inf_dynamic関数がMary Smithのフルネームを推論しなかったことに注意してください。Mary Smithは、既存のデータですでにフルネームが指定されていたためです。

9.1.3.3 例3: パフォーマンスの最適化

いくつかの技術によって、推論拡張関数のパフォーマンスを向上させることができます。そのような技術の1つは、問合せにおいて、リソースの字句の値ではなく数値IDを使用することです。リソースIDのみを使用することにより、拡張関数はリソース・ビュー(resource_id_map_view)を使用する結合を回避し、問合せパフォーマンスを大幅に向上させることができます。推論拡張関数で、新しいトリプルをoutput_tab表に追加する際にリソースIDを使用する(つまり、output_tab表のgid列、sid列、pid列およびoid列のみを使用する)ことによっても、さらにパフォーマンスを向上できます。

次のユーザー定義の推論拡張関数(sem_inf_related)は、同じ姓を持つ従業員について、新規プロパティ:possibleRelativeを推論します。そのような従業員を見つけるためのSQL問合せは、リソースIDのみを使用 (字句の値、リソース・ビューとの結合を使用しない)します。また、この例の推論拡張関数は、リソースIDのみを使用して新しいトリプルを挿入し、関数でINF_EXT_OPT_FLAG_ALL_IDS最適化フラグを有効にすることができます。

-- this user-defined rule adds static triples
create or replace function sem_inf_related(
    src_tab_view         in  varchar2,
    resource_id_map_view in  varchar2,
    output_tab           in  varchar2,
    action               in  varchar2,
    num_calls            in  number,
    tplInferredLastRound in  number,
    options              in  varchar2 default null,
    optimization_flag    out number,
    diag_message         out varchar2
    )
return boolean
as
  lastNamePropertyId  number;
  relatedPropertyId   number;
 
  sqlStmt    varchar2(4000);
  insertStmt varchar2(4000);
  pragma autonomous_transaction;
begin
  if (action = 'RUN') then
    -- retrieve ID of resource that already exists in the data (will
    -- throw exception if resource does not exist).
    lastNamePropertyId := sdo_sem_inference.oracle_orardf_res2vid('http://example.org/lastName');
 
    -- retreive ID of resource or generate a new ID if resource does
    -- not already exist
    relatedPropertyId := sdo_sem_inference.oracle_orardf_add_res('http://example.org/possibleRelative');
 
    -- SQL query to find all employees that share a last name
    sqlStmt :=
      'select ids1.sid employeeId,
              ids2.sid relativeId
       from   ' || src_tab_view || '         ids1,
              ' || src_tab_view || '         ids2
       where  ids1.pid = ' || to_char(lastNamePropertyId,'TM9') || ' 
         AND  ids2.pid = ' || to_char(lastNamePropertyId,'TM9') || ' 
         AND  ids1.oid  = ids2.oid 
       /* avoid employees related to themselves */
         AND  ids1.sid != ids2.sid 
       /* below ensures we have NEWDATA (a no duplicate optimization flag) */
         AND  not exists
               (select 1 
                from   ' || src_tab_view || ' 
                where  sid = ids1.sid 
                  AND  pid = ' || to_char(relatedPropertyId,'TM9') || ' 
                  AND  oid = ids2.sid) 
       /* below ensures we have UNIQDATA (a no duplicate optimization flag) */
         AND  not exists
               (select 1 
                from   ' || output_tab || ' 
                where  sid = ids1.sid 
                  AND  pid = ' || to_char(relatedPropertyId,'TM9') || ' 
                  AND  oid = ids2.sid)';
 
    -- create the insert statement that only uses resource IDs
    insertStmt :=
      'insert /*+ parallel append */ 
       into ' || output_tab || ' (sid, pid, oid) 
       select employeeId, ' || to_char(relatedPropertyId,'TM9') || ', relativeId
       from   (' || sqlStmt || ')';
 
    -- execute the insert statement
    execute immediate insertStmt;
 
    -- commit our changes
    commit;
 
    -- set flag indicating our new triples
    --   1) are specified using only IDs
    --   2) produce no duplicates with the model (src_tab_view)
    --   3) produce no duplicates in the output (output_tab)
    optimization_flag := SDO_SEM_INFERENCE.INF_EXT_OPT_FLAG_ALL_IDS +
                         SDO_SEM_INFERENCE.INF_EXT_OPT_FLAG_NEWDATA_ONLY +
                         SDO_SEM_INFERENCE.INF_EXT_OPT_FLAG_UNIQDATA_ONLY;
  end if;
 
  -- return true to indicate success
  return true;
end sem_inf_related;
/
show errors;

sem_inf_related関数には、前述の例と少数の重要な違いがあります。1つ目に、sem_inf_related関数は、リソースIDのみを使用して問合せを行い、リソースIDのみを使用して新しいトリプルを挿入します。output_tab表に追加されたすべてのトリプルはリソースIDのみを使用するため、関数はINF_EXT_OPT_FLAG_ALL_IDS最適化フラグを有効にできます。パフォーマンスを最適化するため、ファンクションは字句の値全体にリソースIDを使用する必要があります。ただし、これができない場合があります。例2: 動的トリプルの追加のように、新しい字句の値を生成するために字句の値を連結する場合です。例2: 動的トリプルの追加のような場合、SQL問合せでoracle_orardf_res2vidへのコールを埋め込むよりも、リソース・ビュー(resource_id_map_view)を使用して結合することを、通常はお薦めします。これは、別の表を使用した結合とは対照的に、取り得る値ごとにファンクションをコールするオーバーヘッドによります。

sem_inf_related関数のもう1つの大きな違いは、oracle_orardf_add_res関数(oracle_orardf_res2vidと比較して)の使用です。res2vid関数とは異なり、リソースが存在しない場合は、add_res関数がリソース・ビュー(resource_id_map_view)にリソースを追加します。リソース・ビューにリソースを追加することが重要でない場合、推論拡張機能関数はadd_res関数を使用する必要があります。関数を複数回コールしても、重複エントリはリソース・ビューに生成されません。

最後の重要な違いは、SQL問合せに追加されたNOT EXISTS 句です。最初のNOT EXISTS句は、すでにモデルに存在するトリプルまたは他のルール(src_tab_view)によって、推論されるトリプルが重複するかもしれないトリプルの追加を回避しています。このような重複をチェックすることで、sem_inf_relatedINF_EXT_OPT_FLAG_NEWDATA_ONLY最適化フラグを有効にすることができます。2番目のNOT EXISTS句は、現在の推論ラウンド中(num_callsパラメータを参照)の間にsem_inf_related関数によってoutput_tab表に追加されたトリプルが重複するかもしれないトリプルを追加することを避けます。このような重複をチェックすることで、sem_inf_relatedINF_EXT_OPT_FLAG_UNIQDATA_ONLY最適化フラグを有効にすることができます。

sem_inf_dynamicの例と同様に、sem_inf_relatedの例は、その挿入文でPARALLEL SQL問合せヒントを使用します。均等なハードウェア構成でのパラレル実行は、パフォーマンスを大幅に向上させることができます。データ集中型のアプリケーションの場合、通常、適切な入出力サブシステムは、システム全体のパフォーマンスに対する重要な構成要素です。

sem_inf_dynamic関数で伴意を作成するには、次のように、MDSYSユーザーに実行権限を付与し、SEM_APIS.CREATE_ENTAILMENTプロシージャに関数名を渡します。

-- grant appropriate privileges
grant execute on sem_inf_related to mdsys;
 
-- create the entailment
begin
  sem_apis.create_entailment( 
    'EMPLOYEES_INF'
  , sem_models('EMPLOYEES')
  , sem_rulebases('OWLPRIME')
  , passes => SEM_APIS.REACH_CLOSURE
  , inf_ext_user_func_name => 'sem_inf_related' 
);
end;
/

伴意には、sem_inf_relatedによって追加される次の2つの新しいトリプルを含める必要があります。

S                        P                                    O
------------------------ ------------------------------------ ------------------------
http://example.org/John  http://example.org/possibleRelative  http://example.org/Mary
http://example.org/Mary  http://example.org/possibleRelative  http://example.org/John
9.1.3.4 例4: 時間推論(いくつかの関連する例)

ユーザー定義の拡張関数は、特定のデータ型(xsd:dateTimeなど)をトリプルでより利用しやすくすることができます。たとえばユーザー定義の拡張関数では、2つのxsd:dateTime値の違いに基づいてトリプル間の関係を推論することが可能です。この項の3つの例は、2つの異なる時間推論ルールと、これらを1つの伴意に結合する方法を調査します。例では、モデルEVENTEVENT_ONTが存在し、次のセマンティク・データが含まれることを前提とします。

EVENT_ONT

@prefix owl:  <http://www.w3.org/2002/07/owl#> .
@prefix rdf:  <http://www.w3.org/1999/02/22-rdf-syntax-ns#> .
@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .
@prefix xsd:  <http://www.w3.org/2001/XMLSchema#> .
@prefix :     <http://example.org/event/> .
 
# we model two types of events
:Meeting      rdfs:subClassOf :Event .
:Presentation rdfs:subClassOf :Event .
 
# events have topics
:topic        rdfs:domain     :Event .
 
# events have start and end times 
:startTime    rdfs:domain     :Event ;
              rdfs:range      xsd:dateTime .
:endTime      rdfs:domain     :Event ;
              rdfs:range      xsd:dateTime .
 
# duration (in minutes) of an event
:lengthInMins rdfs:domain      :Event ;
              rdfs:range       xsd:integer .
 
# overlaps property identifies conflicting events
:overlaps     rdfs:domain      :Event ;
              rdf:type         owl:SymmetricProperty .
:noOverlap    rdfs:domain      :Event ;
              rdf:type         owl:SymmetricProperty .

EVENT_TBOX

@prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> .
@prefix xsd: <http://www.w3.org/2001/XMLSchema#> .
@prefix :    <http://example.org/event/> .
 
:m1 rdf:type   :Meeting ;
    :topic     "Beta1 launch" ;
    :startTime "2012-04-01T09:30:00-05:00"^^xsd:dateTime ;
    :endTime   "2012-04-01T11:00:00-05:00"^^xsd:dateTime .
 
:m2 rdf:type   :Meeting ;
    :topic     "Standards compliance" ;
    :startTime "2012-04-01T12:30:00-05:00"^^xsd:dateTime ;
    :endTime   "2012-04-01T13:30:00-05:00"^^xsd:dateTime .
 
:p1 rdf:type   :Presentation ;
    :topic     "OWL Reasoners" ;
    :startTime "2012-04-01T11:00:00-05:00"^^xsd:dateTime ;
    :endTime   "2012-04-01T13:00:00-05:00"^^xsd:dateTime .

例は次のとおりです。

9.1.3.4.1 例4a: 期間ルール

次のユーザー定義の推論拡張関数(sem_inf_durations)は、イベントの開始時刻と終了時刻を指定して、イベント期間を分単位で推論します。たとえば、午前9時30分に開始し、午前11時00分に終了するイベントの期間は90分です。次の拡張関数は、イベントごとに開始時刻と終了時刻を抽出し、xsd:dateTime値をOracleタイムスタンプに変換し、タイムスタンプの差を計算します。この拡張関数は、タイムゾーンを処理できます。

create or replace function sem_inf_durations(
    src_tab_view         in  varchar2,
    resource_id_map_view in  varchar2,
    output_tab           in  varchar2,
    action               in  varchar2,
    num_calls            in  number,
    tplInferredLastRound in  number,
    options              in  varchar2 default null,
    optimization_flag    out number,
    diag_message         out varchar2
    )
return boolean
as
  eventClassId        number;
  rdfTypePropertyId   number;
  startTimePropertyId number;
  endTimePropertyId   number;
  durationPropertyId  number;
 
  xsdTimeFormat       varchar2(100);
  sqlStmt             varchar2(4000);
  insertStmt          varchar2(4000);
 
  pragma autonomous_transaction;
begin
  if (action = 'RUN') then
    -- retrieve ID of resource that already exists in the data (will
    -- throw exception if resource does not exist).
    eventClassId        := sdo_sem_inference.oracle_orardf_res2vid('http://example.org/event/Event');
    startTimePropertyId := sdo_sem_inference.oracle_orardf_res2vid('http://example.org/event/startTime');
    endTimePropertyId   := sdo_sem_inference.oracle_orardf_res2vid('http://example.org/event/endTime');
    durationPropertyId  := sdo_sem_inference.oracle_orardf_res2vid('http://example.org/event/lengthInMins');
    rdfTypePropertyId   := sdo_sem_inference.oracle_orardf_res2vid('http://www.w3.org/1999/02/22-rdf-syntax-ns#type');
 
    -- set the TIMESTAMP format we will use to parse XSD times
    xsdTimeFormat := 'YYYY-MM-DD"T"HH24:MI:SSTZH:TZM';
 
    -- query we use to extract the event ID and start/end times.  
    sqlStmt := 
      'select ids1.sid eventId,
              TO_TIMESTAMP_TZ(values1.value_name,''YYYY-MM-DD"T"HH24:MI:SSTZH:TZM'') startTime,
              TO_TIMESTAMP_TZ(values2.value_name,''YYYY-MM-DD"T"HH24:MI:SSTZH:TZM'') endTime
       from   ' || resource_id_map_view || ' values1,
              ' || resource_id_map_view || ' values2,
              ' || src_tab_view || '         ids1,
              ' || src_tab_view || '         ids2,
              ' || src_tab_view || '         ids3
       where  ids1.sid = ids3.sid 
         AND  ids3.pid = ' || to_char(rdfTypePropertyId,'TM9') || ' 
         AND  ids3.oid = ' || to_char(eventClassId,'TM9')      || ' 
         AND  ids1.sid = ids2.sid 
         AND  ids1.pid = ' || to_char(startTimePropertyId,'TM9') || ' 
         AND  ids2.pid = ' || to_char(endTimePropertyId,'TM9')   || ' 
         AND  ids1.oid = values1.value_id 
         AND  ids2.oid = values2.value_id 
       /* ensures we have NEWDATA */
         AND  not exists
               (select 1 
                from   ' || src_tab_view || ' 
                where  sid = ids3.sid 
                  AND  pid = ' || to_char(durationPropertyId,'TM9') || ') 
       /* ensures we have UNIQDATA */
         AND  not exists
               (select 1 
                from   ' || output_tab || ' 
                where  sid = ids3.sid 
                  AND  pid = ' || to_char(durationPropertyId,'TM9') || ')';
 
    -- compute the difference (in minutes) between the two Oracle
    -- timestamps from our sqlStmt query.  Store the minutes as
    -- xsd:integer.
    insertStmt :=
      'insert /*+ parallel append */ into ' || output_tab || ' (sid, pid, o) 
       select eventId, 
              ' || to_char(durationPropertyId,'TM9') || ', 
              ''"'' || minutes || ''"^^xsd:integer''
       from   (
         select eventId,
                (extract(day    from (endTime - startTime))*24*60 +
                 extract(hour   from (endTime - startTime))*60 +
                 extract(minute from (endTime - startTime))) minutes
         from   (' || sqlStmt || '))';
 
    -- execute the query
    execute immediate insertStmt;
 
    -- commit our changes
    commit;
  end if;
 
  -- we already checked for duplicates in src_tab_view (NEWDATA) and
  -- in output_tab (UNIQDATA)
  optimization_flag := SDO_SEM_INFERENCE.INF_EXT_OPT_FLAG_NEWDATA_ONLY +
                       SDO_SEM_INFERENCE.INF_EXT_OPT_FLAG_UNIQDATA_ONLY;
 
  -- return true to indicate success
  return true;
 
  -- handle any exceptions
  exception 
    when others then
      diag_message := 'error occurred: ' || SQLERRM;
      return false;
end sem_inf_durations;
/
show errors;

sem_inf_durations関数は、イベント期間を計算するために、組込みのOracle時間関数を使用します。最初にTO_TIMESTAMP_TZ関数を使用してxsd:dateTimeリテラル値をOracle TIMESTAMPオブジェクトに変換します。2つのOracle TIMESTAMPオブジェクトの差分を取ることで、時間間隔を表すINTERVALオブジェクトを生成します。sem_inf_durations関数は、EXTRACT演算子を使用して、期間間隔から日、時間および分を抽出することによって、各イベントの期間を分単位で計算します。

sem_inf_durations関数は、既存のモデル(src_tab_view)のデータとoutput_tab表のデータの両方に対して重複をチェックするため、INF_EXT_OPT_FLAG_NEWDATA_ONLYINF_EXT_OPT_FLAG_UNIQDATA_ONLYの最適化フラグを有効にできます。(最適化フラグの詳細は、「例3: パフォーマンスの最適化」を参照してください。)

前述の例とは異なり、sem_inf_durationsには例外ハンドラが含まれていることに注意してください。例外ハンドラは、ユーザー定義の推論拡張関数での問題をデバッグする場合に役立ちます。有用なデバッグ・メッセージを生成するには、拡張関数の例外を捕捉し、エラーを反映するdiag_messageパラメータを設定して、拡張関数の実行中にエラーが発生したことを示すFALSEを戻します。sem_inf_durations関数は、すべての例外を捕捉して、diag_messageの値を例外メッセージに設定します。

sem_inf_durations関数で伴意を作成するには、次のように、MDSYSユーザーに実行権限を付与し、SEM_APIS.CREATE_ENTAILMENTプロシージャに関数名を渡します。

-- grant appropriate privileges
grant execute on sem_inf_durations to mdsys;
 
-- create the entailment
begin
  sem_apis.create_entailment( 
    'EVENT_INF'
  , sem_models('EVENT', 'EVENT_ONT')
  , sem_rulebases('OWLPRIME')
  , passes => SEM_APIS.REACH_CLOSURE
  , inf_ext_user_func_name => 'sem_inf_durations' 
);
end;
/

伴意には、OWLPRIMEによって推論されるトリプルに加えて、sem_inf_durationsによって追加される次の3つの新しいトリプルを含める必要があります。

S                            P                                      O
---------------------------- -------------------------------------- ---------
http://example.org/event/m1  http://example.org/event/lengthInMins  90
http://example.org/event/m2  http://example.org/event/lengthInMins  60
http://example.org/event/p1  http://example.org/event/lengthInMins  120
9.1.3.4.2 例4b: オーバーラップ・ルール

次のユーザー定義の推論拡張関数(sem_inf_overlap)は、2つのイベントが重なるかどうかを推測します。一方のイベントの進行中に、一方のイベントが開始する場合、2つのイベントは重なります。関数はイベントのペアごとに開始時刻と終了時刻を抽出し、xsd:dateTimeの値をOracleタイムスタンプに変換して、1つのイベントが他方の進行中に開始するかどうかを計算します。

create or replace function sem_inf_overlap(
    src_tab_view         in  varchar2,
    resource_id_map_view in  varchar2,
    output_tab           in  varchar2,
    action               in  varchar2,
    num_calls            in  number,
    tplInferredLastRound in  number,
    options              in  varchar2 default null,
    optimization_flag    out number,
    diag_message         out varchar2
    )
return boolean
as
  eventClassId        number;
  rdfTypePropertyId   number;
  startTimePropertyId number;
  endTimePropertyId   number;
  overlapsPropertyId  number;
  noOverlapPropertyId number;
 
  xsdTimeFormat       varchar2(100);
  sqlStmt             varchar2(4000);
  insertStmt          varchar2(4000);
 
  pragma autonomous_transaction;
begin
  if (action = 'RUN') then
    -- retrieve ID of resource that already exists in the data (will
    -- throw exception if resource does not exist).
    eventClassId        := sdo_sem_inference.oracle_orardf_res2vid('http://example.org/event/Event');
    startTimePropertyId := sdo_sem_inference.oracle_orardf_res2vid('http://example.org/event/startTime');
    endTimePropertyId   := sdo_sem_inference.oracle_orardf_res2vid('http://example.org/event/endTime');
    overlapsPropertyId  := sdo_sem_inference.oracle_orardf_res2vid('http://example.org/event/overlaps');
    noOverlapPropertyId := sdo_sem_inference.oracle_orardf_res2vid('http://example.org/event/noOverlap');
    rdfTypePropertyId   := sdo_sem_inference.oracle_orardf_res2vid('http://www.w3.org/1999/02/22-rdf-syntax-ns#type');
 
    -- set the TIMESTAMP format we will use to parse XSD times
    xsdTimeFormat := 'YYYY-MM-DD"T"HH24:MI:SSTZH:TZM';
 
    -- query we use to extract the event ID and start/end times.  
    sqlStmt := 
      'select idsA1.sid eventAId,
              idsB1.sid eventBId,
              TO_TIMESTAMP_TZ(valuesA1.value_name,''YYYY-MM-DD"T"HH24:MI:SSTZH:TZM'') startTimeA,
              TO_TIMESTAMP_TZ(valuesA2.value_name,''YYYY-MM-DD"T"HH24:MI:SSTZH:TZM'') endTimeA,
              TO_TIMESTAMP_TZ(valuesB1.value_name,''YYYY-MM-DD"T"HH24:MI:SSTZH:TZM'') startTimeB,
              TO_TIMESTAMP_TZ(valuesB2.value_name,''YYYY-MM-DD"T"HH24:MI:SSTZH:TZM'') endTimeB
       from   ' || resource_id_map_view || ' valuesA1,
              ' || resource_id_map_view || ' valuesA2,
              ' || resource_id_map_view || ' valuesB1,
              ' || resource_id_map_view || ' valuesB2,
              ' || src_tab_view || '         idsA1,
              ' || src_tab_view || '         idsA2,
              ' || src_tab_view || '         idsA3,
              ' || src_tab_view || '         idsB1,
              ' || src_tab_view || '         idsB2,
              ' || src_tab_view || '         idsB3
       where  idsA1.sid = idsA3.sid 
         AND  idsA3.pid = ' || to_char(rdfTypePropertyId,'TM9') || ' 
         AND  idsA3.oid = ' || to_char(eventClassId,'TM9')      || ' 
         AND  idsB1.sid = idsB3.sid 
         AND  idsB3.pid = ' || to_char(rdfTypePropertyId,'TM9') || ' 
         AND  idsB3.oid = ' || to_char(eventClassId,'TM9')      || ' 
       /* only do half the checks, our TBOX ontology will handle symmetries */
         AND  idsA1.sid < idsB1.sid                   
       /* grab values of startTime and endTime for event A */
         AND  idsA1.sid = idsA2.sid 
         AND  idsA1.pid = ' || to_char(startTimePropertyId,'TM9') || ' 
         AND  idsA2.pid = ' || to_char(endTimePropertyId,'TM9')   || ' 
         AND  idsA1.oid = valuesA1.value_id 
         AND  idsA2.oid = valuesA2.value_id 
       /* grab values of startTime and endTime for event B */
         AND  idsB1.sid = idsB2.sid 
         AND  idsB1.pid = ' || to_char(startTimePropertyId,'TM9') || ' 
         AND  idsB2.pid = ' || to_char(endTimePropertyId,'TM9')   || ' 
         AND  idsB1.oid = valuesB1.value_id 
         AND  idsB2.oid = valuesB2.value_id 
       /* ensures we have NEWDATA */
         AND  not exists
               (select 1 
                from   ' || src_tab_view || ' 
                where  sid = idsA1.sid  
                  AND  oid = idsB1.sid 
                  AND  pid in (' || to_char(overlapsPropertyId,'TM9')  || ',' || 
                                    to_char(noOverlapPropertyId,'TM9') || ')) 
       /* ensures we have UNIQDATA */
         AND  not exists
               (select 1
                from   ' || output_tab   || '
                where  sid = idsA1.sid 
                  AND  oid = idsB1.sid 
                  AND  pid in (' || to_char(overlapsPropertyId,'TM9')  || ',' || 
                                    to_char(noOverlapPropertyId,'TM9') || '))';
 
    -- compare the two event times
    insertStmt :=
      'insert /*+ parallel append */ into ' || output_tab || ' (sid, pid, oid) 
       select eventAId, overlapStatusId, eventBId
       from   (
         select eventAId,
                (case 
                 when (startTimeA < endTimeB and 
                       startTimeA > startTimeB) then
                   ' || to_char(overlapsPropertyId,'TM9') || '
                 when (startTimeB < endTimeA and
                       startTimeB > startTimeA) then
                   ' || to_char(overlapsPropertyId,'TM9') || '
                 else
                   ' || to_char(noOverlapPropertyId,'TM9') || '
                 end) overlapStatusId,
                 eventBId
         from   (' || sqlStmt || '))';
 
    -- execute the query
    execute immediate insertStmt;
 
    -- commit our changes
    commit;
  end if;
 
  -- we only use ID values in the output_tab and we check for
  -- duplicates with our NOT EXISTS clause.
  optimization_flag := SDO_SEM_INFERENCE.INF_EXT_OPT_FLAG_ALL_IDS +
                       SDO_SEM_INFERENCE.INF_EXT_OPT_FLAG_NEWDATA_ONLY +
                       SDO_SEM_INFERENCE.INF_EXT_OPT_FLAG_UNIQDATA_ONLY;
 
  -- return true to indicate success
  return true;
 
  -- handle any exceptions
  exception 
    when others then
      diag_message := 'error occurred: ' || SQLERRM;
      return false;
end sem_inf_overlap;
/
show errors;

sem_inf_overlap関数は、「例4b: オーバーラップ・ルール」sem_inf_durations関数と類似しています。2つの主な違いは、sem_inf_overlapの問合せには、新しい字句の値を生成する必要がないために、より多くの結合が含まれており、INF_EXT_OPT_FLAG_ALL_IDS最適化フラグが有効であることです。(最適化フラグの詳細は、「例3: パフォーマンスの最適化」を参照してください。)

sem_inf_overlap関数で伴意を作成するには、次のように、MDSYSユーザーに実行権限を付与し、SEM_APIS.CREATE_ENTAILMENTプロシージャに関数名を渡します。

-- grant appropriate privileges
grant execute on sem_inf_overlap to mdsys;
 
-- create the entailment
begin
  sem_apis.create_entailment( 
    'EVENT_INF'
  , sem_models('EVENT', 'EVENT_ONT')
  , sem_rulebases('OWLPRIME')
  , passes => SEM_APIS.REACH_CLOSURE
  , inf_ext_user_func_name => 'sem_inf_overlap' 
);
end;
/

伴意には、OWLPRIMEによって推論されるトリプルに加えて、sem_inf_overlapによって追加される次の6つの新しいトリプルを含める必要があります。

S                            P                                   O
---------------------------- ----------------------------------- ----------------------------
http://example.org/event/m1  http://example.org/event/noOverlap  http://example.org/event/m2
http://example.org/event/m1  http://example.org/event/noOverlap  http://example.org/event/p1
http://example.org/event/m2  http://example.org/event/noOverlap  http://example.org/event/m1
http://example.org/event/m2  http://example.org/event/overlaps   http://example.org/event/p1
http://example.org/event/p1  http://example.org/event/noOverlap  http://example.org/event/m1
http://example.org/event/p1  http://example.org/event/overlaps   http://example.org/event/m2
9.1.3.4.3 例4c: 期間ルールとオーバーラップ・ルール

この項の例は、例4a: 期間ルール (sem_inf_durations)および例4b: オーバーラップ・ルール (sem_inf_overlap)の拡張機能関数を一緒に使用して、単一の伴意を生成します。拡張ファンクションは、この例では変更されないままです。

複数の拡張関数を使用して伴意を作成するには、SEM_APIS.CREATE_ENTAILMENT inf_ext_user_func_nameパラメータに渡される各拡張関数をカンマを使用して区切ります。次の例では、拡張ファンクションでの適切な権限がMDSYSユーザーに付与されていることを前提とします。

-- use multiple user-defined inference functions
begin
  sem_apis.create_entailment( 
    'EVENT_INF'
  , sem_models('EVENT', 'EVENT_ONT')
  , sem_rulebases('OWLPRIME')
  , passes => SEM_APIS.REACH_CLOSURE
  , inf_ext_user_func_name => 'sem_inf_durations,sem_inf_overlap' 
);
end;
/

伴意には、OWLPRIMEによって推論されるトリプルに加えて、sem_inf_durationsおよびsem_inf_overlapによって追加される次の9つの新しいトリプルを含める必要があります。

S                            P                                      O
---------------------------- -------------------------------------- ----------------------------
http://example.org/event/m1  http://example.org/event/lengthInMins  90
http://example.org/event/m1  http://example.org/event/noOverlap     http://example.org/event/m2
http://example.org/event/m1  http://example.org/event/noOverlap     http://example.org/event/p1
http://example.org/event/m2  http://example.org/event/lengthInMins  60
http://example.org/event/m2  http://example.org/event/noOverlap     http://example.org/event/m1
http://example.org/event/m2  http://example.org/event/overlaps      http://example.org/event/p1
http://example.org/event/p1  http://example.org/event/lengthInMins  120
http://example.org/event/p1  http://example.org/event/noOverlap     http://example.org/event/m1
http://example.org/event/p1  http://example.org/event/overlaps      http://example.org/event/m2

拡張関数のsem_inf_durationsおよびsem_inf_overlapで、同じ最適化フラグを使用する必要はありません。矛盾している最適化フラグで拡張関数を使用する(たとえば、INF_EXT_OPT_FLAG_ALL_IDSを使用する関数と、すべての新しいトリプルを字句の値として挿入する別の関数)ことは可能です。

9.1.3.5 例5: 空間推論

ユーザー定義の推論拡張関数では、WKT (Well-Known Text)のような地理空間データ型を使用して空間推論を実行することもできます。たとえばユーザー定義の拡張関数を使用して、州と市のような形状エンティティ間の包含関係を推論することができます。

この項の例では、あるジオメトリ(米国のある州)に、あるポイント(米国のある市)が含まれるかどうかを推論する方法を示します。この例では、RDFネットワークに空間索引(1.6.6.2項を参照)があることを前提としています。また、モデルSTATESが存在し、これには次のセマンティク・データが含まれているとします。

@prefix orageo: <http://xmlns.oracle.com/rdf/geo/> .
@prefix rdf:    <http://www.w3.org/1999/02/22-rdf-syntax-ns#> .
@prefix rdfs:   <http://www.w3.org/2000/01/rdf-schema#> .
@prefix :       <http://example.org/geo/> .
 
:Colorado rdf:type  :State ;
          :boundary "Polygon((-109.0448 37.0004, -102.0424 36.9949, -102.0534 41.0006, -109.0489 40.9996, -109.0448 37.0004))"^^orageo:WKTLiteral .
:Utah     rdf:type  :State ;
          :boundary "Polygon((-114.0491 36.9982, -109.0462 37.0026, -109.0503 40.9986, -111.0471 41.0006, -111.0498 41.9993, -114.0395 41.9901, -114.0491 36.9982))"^^orageo:WKTLiteral .
:Wyoming  rdf:type  :State ;
          :boundary "Polygon((-104.0556 41.0037, -104.0584 44.9949, -111.0539 44.9998, -111.0457 40.9986, -104.0556 41.0037))"^^orageo:WKTLiteral
 
:StateCapital rdfs:subClassOf :City ;
 
:Denver   rdf:type  :StateCapital ;
          :location "Point(-104.984722 39.739167)"^^orageo:WKTLiteral .
:SaltLake rdf:type  :StateCaptial ;
          :location "Point(-111.883333 40.75)"^^orageo:WKTLiteral .
:Cheyenne rdf:type  :StateCapital ;
          :location "Point(-104.801944 41.145556)"^^orageo:WKTLiteral .

次のユーザー定義の推論拡張関数(sem_inf_capitals)は、WKTジオメトリを使用して各州内の首都を検索します。関数が首都を検出すると、その市が含まれている州の首都であると推測します。

create or replace function sem_inf_capitals(
    src_tab_view         in  varchar2,
    resource_id_map_view in  varchar2,
    output_tab           in  varchar2,
    action               in  varchar2,
    num_calls            in  number,
    tplInferredLastRound in  number,
    options              in  varchar2 default null,
    optimization_flag    out number,
    diag_message         out varchar2
    )
return boolean
as
  stateClassId        number;
  capitalClassId      number;
  
  boundaryPropertyId  number;
  locationPropertyId  number;
  rdfTypePropertyId   number;
  capitalPropertyId   number;
 
  defaultSRID         number := 8307;
 
  xsdTimeFormat       varchar2(100);
  sqlStmt             varchar2(4000);
  insertStmt          varchar2(4000);
 
  pragma autonomous_transaction;
begin
  if (action = 'RUN') then
    -- retrieve ID of resource that already exists in the data (will
    -- throw exception if resource does not exist).
    stateClassId       := sdo_sem_inference.oracle_orardf_res2vid('http://example.org/geo/State');
    capitalClassId     := sdo_sem_inference.oracle_orardf_res2vid('http://example.org/geo/StateCapital');
    boundaryPropertyId := sdo_sem_inference.oracle_orardf_res2vid('http://example.org/geo/boundary');
    locationPropertyId := sdo_sem_inference.oracle_orardf_res2vid('http://example.org/geo/location');
    rdfTypePropertyId  := sdo_sem_inference.oracle_orardf_res2vid('http://www.w3.org/1999/02/22-rdf-syntax-ns#type');
 
    -- retreive ID of resource or generate a new ID if resource does
    -- not already exist
    capitalPropertyId := sdo_sem_inference.oracle_orardf_add_res('http://example.org/geo/capital');
 
    -- query we use to extract the capital cities contained within state boundaries
    sqlStmt := 
      'select idsA1.sid stateId,
              idsB1.sid cityId
       from   ' || resource_id_map_view || ' valuesA,
              ' || resource_id_map_view || ' valuesB,
              ' || src_tab_view || '         idsA1,
              ' || src_tab_view || '         idsA2,
              ' || src_tab_view || '         idsB1,
              ' || src_tab_view || '         idsB2
       where  idsA1.pid = ' || to_char(rdfTypePropertyId,'TM9') || ' 
         AND  idsA1.oid = ' || to_char(stateClassId,'TM9')      || ' 
         AND  idsB1.pid = ' || to_char(rdfTypePropertyId,'TM9') || ' 
         AND  idsB1.oid = ' || to_char(capitalClassId,'TM9')    || ' 
       /* grab geometric lexical values */
         AND  idsA2.sid = idsA1.sid                                  
         AND  idsA2.pid = ' || to_char(boundaryPropertyId,'TM9')|| ' 
         AND  idsA2.oid = valuesA.value_id                           
         AND  idsB2.sid = idsB1.sid                                  
         AND  idsB2.pid = ' || to_char(locationPropertyId,'TM9')|| ' 
         AND  idsB2.oid = valuesB.value_id                           
       /* compare geometries to see if city is contained by state */              
         AND  SDO_RELATE( 
                SDO_RDF.getV$GeometryVal( 
                  valuesA.value_type, 
                  valuesA.vname_prefix, 
                  valuesA.vname_suffix, 
                  valuesA.literal_type, 
                  valuesA.language_type, 
                  valuesA.long_value, 
                  ' || to_char(defaultSRID,'TM9') || '),
                SDO_RDF.getV$GeometryVal(
                  valuesB.value_type, 
                  valuesB.vname_prefix, 
                  valuesB.vname_suffix, 
                  valuesB.literal_type, 
                  valuesB.language_type, 
                  valuesB.long_value, 
                  ' || to_char(defaultSRID,'TM9') || '),
                ''mask=CONTAINS'') = ''TRUE'' 
       /* ensures we have NEWDATA and only check capitals not assigned to a state */
         AND  not exists
               (select 1 
                from   ' || src_tab_view || ' 
                where  pid = ' || to_char(capitalPropertyId,'TM9') || ' 
                  AND  (sid = idsA1.sid OR oid = idsB1.sid)) 
       /* ensures we have UNIQDATA and only check capitals not assigned to a state */
         AND  not exists
               (select 1
                from   ' || output_tab   || '
                where  pid = ' || to_char(capitalPropertyId,'TM9') || ' 
                  AND  (sid = idsA1.sid OR oid = idsB1.sid))';
 
    -- insert new triples using only IDs
    insertStmt :=
      'insert /*+ parallel append */ into ' || output_tab || ' (sid, pid, oid) 
       select stateId, ' || to_char(capitalPropertyId,'TM9') || ', cityId
       from   (' || sqlStmt || ')';
 
    -- execute the query
    execute immediate insertStmt;
 
    -- commit our changes
    commit;
  end if;
 
  -- we only use ID values in the output_tab and we check for
  -- duplicates with our NOT EXISTS clauses.
  optimization_flag := SDO_SEM_INFERENCE.INF_EXT_OPT_FLAG_ALL_IDS +
                       SDO_SEM_INFERENCE.INF_EXT_OPT_FLAG_NEWDATA_ONLY +
                       SDO_SEM_INFERENCE.INF_EXT_OPT_FLAG_UNIQDATA_ONLY;
 
  -- return true to indicate success
  return true;
 
  -- handle any exceptions
  exception 
    when others then
      diag_message := 'error occurred: ' || SQLERRM;
      return false;
end sem_inf_capitals;
/
show errors;

sem_inf_capitals関数と例4a: 期間ルールsem_inf_durations関数は、どちらの関数もネイティブのOracle演算子を利用するために、いくつかのトリプルの字句の値をOracle型に変換する必要があるという点で類似しています。sem_inf_capitalsの例では、SDO_RDF.getV$GeometryVal関数を使用して、WKT字句値のエンコーディングされたポリゴンとポイントを、Oracle Spatial and Graphの SDO_GEOMETRY型に変換します。getV$GeometryVal関数は、ほとんどがリソース・ビュー(resource_id_map_view)と追加の引数によって提供される引数、空間参照システム(SRID)へのIDを必要とします。getV$GeometryVal関数は、ジオメトリをSRIDによって指定される空間参照システムに変換します。sem_inf_capitals関数は、値8307のSRIDによって指定される、デフォルトのOracle Spatial and Graph参照システム(WGS84 Longitude-Latitude)を使用します。(RDFセマンティク・グラフの空間参照システムのサポートの詳細は、「空間のサポート」を参照してください。)

getV$GeometryVal関数を使用して、WKT値をSDO_GEOMETRY型に変換した後、sem_inf_capitals関数は、その州のジオメトリを市のジオメトリと比較して、その州にその市が含まれているかどうかを調べます。SDO_RELATE演算子がこの比較を実行し、州に市が含まれる場合は、リテラル値'TRUE'を戻します。SDO_RELATE演算子は、様々な異なる種類の比較を実行できます。(SDO_RELATEおよびその他の空間演算子の詳細は、『Oracle Spatial and Graph開発者ガイド』を参照してください。)

sem_inf_capitals関数で伴意を作成するには、次のように、MDSYSユーザーに実行権限を付与し、SEM_APIS.CREATE_ENTAILMENTプロシージャに関数名を渡します。

-- grant appropriate privileges
grant execute on sem_inf_capitals to mdsys;
 
-- create the entailment
begin
  sem_apis.create_entailment( 
    'STATES_INF'
  , sem_models('STATES')
  , sem_rulebases('OWLPRIME')
  , passes => SEM_APIS.REACH_CLOSURE
  , inf_ext_user_func_name => 'sem_inf_capitals' 
);
end;
/

伴意には、OWLPRIMEによって推論されるトリプルに加えて、sem_inf_capitalsによって追加される次の3つの新しいトリプルを含める必要があります。

S                                P                               O
-------------------------------- ------------------------------- --------------------------------
http://example.org/geo/Colorado  http://example.org/geo/capital  http://example.org/geo/Denver
http://example.org/geo/Utah      http://example.org/geo/capital  http://example.org/geo/SaltLake
http://example.org/geo/Wyoming   http://example.org/geo/capital  http://example.org/geo/Cheyenne
9.1.3.6 例6: Webサービスのコール

この項では、Oracle GeocoderサービスへのWebサービス・コールを可能にする、ユーザー定義の推論拡張関数(sem_inf_geocoding)と関連するヘルパー・プロシージャ(geocoding)について説明します。ユーザー定義の推論拡張関数は、述語<urn:streetAddress>を使用してトリプルのオブジェクト値を検索し、Oracleの公開Geocoderサービスのエンドポイント(http://maps.oracle.com/geocoder/gcserver)でコールアウトを実行して、経度と緯度の情報を2つの別々のトリプルとして挿入します。

たとえば、セマンティク・モデルに次の表明が含まれているとします。

<urn:NEDC>  <urn:streetAddress>  "1 Oracle Dr., Nashua, NH"

この場合、sem_inf_geocodingを使用した推論コールは、次の新しい表明を生成します。

<urn:NEDC> <http://www.w3.org/2003/01/geo/wgs84_pos#long>  "-71.46421"
<urn:NEDC> <http://www.w3.org/2003/01/geo/wgs84_pos#lat>   "42.75836"
<urn:NEDC> <http://www.opengis.net/geosparql#asWKT>  "POINT(-71.46421 42.75836)"^^<http://www.opengis.net/geosparql#wktLiteral>
<urn:NEDC> <http://xmlns.oracle.com/rdf/geo/asWKT>   "POINT(-71.46421 42.75836)"^^<http://xmlns.oracle.com/rdf/geo/WKTLiteral>

sem_inf_geocoding関数は、次のように定義されます。

create or replace function sem_inf_geocoding(
    src_tab_view         in  varchar2,
    resource_id_map_view in  varchar2,
    output_tab           in  varchar2,
    action               in  varchar2,
    num_calls            in  number,
    tplInferredLastRound in  number,
    options              in  varchar2 default null,
    optimization_flag    out number,
    diag_message         out varchar2
    )
return boolean
as
  pragma autonomous_transaction;
  iCount integer;
  
  nLong number;
  nLat  number;
  nWKT  number;
  nOWKT number;
  nStreetAddr number;
  
  sidTab    dbms_sql.number_table;
  oidTab    dbms_sql.number_table;
  
  vcRequestBody varchar2(32767);
  vcStmt        varchar2(32767);
  vcStreeAddr   varchar2(3000);
  
  type cur_type is ref cursor;
  cursorFind    cur_type; 
  vcLong varchar2(100);
  vcLat  varchar2(100);
begin
  if (action = 'START') then
    nLat := sdo_sem_inference.oracle_orardf_add_res('http://www.w3.org/2003/01/geo/wgs84_pos#lat');
    nLong := sdo_sem_inference.oracle_orardf_add_res('http://www.w3.org/2003/01/geo/wgs84_pos#long');
    nWKT  := sdo_sem_inference.oracle_orardf_add_res('http://www.opengis.net/geosparql#asWKT');
    nOWKT := sdo_sem_inference.oracle_orardf_add_res('http://xmlns.oracle.com/rdf/geo/asWKT');
  end if; 
  
  if (action = 'RUN') then
    nStreetAddr := sdo_sem_inference.oracle_orardf_res2vid('<urn:streetAddress>');
    nLat := sdo_sem_inference.oracle_orardf_res2vid('http://www.w3.org/2003/01/geo/wgs84_pos#lat');
    nLong := sdo_sem_inference.oracle_orardf_res2vid('http://www.w3.org/2003/01/geo/wgs84_pos#long');
    nWKT  := sdo_sem_inference.oracle_orardf_res2vid('http://www.opengis.net/geosparql#asWKT');
    nOWKT := sdo_sem_inference.oracle_orardf_res2vid('http://xmlns.oracle.com/rdf/geo/asWKT');
 
    vcStmt := '
      select /*+ parallel */ distinct s1.sid as s_id, s1.oid as o_id
        from ' || src_tab_view || ' s1
       where s1.pid = :1
         and not exists ( select 1
                            from   ' || src_tab_view || ' x
                           where  x.sid = s1.sid
                             and  x.pid = :2
                        ) ';
    open cursorFind for vcStmt using nStreetAddr, nLong;
    
    loop
      fetch cursorFind bulk collect into sidTab, oidTab limit 10000;
      for i in 1..sidTab.count loop 
        vcStreeAddr := sdo_sem_inference.oracle_orardf_vid2lit(oidTab(i));
        -- dbms_output.put_line('Now processing street addr ' || vcStreeAddr);
        geocoding(vcStreeAddr, vcLong, vcLat);
        execute immediate 'insert into ' || output_tab || '(sid,pid,oid,gid,s,p,o,g)
            values(:1, :2, null, null, null, null, :3, null) '
            using sidTab(i), nLong, '"'||vcLong||'"';
        execute immediate 'insert into ' || output_tab || '(sid,pid,oid,gid,s,p,o,g)
            values(:1, :2, null, null, null, null, :3, null) '
            using sidTab(i), nLat, '"'||vcLat||'"';
        execute immediate 'insert into ' || output_tab || '(sid,pid,oid,gid,s,p,o,g) 
            values(:1, :2, null, null, null, null, :3, null) '
            using sidTab(i), nWKT, '"POINT('|| vcLong || ' ' ||vcLat ||')"^^<http://www.opengis.net/geosparql#wktLiteral>';
        execute immediate 'insert into ' || output_tab || '(sid,pid,oid,gid,s,p,o,g) 
            values(:1, :2, null, null, null, null, :3, null) '
            using sidTab(i), nOWKT, '"POINT('|| vcLong || ' ' ||vcLat ||')"^^<http://xmlns.oracle.com/rdf/geo/WKTLiteral>';
      end loop;
      exit when cursorFind%notfound;
    end loop;   
    commit;
  end if;
  return true;
end;
/
grant execute on sem_inf_geocoding to mdsys;

sem_inf_geocoding関数は、Geocoder Webサービス・エンドポイントで実際のHTTP通信を実行する、次のgeocodingというヘルパー・プロシージャを使用します。Webサーバーに接続するには、適切な権限が必要であることに注意してください。

create or replace procedure geocoding(addr varchar2,
                                     vcLong out varchar2,
                                     vcLat  out varchar2
                                    )
as
  httpReq  utl_http.req;
  httpResp utl_http.resp;
  
  vcRequestBody varchar2(32767);
  
  vcBuffer  varchar2(32767);
  idxLat integer;
  idxLatEnd integer;
begin
  vcRequestBody := utl_url.escape('xml_request=<?xml version="1.0" standalone="yes"?>
    <geocode_request vendor="elocation">
      <address_list> 
      <input_location id="27010">
      <input_address match_mode="relax_street_type">
        <unformatted country="US">
           <address_line value="'|| addr ||'"/>
        </unformatted>
       </input_address>
      </input_location>
    </address_list>
    </geocode_request>
  ');
  dbms_output.put_line('request ' || vcRequestBody);
  
  -- utl_http.set_proxy('<your_proxy_here_if_necessary>', null);
  httpReq := utl_http.begin_request (
    'http://maps.oracle.com/geocoder/gcserver', 'POST');
    
  utl_http.set_header(httpReq, 'Content-Type', 'application/x-www-form-urlencoded');
  utl_http.set_header(httpReq, 'Content-Length', lengthb(vcRequestBody));
  
  utl_http.write_text(httpReq, vcRequestBody);
  
  httpResp := utl_http.get_response(httpReq);
  
  utl_http.read_text(httpResp, vcBuffer, 32767);
  utl_http.end_response(httpResp);
  
  -- dbms_output.put_line('response ' || vcBuffer);
  -- Here we are doing some simple string parsing out of an XML.
  -- It is more robust to use XML functions instead.
  idxLat := instr(vcBuffer, 'longitude="'); 
  idxLatEnd := instr(vcBuffer, '"', idxLat + 12);
  vcLong := substr(vcBuffer, idxLat + 11, idxLatEnd - idxLat - 11);
  dbms_output.put_line('long = ' || vcLong);
  
  idxLat := instr(vcBuffer, 'latitude="');
  idxLatEnd := instr(vcBuffer, '"', idxLat + 11);
  vcLat := substr(vcBuffer, idxLat + 10, idxLatEnd - idxLat - 10);
  dbms_output.put_line('lat = ' || vcLat);
exception
  when others then
    dbms_output.put_line('geocoding: error ' || dbms_utility.format_error_backtrace || ' '
                                             || dbms_utility.format_error_stack);
end;
/

9.2 ユーザー定義関数と集計

RDFセマンティク・グラフ問合せの拡張アーキテクチャによって、ユーザー定義の関数と集計を追加して、SPARQL問合せで使用できます。これには、SEM_MATCH表関数と、support for Apache Jenaが使用されます。

SPARQL 1.1標準には、主に、問合せによって取得されるデータをフィルタ処理して分類するために使用されるいくつかの関数が用意されています。ただし、この標準ではサポートされていない特殊な関数が必要な場合があります。

簡単な例として、特定のタイプに属する値の検索、平方和の値を使用した特定のしきい値よりも大きい値の取得があります。これは、関数を組み合せることで実行することはできますが、この計算を処理する単一の関数があると、問合せをより簡単に短くすることができて有用です。

RDFセマンティク・グラフ問合せの拡張機能によって、独自の問合せ関数と集計を含めることができます。このアーキテクチャにより、次のことが可能になっています。

9.2.1 ユーザー定義関数と集計のデータ型

ユーザー定義関数と集計を作成するとき、RDF語句を表現するためにSDO_RDF_TERMオブジェクト型が使用されます。

SDO_RDF_TERMには次の属性があり、MDSYS.RDF_VALUE$表の列に対応しています(これらの属性については、「文」表1-4を参照してください)。CTX1属性は将来使用するために確保されているもので、対応する列はMDSYS.RDF_VALUE$にはありません。

SDO_RDF_TERM(
  VALUE_TYPE   VARCHAR2(10),
  VALUE_NAME    VARCHAR2(4000), 
  VNAME_PREFIX   VARCHAR2(4000), 
  VNAME_SUFFIX   VARCHAR2(512), 
  LITERAL_TYPE   VARCHAR2(1000), 
  LANGUAGE_TYPE   VARCHAR2(80), 
  LONG_VALUE   CLOB, 
  CTX1   VARCHAR2(4000) )

SDO_RDF_TERMオブジェクトの作成には、次のコンストラクタが使用可能です。1つ目のコンストラクタは、単一の字句RDF語句文字列から、各属性を移入します。2番目と3番目のコンストラクタは、個々の属性値を入力として受け取ります。1番目のRDF語句文字列コンストラクタのみが、VNAME_PREFIXとVNAME_SUFFIXの値を設定します。これらの値は、他のコンストラクタによってNULLに初期化されます。

SDO_RDF_TERM (
  rdf_term_str  VARCHAR2) 
  RETURN SELF;

SDO_RDF_TERM (
  value_type  VARCHAR2, 
  value_name  VARCHAR2, 
  literal_type  VARCHAR2, 
  language_type  VARCHAR2, 
  long_value  CLOB) 
  RETURN SELF;

SDO_RDF_TERM (
  value_type  VARCHAR2, 
  value_name  VARCHAR2, 
  literal_type  VARCHAR2, 
  language_type  VARCHAR2, 
  long_value  CLOB, 
  ctx1 VARCHAR2) 
  RETURN SELF;

SDO_RDF_TERMオブジェクトのリストを格納するために、SDO_RDF_TERM_LIST型が使用され、VARRAY(32767) of SDO_RDF_TERMとして定義されます。

9.2.2 ユーザー定義関数のAPIサポート

ユーザー定義関数は、特定のシグネチャを使用したPL/SQL関数を実装することによって作成され、固有のURIを使用してSPARQL問合せパターンで起動されます。

推論拡張関数コールが正常に実行された後、推論拡張関数で行われた変更を保持するために、コミットが実行されます。pragma autonomous_transactionを指定することによって、推論拡張関数が自律型として定義した場合、実装ロジックの最後で、コミットまたはロールバックする必要があります。推論エンジンでは、伴意の作成時(1ラウンドにつき1回)に、拡張関数を複数回コールする場合があることに注意してください。あるコールのコミットおよびロールバックは、他のコールには影響しません。

9.2.2.1 PL/SQL関数の実装

各ユーザー定義関数は、次の形式のシグネチャを持つPL/SQL関数によって実装される必要があります。

FUNCTION user_function_name (params IN SDO_RDF_TERM_LIST)
  RETURN SDO_RDF_TERM

このシグネチャは任意の数のRDF語句引数(単一のSDO_RDF_TERM_LISTオブジェクトを使用する際に渡される)をサポートし、単一のRDF語句(単一のSDO_RDF_TERMオブジェクトとして表現される)を出力として戻します。これらのパラメータの型チェックまたは他の検証は、実行されません。関数の用途に従ってデータを検証するためのステップを実行する必要があります。

CやJavaなど、他のプログラミング言語で記述された関数に対して、PL/SQLからのコールアウトがサポートされているため、ユーザー定義の問合せ関数を実装するPL/SQL関数は、他のプログラミング言語で記述された関数のラッパーとしてのみ機能します。

9.2.2.2 SPARQL問合せパターンからのユーザー定義関数の起動

これは、ユーザー定義関数がPL/SQLで実装された後、接頭辞<http://xmlns.oracle.com/rdf/extensions/>の後にschema.package_name.function_nameが続く関数URI(対応するPL/SQL関数がPL/SQLパッケージの一部である場合)、またはschema.function_nameが続く関数URI(関数がPL/SQLパッケージの一部でない場合)を使用して、SPARQL問合せパターンから起動することができます。次の2つは、関数URIの例です。

<http://xmlns.oracle.com/rdf/extensions/my_schema.my_package.my_function>(arg_1, …, arg_n)

<http://xmlns.oracle.com/rdf/extensions/my_schema.my_function>(arg_1, …, arg_n)
9.2.2.3 ユーザー定義関数の例

この項では、ユーザー定義関数の実装例と、その関数のFILTER句、SELECT式およびBIND演算での使用について示します。

たとえば、ここではN-triple形式で示されている次のデータが、MYMODELと呼ばれるモデル内に存在しているとします。

<a>  <p>  "1.0"^^xsd:double .
<b>  <p>  "1.5"^^xsd:float .
<c>  <p>  "3"^^xsd:decimal .
<d>  <p>  "4"^^xsd:string .

例9-1 2つの平方和を計算するユーザー定義関数

例9-1は、2つの値を受け取って各値の平方和を計算する、単純なファンクションの実装を示しています。

CREATE OR REPLACE FUNCTION sum_squares (params IN MDSYS.SDO_RDF_TERM_LIST) 
   RETURN MDSYS.SDO_RDF_TERM
   AS 
     retTerm    SDO_RDF_TERM;
     sqr1       NUMBER;
     sqr2       NUMBER;
     addVal     NUMBER;
     val1       SDO_RDF_TERM;
     val2       SDO_RDF_TERM;
   BEGIN 
     –- Set the return value to null.
     retTerm := SDO_RDF_TERM(NULL,NULL,NULL,NULL,NULL);
     –- Obtain the data from the first two parameters.
     val1 := params(1); 
     val2 := params(2);
     –- Convert the value stored in the sdo_rdf_term to number.
     –- If any exception occurs, return the null value.
     BEGIN
       sqr1 := TO_NUMBER(val1.value_name);
       sqr2 := TO_NUMBER(val2.value_name);
       EXCEPTION WHEN OTHERS THEN RETURN retTerm;
     END;
     –- Compute the square sum of both values.
       addVal := (sqr1 * sqr1) + (sqr2 * sqr2);
     –- Set the return value to the desired rdf term type.
     retTerm := SDO_RDF_TERM('LIT',to_char(addVal),
                'http://www.w3.org/2001/XMLSchema#integer','',NULL);
     – Return the new value.
     RETURN retTerm;
END;
/
SHOW ERRORS;

例9-1 sum_squaresファンクションでは、受け取った値のデータ型を検証しません。これはあくまでも例であり、SDO_RDF_TERMのVALUE_NAMEフィールドに格納されている数値の取得には、TO_NUMBERを使用します。

例9-2 FILTER句で使用されるユーザー定義関数

例9-2は、FILTER句で使用されるsum_squaresファンクション(例9-1)を示します。

SELECT s, o
FROM table(sem_match(
'SELECT  ?s ?o
 WHERE { ?s ?p ?o 
 FILTER (<http://xmlns.oracle.com/rdf/extensions/schema.sum_squares>(?o,?o) > 2)}',
sem_models('MYMODEL'),null,null,null,null,''));

例9-2の問合せは、次の結果を戻します。

s                    o                    
-------------------- -------------------- 
b                    1.5
c                    3                  
d                    4

例9-3 SELECT式で使用されるユーザー定義関数

例9-3は、SELECT句の式で使用されるsum_squaresファンクション(例9-1)を示します。

SELECT s, o, sqr_sum
FROM table(sem_match(
'SELECT  ?s ?o 
       (<http://xmlns.oracle.com/rdf/extensions/schema.sum_squares>(?o,?o) AS    
        ?sqr_sum)
 WHERE { ?s ?p ?o }',
sem_models('MYMODEL'),null,null,null,null,''));

例9-3の問合せは、次の結果を戻します。

s                    o                    sqr_sum
-------------------- -------------------- -------------------- 
a                    1                    2
b                    1.5                  4.5
c                    3                    18
d                    4                    32

例9-4 BIND演算で使用されるユーザー定義関数

例9-4は、BIND演算で使用されるsum_squaresファンクション(例9-1)を示します。

SELECT s, o, sqr_sum
FROM table(sem_match(
'SELECT  ?s ?o ?sqr_sum
 WHERE { ?s ?p ?o .
 BIND (<http://xmlns.oracle.com/rdf/extensions/schema.sum_squares>(?o,?o) AS
       ?sqr_sum)}',
sem_models('MYMODEL'),null,null,null,null,''));

例9-4の問合せは、次の結果を戻します。

s                    o                    sqr_sum
-------------------- -------------------- -------------------- 
a                    1                    2
b                    1.5                  4.5
c                    3                    18
d                    4                    32

9.2.3 ユーザー定義の集計のAPIサポート

ユーザー定義の集計は、一連のインタフェース・メソッドを実装するPL/SQLオブジェクト型を定義することによって実装されます。ユーザー定義の集計を作成したら、固有のURIを使用してそれを起動します。

9.2.3.1 ODCIAggregateインタフェース

ユーザー定義の集計では、ODCIAggregate PL/SQLインタフェースを使用します。このインタフェースの詳細は、Oracle Databaseデータ・カートリッジ開発者ガイドのユーザー定義集計関数に関する章を参照してください。

ODCIAggregateインタフェースは、4つの主要な関数を実装するPL/SQLオブジェクト型によって実装されます。

  • ODCIAggregateInitialize

  • ODCIAggregateIterate

  • ODCIAggregateMerge

  • ODCIAggregateTerminate

ユーザー定義関数(「ユーザー定義関数のAPIサポート」を参照)と同様に、ユーザー定義の集計は、SDO_RDF_TERM_LISTオブジェクトとして渡される任意の数のRDF語句引数を受け取り、SDO_RDF_TERMオブジェクトとして表現される単一のRDF語句の値を返します。

このスキームによって、PL/SQL ODCIAggregateインタフェース関数のシグネチャは、次のようになります(my_aggregate_obj_typeは実際のオブジェクト・タイプ名)。

STATIC FUNCTION ODCIAggregateInitialize(
        sctx IN OUT my_aggregate_obj_type)
RETURN NUMBER
 
MEMBER FUNCTION ODCIAggregateIterate(
        self       IN OUT my_aggregate_obj_type
       ,value      IN MDSYS.SDO_RDF_TERM_LIST)
RETURN NUMBER
 
MEMBER FUNCTION ODCIAggregateMerge(
        self IN OUT my_aggregate_obj_type
       ,ctx2 IN     my_aggregate_obj_type)
RETURN NUMBER
 
MEMBER FUNCTION ODCIAggregateTerminate (
        self IN my_aggregate_obj_type
       ,return_value OUT MDSYS.SDO_RDF_TERM
       ,flags IN NUMBER)              
RETURN NUMBER
9.2.3.2 ユーザー定義の集計の起動

これは、ユーザー定義の集計がPL/SQLで実装された後、接頭辞<http://xmlns.oracle.com/rdf/aggExtensions/>の後にschema_name.aggregate_nameを続けて構築される集計URIを参照することによって、SPARQL問合せから起動することができます。集計URIの例は次のとおりです。

<http://xmlns.oracle.com/rdf/aggExtensions/schema.my_aggregate>(arg_1, …, arg_n)

DISTINCT修飾子は、ユーザー定義の集計で次の例のように使用できます。

<http://xmlns.oracle.com/rdf/aggExtensions/schema.my_aggregate>(DISTINCT arg_1)

この場合、distinct引数の値のみが集計に渡されます。ただしDISTINCT修飾子は、厳密に1つの引数の集計でのみ使用できることに注意してください。

9.2.3.3 ユーザー定義の集計の例

この項では、ユーザー定義の集計を実装して使用する例を示します。たとえば、ここではN-triple形式で示されている次のデータが、MYMODELと呼ばれるモデル内に存在しているとします。

<a>  <p>  "1.0"^^xsd:double .
<b>  <p>  "1.5"^^xsd:float .
<c>  <p>  "3"^^xsd:decimal .
<c>  <p>  "4"^^xsd:decimal .
<d>  <p>  "4"^^xsd:string .

例9-5 ユーザー定義の集計の実装

例9-5は、単純なユーザー定義の集計の実装(countSameType)を示しています。この集計には2つの引数があり、1番目は任意のRDF語句、2番目は定数のデータ型URIです。集計は、1番目の引数の位置からのRDF語句にある、2番目の引数と等しいデータ型の数をカウントします。

-- Aggregate type creation
CREATE OR REPLACE TYPE countSameType authid current_user AS OBJECT(
 
count NUMBER, –- Variable to store the number of same-type terms.
 
–- Mandatory Functions for aggregates 
STATIC FUNCTION ODCIAggregateInitialize(
        sctx IN OUT countSameType)
RETURN NUMBER,
 
MEMBER FUNCTION ODCIAggregateIterate(
         self       IN OUT countSameType
       , value      IN MDSYS.SDO_RDF_TERM_LIST)
RETURN NUMBER,
 
MEMBER FUNCTION ODCIAggregateMerge(
        self IN OUT countSameType
       ,ctx2 IN     countSameType)
RETURN NUMBER,
 
MEMBER FUNCTION ODCIAggregateTerminate (
        self IN countSameType
       ,return_value OUT MDSYS.SDO_RDF_TERM
       ,flags IN NUMBER)              
RETURN NUMBER
);
/
SHOW ERRORS;
 
–- Interface function for the user-defined aggregate
CREATE OR REPLACE FUNCTION countSameAs (input MDSYS.SDO_RDF_TERM_LIST) RETURN MDSYS.SDO_RDF_TERM
PARALLEL_ENABLE AGGREGATE USING countSameType;
/
show errors;
 
–- User-defined aggregate body
CREATE OR REPLACE TYPE BODY countSameType IS
 
STATIC FUNCTION ODCIAggregateInitialize(
         sctx            IN OUT countSameType)
RETURN NUMBER IS
BEGIN
  sctx := countSameType (0); –- Aggregate initialization
  RETURN ODCIConst.Success;
END;
 
MEMBER FUNCTION ODCIAggregateIterate(
         self           IN OUT countSameType
       , value          IN MDSYS.SDO_RDF_TERM_LIST )
RETURN NUMBER IS
BEGIN
  -- Increment count if the first argument has a literal type
  -- URI equal to the value of the second argument
  IF (value(1).literal_type = value(2).value_name) THEN
    self.count := self.count + 1;
  END IF;                                    
  RETURN ODCIConst.Success;
END;   
 
MEMBER FUNCTION ODCIAggregateMerge(
         self            IN OUT countSameType
        ,ctx2            IN countSameType)
RETURN NUMBER IS
BEGIN
  –- Sum count to merge parallel threads.
  self.count := self.count + ctx2.count;   
  RETURN ODCIConst.Success;
END;
 
MEMBER FUNCTION ODCIAggregateTerminate(
         self            IN countSameType
        ,return_value    OUT MDSYS.SDO_RDF_TERM
        ,flags           IN NUMBER)              
RETURN NUMBER IS
BEGIN
   -- Set the return value
   return_value := MDSYS.SDO_RDF_TERM('LIT',to_char(self.count),
     'http://www.w3.org/2001/XMLSchema#decimal',NULL,NULL); RETURN  ODCIConst.Success;
END;
 
END;
/
SHOW ERRORS;

例9-6 GROUP BY句なしで使用されるユーザー定義の集計

例9-6は、問合せ結果のすべてのグループに使用されるcountSameType集計(例9-5)を示しています。

FROM o
from table(sem_match(
'SELECT 
 (<http://xmlns.oracle.com/rdf/aggExtensions/schema.countSameType>(?o,xsd:decimal) 
  AS ?o)
 WHERE { ?s ?p ?o }',
sem_models('MYMODEL'),null,null,null,null,''));

例9-6の問合せは、次の結果を戻します。

o                    
-------------------- 
2                    

例9-7 GROUP BY句とともに使用されるユーザー定義の集計

例9-7は、GROUP BY句から形成される一連のグループに使用されるcountSameType集計(例9-5)を示しています。

select s, o
from table(sem_match(
'SELECT ?s
 (<http://xmlns.oracle.com/rdf/aggExtensions/schema.countSameType>(?o,xsd:decimal) 
  AS ?o)
 WHERE { ?s ?p ?o } GROUP BY ?s',
sem_models('MYMODEL'),null,null,null,null,''));

例9-7の問合せは、次の結果を戻します。

s                    o                    
-------------------- -------------------- 
a                    0
b                    0
c                    2                    
d                    0