13 パイプライン・テーブル・ファンクションとパラレル・テーブル・ファンクションの使用

テーブル・ファンクションでは、汎用データ型ANYTYPEANYDATAおよびANYDATASETがよく使用されます。

13.1 テーブル・ファンクションの概要

テーブル・ファンクションとは、物理データベース表と同様に問合せできる行のコレクション(ネストした表またはVARRAY)を生成するファンクションです。問合せのFROM句には、データベース表名と同様にテーブル・ファンクションを使用します。

テーブル・ファンクションは、行のコレクションを入力として取ることができます。入力コレクション・パラメータには、コレクション型またはREF CURSORを使用できます。

テーブル・ファンクションをパラレルで実行して、戻された行を中間ステージングなしで次のプロセスに直接にストリーミングできます。テーブル・ファンクションから戻されるコレクションの行はパイプライン化できます。つまり、テーブル・ファンクションの入力の処理がすべて完了してから単一のバッチで出力するかわりに、行の生成と同時にその行を反復的に戻すことができます。

テーブル・ファンクションのストリーミング、パイプライン化およびパラレル実行により、次のようにしてパフォーマンスを向上させることができます。

  • テーブル・ファンクションのマルチスレッド化された同時実行が可能になります。

  • プロセス間の中間的なステージングが不要になります。

  • 問合せのレスポンス時間を短縮できます。非パイプライン化テーブル・ファンクションでは、問合せから1つの結果行が戻される前に、テーブル・ファンクションによって戻されたコレクション全体を構造化してサーバーに戻す必要があります。パイプライン化により、行の生成と同時にその行を反復的に戻すことができるようになります。また、オブジェクト・キャッシュがコレクション全体をマテリアライズする必要がなくなるため、テーブル・ファンクションに必要なメモリーを削減できます。

  • コレクション全体が表またはメモリーにステージングされるまで待ってからコレクション全体を戻すかわりに、行が生成されるとテーブル・ファンクションにより戻されたコレクションから結果行を反復的に提供します。

図13-1は、データが最終的にデータベースにロードされる前に、テーブル・ファンクションによって実装されたいくつか(この例では3つ)の変換を通過する、一般的なデータ処理のシナリオを示しています。このシナリオでは、テーブル・ファンクションがパラレルに実行されず、それぞれの変換が行われた後に結果のコレクション全体をステージングする必要があります。

図13-1 非パラレル、非パイプライン・テーブル・ファンクションを使用した一般的なデータ処理

図13-1の説明が続きます
「図13-1 非パラレル、非パイプライン化テーブル・ファンクションを使用した一般的なデータ処理」の説明

これに対して図13-2は、ストリームおよびパラレル実行により、同じ使用例をどのように簡素化できるかを示しています。

図13-2 パイプライン化およびパラレル実行を使用したデータ処理

図13-2の説明が続きます
「図13-2 パイプライン化およびパラレル実行を使用したデータ処理」の説明

13.2 テーブル・ファンクションの概念

テーブル・ファンクションと、これらのテーブル・ファンクションのパイプライン化およびパラレル実行に関連するいくつかの概念について考えます。

13.2.1 テーブル・ファンクション

テーブル・ファンクションは、コレクション型のインスタンスを戻し、問合せのFROM句でファンクションをコールすることで、表と同じように問い合せることができます。テーブル・ファンクションではTABLEキーワードを使用します。

次の例に、入力としてCLOBを取り、コレクション型BookSet_tのインスタンスを戻すテーブル・ファンクションGetBooksを示します。CLOB列には、なんらかの書式(独自の書式またはXMLなどの標準に準拠する書式)で書籍リストを示すカタログが格納されています。このテーブル・ファンクションは、すべてのカタログおよび対応する書籍リストを戻します。コレクション型BookSet_tは、例13-1で定義されています。

CLOBは、例13-2で示されているように、表Catalogsに格納されています。

関数GetBooks()例13-3で定義されています。

例13-4の問合せでは、すべてのカタログおよび対応する書籍リストが戻されます。

例13-1 コレクション型の作成

CREATE TYPE Book_t AS OBJECT
( name VARCHAR2(100),
  author VARCHAR2(30),
  abstract VARCHAR2(1000));

CREATE TYPE BookSet_t AS TABLE OF Book_t;

例13-2 表でのClobの格納

CREATE TABLE Catalogs
( name VARCHAR2(30), 
  cat CLOB);

例13-3 コレクション型を戻すファンクションの作成

CREATE FUNCTION GetBooks(a CLOB) RETURN BookSet_t;

例13-4 問合せでのコレクション型の使用

SELECT c.name, Book.name, Book.author, Book.abstract
  FROM Catalogs c, TABLE(GetBooks(c.cat)) Book;

13.2.2 パイプライン・テーブル・ファンクション

データがパイプライン化されているとみなされるのは、プロデューサ(変換)により生成された直後に、次の変換に入力される前にテーブルやキャッシュにステージングされることなく、コンシューマ(変換)により使用される場合です。

パイプライン化により、テーブル・ファンクションは行を高速で戻すことができ、テーブル・ファンクションの結果をキャッシュするために必要なメモリーが減少します。

パイプライン・テーブル・ファンクションは、テーブル・ファンクションの結果のコレクションをサブセットで戻すことができます。戻されたコレクションは、必要に応じてフェッチできるストリームと同様に動作します。これにより、テーブル・ファンクションを仮想表と同様に使用できます。

パイプライン・テーブル・ファンクションを実装するには、次の2つの方法があります。

  • ネイティブPL/SQLアプローチでは、コンシューマとプロデューサが個別の実行スレッド(同じプロセス・コンテキストまたは異なるプロセス・コンテキスト)で実行され、パイプまたはキューイング・メカニズムを介して通信できます。このアプローチは、コルーチンの実行に似ています。

  • インタフェース・アプローチでは、コンシューマとプロデューサは、同じ実行スレッドで実行されます。プロデューサは、結果セットの生成後に制御をコンシューマに明示的に戻します。また、プロデューサは現在の状態をキャッシュするため、コンシューマにより再び起動された場合に、前回の最終位置から再開できます。

    インタフェース・アプローチでは、手続き型言語で適切に定義された一連のインタフェースを実装する必要があります。

コルーチン実行モデルはパイプライン・テーブル・ファンクションの実装に関して同様のネイティブPL/SQLメカニズムを提供しますが、このモデルはCまたはJavaで記述されたテーブル・ファンクションには使用できません。これに対して、インタフェース・アプローチはこの種のテーブル・ファンクションに使用できます。このアプローチでは、プロデューサは次回の起動時に現在の状態をリストアできるように、状態情報を戻す前にコンテキスト・オブジェクトに保存する必要があります。

ここでは、テーブル・ファンクションという用語が、パイプライン・テーブル・ファンクション(コレクションを反復的に、パイプライン化された方法で戻すテーブル・ファンクション)を表すものとします。

13.2.3 パイプライン・テーブル・ファンクションとREF CURSOR引数

パイプライン・テーブル・ファンクションは、標準ファンクションが受け入れる引数をすべて受け入れることができます。REF CURSORを引数として受け入れるテーブル・ファンクションは、変換ファンクションとして使用できます。つまり、REF CURSORを使用して入力行をフェッチし、入力行に対してなんらかの変換を実行し、結果を(インタフェース・アプローチまたはネイティブPL/SQLアプローチを使用して)パイプラインから出します。

