この章では、テーブル・ファンクションについて説明します。また、テーブル・ファンクションとともに使用する汎用データ型ANYTYPE、ANYDATAおよびANYDATASETについても説明します。
この章の内容は、次のとおりです。
テーブル・ファンクションとは、物理データベース表と同様に問合せできる行のコレクション(ネストした表またはVARRAY)を生成するファンクションです。問合せのFROM句には、データベース表名と同様にテーブル・ファンクションを使用します。
テーブル・ファンクションは、入力として行のコレクションを取ることができます。入力コレクション・パラメータには、コレクション型またはREF CURSORを使用できます。
テーブル・ファンクションの実行をパラレル化でき、戻された行を中間的なステージングなしで次のプロセスに直接送ることができます。テーブル・ファンクションから戻されたコレクションに含まれる行を、パイプライン化することもできます。つまり、テーブル・ファンクションの入力の処理がすべて完了した後に単一のバッチで戻すかわりに、生成されるたびに反復的に戻すことができます。
テーブル・ファンクションのストリーム、パイプライン化およびパラレル実行により、次のようにしてパフォーマンスを向上させることができます。
テーブル・ファンクションのマルチスレッド化された同時実行が可能になります。
プロセス間の中間的なステージングが不要になります。
問合せの応答時間が短縮されます。非パイプライン・テーブル・ファンクションの場合、問合せで1つの結果行が戻されるには、その前にテーブル・ファンクションから戻されるコレクション全体が構成され、サーバーに戻される必要があります。パイプライン化により、行が生成されるたびに反復的に戻すことができます。また、これにより、オブジェクト・キャッシュではコレクション全体をマテリアライズする必要がないため、テーブル・ファンクションに必要なメモリーも減少します。
コレクション全体が表またはメモリーにステージングされるまで待ってからコレクション全体を戻すかわりに、行が生成されるとテーブル・ファンクションにより戻されたコレクションから結果行を反復的に提供します。
図13-1に、典型的なデータ処理の使用例を示します。この場合、データはテーブル・ファンクションにより実装される複数(この例では3つ)の変換を通ってから、最終的にデータベースにロードされます。この使用例では、テーブル・ファンクションはパラレル化されておらず、各変換の後に結果のコレクション全体をステージングする必要があります。
図13-1 パラレル化されていない非パイプライン・テーブル・ファンクションを使用した典型的なデータ処理

