13 パイプライン・テーブル・ファンクションとパラレル・テーブル・ファンクションの使用
テーブル・ファンクションでは、汎用データ型ANYTYPE
、ANYDATA
およびANYDATASET
がよく使用されます。
13.1 テーブル・ファンクションの概要
テーブル・ファンクションとは、物理データベース表と同様に問合せできる行のコレクション(ネストした表またはVARRAY)を生成するファンクションです。問合せのFROM
句には、データベース表名と同様にテーブル・ファンクションを使用します。
テーブル・ファンクションは、行のコレクションを入力として取ることができます。入力コレクション・パラメータには、コレクション型またはREF CURSOR
を使用できます。
テーブル・ファンクションをパラレルで実行して、戻された行を中間ステージングなしで次のプロセスに直接にストリーミングできます。テーブル・ファンクションから戻されるコレクションの行はパイプライン化できます。つまり、テーブル・ファンクションの入力の処理がすべて完了してから単一のバッチで出力するかわりに、行の生成と同時にその行を反復的に戻すことができます。
テーブル・ファンクションのストリーミング、パイプライン化およびパラレル実行により、次のようにしてパフォーマンスを向上させることができます。
-
テーブル・ファンクションのマルチスレッド化された同時実行が可能になります。
-
プロセス間の中間的なステージングが不要になります。
-
問合せのレスポンス時間を短縮できます。非パイプライン化テーブル・ファンクションでは、問合せから1つの結果行が戻される前に、テーブル・ファンクションによって戻されたコレクション全体を構造化してサーバーに戻す必要があります。パイプライン化により、行の生成と同時にその行を反復的に戻すことができるようになります。また、オブジェクト・キャッシュがコレクション全体をマテリアライズする必要がなくなるため、テーブル・ファンクションに必要なメモリーを削減できます。
-
コレクション全体が表またはメモリーにステージングされるまで待ってからコレクション全体を戻すかわりに、行が生成されるとテーブル・ファンクションにより戻されたコレクションから結果行を反復的に提供します。
図13-1は、データが最終的にデータベースにロードされる前に、テーブル・ファンクションによって実装されたいくつか(この例では3つ)の変換を通過する、一般的なデータ処理のシナリオを示しています。このシナリオでは、テーブル・ファンクションがパラレルに実行されず、それぞれの変換が行われた後に結果のコレクション全体をステージングする必要があります。
これに対して図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
ファンクションの引数に渡される場合、ファンクションの実行が開始される時点で参照先カーソルがオープンしていることに注意してください。
また、カーソル操作(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は、テーブル・ファンクションを含む問合せの実行時にメソッドを起動して次のステップを実行します。
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()
はテーブル・ファンクションが再起動されるたびにコールされるのではなく、問合せ時に一度のみコールされます。これには、次のような利点があります。
-
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操作の実行
テーブル・ファンクションを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)
の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
でパーティション化される場合、ファンクションStockPivot
はStockTable
のスキャンを実行するデータフロー演算子と結合されるため、同じパーティション化を示します。
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
は、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と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オブジェクト・リレーショナル開発者ガイド』を参照してください。