たとえば、次のコードは、StockPivotファンクションを定義する宣言を示しています。このファンクションは、型(Ticker, OpenPrice, ClosePrice)の1行を書式(Ticker, PriceType, Price)の2行に変換します。行("ORCL", 41, 42)に対してStockPivotをコールすると、("ORCL", "O", 41)および("ORCL", "C", 42)の2行が生成されます。

テーブル・ファンクションの入力データは、テーブルStockTableなどのソースから取り込むことができます。

CREATE TABLE StockTable (
  ticker VARCHAR(4),
  openprice NUMBER,
  closeprice NUMBER
);

宣言は例13-5に示されるとおりです。

例13-6では、StockPivotテーブル・ファンクションを使用します。

この問合せでは、パイプライン・テーブル・ファンクションStockPivotCURSOR副問合せのSELECT * FROM StockTableから行をフェッチし、変換を実行し、結果を表としてパイプラインでユーザーに戻します。入力行ごとに2つの出力行(コレクション要素)が生成されます。

前述の例のようにCURSOR副問合せがSQLからREF CURSORファンクションの引数に渡される場合、ファンクションの実行が開始される時点で参照先カーソルがオープンしていることに注意してください。

また、カーソル操作(SELECT FOR UPDATEおよびWHERE CURRENT OF)は、テーブル・ファンクションに基づくREF CURSOR変数には実行できません。

関連項目:

このテーブル・ファンクションをインタフェース・アプローチによりCおよびJavaの両方で実装する方法の詳細は、「パイプライン・テーブル・ファンクション: インタフェース・アプローチの例」を参照してください。

例13-5 REF CURSOR引数を使用したパイプライン・テーブル・ファンクションの宣言

-- Create the types for the table function's output collection 
-- and collection elements

CREATE TYPE TickerType AS OBJECT 
(
  ticker VARCHAR2(4),
  PriceType VARCHAR2(1),
  price NUMBER
);

CREATE TYPE TickerTypeSet AS TABLE OF TickerType;

-- Define the ref cursor type

CREATE PACKAGE refcur_pkg IS
  TYPE refcur_t IS REF CURSOR RETURN StockTable%ROWTYPE;
END refcur_pkg;
/

-- Create the table function

CREATE FUNCTION StockPivot(p refcur_pkg.refcur_t) RETURN TickerTypeSet
PIPELINED ... ;
/

例13-6 REF CURSOR引数を使用したパイプライン・テーブル・ファンクションの使用

SELECT * FROM TABLE(StockPivot(CURSOR(SELECT * FROM StockTable)));

13.2.4 テーブル・ファンクションのパラレル実行

SELECT構文のリストに示されるファンクションをパラレル実行すると、ファンクションの実行はプッシュダウンされ、複数のスレーブ・スキャン・プロセスにより実行されます。各スレーブ・スキャン・プロセスでは、ファンクションはその入力データのセグメントに対して実行されます。

たとえば、次の問合せ

SELECT f(col1) FROM tab;

この問合せは、fが純ファンクションの場合はパラレル化されます。スレーブ・スキャン・プロセスでは、次のようなSQLが実行されます。

SELECT f(col1) FROM tab WHERE ROWID BETWEEN :b1 AND :b2;

各スレーブ・スキャンがROWIDの範囲内で実行され、含まれる行それぞれにファンクションfが適用されます。その後、ファンクションfがスキャン・プロセスによって実行されます。スキャン・プロセスに依存せずに実行されることはありません。

SELECTリスト内で指定されるファンクションとは異なり、テーブル・ファンクションはFROM句内でコールされ、コレクションを戻します。このことは、テーブル・ファンクションの入力データをスレーブ・スキャンの間でパーティション化する方法に影響します。その理由は、テーブル・ファンクションが実行する操作に適した方法でパーティション化を行う必要があるからです。(たとえば、ORDER BY操作の場合は、入力をレンジ・パーティション化する必要があり、GROUP BY操作の場合は、入力をハッシュ・パーティション化する必要があります。)

入力データのパーティション化で説明するように、テーブル・ファンクション自体の宣言内で、適切なパーティション化アプローチが指定されます。次に、ファンクションは2段階の操作で実行されます。最初に、1組目のスレーブ・プロセスによりデータがファンクションの宣言で指定されたとおりにパーティション化されてから、2組目のスレーブ・スキャンによりテーブル・ファンクションがパーティション化されたデータに対してパラレルに実行されます。次の問合せのテーブル・ファンクションには、REF CURSORパラメータがあります。

SELECT * FROM TABLE(f(CURSOR(SELECT * FROM tab)));

スキャンは1組目のスレーブ・プロセスにより実行され、行が(ファンクション宣言で指定されたパーティション化方法に基づいて)2組目のスレーブ・プロセスに再配布され、そこでファンクションfが実際にパラレルに実行されます。

13.3 パイプライン・テーブル・ファンクション

パイプライン・テーブル・ファンクションの実装に関連する問題について考えます。

13.3.1 パイプライン・テーブル・ファンクションの実装の選択肢

前述のように、パイプライン・テーブル・ファンクションの実装については、インタフェース・アプローチとPL/SQLアプローチの2つがサポートされています。

インタフェース・アプローチでは、ユーザーは開始、フェッチおよびクローズ操作で構成される事前定義済のOracleインタフェースを実装する型を提供する必要があります。この型は、作成時のテーブル・ファンクションに関連付けられます。問合せの実行中は、fetchメソッドが繰り返し起動されて結果を反復的に取得します。インタフェース・アプローチでは、テーブル・ファンクションに関連付けられている実装タイプのメソッドを、サポートされている任意の内部言語または外部言語(PL/SQL、C/C++およびJavaなど)で実装できます。

PL/SQLアプローチでは、1つのPL/SQLファンクションに、コレクション全体を1つの値として戻すかわりにファンクションから結果(コレクションの1要素)をパイプライン化するための特別な命令が含まれています。ネイティブPL/SQLアプローチの方が、PL/SQLファンクションを1つのみ記述すればよいため実装が容易です。

パイプライン・テーブル・ファンクションの実装に使用するアプローチは、ファンクションの使用方法には影響しません。SQL文におけるパイプライン・テーブル・ファンクションの使用方法は、実装に使用したアプローチに関係なく同一です。

13.3.2 パイプライン・テーブル・ファンクションの宣言

パイプライン・テーブル・ファンクションを宣言するには、PIPELINEDキーワードを指定します。このキーワードは、ファンクションが行を反復的に戻すことを示します。パイプライン・テーブル・ファンクションの戻り型は、コレクション型(ネストしたtableまたはVARRAY)である必要があります。

例13-7に、インタフェース・アプローチを使用して実装されたパイプライン・テーブル・ファンクションの宣言を示します。ファンクションGetBooksおよびStockPivotのインタフェース・ルーチンは、それぞれBookMethods型およびStockPivotImpl型で実装されています。

例13-8に、ネイティブPL/SQLアプローチを使用して実装された同じテーブル・ファンクションの宣言を示します。

例13-7 インタフェース・アプローチのパイプライン・テーブル・ファンクションの宣言

CREATE FUNCTION GetBooks(cat CLOB) RETURN BookSet_t PIPELINED USING BookMethods;

CREATE FUNCTION StockPivot(p refcur_pkg.refcur_t) 
  RETURN TickerTypeSet PIPELINED USING StockPivotImpl;

例13-8 ネイティブPL/SQLアプローチのパイプライン・テーブル・ファンクションの宣言

CREATE FUNCTION GetBooks(cat CLOB) RETURN BookSet_t PIPELINED IS ...;

CREATE FUNCTION StockPivot(p refcur_pkg.refcur_t) RETURN TickerTypeSet
PIPELINED IS...;

