ヘッダーをスキップ
Oracle Databaseデータ・カートリッジ開発者ガイド
11gリリース1(11.1)
E05688-02
  目次
目次
索引
索引

戻る
戻る
次へ
次へ
 

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

この章では、テーブル・ファンクションについて説明します。また、テーブル・ファンクションとともに使用する汎用データ型ANYTYPEANYDATAおよびANYDATASETについても説明します。

この章の内容は、次のとおりです。

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

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

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

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

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

図13-1に、典型的なデータ処理の使用例を示します。この場合、データはテーブル・ファンクションにより実装される複数(この例では3つ)の変換を通ってから、最終的にデータベースにロードされます。この使用例では、テーブル・ファンクションはパラレル化されておらず、各変換の後に結果のコレクション全体をステージングする必要があります。

図13-1 パラレル化されていない非パイプライン・テーブル・ファンクションを使用した典型的なデータ処理

図13-1の説明は次にあります。
「図13-1 パラレル化されていない非パイプライン・テーブル・ファンクションを使用した典型的なデータ処理」の説明

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

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

図13-2の説明は次にあります。
「図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に格納されています。

例13-2 表でのClobの格納方法

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

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

例13-3 コレクション型を戻す関数の作成方法

CREATE FUNCTION GetBooks(a CLOB) RETURN BookSet_t;

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

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

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

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

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

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

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

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

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

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

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

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

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

パイプライン・テーブル・ファンクションと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-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)));

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

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


関連項目

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

エラーおよび制限事項

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

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

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アプローチを使用して実装された同じテーブル・ファンクションの宣言を示します。

例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...;

ネイティブ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テーブル・ファンクションから別のPL/SQLテーブル・ファンクションにパイプライン化されます。例13-10では、ファンクションgの結果をファンクションfにパイプライン化します。

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

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

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

PIPE ROWとAUTONOMOUS_TRANSACTIONの併用

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

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

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

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

  1. ODCITableStart()ファンクションを使用して、スキャン・コンテキスト・パラメータの初期化から始めます。

  2. 結果コレクションに含まれる行のサブセットを生成するためにフェッチします。ODCITableFetch()メソッドは、コレクション全体を戻すために必要な回数だけ起動されます。

  3. 最後の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型を宣言できるのはパッケージ内のみで、パッケージ内で定義された型を型のメソッドで仮引数の型として使用することはできません。ODCITableStartREF CURSOR型を使用するには、システム定義のSYS_REFCURSOR型を使用する必要があります。

フェッチ・ルーチン

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

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

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

クローズ・ルーチン

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

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

図13-3の説明は次にあります。
「図13-3 テーブル・ファンクションの行ソースの実行のフローチャート」の説明

記述メソッド

テーブル・ファンクションの戻り型の構造を静的に定義できない場合があります。行の形状が問合せごとに異なる場合や、テーブル・ファンクションの起動時に使用された実際の引数に応じて異なる場合があります。この種のテーブル・ファンクションは、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インタフェースが関連付けられています。


関連項目

AnyDataSetおよびAnyTypeについては、「一時型と汎用型」を参照してください。

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でない場合は予期できない結果となることに注意してください。

PL/SQL

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

例13-17 テーブル・ファンクション問合せに対するREF CURSOR変数の定義方法

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

テーブル・ファンクションのカーソルのフェッチ・セマンティクスは、標準カーソルの場合と同じです。テーブル・ファンクションに基づく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操作の実行

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

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

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

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

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

テーブル・ファンクションをUPDATEINSERTまたは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操作に対して定義できます。

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

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

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

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

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

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

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

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

例13-21 一連の行をREF CURSORでPL/SQLファンクションに渡す方法

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

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

例13-22 副問合せの結果を直接ファンクションに渡す方法

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

複数のREF 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-25 テーブル・ファンクション間で戻り値をREF CURSORを使用して渡す方法

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

問合せのREF CURSORの明示的なオープン

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

例13-26 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;

JavaおよびC/C++ファンクションに対するPL/SQL 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でパーティション化される場合、ファンクションStockPivotStockTableのスキャンを実行するデータフロー演算子と結合されるため、同じパーティション化を示します。

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_id12または3のいずれかです。ファンクションへの入力行は、department_id1の行がすべて1つのスレーブに送られ、department_id2の行がすべて別のスレーブに送られるように、department_idHASHパーティション化できます。

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

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

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

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

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

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

  3. 問合せ時に高速でアクセスできるように、索引データを格納する表の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()の新しい手順は次のようになります。

一時型と汎用型

表13-1に、Oracleの3つの特別なSQLデータ型をリストします。これにより、型の記述、データ・インスタンスおよび他のSQL型(オブジェクトやコレクション型など)のデータ・インスタンス・セットを動的にカプセル化してアクセスできます。また、この3つの特別な型を使用すると、無名コレクション型などの無名または名前のない型を作成できます。

この3つのSQL型は不透明型として実装されます。これらの型の内部構造はデータベースで認識されず、そのデータを問い合せるには専用のファンクション(通常は3GLルーチン)を実装する必要があります。Oracleには、この種のファンクションを実装するためのOCI APIと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型の1つの表列に格納することもできます。たとえば、アドバンスト・キューイングでANYDATAを使用すると、データの異機種型のキュー・モデルを作成できます。基礎となるデータ型のデータは、他のデータと同様に問合せ可能です。

3つの汎用SQL型には、それをモデル化する3つのOCI型が対応しています。各型には、それぞれの型を作成してアクセスするための一連のファンクションがあります。


関連項目

  • OCITypeOCIAnyDataおよびOCIAnyDataSet APIおよび使用方法の詳細は、『Oracle Call Interfaceプログラマーズ・ガイド』を参照してください。

  • ANYTYPEANYDATAおよびANYDATASET型へのインタフェースの詳細と、ANYTYPEANYDATAおよびANYDATASETとともに使用するDBMS_TYPESパッケージ(組込み型およびユーザー定義型の定数を定義)の詳細は、『Oracle Database PL/SQLパッケージ・プロシージャおよびタイプ・リファレンス』を参照してください。