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