13.3.3 ネイティブPL/SQLアプローチの実装

PL/SQLでは、PIPE ROW文がテーブル・ファンクションに1行をパイプさせて処理を続行させます。この文により、PL/SQLテーブル・ファンクションは生成された行を即時に戻すことができます。例13-9に例を示します。パフォーマンス上の理由から、PL/SQLランタイム・システムはコンシューマに行をバッチで提供します。

例13-9で、PIPE ROW(out_rec)文はPL/SQLテーブル・ファンクションからのデータをパイプライン化します。

PIPE ROW文を使用できるのはパイプライン・テーブル・ファンクションの本体のみで、他の場所で使用するとエラーが発生します。行を戻さないパイプライン・テーブル・ファンクションの場合は、PIPE ROW文を省略できます。

パイプライン・テーブル・ファンクションには、値を戻さないRETURN文が必要です。RETURN文は、制御をコンシューマに移して、次回のフェッチで確実にNO_DATA_FOUND例外が取得されるようにします。

例13-9 ネイティブPL/SQLアプローチのパイプライン・テーブル・ファンクションの実装

CREATE FUNCTION StockPivot(p refcur_pkg.refcur_t) RETURN TickerTypeSet
PIPELINED IS
  out_rec TickerType := TickerType(NULL,NULL,NULL);
  in_rec p%ROWTYPE;
BEGIN
  LOOP
    FETCH p INTO in_rec; 
    EXIT WHEN p%NOTFOUND;
    -- first row
    out_rec.ticker := in_rec.Ticker;
    out_rec.PriceType := 'O';
    out_rec.price := in_rec.OpenPrice;
    PIPE ROW(out_rec);
    -- second row
    out_rec.PriceType := 'C';   
    out_rec.Price := in_rec.ClosePrice;
    PIPE ROW(out_rec);
  END LOOP;
  CLOSE p;
  RETURN;
END;
/

13.3.4 PL/SQLテーブル・ファンクション間のパイプライン化

シリアル実行の場合、結果はコルーチン実行に類似したアプローチを使用して、あるPL/SQLテーブル・ファンクションから別のPL/SQLテーブル・ファンクションにパイプライン化されます。例13-10では、ファンクションgの結果をファンクションfにパイプライン化します。

パラレル実行の動作も同様ですが、各ファンクションは異なるプロセスまたは一連のプロセスで実行されます。

例13-10 あるファンクションから別のファンクションへのファンクション結果のパイプライン化

SELECT * FROM TABLE(f(CURSOR(SELECT * FROM TABLE(g()))));

13.3.5 PIPE ROWとAUTONOMOUS_TRANSACTIONの併用

テーブル・ファンクションは行の生成時にコール側ルーチンとの間で制御をやりとりするため、テーブル・ファンクションとPRAGMA AUTONOMOUS_TRANSACTIONの併用に関しては制限事項があります。テーブル・ファンクションが自律型トランザクションの一部である場合は、コール側サブプログラムでのエラーを回避するために、各PIPE ROW文の前にCOMMITまたはROLLBACKが必要です。

13.3.6 インタフェース・アプローチの実装

インタフェース・アプローチを使用するには、ODCITableインタフェースを実装する実装タイプを定義する必要があります。このインタフェースは、Oracleにより指定されたシグネチャを持つ開始、フェッチおよびクローズ・ルーチンで構成され、これらのルーチンを型のメソッドとして実装します。

Oracleは、テーブル・ファンクションを含む問合せの実行時にメソッドを起動して次のステップを実行します。

  1. ODCITableStart()ファンクションを使用して、スキャン・コンテキスト・パラメータの初期化から始めます。
  2. 結果コレクションに含まれる行のサブセットを生成するためにフェッチします。ODCITableFetch()メソッドは、コレクション全体を戻すために必要な回数だけ起動されます。
  3. 最後のODCITableFetch()後にODCITableClose()を使用して、クローズおよびクリーン・アップ(メモリーの解放など)を実行します。

また、ODCITableインタフェースでは、コンパイル時に起動されるODCITablePrepare()およびODCITableDescribe()という2つのオプション・ルーチンも定義されます。

  • ODCITableDescribe()は、テーブル・ファンクションから戻されるデータ型の構造を静的に定義できない場合に、これを決定します。

  • ODCITablePrepare()は、スキャン・コンテキスト・パラメータを初期化します。実装されている場合は、このメソッドにより準備されるスキャン・コンテキストはODCITableStart()ルーチンに渡され、テーブル・ファンクションの複数回の再開の間も維持されます。また、投影情報を提供し、一時無名型の戻りをサポートします。

13.3.6.1 スキャン・コンテキスト

fetchメソッドで次の行セットを生成するには、テーブル・ファンクションが、別の行セットをフェッチするためのインタフェース・ルーチンの連続的な起動間で、コンテキストを維持できる必要があります。このコンテキストはスキャン・コンテキストと呼ばれ、実装タイプの属性で定義されます。テーブル・ファンクションは、スキャン・コンテキストを実装タイプのオブジェクト・インスタンス内でモデル化することで保持します。

13.3.6.2 開始ルーチン

開始ルーチンODCITableStart()は、テーブル・ファンクションからの行の取得を開始するために起動される最初のルーチンです。通常、このルーチンでは、スキャンに必要な設定が実行され、スキャン・コンテキストが(オブジェクト・インスタンスsctxとして)作成されてOracleに戻されます。ただし、ODCITablePrepare()が実装されている場合は、このルーチンによりスキャン・コンテキストが作成されてから、ODCITableStart()ルーチンに渡されます。ユーザーがSELECT文で指定するテーブル・ファンクションの引数は、このルーチンにパラメータとして渡されます。

テーブル・ファンクションのREF CURSOR引数は、ODCITableStart()の宣言内でSYS_REFCURSOR型として宣言する必要があることに注意してください。標準のREF CURSOR型は、ODCITableStart()に仮引数の型として使用できません。標準のREF CURSOR型を宣言できるのはパッケージ内のみで、パッケージ内で定義された型を型のメソッドで仮引数の型として使用することはできません。ODCITableStartREF CURSOR型を使用するには、システム定義のSYS_REFCURSOR型を使用する必要があります。

13.3.6.3 フェッチ・ルーチン

フェッチ・ルーチンODCITableFetch()は、テーブル・ファンクションの結果セットに含まれる行をすべて取得するために、Oracleにより1回以上起動されます。スキャン・コンテキストはパラメータとして渡されます。このルーチンは、1行以上の次のサブセットを戻します。

フェッチ・ルーチンは、テーブル・ファンクションから行がすべて戻されるまで、Oracleにより繰り返しコールされます。ODCITableFetch()が起動されるたびに戻される行数が多ければ、必要なフェッチのコール回数が減少し、パフォーマンスが向上します。テーブル・ファンクションは、すべての行が戻されたことを示すためにNULLのコレクションを戻す必要があります。

nrowsパラメータは、現在のOCIコールを完了するために必要な行の数を示します。たとえば、現在のOCIコールが100行を要求したODCITableFetch()であり、20行が戻された場合は、nrowsパラメータが80となります。フェッチ・ファンクションでは、様々な行数を戻すことができます。このパラメータの主な目的は、ODCITableFetch()が実際に必要とされる行数よりも多くの行数を戻さないようにすることです。ODCITableFetch()がこのパラメータの値を超える行を返した場合は、行がキャッシュされて後続のODCITableFetch()コールで戻されるか、すべての行がフェッチされる前にOCI文のハンドルがクローズされた場合は、破棄されます。

関連項目:

ODCITableFetch()

