13 パイプライン・テーブル・ファンクションとパラレル・テーブル・ファンクションの使用
テーブル・ファンクションでは、多くの場合テーブル・ファンクションと汎用データ型ANYTYPE
、ANYDATA
およびANYDATASET
が使用されます。
13.1 テーブル・ファンクションの概要
テーブル・ファンクションとは、物理データベース表と同様に問合せできる行のコレクション(ネストした表またはVARRAY)を生成するファンクションです。問合せのFROM
句には、データベース表名と同様にテーブル・ファンクションを使用します。
テーブル・ファンクションは、行のコレクションを入力として取ることができます。入力コレクション・パラメータには、コレクション型またはREF CURSOR
を使用できます。
テーブル・ファンクションはパラレルで実行できます。また、戻された行を中間的なステージングなしで次のプロセスに直接ストリーミングできます。テーブル・ファンクションから戻されたコレクションに含まれる行をパイプライン化することもできます。つまり、テーブル・ファンクションの入力の処理がすべて完了した後に単一のバッチで戻すのではなく、生成されるたびに反復的に戻すことができます。
テーブル・ファンクションのストリーミング、パイプライン化およびパラレル実行により、パフォーマンスが次のように向上します。
-
テーブル・ファンクションのマルチスレッド化された同時実行が可能になります。
-
プロセス間の中間的なステージングが不要になります。
-
問合せの応答時間が短縮されます。非パイプライン・テーブル・ファンクションの場合、問合せで1行の結果行を戻すには、事前にテーブル・ファンクションから戻されるコレクション全体を構成し、サーバーに戻す必要があります。パイプライン化により、生成される行を反復的に戻すことができます。この場合、オブジェクト・キャッシュでコレクション全体をマテリアライズする必要がないため、テーブル・ファンクションに必要なメモリーも削減されます。
-
コレクション全体が表またはメモリーにステージングされるまで待ってからコレクション全体を戻すかわりに、行が生成されるとテーブル・ファンクションにより戻されたコレクションから結果行を反復的に提供します。
図13-1は標準的なデータ処理の使用例を示しています。この処理では、データがテーブル・ファンクションにより実装される複数(この例では3つ)の変換を通って最終的にデータベースにロードされます。この使用例では、テーブル・ファンクションがパラレルで実行されず、それぞれの変換の後で結果のコレクション全体をステージングする必要があります。
図13-1 パラレル化されていない非パイプライン・テーブル・ファンクションでの標準的なデータ処理
「図13-1 パラレル化されていない非パイプライン・テーブル・ファンクションでの標準的なデータ処理」の説明
これに対して図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
テーブル・ファンクションを使用します。
この問合せでは、パイプライン・テーブル・ファンクションStockPivot
はCURSOR
副問合せのSELECT * FROM StockTable
から行をフェッチし、変換を実行し、結果を表としてパイプラインでユーザーに戻します。入力行ごとに2つの出力行(コレクション要素)が生成されます。
前出の例のようにCURSOR
副問合せがSQLからREF CURSOR
ファンクション引数に渡されると、ファンクションの実行が開始される時点で参照先カーソルがオープンしていることに注意してください。
テーブル・ファンクションに基づくREF CURSOR
変数には、カーソル操作(SELECT FOR UPDATE
およびWHERE CURRENT OF
)を実行できないことにも注意してください。
関連項目:
このテーブル・ファンクションをインタフェース・アプローチにより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
キーワードを指定します。このキーワードは、ファンクションが行を反復的に戻すことを示します。パイプライン・テーブル・ファンクションの戻り型は、コレクション型(ネストした表
または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は、テーブル・ファンクションを含む問合せの実行時にメソッドを起動して次のステップを実行します。
ODCITableStart()
ファンクションを使用して、スキャン・コンテキスト・パラメータの初期化から始めます。- 結果コレクションに含まれる行のサブセットを生成するためにフェッチします。
ODCITableFetch()
メソッドは、コレクション全体を戻すために必要な回数だけ起動されます。 - 最後の
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
型を宣言できるのはパッケージ内のみで、パッケージ内で定義された型を型のメソッドで仮引数の型として使用することはできません。ODCITableStart()
でREF 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文ハンドルがクローズされた場合は、戻された行が破棄されます。
関連項目:
13.3.6.4 クローズ・ルーチン
クローズ・ルーチンODCITableClose()は、最後のフェッチの起動後にOracleにより起動されます。スキャン・コンテキストはパラメータとして渡されます。このルーチンにより、必要なクリーン・アップ操作が実行されます。
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;
関連項目:
-
AnyDataSet
およびAnyType
については、一時型と汎用型を参照してください。
13.3.6.6 問合せを実行するための準備(準備メソッド)
ODCITablePrepare()
は、問合せのコンパイル時に起動されます。このメソッドでは、問合せの実行時間を短縮するための情報が生成され保存されます。
ODCITablePrepare()
を実装しない場合は、コールされるたびにODCITableStart()
が初期化されます。ただし、ODCITablePrepare()
を実装すると、問合せの実行時にODCITableStart()
に渡されるスキャン・コンテキストが初期化されるため、起動時間の短縮になります。また、ODCITablePrepare()
が実装されている場合、ODCITableClose()
はテーブル・ファンクションが再起動されるたびにコールされるのではなく、問合せ時に1回のみコールされます。これには、次のような利点があります。
-
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-18よりも例13-19の方が効率的に実行されます。
また、例13-18では、2つのSQL文の実行に関連するオーバーヘッドが生じるほか、例13-19のように2つのファンクション間で結果をパイプライン化して効率を高めていないことから、例13-18の方が処理が遅くなります。
例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操作の実行
テーブル・ファンクションをUPDATE
、INSERT
または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.4 パラレル・テーブル・ファンクション
パラレルに実行されるテーブル・ファンクションの場合は、パーティション化された入力パラメータが必要です。テーブル・ファンクションに対するパラレル化が有効になるのは、次の条件が両方とも満たされている場合のみです。
-
ファンクションの宣言に
PARALLEL_ENABLE
句が含まれていること -
PARTITION BY
句にREF CURSOR
が1つのみ指定されていることPARALLEL_ENABLE
句の一部である入力のREF CURSOR
にPARTITION 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)
型の行を取り、(Ticker varchar(4), PriceType varchar(1), Price number)
型の行を生成します。これにより、行("ORCL", 41, 42
)から("ORCL", "O", 41
)および("ORCL", "C", 42
)の2行が生成されます。
rec_tab_type
のかわりに、TickerTypeSet
(例13-5で作成)を直接使用するか、または同じように定義できます。
ファンクションf
を使用すると、例13-32に示すように、Stocks
表から別の表を生成できます。
StockTable
がパラレルにスキャンされてOpenPrice
でパーティション化される場合は、ファンクションStockPivot
がStockTable
のスキャンを実行するデータフロー演算子と結合されるため、同じパーティション化が行われます。
StockTable
がパーティション化されず、そのスキャンがパラレルに実行されない場合は、例13-33に示されるように、AlternateStockTable
への挿入も逐次実行されます。
ファンクションg()
がパラレルに実行されてANY
によりパーティション化される場合、パラレル挿入は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 パラレル実行: パーティション化とクラスタ化
パーティション化とクラスタ化は混同されがちですが、実行内容が異なります。パラレル実行では、クラスタ化しなくてもパーティション化で十分な場合があります。
SmallAggr
というファンクションでdepartment_id
ごとに給与のインメモリー集計を実行するとします。department_id
は、1
、2
、3
のいずれかです。このファンクションへの入力行はdepartment_id
でHASH
によってパーティション化できるため、department_id
が1
であるすべての行が1つのスレーブへ、department_id
が2
であるすべての行が別のスレーブへと、次々に移動します。
ファンクションで集計を実行するために入力行をdepartment_id
でクラスタ化する必要はありません。各スレーブには1
x3
の配列SmallSum[1..3]
があり、そこでメモリー内の各department_id
の集計の合計がSmallSum[department_id]
に加算されます。これに対して、department_id
の一意の値の数が極端に多い場合は、クラスタ化を使用して部門の集計を計算し、それをディスクに一度に1 department_id
ずつ書き込むことができます。
13.6 ドメイン索引のパラレル作成
ドメイン索引では、通常、大量のデータが処理されるため、ドメイン索引を作成するとプロセスの実行時間が長くなることがあります。テーブル・ファンクションのパラレル処理機能を使用すると、このボトルネックを軽減でき、テーブル・ファンクションを使用してドメイン索引をパラレルに作成できます。
通常、ODCIIndexCreate()
ルーチンでは次のステップが実行されます。
-
索引データを格納するための表が作成されます。
-
実表から
keycols
およびrowid
などの関連データがフェッチされ、変換されて、変換済の関連データが索引データ格納用に作成された表に挿入されます。 -
問合せ時に高速でアクセスできるように、索引データを格納する表の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()
の新しい手順は次のようになります。
-
索引のメタデータ構造(索引データを格納する表)が作成されます。
-
IndexLoad()
ファンクションがコミット済データにアクセスできるように、トランザクションが明示的にコミットされます。 -
次のコード例に示すように、
IndexLoad()
をパラレルで起動します。ドメイン索引のパラレル・ロードの起動とマージ
status := ODCIIndexMerge(CURSOR( SELECT * FROM TABLE(ODCIIndexLoad(ia, parms, CURSOR( SELECT key_cols, ROWID FROM basetable)))))
-
2次索引の構造が作成されます。
関連項目:
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 APIとPL/SQL APIの両方が用意されています。
表13-1 汎用SQL型
型 | 説明 |
---|---|
|
型記述型。
|
|
自己記述的データ・インスタンスの型。 |
|
自己記述的なデータ集合の型。 |
この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
に対応しています。
関連項目:
-
OCIType
、OCIAnyData
、OCIAnyDataSet
APIおよびそれらの使用方法の詳細は、『Oracle Call Interfaceプログラマーズ・ガイド』を参照してください。 -
ANYTYPE
、ANYDATA
、ANYDATASET
の各型へのインタフェースの詳細と、ANYTYPE
、ANYDATA
、ANYDATASET
で使用するDBMS_TYPES
パッケージ(組込み型とユーザー定義型の定数を定義)の詳細は、Oracle Database PL/SQLパッケージ・プロシージャおよびタイプ・リファレンスを参照してください。 -
汎用型、一時型、不透明型の定義については、Oracle Databaseオブジェクト・リレーショナル開発者ガイドを参照してください。