この章では、ユーザー定義演算子を示し、索引タイプの使用、不使用にかかわらず、演算子の使用方法について説明します。
この章の内容は、次のとおりです。
ユーザー定義演算子は、トップレベルのスキーマ・オブジェクトです。ユーザー定義演算子には、<、>および=などの組込み演算子と同様に様々な機能があります。たとえば、いずれも同じ状況で起動できます。この種の演算子はSQL文を簡素化し、短くて読みやすいものにすることにより使用しやすくします。
ユーザー定義演算子の内容は、次のとおりです。
表、ビュー、型およびスタンドアロン・ファンクションと同じネームスペースにあって名前で識別されます。
指定のコンテキストにおける演算子の動作を定義するファンクションにバインドされます。
各演算子を使用できる状況を示す権限により制御されます。
通常は、データベースに組み込まれていない索引の定義に使用できる索引タイプに関連付けられています。
|
関連項目 構文と権限の詳細は、『Oracle Database SQL言語リファレンス』を参照してください。 |
演算子バインディングにより、演算子をその実装に使用するファンクションのシグネチャに関連付けます。シグネチャは、ファンクションの引数のデータ型、出現順序およびファンクションの戻り型のリストで構成されています。演算子バインディングにより、Oracleに対して演算子の起動時に実行するファンクションを指示します。各ファンクションのシグネチャが異なる場合は、1つの演算子を複数のファンクションにバインドできます。ファンクションが異なるものとみなされるには、各ファンクションの引数リストが異なっている必要があります。戻り値のデータ型が一致しなくても引数リストが一致しているファンクションは、異なるものとはみなされず、同じ演算子にはバインドできません。
演算子に可能なバインド先は、次のとおりです。
スタンドアロン・ファンクション
パッケージ・ファンクション
ユーザー定義型のメンバー・メソッド
演算子は、アクセス可能な任意のスキーマ内でファンクションおよびスキーマにバインドできます。各演算子の作成時に、バインディングを1つ以上指定する必要があります。一意でない演算子バインディングを指定すると、Oracleサーバーでエラーになります。
演算子と演算子バインディングを作成するには、次の権限が必要です。
CREATE OPERATORまたはCREATE ANY OPERATOR権限
参照されるファンクション、演算子、パッケージまたは型に対するEXECUTE権限
ユーザー定義演算子を削除するには、その演算子の所有者であるか、またはDROP ANY OPERATOR権限が必要です。
ユーザー定義演算子を式で起動するには、その演算子の所有者であるか、またはそれに対するEXECUTE権限が必要です。
演算子を作成するには、CREATE OPERATOR文で演算子名とバインディングを指定します。例9-1では、演算子Contains()をOrdsysスキーマに作成し、テキスト・ドメインと空間ドメイン内で実装を提供するファンクションにバインドします。
演算子とすべてのバインディングを削除するには、DROP OPERATOR文で演算子名を指定します。例9-2では、演算子Contains()を削除します。
デフォルトのDROP動作はDROP RESTRICTです。つまり、演算子バインディングのいずれかに依存索引タイプまたは補助演算子が存在する場合、DROP操作は禁止されます。
デフォルト動作をオーバーライドするには、FORCEオプションを使用します。例9-3では、演算子とすべてのバインディングを削除し、依存索引タイプ・オブジェクトと依存補助演算子に無効マークを付けます。
ALTER OPERATOR文を使用すると、既存の演算子についてバインディングを追加または削除できます。例9-4では、演算子Contains()にバインディングを追加します。
例9-4 演算子へのバインディングの追加方法
ALTER OPERATOR Ordsys.Contains ADD BINDING (music.artist, music.artist) RETURN NUMBER USING music.contains;
変更操作を実行するには、次のように特定の権限が必要です。
演算子を変更するには、その演算子が自分のスキーマ内に存在するか、ALTER ANY OPERATOR権限を持っている必要があります。
参照される演算子とファンクションに対するEXECUTE権限が必要です。
ALTER OPERATOR文には、次の制限が適用されます。
発行できるのは、既存の演算子に関連するALTER OPERATOR文のみです。
各ALTER OPERATOR文で追加または削除できるバインディングは1つのみです。
ALTER OPERATOR文で演算子のバインディングのみを削除することはできません。DROP OPERATOR文を使用して演算子を削除します。バインディングなしの演算子は存在できません。
索引タイプに関連付けられている演算子にバインディングを追加する場合は、ALTER INDEXTYPE ADD OPERATOR文も発行しなければ、バインディングは索引タイプに関連付けられません。
演算子にコメント・テキストを追加するには、COMMENT文で名前とテキストを指定します。例9-5では、Contains()演算子に関する情報を提供します。
例9-5 演算子のコメント付けの方法
COMMENT ON OPERATOR Ordsys.Contains IS 'a number indicating whether the text contains the key';
演算子に関するコメントは、次のビューを介してデータ・ディクショナリ内で使用できます。
USER_OPERATOR_COMMENTS
ALL_OPERATOR_COMMENTS
DBA_OPERATOR_COMMENTS
COMMENT ANY OPERATOR権限がない場合、コメント付けできるのは自分のスキーマ内にある演算子のみです。
組込み演算子と同様に、ユーザー定義演算子は式を使用できる場所であればどこでも起動できます。たとえば、ユーザー定義演算子を次の場所で使用できます。
SELECTコマンドのSELECT構文のリスト
WHERE句の条件
ORDER BY句およびGROUP BY句
演算子が起動されると、Oracleはバインドされているファンクションを実行して演算子を評価します。演算子に複数のファンクションがバインドされている場合は、引数のデータ型が起動のデータ型と(暗黙的型変換後に)一致しているファンクションが実行されます。起動された演算子の引数リストが、その演算子にバインドされているファンクションのシグネチャと一致しない場合は、エラーが発生します。ユーザー定義演算子には複数のバインディングを指定できるため、オーバーロードされるファンクションとして使用できます。
例9-6では、演算子Contains()を作成するとします。
例9-6 Contains()演算子の作成方法
CREATE OPERATOR Ordsys.Contains BINDING (VARCHAR2, VARCHAR2) RETURN NUMBER USING text.contains, (spatial.geo, spatial.geo) RETURN NUMBER USING spatial.contains;
例9-7でContains()が使用される場合、ファンクションのシグネチャが演算子の引数のデータ型と一致しているため、演算子の起動Contains(resume, 'Oracle')により、ファンクションtext.contains(resume, 'Oracle')が実行されます。同様に、演算子の起動Contains(location, :bay_area)により、ファンクションspatial.contains(location, :bay_area)が実行されます。
例9-7 演算子Contains()の問合せ時の使用方法
SELECT * FROM Employee WHERE Contains(resume, 'Oracle')=1 AND Contains(location, :bay_area)=1;
次の文を実行すると、引数のデータ型と一致する演算子バインディングがないため、エラーになります。
SELECT * FROM Employee
WHERE Contains(address, employee_addr_type('123 Main Street', 'Anytown', 'CA',
'90001'))=1;
通常、演算子は索引タイプに関連付けて定義されます。ファンクション実装を指定して演算子を作成した後、索引スキャンによるこれらの演算子の評価をサポートする索引タイプを作成できます。
WHERE句の外にある演算子は、実際にはそれを実装するファンクションの代替であり、この種の演算子の意味はファンクション実装により決定されます。WHERE句の中にある演算子は、ファンクション実装を使用して評価される場合と、索引スキャンにより評価される場合があります。この項では、様々な状況と評価方法について説明します。
WHERE句の中にある演算子は、索引タイプにより指定されたスキャン方法を使用し、索引スキャンで効果的に評価できます。そのためには、次の作業が必要です。
演算子の評価をサポートする索引タイプの作成
特定の書式による演算子述語の認識
ドメイン索引の選択
適切な索引スキャンの設定
索引スキャン・メソッドの実行
以降の各項では、これらの各ステップについて詳しく説明します。
索引タイプでは、演算子の戻り値の上限と下限の範囲で表すことのできる演算子述語の効率的な評価がサポートされています。特に、例9-8にリストされている形式の述語は、索引スキャン・ベースの評価の候補です。
例9-8 演算子述語
op(...) LIKE value_expression op(...) relop value_expression
value_expressionは、ドメイン索引キーとして使用できる定数(列ではない)に対して評価される必要があります。relopは、<、<=、=、>=または>のいずれかです。
例9-8の書式のいずれかに、Oracleで内部的に変換できる演算子述語では、索引スキャン・ベースの評価も使用できます。
op(...) + 2 = 3のように、演算子を式に使用すると、索引スキャン・ベースの評価が除外されます。
op() is NULL形式の述語はファンクション実装を使用して評価されます。
演算子を索引スキャン・ベースで評価できるのは、演算子が索引タイプにより索引付けされた列またはオブジェクトの属性を対象とする場合のみです。オプティマイザは、問合せ実行計画の生成中に選択性とコストを考慮して、索引付けされた実装とファンクション実装の間で最終決定を行います。
例9-9の問合せを考えてみます。
オプティマイザは、次の場合にはContains()演算子の評価にドメイン索引を使用するように選択できます。
resume列に索引が定義されている場合
索引がTextIndexTypeタイプの場合
TextIndexTypeで適切なContains()演算子がサポートされている場合
前述の条件のいずれかが保持されない場合は、Employees表のフルスキャンが実行され、Contains()のファンクション実装がポストフィルタとして適用されます。ただし、前述の条件がすべて満たされている場合、オプティマイザは選択性およびコストのファンクションを使用し、索引ベース評価のコストを全表スキャンと比較して、適切な実行計画を生成します。
例9-10で、少し異なる問合せを考えてみます。
例9-10 Contains()演算子の複雑な問合せ時の使用方法
SELECT * FROM Employees WHERE Contains(resume, 'Oracle') =1 AND id =100;
この問合せでは、Employees表にid列の索引、resume列の索引または2つのビットマップ・マージを介してアクセスできます。オプティマイザは3つの計画のコストを見積もり、最も低コストの計画を選択します。これにより、idの索引を使用して結果行にContains()演算子を適用できます。その場合、ドメイン索引ではなくContains()のファンクション実装が使用されます。
演算子述語の評価用にドメイン索引が選択された場合は、索引スキャンが設定されます。索引スキャンは、対応する索引タイプ実装の一部として指定されたスキャン・メソッド(ODCIIndexStart()、ODCIIndexFetch()、ODCIIndexClose())により実行されます。ODCIIndexStart()メソッドは、名前、引数および述語を記述する上限と下限などの演算子関連情報を使用して起動されます。ODCIIndexStart()コール後に、一連のフェッチが実行されて述語を満たす行のROWIDが取得され、最後にSQLカーソルが破棄されるときにODCIIndexClose()がコールされます。
索引スキャン・ルーチンを実装するには、その起動方法と複数の起動セットのインターリーブ方法を結合する必要があります。
例として、例9-11での問合せを考えてみます。
例9-11 Contains()演算子の複数の表での問合せ時の使用方法
SELECT * FROM Emp1, Emp2 WHERE Contains(Emp1.resume, 'Oracle') =1 AND Contains(Emp2.resume, 'Unix') =1 AND Emp1.id = Emp2.id;
オプティマイザが両方の表のresume列でドメイン索引を使用するように選択した場合は、索引タイプ・ルーチンを例9-12で示されている順序で起動できます。
例9-12 Contains()演算子問合せに対する索引タイプ・ルーチンの起動方法
start(ctx1, ...); /* corr. to Contains(Emp1.resume, 'Oracle') */ start(ctx2, ...); /* corr. to Contains(Emp2.resume, 'Unix'); fetch(ctx1, ...); fetch(ctx2, ...); fetch(ctx1, ...); ... close(ctx1); close(ctx2);
この例では、単一の索引タイプ・ルーチンがContains()演算子のインスタンスごとに複数回起動されます。同じ索引タイプ・ルーチンを介して同時に多数の演算子を評価できます。必要な情報をすべてパラメータを介して取得するCREATEルーチンなどのルーチンは、コール間で状態を維持する必要がないため、同時に複数の演算子を評価しても問題はありません。コール間で状態を維持する必要のあるFETCHルーチンなどの他のルーチンは、次に戻される行を知る必要があります。これらのルーチンは、各コールで渡されるSELFパラメータで状態情報を維持する必要があります。SELFパラメータ(実装タイプのインスタンス)を使用すると、状態全体(大きすぎる場合は状態を格納するカーソル時間メモリーへのハンドル)を格納できます。
WHERE句の外で使用される演算子は、ファンクション実装を使用して評価されます。例9-13の文を実行するには、この文を実行するために、OracleはEmployee表をスキャンし、resumeのインスタンスごとにContains()のファンクション実装を起動し、現在行のresume(テキスト・データ)の実際の値を渡します。このファンクションでは、resume列に作成されたドメイン索引が使用されないことに注意してください。
ファンクション実装ではドメイン索引を使用できるため、以降の各項では、ドメイン索引を使用するファンクションの作成方法と、システムによる起動方法について説明します。
Contains()など、多数のドメイン固有演算子の場合、ファンクション実装には次の2つのオプションがあります。
演算子がドメイン索引を持つ列またはOBJECT属性を対象とする場合、ファンクションでは実引数の値ではなく索引データを調べて演算子を評価できます。
たとえば、Employee表の特定の行でContains(resume, 'Oracle')が起動される場合、ファンクションではresume列に定義されているテキスト・ドメイン索引を調べ、resumeを含む行のROWIDに基づいて演算子を評価する方が、resumeテキスト・データ引数を処理するよりも容易です。
演算子が適切なドメイン索引の定義されていない列を対象とする場合や、リテラル値(列以外)を使用して起動される場合、ファンクション実装では引数の値に基づいて演算子が評価されます。これは、すべての演算子バインディングに対するデフォルト動作です。
演算子で両方のオプションを処理させるには、演算子にオリジナルの引数のみでなく次の3つの引数を持つファンクション実装を提供します。
索引コンテキスト: ドメイン索引情報と、演算子が評価される行のROWID。
スキャン・コンテキスト: 表の他の行を対象とする同じ演算子の以降の起動と状態を共有するためのコンテキスト値。
スキャン・フラグ: 現行のコールがすべてのクリーン・アップ操作を実行する必要のある最後の起動かどうかを示します。
例9-14のファンクションTextContains()は、Contains()演算子について索引ベースのファンクション実装を提供します。
例9-14 索引ベースのファンクションでのContains()演算子の実装方法
CREATE FUNCTION TextContains (Text IN VARCHAR2, Key IN VARCHAR2, indexctx IN ODCIIndexCtx, scanctx IN OUT TextIndexMethods, scanflg IN NUMBER) RETURN NUMBER AS BEGIN ....... END TextContains;
Contains()演算子は、例9-15に示されているように、ファンクション実装に次のようにバインドされます。
例9-15 Contains()演算子のファンクション実装へのバインド方法
CREATE OPERATOR Contains BINDING (VARCHAR2, VARCHAR2) RETURN NUMBER WITH INDEX CONTEXT, SCAN CONTEXT TextIndexMethods USING TextContains;
WITH INDEX CONTEXT句には、適用可能なドメイン索引をファンクション実装で使用できるように指定します。SCAN CONTEXTには、スキャン・コンテキスト引数のデータ型を指定します。このデータ型は、この演算子をサポートしている索引タイプの実装タイプと一致する必要があります。
演算子がWHERE句の外にある場合、Oracleは演算子のファンクション実装を起動します。ファンクション実装が索引ベースであるか、索引タイプを使用するように定義されている場合は、演算子の最初の引数が適切な索引タイプのドメイン索引が定義されている列またはオブジェクト属性である場合にのみ、追加の索引情報が引数として渡されます。
たとえば、問合せSELECT Contains(resume, 'Oracle & Unix') FROM Employeesでは、Oracleは索引ベースのファンクション実装を使用し、resumeデータのかわりにresume列のドメイン索引に関する索引情報を渡して、Contains()演算子を評価します。
索引ベースのファンクション実装を実行するために、Oracleは引数を次の方法で設定します。
引数の初期セットは、演算子のユーザーが指定したセットと同じです。
最初の引数が列でない場合は、ODCIIndexCtx属性がNULLに設定されます。
最初の引数が列の場合は、ODCIIndexCtx属性が次のように設定されます。
適用可能なドメイン索引が存在する場合、ODCIIndexInfo属性にはその情報が含まれ、それ以外の場合はNULLに設定されます。
rowid属性には、操作される行のROWIDが保持されます。
演算子の初回起動時にスキャン・コンテキストがNULLに設定されます。これはIN/OUTパラメータであるため、初回起動からの戻り値が第2の起動に渡されます。
演算子のすべての正常起動についてスキャン・フラグがRegularCallに設定されます。最後の起動後に、ファンクション実装がもう1度起動され、その時点で必要なクリーン・アップ・アクションを実行できます。このコール時に、スキャン・フラグがCleanupCallに設定され、スキャン・コンテキスト以外の引数がすべてNULLに設定されます。
索引情報が渡されると、実装ではROWIDをキーとして使用し、ドメイン索引検索により演算子の値を計算できます。ドメイン索引に関連付けられている索引構造は、索引メタデータを使用して識別されます。通常は、スキャン・コンテキストを使用して、同じ演算子の以降の起動と状態が共有されます。
演算子をサポートしている索引タイプがない場合、または列のドメイン索引が演算子に最初の引数として渡されない場合、索引コンテキスト引数はNULLになります。ただし、スキャン・コンテキスト引数は引き続き使用可能であり、前述のように使用できます。そのため、問合せで索引が使用されない場合も、演算子は起動間で状態を維持できます。
WHERE句内の演算子は、行をフィルタリングするのみでなく、補助データを戻す必要がある場合があります。補助データは、それぞれが次のような1つ以上の演算子としてモデル化されます。
対応する主演算子に結合する1つのリテラル数値引数
主演算子の索引スキャン・ベース実装により生成された状態にアクセスするファンクション実装
例9-16の問合せでは、主演算子Contains()は、条件を満たす行を判別し各行のスコア値を計算する索引スキャンを使用して評価できます。Score演算子のファンクション実装は、索引スキャンにより生成された状態にアクセスして、ROWIDで識別された特定の行のスコアを取得します。リテラル引数1は、補助演算子Scoreを、補助データを生成する主演算子Contains()に関連付けます。
例9-16 Contains()演算子を使用した補助データへのアクセス方法
SELECT Score(1) FROM Employees WHERE Contains(resume, 'OCI & UNIX', 1) =1;
補助演算子のファンクション実装では、ドメイン索引または主演算子により生成された状態を使用できます。ファンクション実装には、起動時にさらに3つの引数が渡されます。
索引コンテキスト(ドメイン索引情報を含む)
スキャン・コンテキスト(主演算子により生成された状態へのアクセスを提供)
スキャン・フラグ(ファンクション実装が最後に起動されるかどうかを示す)
以降の各項では、補助データをモデル化する演算子を定義して起動する方法について説明します。
補助データを計算する演算子バインディングは、主バインディングと呼ばれます。例9-17では、演算子Contains()に主バインディングを定義します。
例9-17 Contains()演算子を使用した補助データの比較方法
CREATE OPERATOR Contains BINDING (VARCHAR2, VARCHAR2) RETURN NUMBER WITH INDEX CONTEXT, SCAN CONTEXT TextIndexMethods COMPUTE ANCILLARY DATA USING TextContains;
この定義により、Contains()について次の2つのバインディングが登録されます。
CONTAINS(VARCHAR2, VARCHAR2)このバインディングは、補助データが不要な場合に使用されます。
CONTAINS(VARCHAR2, VARCHAR2, NUMBER)。このバインディングは、補助データが必要な場合に使用されます(NUMBER引数により、このバインディングが補助演算子バインディングに関連付けられます)。
この2つのバインディングは、次のように1つのファンクション実装を使用します。
TextContains(VARCHAR2,VARCHAR2,ODCIIndexCtx,TextIndexMethods, NUMBER).
補助データをモデル化する演算子バインディングは、補助バインディングと呼ばれます。補助データ演算子のファンクション実装は、索引ベースのファンクション実装に類似しています。ファンクションを定義した後、ANCILLARY TO属性を追加して演算子にバインドします。この属性は、ファンクション実装で主演算子バインディングと状態を共有する必要があることを示します。
補助演算子バインディングのファンクション実装には、主演算子バインディングのファンクション実装と同じシグネチャを使用する必要があることに注意してください。
例9-18に、TextScore()ファンクション内の補助演算子の評価方法を示します。
例9-18 補助演算子の評価方法
CREATE FUNCTION TextScore (Text IN VARCHAR2, Key IN VARCHAR2, indexctx IN ODCIIndexCtx, scanctx IN OUT TextIndexMethods, scanflg IN NUMBER) RETURN NUMBER AS BEGIN ....... END TextScore;
TextScore()定義を使用して、例9-19に示されているような補助バインディングを作成できます。
例9-19 補助演算子バインディングの作成方法
CREATE OPERATOR Score BINDING (NUMBER) RETURN NUMBER ANCILLARY TO Contains(VARCHAR2, VARCHAR2) USING TextScore;
ANCILLARY TO句には、Scoreと主演算子バインディングCONTAINS(VARCHAR2, VARCHAR2)が状態を共有するように指定します。
補助演算子バインディングは、Score(1)、Score(2)など、1つのリテラル数値引数を使用して起動されます。
補助データに対応する演算子は、ユーザーが1つの数値引数を使用して起動します。この数値引数は補助操作と主演算子の起動時の両方でリテラルである必要があるため、演算子の関連付けは問合せのコンパイル時に実行できます。
対応する主演算子を判別するために、Oracleは補助演算子に渡された数値を主演算子の最後の引数として渡された数値と照合します。一致する主演算子の起動が見つからない場合、または複数が検出される場合はエラーになります。一致する主演算子の起動が検出されると、次のようになります。
主演算子の引数が補助演算子でオペランドになります。
補助演算子と主演算子の実行に同じスキャン・コンテキストが渡されます。
たとえば、例9-16の問合せでは、Scoreの起動は数値引数1に基づいてContains()の補助として判別され、Scoreのファンクション実装はオペランド(resume, 'Oracle&Unix', indexctx, scanctx, scanflg)を取得します。scanctxは、Contains()の起動と共有されます。