13.3.6.4 クローズ・ルーチン

クローズ・ルーチンODCITableClose()は、最後のフェッチの起動後にOracleにより起動されます。スキャン・コンテキストはパラメータとして渡されます。このルーチンにより、必要なクリーン・アップ操作が実行されます。

図13-3 テーブル・ファンクションの行ソースの実行のフローチャート

図13-3の説明が続きます
「図13-3 テーブル・ファンクションの行ソースの実行のフローチャート」の説明
13.3.6.5 戻されるデータ構造の記述(記述メソッド)

テーブル・ファンクションの戻り型の構造を静的に定義できない場合があります。行の形状が問合せごとに異なる場合や、テーブル・ファンクションの起動時に使用された実際の引数に応じて異なる場合があります。この種のテーブル・ファンクションは、AnyDataSetを戻すように宣言できます。AnyDataSetは汎用コレクション型です。この型を使用すると、任意の(任意の要素型の)コレクションをモデル化し、AnyDataSetインスタンスの構成と要素へのアクセスを可能にする一連のAPI(PL/SQLおよびCの両方)を関連付けることができます。

次の例に、構造がファンクション作成時に一定でないAnyDataSetコレクションを戻すように宣言されたテーブル・ファンクションを示します。

CREATE FUNCTION AnyDocuments(VARCHAR2) RETURN ANYDATASET 
PIPELINED USING DocumentMethods;

ODCITableDescribe()を実装すると、書式がテーブル・ファンクションの実パラメータに依存する場合に、結果コレクション内の要素の書式を検出できます。ODCITableDescribe()は、特定の型情報を取得するために問合せのコンパイル時にOracleにより起動されます。通常、このルーチンはユーザー引数を使用して戻り行の形状を判別します。戻されたコレクション内の要素の書式は、AnyTypeのインスタンスを戻すことでOracleに伝えられます。

AnyTypeインスタンスは、特定の問合せで戻される行の実際の構造を指定します。AnyDataSetと同様に、AnyTypeにはメタデータ情報の構成およびアクセスに使用する一連のPL/SQLおよびCインタフェースが関連付けられています。

AnyDocumentsファンクションの例13-11の問合せで、書籍または雑誌に関する情報が戻されます。

例13-12は、ODCITableDescribe()メソッドの実装で、指定した場所にあるXML文書のDTDが参照され、適切なAnyType値(書籍または雑誌)が戻されます。AnyTypeインスタンスは、フィールド名およびデータ型情報を指定してコンストラクタAPIを起動することで構成されます。

OracleはODCITableDescribe()の起動時に、AnyType OUT引数で戻される型情報を使用して、例13-12にあるx.Abstract属性への参照などのコマンドラインの参照を解決します。この機能を適用できるのは、戻される型が名前付きの型(したがって名前付き属性)である場合のみです。

ODCITableDescribeのもう1つの機能は、SELECT *問合せの実行時に、たとえばOCIインタフェースを使用してSELECT構文のリストのパラメータを記述できることです。取得される情報には、ODCITableDescribe()から戻された型のトップレベル属性ごとにSELECT構文のリスト項目が1つ反映されます。

ODCITableDescribe()はコンパイル時にコールされるため、テーブル・ファンクションにはコンパイル時に定数などの値を持つ引数を1つ以上指定する必要があります。様々な引数を指定してテーブル・ファンクションを使用すると、例13-13で示されているように、そのファンクションから異なる戻り型を取得できます。

ODCITableDescribe()機能を使用できるのは、テーブル・ファンクションの実装にインタフェース・アプローチが使用されている場合のみです。ANYDATASETを戻すテーブル・ファンクションのネイティブPL/SQL実装では、サーバーに対して不透明な構造を持つ行が戻されます。

例13-11 AnyTypeデータの問合せ

SELECT * FROM 
  TABLE(AnyDocuments('http://.../documents.xml')) x
  WHERE x.Abstract like '%internet%';

例13-12 ODCITableDescribe()メソッドの実装

CREATE TYPE Mag_t AS OBJECT
(   name VARCHAR2(100),
    publisher VARCHAR2(30),
    abstract VARCHAR2(1000)
);

STATIC FUNCTION ODCITableDescribe(rtype OUT ANYTYPE, 
                                        url VARCHAR2)
IS BEGIN    
    Contact specified web server and retrieve document...
    Check XML doc schema to determine if books or mags...
    IF books THEN
        rtype=AnyType.AnyTypeGetPersistent('SYS','BOOK_T');
    ELSE
        rtype=AnyType.AnyTypeGetPersistent('SYS','MAG_T');
    END IF;
END;

例13-13 AnyTypeを戻すファンクションの使用

-- Issue a query for books
SELECT x.Name, x.Author
FROM TABLE(AnyDocuments('Books.xml')) x;

-- Issue a query for magazines
SELECT x.Name, x.Publisher
FROM TABLE(AnyDocuments('Magazines.xml')) x;

関連項目:

13.3.6.6 問合せの実行に向けた準備(準備メソッド)

ODCITablePrepare()は、問合せのコンパイル時に起動されます。このメソッドでは、問合せの実行時間を短縮するための情報が生成され保存されます。

ODCITablePrepare()を実装しない場合は、コールされるたびにODCITableStart()が初期化されます。ただし、ODCITablePrepare()を実装すると、問合せの実行時にODCITableStart()に渡されるスキャン・コンテキストが初期化されるため、起動時間の短縮になります。また、ODCITablePrepare()が実装されている場合、ODCITableClose()はテーブル・ファンクションが再起動されるたびにコールされるのではなく、問合せ時に一度のみコールされます。これには、次のような利点があります。

  • ODCITableClose()のコール回数が減少し、実行時間が短縮されます。

  • テーブル・ファンクションの再起動から再起動までスキャン・コンテキストを維持できます。

また、ODCITablePrepare()はテーブル・ファンクションに投影情報を提供します。ユーザー定義型(UDT)のコレクションを戻すテーブル・ファンクションについてODCITablePrepare()を実装しない場合、どの属性が使用されるかを知る手段がないため、各要素のUDTの各属性をテーブル・ファンクションで設定する必要があります。これに対して、標準の表から選択すると必要な列のみがフェッチされるため、多くの場合は高速になります。ただし、ODCITablePrepare()を実装すると、例13-14に示されているように、属性の位置の配列を作成し、戻り型情報をODCITabFuncInfo型の引数に記録し、この情報をスキャン・コンテキストに保存できます。

また、ODCITablePrepare()を実装すると、テーブル・ファンクションで一時無名型を戻すことができます。ODCITablePrepare()は問合せのコンパイル終了時にコールされるため、記述メソッドにより作成されたテーブル・ディスクリプタ・オブジェクト(TDO)を渡すことができます。記述メソッドでは、一時無名TDOを作成して戻すことができます。このTDOは問合せの実行時に使用できるようにOracleにより変換され、変換済のTDOがRetType属性で準備メソッドに渡されます。記述メソッドが無名でない型のTDOを戻す場合、そのTDOは変換済TDOと同じです。つまり、テーブル・ファンクションの戻り値に応じて次のようになります。

  • 名前付きコレクション型。RetType属性にはこの型のTDOが含まれます。

  • AnyDataSet。および、記述メソッドで名前付きの型が戻される場合、RetType属性には名前付きの型のTDOが含まれます。

  • AnyDataSet。および、記述メソッドで無名の型が戻される場合は、この型がOracleにより変換され、RetTypeには変換済TDOが含まれます。

例13-14 属性の位置の配列の作成とスキャン・コンテキストへの保存