これに対して図13-2は、ストリームおよびパラレル実行により、同じ使用例をどのように簡素化できるかを示しています。
この項では、テーブル・ファンクションとそのパイプライン化およびパラレル実行に関連する一部の概念について説明します。
テーブル・ファンクションはコレクション型のインスタンスを戻します。問合せのFROM句でコールすることにより、表と同様に問い合せることができます。テーブル・ファンクションには、TABLEキーワードを使用します。
次の例に、入力としてCLOBを取り、コレクション型BookSet_tのインスタンスを戻すテーブル・ファンクションGetBooksを示します。CLOB列には、なんらかの書式(独自の書式またはXMLなどの標準に準拠する書式)で書籍リストを示すカタログが格納されています。このテーブル・ファンクションは、すべてのカタログおよび対応する書籍リストを戻します。コレクション型BookSet_tは、例13-1で定義されています。
例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;
CLOBは、例13-2で示されているように、表Catalogsに格納されています。
関数GetBooks()は例13-3で定義されています。
例13-4の問合せでは、すべてのカタログおよび対応する書籍リストが戻されます。
データがパイプライン化されているとみなされるのは、プロデューサ(変換)により生成された直後に、次の変換に入力される前にテーブルやキャッシュにステージングされることなく、コンシューマ(変換)により使用される場合です。
パイプライン化により、テーブル・ファンクションは行を高速で戻すことができ、テーブル・ファンクションの結果をキャッシュするために必要なメモリーが減少します。
パイプライン・テーブル・ファンクションは、テーブル・ファンクションの結果のコレクションをサブセットで戻すことができます。戻されたコレクションは、必要に応じてフェッチできるストリームと同様に動作します。これにより、テーブル・ファンクションを仮想表と同様に使用できます。
パイプライン・テーブル・ファンクションを実装するには、次の2つの方法があります。
ネイティブPL/SQLアプローチでは、コンシューマとプロデューサが個別の実行スレッド(同じプロセス・コンテキストまたは異なるプロセス・コンテキスト)で実行され、パイプまたはキューイング・メカニズムを介して通信できます。このアプローチは、コルーチンの実行に似ています。
インタフェース・アプローチでは、コンシューマとプロデューサは、同じ実行スレッドで実行されます。プロデューサは、結果セットの生成後に制御をコンシューマに明示的に戻します。また、プロデューサは現在の状態をキャッシュするため、コンシューマにより再び起動された場合に、前回の最終位置から再開できます。
インタフェース・アプローチでは、手続き型言語で適切に定義された一連のインタフェースを実装する必要があります。
コルーチン実行モデルはパイプライン・テーブル・ファンクションの実装に関して同様のネイティブPL/SQLメカニズムを提供しますが、このモデルはCまたはJavaで記述されたテーブル・ファンクションには使用できません。これに対して、インタフェース・アプローチはこの種のテーブル・ファンクションに使用できます。このアプローチでは、プロデューサは次回の起動時に現在の状態をリストアできるように、状態情報を戻す前にコンテキスト・オブジェクトに保存する必要があります。
この章の残りの部分では、テーブル・ファンクションという用語は、パイプライン・テーブル・ファンクション(反復的にパイプライン化された方法でコレクションを戻すテーブル・ファンクション)を指します。
パイプライン・テーブル・ファンクションは、標準ファンクションが受け入れる引数をすべて受け入れることができます。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-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では、StockPivotテーブル・ファンクションを使用します。
例13-6 REF CURSOR引数を使用したパイプライン・テーブル・ファンクションの使用方法
SELECT * FROM TABLE(StockPivot(CURSOR(SELECT * FROM StockTable)));
この問合せでは、パイプライン・テーブル・ファンクションStockPivotはCURSOR副問合せのSELECT * FROM StockTableから行をフェッチし、変換を実行し、結果を表としてパイプラインでユーザーに戻します。入力行ごとに2つの出力行(コレクション要素)が生成されます。
前述のようにCURSOR副問合せがSQLからREF CURSORファンクションの引数に渡される場合、ファンクションの実行が開始される時点で参照先カーソルがすでにオープンしていることに注意してください。
|
関連項目 このテーブル・ファンクションをインタフェース・アプローチによりCおよびJavaの両方で実装する方法の詳細は、第17章「パイプライン・テーブル・ファンクション: インタフェース・アプローチの例」を参照してください。 |
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が実際にパラレルに実行されます。
この項では、パイプライン・テーブル・ファンクションの実装に関連する問題点について説明します。
前述のように、パイプライン・テーブル・ファンクションの実装については、インタフェース・アプローチとPL/SQLアプローチの2つがサポートされています。
インタフェース・アプローチでは、ユーザーは開始、フェッチおよびクローズ操作で構成される事前定義済のOracleインタフェースを実装する型を提供する必要があります。この型は、作成時のテーブル・ファンクションに関連付けられます。問合せの実行中は、fetchメソッドが繰り返し起動されて結果を反復的に取得します。インタフェース・アプローチでは、テーブル・ファンクションに関連付けられている実装タイプのメソッドを、サポートされている任意の内部言語または外部言語(PL/SQL、C/C++およびJavaなど)で実装できます。
PL/SQLアプローチでは、1つのPL/SQLファンクションに、コレクション全体を1つの値として戻すかわりにファンクションから結果(コレクションの1要素)をパイプライン化するための特別な命令が含まれています。ネイティブPL/SQLアプローチの方が、PL/SQLファンクションを1つのみ記述すればよいため実装が容易です。
パイプライン・テーブル・ファンクションの実装に使用するアプローチは、ファンクションの使用方法には影響しません。SQL文におけるパイプライン・テーブル・ファンクションの使用方法は、実装に使用したアプローチに関係なく同一です。
パイプライン・テーブル・ファンクションを宣言するには、PIPELINEDキーワードを指定します。このキーワードは、ファンクションが行を反復的に戻すことを示します。パイプライン・テーブル・ファンクションの戻り型は、コレクション型(ネストした表またはVARRAY)である必要があります。
例13-7に、インタフェース・アプローチを使用して実装されたパイプライン・テーブル・ファンクションの宣言を示します。ファンクションGetBooksおよびStockPivotのインタフェース・ルーチンは、それぞれBookMethods型およびStockPivotImpl型で実装されています。
例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アプローチを使用して実装された同じテーブル・ファンクションの宣言を示します。
PL/SQLでは、PIPE ROW文がテーブル・ファンクションに1行をパイプさせて処理を続行させます。この文により、PL/SQLテーブル・ファンクションは生成された行を即時に戻すことができます。例13-9に例を示します。パフォーマンスのために、PL/SQLランタイム・システムはコンシューマに行をバッチで提供します。
例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-9で、PIPE ROW(out_rec)文はPL/SQLテーブル・ファンクションからのデータをパイプライン化します。
PIPE ROW文を使用できるのはパイプライン・テーブル・ファンクションの本体のみで、他の場所で使用するとエラーが発生します。行を戻さないパイプライン・テーブル・ファンクションの場合は、PIPE ROW文を省略できます。
パイプライン・テーブル・ファンクションには、値を戻さないRETURN文が必要です。RETURN文は、制御をコンシューマに移して、次回のフェッチで確実にNO_DATA_FOUND例外が取得されるようにします。
シリアル実行の場合、結果はコルーチン実行に類似したアプローチを使用して、あるPL/SQLテーブル・ファンクションから別のPL/SQLテーブル・ファンクションにパイプライン化されます。例13-10では、ファンクションgの結果をファンクションfにパイプライン化します。
パラレル実行の動作も同様ですが、各ファンクションは異なるプロセスまたは一連のプロセスで実行されます。
テーブル・ファンクションは行の生成時にコール側ルーチンとの間で制御をやりとりするため、テーブル・ファンクションとPRAGMA AUTONOMOUS_TRANSACTIONの併用に関しては制限事項があります。テーブル・ファンクションが自律型トランザクションの一部である場合は、コール側サブプログラムでのエラーを回避するために、各PIPE ROW文の前にCOMMITまたはROLLBACKが必要です。
インタフェース・アプローチを使用するには、ODCITableインタフェースを実装する実装タイプを定義する必要があります。このインタフェースは、Oracleにより指定されたシグネチャを持つ開始、フェッチおよびクローズ・ルーチンで構成され、これらのルーチンを型のメソッドとして実装します。
Oracleは、テーブル・ファンクションを含む問合せの実行時にメソッドを起動して次のステップを実行します。
ODCITableStart()ファンクションを使用して、スキャン・コンテキスト・パラメータの初期化から始めます。
結果コレクションに含まれる行のサブセットを生成するためにフェッチします。ODCITableFetch()メソッドは、コレクション全体を戻すために必要な回数だけ起動されます。
最後のODCITableFetch()後にODCITableClose()を使用して、クローズおよびクリーン・アップ(メモリーの解放など)を実行します。
また、ODCITableインタフェースでは、コンパイル時に起動されるODCITablePrepare()およびODCITableDescribe()という2つのオプション・ルーチンも定義されます。
ODCITableDescribe()は、テーブル・ファンクションから戻されるデータ型の構造を、静的に定義できない場合に決定します。
ODCITablePrepare()は、スキャン・コンテキスト・パラメータを初期化します。実装されている場合は、このメソッドにより準備されるスキャン・コンテキストはODCITableStart()ルーチンに渡され、テーブル・ファンクションの複数回の再開の間も維持されます。また、投影情報を提供し、一時無名型の戻りをサポートします。
fetchメソッドで次の行セットを生成するには、別の行セットをフェッチするためにテーブル・ファンクションでインタフェース・ルーチンを連続的に起動する間にコンテキストを維持できる必要があります。このコンテキストはスキャン・コンテキストと呼ばれ、実装タイプの属性で定義されます。テーブル・ファンクションは、スキャン・コンテキストを実装タイプのオブジェクト・インスタンス内でモデル化することで保持します。
開始ルーチンODCITableStart()は、テーブル・ファンクションからの行の取得を開始するために起動される最初のルーチンです。通常、このルーチンでは、スキャンに必要な設定が実行され、スキャン・コンテキストが(オブジェクト・インスタンスsctxとして)作成されてOracleに戻されます。ただし、ODCITablePrepare()が実装されている場合は、このルーチンによりスキャン・コンテキストが作成されてから、ODCITableStart()ルーチンに渡されます。ユーザーがSELECT文で指定するテーブル・ファンクションの引数は、このルーチンにパラメータとして渡されます。
テーブル・ファンクションのREF CURSOR引数は、ODCITableStart()の宣言内でSYS_REFCURSOR型として宣言する必要があることに注意してください。標準のREF CURSOR型は、ODCITableStart()に仮引数の型として使用できません。標準のREF CURSOR型を宣言できるのはパッケージ内のみで、パッケージ内で定義された型を型のメソッドで仮引数の型として使用することはできません。ODCITableStartでREF CURSOR型を使用するには、システム定義のSYS_REFCURSOR型を使用する必要があります。
フェッチ・ルーチンODCITableFetch()は、テーブル・ファンクションの結果セットに含まれる行をすべて取得するために、Oracleにより1回以上起動されます。スキャン・コンテキストはパラメータとして渡されます。このルーチンは、1行以上の次のサブセットを戻します。
フェッチ・ルーチンは、テーブル・ファンクションから行がすべて戻されるまで、Oracleにより繰り返しコールされます。ODCITableFetch()が起動されるたびに戻される行数が多ければ、必要なフェッチのコール回数が減少し、パフォーマンスが向上します。テーブル・ファンクションは、すべての行が戻されたことを示すためにNULLのコレクションを戻す必要があります。
nrowsパラメータは、現行のOCIコールを満たすために必要な行数を示します。たとえば、現行のOCIコールが100行を要求したODCITableFetch()で、すでに20行が戻されている場合、nrowsパラメータは80となります。フェッチ・ファンクションは異なる行数を戻すことができます。このパラメータの主な目的は、ODCITableFetch()で必要以上に多くの行が戻されないようにすることです。ODCITableFetch()がこのパラメータの値より多数の行を戻すと、その行はキャッシュされ、以降のODCITableFetch()コールで戻されます。または、すべてがフェッチされる前にOCI文ハンドルがクローズされると、戻された行は破棄されます。
クローズ・ルーチンODCITableClose()は、最後のフェッチの起動後にOracleにより起動されます。スキャン・コンテキストはパラメータとして渡されます。このルーチンにより、必要なクリーン・アップ操作が実行されます。
テーブル・ファンクションの戻り型の構造を静的に定義できない場合があります。行の形状が問合せごとに異なる場合や、テーブル・ファンクションの起動時に使用された実際の引数に応じて異なる場合があります。この種のテーブル・ファンクションは、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-11 AnyTypeデータの問合せ方法
SELECT * FROM
TABLE(AnyDocuments('http://.../documents.xml')) x
WHERE x.Abstract like '%internet%';
例13-12は、ODCITableDescribe()メソッドの実装で、指定した場所にあるXML文書のDTDが参照され、適切なAnyType値(書籍または雑誌)が戻されます。AnyTypeインスタンスは、フィールド名およびデータ型情報を指定してコンストラクタAPIを起動することで構成されます。
例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;
OracleはODCITableDescribe()の起動時に、AnyType OUT引数で戻される型情報を使用して、例13-12にあるx.Abstract属性への参照などのコマンドラインの参照を解決します。この機能を適用できるのは、戻される型が名前付きの型(したがって名前付き属性)である場合のみです。
ODCITableDescribeのもう1つの機能は、SELECT *問合せの実行時に、たとえばOCIインタフェースを使用してSELECT構文のリストのパラメータを記述できることです。取得される情報には、ODCITableDescribe()から戻された型のトップレベル属性ごとにSELECT構文のリスト項目が1つ反映されます。
ODCITableDescribe()はコンパイル時にコールされるため、テーブル・ファンクションにはコンパイル時に定数などの値を持つ引数を1つ以上指定する必要があります。様々な引数を指定してテーブル・ファンクションを使用すると、例13-13で示されているように、そのファンクションから異なる戻り型を取得できます。
例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;
ODCITableDescribe()機能を使用できるのは、テーブル・ファンクションの実装にインタフェース・アプローチが使用されている場合のみです。ANYDATASETを戻すテーブル・ファンクションのネイティブPL/SQL実装では、サーバーに対して不透明な構造を持つ行が戻されます。
ODCITablePrepare()は、問合せのコンパイル時に起動されます。このメソッドでは、問合せの実行時間を短縮するための情報が生成され保存されます。
ODCITablePrepare()を実装しない場合は、コールされるたびにODCITableStart()が初期化されます。ただし、ODCITablePrepare()を実装すると、問合せの実行時にODCITableStart()に渡されるスキャン・コンテキストが初期化されるため、起動時間の短縮になります。また、ODCITablePrepare()が実装されている場合、ODCITableClose()はテーブル・ファンクションが再起動されるたびにコールされるのではなく、問合せ時に1度のみコールされます。これには次のメリットがあります。
ODCITableClose()のコール回数が減少し、実行時間が短縮されます。
テーブル・ファンクションの再起動から再起動までスキャン・コンテキストを維持できます。
また、ODCITablePrepare()はテーブル・ファンクションに投影情報を提供します。ユーザー定義型(UDT)のコレクションを戻すテーブル・ファンクションについてODCITablePrepare()を実装しない場合、どの属性が使用されるかを知る手段がないため、各要素のUDTの各属性をテーブル・ファンクションで設定する必要があります。これに対して、標準の表から選択すると必要な列のみがフェッチされるため、多くの場合は高速になります。ただし、ODCITablePrepare()を実装すると、例13-14に示されているように、属性の位置の配列を作成し、戻り型情報をODCITabFuncInfo型の引数に記録し、この情報をスキャン・コンテキストに保存できます。
例13-14 属性の位置の配列の作成方法とスキャン・コンテキストへの保存方法
CREATE TYPE SYS.ODCITabFuncInto AS OBJECT ( Attrs SYS.ODCINumberList, RetType SYS.AnyType );
また、ODCITablePrepare()を実装すると、テーブル・ファンクションで一時無名型を戻すことができます。ODCITablePrepare()は問合せのコンパイル終了時にコールされるため、記述メソッドにより作成されたテーブル・ディスクリプタ・オブジェクト(TDO)を渡すことができます。記述メソッドでは、一時無名TDOを作成して戻すことができます。このTDOは問合せの実行時に使用できるようにOracleにより変換され、変換済のTDOがRetType属性で準備メソッドに渡されます。記述メソッドが無名でない型のTDOを戻す場合、そのTDOは変換済TDOと同じです。つまり、テーブル・ファンクションの戻り値に応じて次のようになります。
名前付きコレクション型。RetType属性にはこの型のTDOが含まれます。
AnyDataSet。および、記述メソッドで名前付きの型が戻される場合、RetType属性には名前付きの型のTDOが含まれます。
AnyDataSet。および、記述メソッドで無名の型が戻される場合は、この型がOracleにより変換され、RetTypeには変換済TDOが含まれます。
実装にネイティブPL/SQLアプローチとインタフェース・アプローチのどちらが使用されているかに関係なく、パイプライン・テーブル・ファンクションは、SELECT文のFROM句で使用されます。結果行は、例13-15で示されているように、Oracleによりテーブル・ファンクション実装から反復的に取得されます。
例13-15 テーブル・ファンクションを使用した反復的な行の取得方法
SELECT x.Ticker, x.Price FROM TABLE(StockPivot(CURSOR(SELECT * FROM StockTable))) x WHERE x.PriceType='C';
テーブル・ファンクションを同じ問合せまたは別個の問合せで複数回起動すると、基礎となる実装が複数回実行されます。つまり、例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());
ただし、テーブル・ファンクションの出力が引数として渡された値でのみ決定される場合は、ファンクションが渡された値の組合せごとに常に同じ結果値が生成されるように、ファンクションDETERMINISTICを宣言できます。これにより、行は自動的にバッファリングされます。この場合、データベースにはDETERMINISTICマークが付いているファンクションが実際にDETERMINISTICであるかどうかを認識する手段がなく、実際にはDETERMINISTICでない場合は予期できない結果となることに注意してください。
例13-17で示されているように、PL/SQLのREF CURSOR変数を、テーブル・ファンクションの問合せに対して定義できます。
テーブル・ファンクションのカーソルのフェッチ・セマンティクスは、標準カーソルの場合と同じです。テーブル・ファンクションに基づくREF CURSORの割当てには、特別なセマンティクスはありません。
ただし、SQLオプティマイザでは、PL/SQL文にまたがる最適化は実行されません。
BEGIN
OPEN r FOR SELECT * FROM TABLE(f(CURSOR(SELECT * FROM tab)));
SELECT * BULK COLLECT INTO rec_tab FROM TABLE(g(r));
END;
この場合と次の場合、最適化は実行されません。
SELECT * FROM TABLE(g(CURSOR(SELECT * FROM TABLE(f(CURSOR(SELECT * FROM tab))))));
また、2つのSQL文の実行に関連付けられたオーバーヘッドにより、最初の例は遅くなり、2つのファンクション間で結果をパイプライン化することで第2例のように能率を得られません。
テーブル・ファンクションでDML文を実行するには、自律型トランザクションのプラグマで宣言する必要があります。例13-18で示されるように、このプラグマにより、ファンクションは他のプロセスと共有されない自律型トランザクションで実行されます。
例13-18 自律型トランザクションのプラグマでのテーブル・ファンクションの宣言方法
CREATE FUNCTION f(p SYS_REFCURSOR) return CollType PIPELINED IS
PRAGMA AUTONOMOUS_TRANSACTION;
BEGIN ... END;
パラレル実行中に、テーブル・ファンクションの各インスタンスにより独立したトランザクションが作成されます。
テーブル・ファンクションをUPDATE、INSERTまたはDELETE文のターゲット表として指定することはできません。たとえば、次の文を実行するとエラーになります。
UPDATE F(CURSOR(SELECT * FROM tab)) SET col = value;
INSERT INTO f(...) VALUES ('any', 'thing');
ただし、例13-19で示されているように、テーブル・ファンクションのビューを作成し、INSTEAD OFトリガーを使用して更新することはできます。
例13-19 表でのビューの作成方法
CREATE VIEW BookTable AS
SELECT x.Name, x.Author
FROM TABLE(GetBooks('data.txt')) x;
例13-20に、ユーザーがBookTableビューに行を挿入する場合に、INSTEAD OFトリガーがどのように起動されるかを示します。
例13-20 行がビューに挿入される際のINSTEAD OFトリガーの起動方法
CREATE TRIGGER BookTable_insert INSTEAD OF INSERT ON BookTable REFERENCING NEW AS n FOR EACH ROW BEGIN ... END; INSERT INTO BookTable VALUES (...);
INSTEAD OFトリガーは、テーブル・ファンクションについて作成されたビューでの全DML操作に対して定義できます。
パラレルに実行されるテーブル・ファンクションの場合は、パーティション化された入力パラメータが必要です。テーブル・ファンクションに対するパラレル化が有効になるのは、次の条件が両方とも満たされている場合のみです。
ファンクションの宣言にPARALLEL_ENABLE句が含まれていること
PARTITION BY句にREF CURSORが1つのみ指定されていること
PARALLEL_ENABLE句の一部である入力のREF CURSORにPARTITION BY句が指定されていなければ、SQLコンパイラはデータのパーティション化方法を適切に判別できません。
例13-21に示されるように、一連の行をREF CURSORパラメータでPL/SQLファンクションに渡すことができます。
例13-22で示されているように、副問合せの結果をファンクションに直接渡すことができます。副問合せの結果をREF CURSORパラメータとして渡す必要があることを示すために、CURSORキーワードが必要です。
例13-23で示されているように、PL/SQLファンクションは、複数のREF CURSOR入力変数を受け入れることができます。
例13-23 一連の行を複数のREF CURSORパラメータでPL/SQLファンクションに渡す方法
CREATE FUNCTION g(p1 pkg.refcur_t1, p2 pkg.refcur_t2) RETURN... PIPELINED ... ;
ファンクションgは例13-24で示されているように起動できます。
例13-24 複数のREF CURSORパラメータを使用したファンクションの起動方法
SELECT * FROM TABLE(g(CURSOR(SELECT empno FROM tab), CURSOR(SELECT * FROM emp));
例13-25に示されているように、戻されたデータ上で反復するREF CURSORを作成すると、テーブル・ファンクションの戻り値を他のテーブル・ファンクションに渡すことができます。
例13-26に示されているように、問合せのREF CURSORを明示的にオープンし、それをパラメータとしてテーブル・ファンクションに渡すことができます。
パラレル・テーブル・ファンクションとパイプライン・テーブル・ファンクションは、PL/SQLのみでなくC/C++およびJavaでも記述できます。PL/SQLとは異なり、C/C++およびJavaではREF CURSOR型はサポートされませんが、C/C++ファンクションおよびJavaファンクションにREF CURSOR引数を渡すことはできます。
テーブル・ファンクションがCのコールアウトとして実装されている場合、コールアウトに渡されたIN REF CURSOR引数は自動的に実行済OCI文ハンドルとして使用可能になります。このハンドルは、他の実行済文ハンドルと同様に使用できます。
IN OUTパラメータとして渡されたコールアウトのREF CURSOR引数は、コールアウトに入るときに実行済文ハンドルに変換され、出るときに文ハンドルがREF CURSORに変換されます。(インバウンドとアウトバウンドの文ハンドルは異なる場合があります。)
例13-26に示されているように、REF CURSOR型がコールアウトのOUT引数または戻り型として使用される場合、コールアウトは文ハンドルを戻す必要があります。この文ハンドルは、コール元のためにREF CURSORに変換されます。
例13-27コールアウトでの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;
}
ファンクションが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引数をパーティション化するには、ANYでパーティション化する必要があります。弱い型指定を持つREF CURSOR引数は、RANGEまたはHASHではパーティション化できません。テーブル・ファンクションには、弱い型指定を持つREF CURSOR引数を使用しないことをお薦めします。
例13-28で示されているように、テーブル・ファンクション宣言では、1つのREF CURSORパラメータについてのみデータのパーティション化を指定できます。PARALLEL_ENABLE句のPARTITION BY句では、パーティション化する入力カーソルの1つとパーティション化に使用する列を指定します。
例13-28 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;
列リストで明示的な列名を指定すると、パーティション化方法としてRANGEまたはHASHを指定できます。入力行は、指定した列でハッシュ・パーティション化またはレンジ・パーティション化されます。
ANYキーワードを使用すると、ファンクションの動作が入力データのパーティション化に依存しないように指定できます。このキーワードを使用すると、ランタイム・システムではデータがスレーブ間でランダムにパーティション化されます。このキーワードは、1行を取ってその列を操作し、この行の列のみに基づいて出力行を生成するファンクションに適しています。
たとえば、例13-29に示すピボットに類似するファンクション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行が生成されます。
例13-29 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;
push ret_rec;
END LOOP;
RETURN;
END;
ファンクションfを使用すると、例13-30に示されるように、Stocks表から別の表を生成できます。
表13-30 REF CURSORを使用して別の表から表を生成する方法
INSERT INTO AlternateStockTable SELECT * FROM TABLE(StockPivot(CURSOR(SELECT * FROM StockTable)));
StockTableがパラレルにスキャンされてOpenPriceでパーティション化される場合、ファンクションStockPivotはStockTableのスキャンを実行するデータフロー演算子と結合されるため、同じパーティション化を示します。
StockTableがパーティション化されず、そのスキャンがパラレルに実行されない場合は、例13-31に示されるように、AlternateStockTableへの挿入も逐次実行されます。
例13-31 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;
ファンクションg()がパラレルに実行されてANYによりパーティション化される場合、パラレルINSERTはg()と同じデータフロー演算子に所属できます。
ANYキーワードが指定されている場合、データはスレーブ間でランダムにパーティション化されます。これは、実際にはファンクションが入力パラメータ関連のスキャンを実行するのと同じスレーブ・セットで実行されることを意味します。
この場合、データの再配布や再パーティション化は不要です。カーソルp自体がパラレル化されなければ、受信データは列リストにある列でランダムにパーティション化されます。このパーティション化には、ラウンドロビンによる表キューが使用されます。
リーフ・レベルのテーブル・ファンクション(REF CURSORが関係しない単一操作を実行するファンクション)のパラレル実行を使用するには、REF CURSORが必要です。
たとえば、ファンクションで一連の外部ファイルをパラレルに読み取り、ファイルに含まれるレコードを戻す必要があるとします。REF CURSORの機能を提供するには、最初にテーブルを作成してファイル名を移入します。これにより、例13-32に示されているように、この表のREF CURSORをテーブル・ファンクション(readfiles())にパラメータとして渡すことができます。
例13-32 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-33の構文を使用します。
例13-33 データ・ストリーム入力の制御方法
FUNCTION f(p ref_cursor_type) RETURN tab_rec_type [PIPELINED] {[ORDER | CLUSTER] BY column_list} PARALLEL_ENABLE({PARTITION p BY [ANY | (HASH | RANGE) column_list]} ) IS BEGIN ... END;
入力ストリームは、ファンクションの逐次実行またはパラレル実行について指定できます。
ORDER BYまたはCLUSTER BY句を指定しなければ、行はランダム順に入力されます。パラレル実行のORDER BYのセマンティクスは、SQL文のORDER BY句のセマンティクスとは異なります。SQL文では、ORDER BY句はデータ・セット全体をグローバルに順序付けします。テーブル・ファンクションでは、ORDER BY句はそれぞれの行をスレーブで実行中のテーブル・ファンクションの各インスタンスに対してローカルに順序付けします。
例13-34に、入力ストリームを順序付けするための構文を示します。この例では、ファンクションf()は種類(Region, Sales)の行を取り、各地域の平均売上高を示す書式(Region, AvgSales)の行を戻します。
例13-34 入力ストリームの順序付けの方法
CREATE FUNCTION f(p ref_cursor_type) RETURN tab_rec_type PIPELINED
CLUSTER BY Region
PARALLEL_ENABLE(PARTITION p BY Region) IS
ret_rec rec_type;
cnt number;
sum number;
BEGIN
FOR rec IN p LOOP
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;
パーティション化とクラスタ化は混同されがちですが、実行内容が異なります。パラレル実行では、クラスタ化しなくてもパーティション化で十分な場合があります。
department_idごとに給与のメモリー内集計を実行するファンクションSmallAggrを考えてみます。department_idは1、2または3のいずれかです。ファンクションへの入力行は、department_idが1の行がすべて1つのスレーブに送られ、department_idが2の行がすべて別のスレーブに送られるように、department_idでHASHパーティション化できます。
ファンクションで集計を実行するために入力行をdepartment_idでクラスタ化する必要はありません。各スレーブには1x3の配列SmallSum[1..3]があり、そこでメモリー内の各department_idの集計の合計がSmallSum[department_id]に加算されます。これに対して、department_idの一意の値の数が極端に多い場合は、クラスタ化を使用して部門の集計を計算し、それをディスクに一度に1 department_idずつ書き込むことができます。
ドメイン索引では、通常、大量のデータが処理されるため、ドメイン索引を作成するとプロセスの実行時間が長くなることがあります。テーブル・ファンクションのパラレル処理機能を使用すると、このボトルネックを軽減でき、テーブル・ファンクションを使用してドメイン索引をパラレルに作成できます。
通常、ODCIIndexCreate()ルーチンでは次のステップが実行されます。
索引データを格納するための表が作成されます。
実表からkeycolsおよびrowidなどの関連データがフェッチされ、変換されて、変換済の関連データが索引データ格納用に作成された表に挿入されます。
問合せ時に高速でアクセスできるように、索引データを格納する表の2次索引が作成されます。
手順2が、ドメイン索引の作成におけるボトルネックです。このステップは、これらの操作をパラレル・テーブル・ファンクションにカプセル化し、そのファンクションをODCIIndexCreate()ファンクションからコールすることで高速化できます。例13-35に、テーブル・ファンクションIndexLoad()が定義されています。
例13-35 ドメイン索引をパラレルにロードする方法
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;
pは次の書式のカーソルです。
SELECT /*+ PARALLEL (base_table, par_degree) */ keycols ,rowid FROM base_table
par_degree値は明示的に指定できます。それ以外の場合は、実表の並列度から導出されます。
IndexLoad()の複数のインスタンスから結果をマージするには、例13-36で定義されているようなファンクションIndexMerge()が必要です。
例13-36 ドメイン索引をパラレルにロードした結果をマージする方法
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;
ODCIIndexCreate()の新しい手順は次のようになります。
索引のメタデータ構造(つまり、索引データを格納する表)が作成されます。
IndexLoad()ファンクションでコミット済データを認識できるように、トランザクションが明示的にコミットされます。
IndexLoad()がパラレルにコールされます。
2次索引の構造が作成されます。
表13-1に、Oracleの3つの特別なSQLデータ型をリストします。これにより、型の記述、データ・インスタンスおよび他のSQL型(オブジェクトやコレクション型など)のデータ・インスタンス・セットを動的にカプセル化してアクセスできます。また、この3つの特別な型を使用すると、無名コレクション型などの無名または名前のない型を作成できます。
この3つのSQL型は不透明型として実装されます。これらの型の内部構造はデータベースで認識されず、そのデータを問い合せるには専用のファンクション(通常は3GLルーチン)を実装する必要があります。Oracleには、この種のファンクションを実装するためのOCI APIとPL/SQL APIの両方が用意されています。
表13-1 汎用SQL型
| 型 | 説明 |
|---|---|
|
|
型の記述タイプ。
|
|
自己記述的なデータ・インスタンス・タイプ。 |
|
|
自己記述的なデータ・セット・タイプ。 |
この3つの型はそれぞれ、データベースに対してネイティブな組込み型や、名前の有無にかかわらずオブジェクト型およびコレクション型と併用できます。型は、型記述、単独インスタンスおよび他の型のインスタンス・セットを動的に操作するための汎用的な方法を提供します。APIを使用すると、あらゆる種類の型について一時的なANYTYPE記述を作成できます。同様に、任意のSQL型のデータ値を作成するかANYDATAに変換(キャスト)し、ANYDATAをSQL型に変換できます。さらに、値セットとANYDATASETの場合も同様です。
汎用型により、ストアド・プロシージャの操作が簡素化されます。汎用型を使用すると、標準型の記述とデータをカプセル化し、カプセル化した情報を汎用型のパラメータに渡すことができます。プロシージャ本体では、あらゆる型のカプセル化データと型記述の処理方法を詳細に指定できます。
また、基礎となる様々な型のカプセル化されたデータをANYDATA型またはANYDATASET型の1つの表列に格納することもできます。たとえば、アドバンスト・キューイングでANYDATAを使用すると、データの異機種型のキュー・モデルを作成できます。基礎となるデータ型のデータは、他のデータと同様に問合せ可能です。
3つの汎用SQL型には、それをモデル化する3つのOCI型が対応しています。各型には、それぞれの型を作成してアクセスするための一連のファンクションがあります。
|
関連項目
|