CREATE TYPE SYS.ODCITabFuncInto AS OBJECT (
  Attrs SYS.ODCINumberList,
  RetType SYS.AnyType
);

13.3.7 テーブル・ファンクションの問合せ

実装にネイティブPL/SQLアプローチとインタフェース・アプローチのどちらが使用されているかに関係なく、パイプライン・テーブル・ファンクションは、SELECT文のFROM句で使用されます。結果行は、例13-15で示されているように、Oracleによりテーブル・ファンクション実装から反復的に取得されます。

ただし、テーブル・ファンクションの出力が引数として渡された値でのみ決定され、渡された値の組合せごとにファンクションが常に同じ結果値を生成する場合は、ファンクションをDETERMINISTICとして宣言できます。この場合、行は自動的にバッファリングされます。ただし、DETERMINISTICとマークされたファンクションが実際にDETERMINISTICかどうかをデータベースが認識できる方法は存在せず、そうでない場合の結果は予測できません。

例13-15 テーブル・ファンクションを使用した反復的な行の取得

SELECT x.Ticker, x.Price 
FROM TABLE(StockPivot(CURSOR(SELECT * FROM StockTable))) x
WHERE x.PriceType='C';
13.3.7.1 テーブル・ファンクションへの複数コールの実装

テーブル・ファンクションを同じ問合せまたは別個の問合せで複数回起動すると、基礎となる実装が複数回実行されます。つまり、例13-16で示されているように、通常、行のバッファリングや再利用は発生しません。

例13-16 テーブル・ファンクションに対する複数の呼び出しの使用

SELECT * FROM TABLE(f(...)) t1, TABLE(f(...)) t2 
  WHERE t1.id = t2.id;
  
SELECT * FROM TABLE(f());

SELECT * FROM TABLE(f());
13.3.7.2 PL/SQL REF CURSOR変数の使用

例13-17で示されているように、PL/SQLのREF CURSOR変数を、テーブル・ファンクションの問合せに対して定義できます。

テーブル・ファンクションのカーソルのフェッチ・セマンティクスは、標準カーソルの場合と同じです。テーブル・ファンクションに基づくREF CURSORの割当てには、特別なセマンティクスはありません。

ただし、SQLオプティマイザは、複数のPL/SQL文の間での最適化を行いません。したがって、例13-19のほうが例13-18よりも優れた実行効率を得られます。

また、例13-18では、2つのSQL文の実行に関連するオーバーヘッドがあり、例13-19のように、2つのファンクション間で結果をパイプライン化することによって実現される効率化を利用できないため、実行速度が遅くなります。

例13-17 テーブル・ファンクション問合せのためのREF CURSOR変数の定義

OPEN c FOR SELECT * FROM TABLE(f(...));

例13-18 REF CURSOR変数の使用

BEGIN
    OPEN r FOR SELECT * FROM TABLE(f(CURSOR(SELECT * FROM tab)));
    SELECT * BULK COLLECT INTO rec_tab FROM TABLE(g(r));
END;

例13-19 REF CURSOR変数のより効果的な使用

SELECT * FROM TABLE(g(CURSOR(SELECT * FROM
  TABLE(f(CURSOR(SELECT * FROM tab))))));

13.3.8 テーブル・ファンクション内でのDML操作の実行

テーブル・ファンクションでDML文を実行するには、自律型トランザクションのプラグマで宣言する必要があります。例13-20で示されるように、このプラグマにより、ファンクションは他のプロセスと共有されない自律型トランザクションで実行されます。

パラレル実行中に、テーブル・ファンクションの各インスタンスにより独立したトランザクションが作成されます。

例13-20 自律型トランザクションのプラグマでのテーブル・ファンクションの宣言

CREATE FUNCTION f(p SYS_REFCURSOR) return CollType PIPELINED IS
    PRAGMA AUTONOMOUS_TRANSACTION; 
BEGIN ... END;

13.3.9 テーブル・ファンクションに対するDML操作の実行

テーブル・ファンクションをUPDATEINSERTまたはDELETE文のターゲット表として指定することはできません。たとえば、次の文を実行するとエラーになります。

UPDATE F(CURSOR(SELECT * FROM tab)) SET col = value; 
INSERT INTO f(...) VALUES ('any', 'thing'); 

ただし、例13-21で示されているように、テーブル・ファンクションのビューを作成し、INSTEAD OFトリガーを使用して更新することはできます。

例13-22に、ユーザーがBookTableビューに行を挿入する場合に、INSTEAD OFトリガーがどのように起動されるかを示します。

INSTEAD OFトリガーは、テーブル・ファンクションについて作成されたビューでの全DML操作に対して定義できます。

例13-21 表でのビューの作成

CREATE VIEW BookTable AS 
  SELECT x.Name, x.Author
  FROM TABLE(GetBooks('data.txt')) x;

例13-22 行がビューに挿入される際のINSTEAD OFトリガーの起動方法

CREATE TRIGGER BookTable_insert
INSTEAD OF INSERT ON BookTable
REFERENCING NEW AS n
FOR EACH ROW
BEGIN
  ...
END;
INSERT INTO BookTable VALUES (...);

13.3.10 テーブル・ファンクションの例外の処理

テーブル・ファンクションでの例外処理動作は、標準のユーザー定義ファンクションの場合と同じです。

CやJavaなど、一部の言語にはユーザー指定の例外処理のためのメカニズムが用意されています。テーブル・ファンクション内で発生した例外が処理される場合、テーブル・ファンクションにより例外ハンドラが実行され、処理が続行されます。例外ハンドラを終了すると、制御が外側のスコープに移ります。例外が消去されると、実行が正常に進行します。

テーブル・ファンクションに未処理例外があると、親トランザクションがロールバックされます。

13.4 パラレル・テーブル・ファンクション

パラレルに実行されるテーブル・ファンクションの場合は、パーティション化された入力パラメータが必要です。テーブル・ファンクションに対するパラレル化が有効になるのは、次の条件が両方とも満たされている場合のみです。

  • ファンクションの宣言にPARALLEL_ENABLE句が含まれていること

  • PARTITION BY句にREF CURSORが1つのみ指定されていること

    PARALLEL_ENABLE句の一部である入力のREF CURSORPARTITION BY句が指定されていなければ、SQLコンパイラはデータのパーティション化方法を適切に判別できません。

13.4.1 カーソル変数を使用したデータの入力

例13-23に示されるように、一連の行をREF CURSORパラメータでPL/SQLファンクションに渡すことができます。

例13-24で示されているように、副問合せの結果をファンクションに直接渡すことができます。副問合せの結果をREF CURSORパラメータとして渡す必要があることを示すために、CURSORキーワードが必要です。

例13-23 REF CURSORでの一連の行のPL/SQLファンクションへの引き渡し

FUNCTION f(p1 IN SYS_REFCURSOR) RETURN ... ;

例13-24 副問合せからファンクションへの結果の直接受け渡し

SELECT * FROM TABLE(f(CURSOR(SELECT empno FROM tab)));
13.4.1.1 複数のREF CURSOR入力変数の使用

例13-25で示されているように、PL/SQLファンクションは、複数のREF CURSOR入力変数を受け入れることができます。

ファンクションg例13-26で示されているように起動できます。

例13-27に示されているように、戻されたデータ上で反復するREF CURSORを作成すると、テーブル・ファンクションの戻り値を他のテーブル・ファンクションに渡すことができます。

例13-25 REF CURSORを通じた一連の行のPL/SQLファンクションへの引き渡し

CREATE FUNCTION g(p1 pkg.refcur_t1, p2 pkg.refcur_t2) RETURN...
  PIPELINED ... ;

例13-26 複数のREF CURSORパラメータを使用するファンクションの起動

SELECT * FROM TABLE(g(CURSOR(SELECT empno FROM tab),
  CURSOR(SELECT * FROM emp));

例13-27 REF CURSORを使用したテーブル・ファンクション間での戻り値の引き渡し

SELECT * FROM TABLE(f(CURSOR(SELECT * FROM TABLE(g(...)))));
13.4.1.2 問合せのREF CURSORの明示的なオープン

例13-28に示されているように、問合せのREF CURSORを明示的にオープンし、それをパラメータとしてテーブル・ファンクションに渡すことができます。

例13-28 問合せのREF CURSORのテーブル・ファンクション・パラメータとしての明示的な使用

BEGIN
  OPEN r FOR SELECT * FROM TABLE(f(...));
  -- Must return a single row result set.
  SELECT * INTO rec FROM TABLE(g(r));
END;
13.4.1.3 JavaおよびC/C++ファンクションに対するPL/SQL REF CURSOR引数

パラレル・テーブル・ファンクションおよびパイプライン・テーブル・ファンクションは、C/C++、JavaまたはPL/SQLで記述できます。PL/SQLとは異なり、C/C++およびJavaではREF CURSOR型はサポートされませんが、C/C++ファンクションおよびJavaファンクションにREF CURSOR引数を渡すことはできます。

テーブル・ファンクションがCのコールアウトとして実装されている場合、コールアウトに渡されたIN REF CURSOR引数は自動的に実行済OCI文ハンドルとして使用可能になります。このハンドルは、他の実行済文ハンドルと同様に使用できます。

IN OUTパラメータとして渡されたコールアウトのREF CURSOR引数は、コールアウトに入るときに実行済文ハンドルに変換され、出るときに文ハンドルがREF CURSORに変換されます。(インバウンドとアウトバウンドの文ハンドルは異なる場合があります。)

例13-28に示されているように、REF CURSOR型がコールのOUT引数または戻り型として使用される場合、コールは文ハンドルを戻す必要があります。この文ハンドルは、コール元のためにREF CURSORに変換されます。

ファンクションがJavaコールとして記述されている場合、IN REF CURSOR引数は自動的にJavaのResultSetクラスのインスタンスに変換されます。IN REF CURSORからResultSetへのマッピングを使用できるのは、OCIベースのFAT JDBCドライバを使用している場合のみです。Thin JDBCドライバには使用できません。Cコールでの実行済の文ハンドルと同様に、REF CURSORがファンクションのIN OUT引数、OUT引数または戻り型の場合、JavaのResultSetはコール元に戻される途中でPL/SQLのREF CURSORに変換されます。

事前定義済の弱い型指定を持つREF CURSOR型のSYS_REFCURSORもサポートされます。SYS_REFCURSORを使用すれば、使用前に最初にパッケージにREF CURSOR型を作成する必要はありません。この弱い型指定を持つREF CURSOR型は、型のメソッドと同様にパッケージの型を受け入れることができないODCITableStart()メソッドで使用できます。

一方、強い型指定を持つREF CURSORを使用するには、PL/SQLパッケージを作成して、そこで強い型指定を持つREF CURSORを宣言する必要があります。また、強い型指定を持つREF CURSOR型をテーブル・ファンクションの引数として使用する場合、REF CURSOR引数の実際の型が列の型と一致する必要があります。一致しなければエラーが生成されます。

弱い型指定を持つREF CURSOR引数をパーティション化するには、ANYを使用してパーティション化する必要があります。その理由は、弱い型指定を持つREF CURSOR引数は、RANGEまたはHASHを使用してパーティション化できないからです。テーブル・ファンクションには、弱い型指定を持つREF CURSOR引数を使用しないことをお薦めします。

例13-29 コールでのREF CURSORの使用

CREATE OR replace PACKAGE p1 AS 
  TYPE rc IS REF cursor; 
  END; 

CREATE OR REPLACE LIBRARY MYLIB AS 'mylib.so'; 

CREATE OR REPLACE FUNCTION MyCallout (stmthp p1.rc) 
  RETURN binary_integer AS LANGUAGE C LIBRARY MYLIB 
  WITH CONTEXT 
  PARAMETERS (context, stmthp ocirefcursor, RETURN sb4); 

sb4 MyCallout (OCIExtProcContext *ctx, OCIStmt ** stmthp) 
  OCIEnv *envhp;                /* env. handle */ 
  OCISvcCtx *svchp;             /* service handle */ 
  OCIError *errhp;              /* error handle */ 
  OCISession *usrhp;            /* user handle */ 

  int errnum = 29400;           /* choose some oracle error number */ 
  char errmsg[512];             /* error message buffer */ 
  size_t errmsglen;             /* Length of error message */ 
  OCIDefine *defn1p = (OCIDefine *) 0; 
  OCINumber *val=(OCINumber *)0; 

  OCINumber *rval = (OCINumber *)0; 
  sword status =  0; 
  double num=0; 
  val = (OCINumber*) OCIExtProcAllocCallMemory(ctx, sizeof(OCINumber)); 
  /* Get OCI handles */ 
  if (GetHandles(ctx, &envhp, &svchp, &errhp, &usrhp,&rval)) 
    return -1; 
  /* Define the fetch buffer */ 
  psdro_checkerr(NULL, errhp, OCIDefineByPos(*stmthp, &defn1p, errhp, (ub4) 1, 
                                            (dvoid *) &num, (sb4) sizeof(num), 
                                            SQLT_FLT, (dvoid *) 0, (ub2 *)0, 
                                            (ub2 *)0, (ub4) OCI_DEFAULT)); 

  /* Fetch loop */ 
  while ((status = OCIStmtFetch(*stmthp, errhp, (ub4) 1,  (ub4) OCI_FETCH_NEXT, 
                                (ub4) OCI_DEFAULT)) == OCI_SUCCESS || 
         status == OCI_SUCCESS_WITH_INFO) 
  { 
    printf("val=%lf\n",num); 
  } 
  return 0; 
} 

13.4.2 入力データのパーティション化

例13-30で示されているように、テーブル・ファンクション宣言では、1つのREF CURSORパラメータについてのみデータのパーティション化を指定できます。PARALLEL_ENABLE句のPARTITION BY句では、パーティション化する入力カーソルの1つとパーティション化に使用する列を指定します。

列リストで明示的な列名を指定すると、パーティション化方法としてRANGEまたはHASHを指定できます。入力行は、指定した列でハッシュ・パーティション化またはレンジ・パーティション化されます。

ANYキーワードを使用すると、ファンクションの動作が入力データのパーティション化に依存しないように指定できます。このキーワードを使用すると、ランタイム・システムではデータがスレーブ間でランダムにパーティション化されます。このキーワードは、1行を取ってその列を操作し、この行の列のみに基づいて出力行を生成するファンクションに適しています。

たとえば、例13-31に示すピボットに類似するファンクションStockPivot()は、入力として型(Ticker varchar(4), OpenPrice number, ClosePrice number)の1行を取り、(Ticker varchar(4), PriceType varchar(1), Price number)型の行を生成します。そのため、行("ORCL", 41, 42)から("ORCL", "O", 41)および("ORCL", "C", 42)の2行が生成されます。

rec_tab_typeのかわりに、例13-5で作成されたTickerTypeSetを直接使用することも、これを同じ方法で定義することもできます。

ファンクションfを使用すると、例13-32に示されるように、Stocks表から別の表を生成できます。

StockTableがパラレルにスキャンされてOpenPriceでパーティション化される場合、ファンクションStockPivotStockTableのスキャンを実行するデータフロー演算子と結合されるため、同じパーティション化を示します。

StockTableがパーティション化されず、そのスキャンがパラレルに実行されない場合は、例13-33に示されるように、AlternateStockTableへの挿入も逐次実行されます。

ファンクションg()がパラレルに実行されてANYによりパーティション化される場合、パラレルINSERTはg()と同じデータフロー演算子に所属できます。

ANYキーワードが指定されている場合、データはスレーブ間でランダムにパーティション化されます。これは、実際にはファンクションが入力パラメータ関連のスキャンを実行するのと同じスレーブ・セットで実行されることを意味します。

この場合、データの再配布や再パーティション化は不要です。カーソルp自体がパラレル対応化されなければ、受信データは列リストにある列でランダムにパーティション化されます。このパーティション化には、ラウンドロビンによる表キューが使用されます。

パーティション化されたXMLType表上、またはレンジ、リストまたはハッシュ・パーティション化を使用してパーティション化されたXMLType列を含む表上にXMLIndex索引を作成する場合、その索引は実表と同一レベルでパーティション化されます。

例13-30 REF CURSORパラメータに対するデータのパーティション化の指定

CREATE FUNCTION f(p ref_cursor_type) RETURN rec_tab_type PIPELINED
  PARALLEL_ENABLE(PARTITION p BY [{HASH | RANGE} (column_list) | ANY ]) IS
BEGIN ... END;

例13-31 StockPivot()ファンクションの実装

CREATE FUNCTION StockPivot(p refcur_pkg.refcur_t) RETURN rec_tab_type PIPELINED
   PARALLEL_ENABLE(PARTITION p BY ANY) IS
   ret_rec rec_type;
 
BEGIN
   FOR rec IN p LOOP
      ret_rec.Ticker := rec.Ticker;
      ret_rec.PriceType := "O";
      ret_rec.Price := rec.OpenPrice;
      PIPE ROW(ret_rec);
 
      ret_rec.Ticker := rec.Ticker;   -- Redundant; not required
      ret_rec.PriceType := "C";
      ret_rec.Price := rec.ClosePrice;
      PIPE ROW ret_rec;
   END LOOP;
   RETURN;
 
END;

表13-32 REF CURSORを使用した別の表からの表の生成

INSERT INTO AlternateStockTable
   SELECT * FROM 
   TABLE(StockPivot(CURSOR(SELECT * FROM StockTable)));

例13-33 REF CURSORを使用したスキャンおよび挿入

CREATE FUNCTION g(p refcur_pkg.refcur_t) RETURN ... PIPELINED
  PARALLEL_ENABLE (PARTITION p BY ANY)
BEGIN 
  ... 
END;

INSERT INTO AlternateStockTable
  SELECT * FROM TABLE(f(CURSOR(SELECT * FROM Stocks))), TABLE(g(CURSOR( ... )))
    WHERE join_condition;

13.4.3 リーフ・レベルのテーブル・ファンクションのパラレル実行

テーブル・ファンクションのパラレル実行には、入力パラメータとしてREF CURSORが必要です。テーブル・ファンクションに入力として行セットを指定する必要が元々ない場合は、REF CURSORを入力として必須とするようにファンクションを再設計する必要があります。

たとえば、次のファンクションは、一連の外部ファイルをパラレルに読み取り、それらに含まれるレコードを戻します。REF CURSORの機能を提供するには、最初にテーブルを作成してファイル名を移入します。これにより、例13-34に示されているように、この表のREF CURSORをテーブル・ファンクション(readfiles())にパラメータとして渡すことができます。

例13-34 REF CURSORを使用した一連の外部ファイルの読取り

CREATE TABLE filetab(filename VARCHAR(20));

INSERT INTO filetab VALUES('file0');   
INSERT INTO filetab VALUES('file1');  
...
INSERT INTO filetab VALUES('fileN');

SELECT * FROM TABLE(readfiles(CURSOR(SELECT filename FROM filetab)));

CREATE FUNCTION readfiles(p pkg.rc_t) RETURN coll_type
  PARALLEL_ENABLE(PARTITION p BY ANY) IS
  ret_rec rec_type;
BEGIN
  FOR rec IN p LOOP
    done := FALSE;
    WHILE (done = FALSE) LOOP
         done := readfilerecord(rec.filename, ret_rec);
         PIPE ROW(ret_rec);
    END LOOP;
  END LOOP;
  RETURN;
END;

13.5 テーブル・ファンクションの入力データ・ストリーム

データ・ストリームは、テーブル・ファンクションでカーソル引数からフェッチした行を順序付けまたはクラスタ化する方法のことです。ファンクションは、入力データを次のいずれかの方法でストリーム化できます。

  • 受信行の順序付けには制限を適用しません。

  • 特定のキー列または複数のキー列で順序付けします。

  • 特定のキーでクラスタ化します。

入力ストリームの動作を制御するには、例13-36の構文を使用します。

入力ストリームは、ファンクションの逐次実行またはパラレル実行について指定できます。ORDER BYまたはCLUSTER BY句を指定しなければ、行はランダム順に入力されます。パラレル実行のORDER BYのセマンティクスは、SQL文のORDER BY句のセマンティクスとは異なります。SQL文では、ORDER BY句はデータ・セット全体をグローバルに順序付けします。テーブル・ファンクションでは、ORDER BY句はそれぞれの行をスレーブで実行中のテーブル・ファンクションの各インスタンスに対してローカルに順序付けします。

クラスタ化すると、同じキー値を持つ行が隣り合って表示されますが、それ以外の場合には行は順序付けされません。

exprはファンクションで受け取ったREFCURSORである必要があります。

例13-35に、入力ストリームを順序付けするための構文を示します。この例では、ファンクションf()は種類(Region, Sales)の行を取り、各地域の平均売上高を示す書式(Region, AvgSales)の行を戻します。

13.5.1 入力ストリームの設定

例13-35 入力ストリームの順序付け

CREATE FUNCTION f(p ref_cursor_type) RETURN tab_rec_type PIPELINED
  CLUSTER p BY (Region)
  PARALLEL_ENABLE(PARTITION p BY HASH(Region)) IS
  ret_rec rec_type;
  cnt number;
  sum number;
BEGIN
  LOOP
  FETCH p INTO rec;
  EXIT WHEN p%NOTFOUND;
    IF (first rec in the group) THEN
        cnt := 1;
        sum := rec.Sales;
    ELSIF (last rec in the group) THEN
      IF (cnt <> 0) THEN
        ret_rec.Region := rec.Region;
          ret_rec.AvgSales := sum/cnt;
          PIPE ROW(ret_rec);
        END IF;
    ELSE
       cnt := cnt + 1;
      sum := sum + rec.Sales;
    END IF;
  END LOOP;
  RETURN;
END

例13-36 入力データ・ストリームの制御

FUNCTION f(p ref_cursor_type) RETURN tab_rec_type [PIPELINED]
         {[ORDER | CLUSTER] expr BY column_list}
         PARALLEL_ENABLE({PARTITION p BY 
           [ANY | {HASH | RANGE} column_list]} )
IS
BEGIN 
  ...
END;

13.5.2 パラレル実行: パーティション化とクラスタ化

パーティション化とクラスタ化は混同されがちですが、実行内容が異なります。パラレル実行では、クラスタ化しなくてもパーティション化で十分な場合があります。

department_idの給与のインメモリ集計を実行するファンクションSmallAggrについて考えます。department_idは、12または3のいずれかです。ファンクションへの入力行は、department_idに対するHASHに基づいてパーティション化できます。このため、department_id1であるすべての行が1つのスレーブに移動し、department_id2であるすべての行が別のスレーブに移動するという方針で処理が行われます。

ファンクションで集計を実行するために入力行をdepartment_idでクラスタ化する必要はありません。各スレーブには1x3の配列SmallSum[1..3]があり、そこでメモリー内の各department_idの集計の合計がSmallSum[department_id]に加算されます。これに対して、department_idの一意の値の数が極端に多い場合は、クラスタ化を使用して部門の集計を計算し、それをディスクに一度に1 department_idずつ書き込むことができます。

13.6 ドメイン索引のパラレル作成

ドメイン索引では、通常、大量のデータが処理されるため、ドメイン索引を作成するとプロセスの実行時間が長くなることがあります。テーブル・ファンクションのパラレル処理機能を使用すると、このボトルネックを軽減でき、テーブル・ファンクションを使用してドメイン索引をパラレルに作成できます。

通常、ODCIIndexCreate()ルーチンでは次のステップが実行されます。

  1. 索引データを格納するための表が作成されます。

  2. 実表からkeycolsおよびrowidなどの関連データがフェッチされ、変換されて、変換済の関連データが索引データ格納用に作成された表に挿入されます。

  3. 問合せ時に高速でアクセスできるように、索引データを格納する表の2次索引が作成されます。

手順2が、ドメイン索引の作成におけるボトルネックです。このステップは、これらの操作をパラレル・テーブル・ファンクションにカプセル化し、そのファンクションをODCIIndexCreate()ファンクションからコールすることで高速化できます。例13-37に、テーブル・ファンクションIndexLoad()が定義されています。

pは次の書式のカーソルです。

SELECT /*+ PARALLEL (base_table, par_degree) */ keycols ,rowid 
  FROM base_table

par_degree値は明示的に指定できます。それ以外の場合は、実表の並列度から導出されます。

IndexLoad()の複数のインスタンスから結果をマージするには、例13-38で定義されているようなファンクションIndexMerge()が必要です。

ODCIIndexCreate()の新しい手順は次のようになります。

  1. 索引のメタデータ構造(索引データを格納する表)が作成されます。

  2. IndexLoad()ファンクションでコミット済データを認識できるように、トランザクションが明示的にコミットされます。

  3. 次のコード例に示すように、IndexLoad()がパラレルに起動されます。

    ドメイン索引のパラレル・ロードのマージの起動

    status := ODCIIndexMerge(CURSOR(
      SELECT * FROM TABLE(ODCIIndexLoad(ia, parms, CURSOR(
        SELECT key_cols, ROWID FROM basetable)))))
    
  4. 2次索引の構造が作成されます。

関連項目:

ODCIIndexCreate()

13.6.1 ドメイン索引のロード

2次索引の形成には非常に時間がかかるため、「ドメイン索引のパラレルでのロード」の項で示すようにドメイン索引のパラレル・ロードを実装した後、「ドメイン索引のパラレル・ロードの結果のマージ」の例で示すようにそれらを再結合します。

例13-37 ドメイン索引のパラレルでのロード

CREATE FUNCTION IndexLoad(ia ODCIIndexInfo, parms VARCHAR2,
                          p refcur-type)
RETURN status_code_type
PARALLEL_ENABLE(PARTITION p BY ANY)  
PRAGMA AUTONOMOUS_TRANSACTION
IS
BEGIN
  FOR rec IN p LOOP
    - process each rec and determine the index entry
    - derive name of index storage table from parameter ia
    - insert into table created in ODCIIndexCreate
  END LOOP;
  COMMIT; -- explicitly commit the autonomous txn
  RETURN ODCIConst.Success;
END;

例13-38 ドメイン索引のパラレル・ロードの結果のマージ

CREATE FUNCTION IndexMerge(p refcur-type) 
RETURN NUMBER
IS
BEGIN
  FOR rec IN p LOOP
    IF (rec != ODCIConst.Success)
      RETURN Error;
  END LOOP;
  RETURN Success; 
END;

13.7 一時型と汎用型

Oracleには、型の記述、データ・インスタンス、およびオブジェクト型やコレクション型を含むその他のSQL型のデータ・インスタンスの動的なカプセル化およびアクセスを可能にする3つの特殊なSQLデータ型があります。この3つの特殊な型は、匿名のコレクション型を含めた匿名(名前のない)型の作成にも使用できます。表13-1を参照してください。

これら3つのSQL型は不透明型として実装されています。これらのタイプの内部構造はデータベースには認識されません。これらのデータの問合せは、ファンクション(通常は3GLルーチン)を実装することによってのみ実行可能です。Oracleは、そのようなファンクションを実装するためにOCIとPL/SQL APIの両方を提供しています。

表13-1 汎用SQL型

説明

SYS.ANYTYPE

型記述型。SYS.ANYTYPEには、名前が付いているか付いていないかにかかわらず、オブジェクト型およびコレクション型を含めた、任意のSQL型の型記述を含めることができます。

ANYTYPEには、永続型の型の記述を含めることができますが、ANYTYPE自体は一時型です。つまり、ANYTYPE自体の値がデータベースに自動的に格納されることはありません。永続型を作成するには、SQLからCREATE TYPE文を使用します。

SYS.ANYDATA

自己記述的データ・インスタンスの型。SYS.ANYDATAには、与えられた型のインスタンス、データ、およびその型の記述が含まれています。SYS.ANYDATAは、この意味で自己記述的です。ANYDATAは、データベースに永続的に格納できます。

SYS.ANYDATASET

自己記述的なデータ集合の型。SYS.ANYDATASETには、与えられた型の記述、およびその型のデータ・インスタンスの集合が含まれています。ANYDATASETは、データベースに永続的に格納できます。

この3つの型はそれぞれ、データベースに対してネイティブな組込み型や、名前の有無にかかわらずオブジェクト型およびコレクション型と併用できます。型は、型記述、単独インスタンスおよび他の型のインスタンス・セットを動的に操作するための汎用的な方法を提供します。APIを使用すると、あらゆる種類の型について一時的なANYTYPE記述を作成できます。同様に、任意のSQL型のデータ値を作成またはANYDATAに変換(キャスト)することも、ANYDATAをSQL型に変換する(戻す)こともできます。さらに、値セットとANYDATASETの場合も同様です。

汎用型は、ストアド・プロシージャを使用した作業を簡単にします。汎用型を使用して、標準型の記述およびデータをカプセル化し、カプセル化された情報を汎用型のパラメータに渡せます。プロシージャ本体では、カプセル化されたデータおよび任意の型の型記述の処理方法を詳細に記述できます。

また、基礎となる様々な型のカプセル化されたデータを、型ANYDATAまたはANYDATASETの単一の表の列に格納することもできます。たとえば、アドバンスト・キューイングでANYDATAを使用すると、データの異機種型のキュー・モデルを作成できます。基礎となるデータ型のデータは、他の任意のデータ同様に、問合せを実行できます。

ただし、一時的な型(ANYTYPE APIを介して構成された名前のない型など)に対して作成されたANYDATAおよびANYDATASETオブジェクトは、ANYDATAまたはANYDATASET表列に永続的に格納できません。

前述の3つの汎用SQL型に対応するのが、それらをモデル化するOCI型です。各OCI型は、それぞれの型の作成およびアクセスに使用する関数のセットを備えています。

  • OCIType: SYS.ANYTYPEに対応しています。

  • OCIAnyData: SYS.ANYDATAに対応しています。

  • OCIAnyDataSet: SYS.ANYDATASETに対応しています。

関連項目: