PostgreSQLの演算子定義では、システムに演算子がどう振舞うかに関する有用なことを通知する、幾つかのオプション句を持つことができます。 これらの句により演算子を使用する問い合わせの実行速度がかなり向上しますので、これらの句は適切な時には常に提供しなければなりません。 しかし、提供する時にはそれらが正しいことを確認しなければいけません! 間違って最適化用の句を使用すると、サーバプロセスのクラッシュ、わけの判らないおかしな出力、その他有害な事が起こり得ます。 最適化用の句について解らなければ、使用しなくても構いません。 使用された時よりも問い合わせの実行が遅くなるかもしれないというだけです。
PostgreSQLの今後のバージョンで、最適化用の句は更に追加される可能性があります。 ここで説明するものは全て、バージョン8.0.4で有効なものです。
COMMUTATOR句が与えられた場合、それは定義する演算子の交代演算子となる演算子の名前です。 取り得る全ての入力値x、yに対して、(x A y)が(y B x)と等しい時、演算子Aは演算子Bの交代演算子であるといいます。 また、BはAの交代演算子となることにも注意してください。 例えば、通常、特定のデータ型用の演算子<と>は互いの交代演算子になります。 また、通常、演算子+は自身が交代演算子となります。 しかし、通常、演算子-は交代演算子を持ちません。
交代可能な演算子の左オペランドの型は、その交代演算子の右オペランドの型と同一で、その逆もまた同様です。 従って、PostgreSQLで交代演算子を検索する時に必要なものは交代演算子の名前のみになりますので、COMMUTATOR句でそれのみを与えておけば十分です。
インデックスや結合句で使用される演算子では交代演算子の情報を提供することが必須です。 これにより、問い合わせオプティマイザがその句を他の実行計画型で必要とされる形式に"ひっくり返す"ことができるためです。 例えば、tab1.x = tab2.yのようなWHERE句を持った問い合わせを考えてみます。 ここでtab1.xとtab2.yはユーザ定義型で、tab2.yにはインデックスが付いていると仮定します。 オプティマイザは、この句をtab2.y = tab1.xという形にひっくり返す方法を知らない限り、インデックススキャンを生成できません。 インデックススキャン機構は演算子の左側にインデックス付けされた列があることを想定しているためです。 PostgreSQLは簡単にこの変形が有効であると前提しません。 =演算子の作成者がこれが有効であることを、交換演算子情報を持つ演算子であると印付けて指定しなければなりません。
自己交代演算子を定義する場合は、単にそれを指定するだけです。 交代演算子の対を定義する場合は少し複雑になります。 最初に他の未定義のものを参照するものをどう定義するのかということが問題となります。 この問題には下記の2つの解決方法があります。
1つ目の方法は、最初の演算子を定義する際にCOMMUTATOR句を省略し、2番目の演算子の定義では、COMMUTATOR句に最初の演算子を与えるという方法です。 PostgreSQLは交代演算子が対になっていることが解っていますので、2番目の定義を見た時に、自動的に最初の定義に戻ってその未定義になっているCOMMUTATOR句を設定します。
もう1つの方法は、両方の定義にCOMMUTATOR句を含めるというもっと素直な方法です。 PostgreSQLは最初の定義を処理する際に、COMMUTATORが存在しない演算子を参照していることが解ると、システムはその演算子用の仮のエントリをシステムカタログに作成します。 この仮エントリには、PostgreSQLがこの時点で推定できる、演算子名、左オペランドの型、右オペランドの型、及び結果の型についてのみの有効なデータが入ります。 最初の演算子のカタログエントリはこの仮エントリに結び付きます。 この後、2番目の演算子が定義されたら、システムはその仮エントリに2番目の定義から得られる追加情報を更新します。 更新される前に仮の演算子を使用すると、エラーメッセージが出力されます。
NEGATOR句が与えられた場合、それは定義する演算子の否定子となる演算子の名前です。 入力値、xとyの取り得る全ての値に対して両方の演算子がブール値を返し、(x A y)がNOT (x B y)と等しい場合、演算子Aは演算子Bの否定子であるといいます。 また、BはAの否定子でもあることに注意してください。 例えば、ほとんどのデータ型では<と>=は否定子の対となります。 演算子が自身の否定子になることは決してありません。
交換演算子と異なり、単項演算子の対は互いに否定子として有効に指定されます。 つまり全てのxに対して(A x)がNOT (B x)と等しいことを意味します。 右単項演算子でも同様です。
ある演算子の否定子は、その演算子定義の左オペランド、右オペランドと同じ型を持たなければなりません。 ということは、COMMUTATOR句と同様に演算子の名前のみNEGATOR句で与えるだけです。
NOT (x = y)という式をx <> yという形に単純化させることが可能なので、否定子があると問い合わせオプティマイザにとって非常に役に立ちます。 他の再配置の結果としてNOT操作が挿入されることがありますので、この現象は思ったより頻繁に起こります。
否定子の対は、上記の交代演算子のペアで説明した方法と同じ方法で定義することができます。
RESTRICT句が与えられた場合、それは、その演算子用の制限選択評価関数を指定します。 (演算子名ではなく関数名であることに注意してください。) RESTRICT句はboolean型を返す二項演算子に対してのみ有効です。 制限選択評価の目的は、現在の演算子と特定の定数値についてのWHERE句
column OP constant
の条件を満たすテーブル内の行の割合を推測することです。 この形式を持ったWHERE句によって、どのくらいの行が除外されるのかを通知することで、オプティマイザの手助けをします。 (定数値が左項にあったら何が起こるかという疑問が生じるかもしれませんが、それはCOMMUTATORが存在する理由のひとつでもあります。)
新しい制限選択評価関数の記述方法は本章の内容を越えていますが、幸いな事に、数多いユーザ定義の演算子に対し通常いくつかのシステム標準の評価関数を使用すれば事足ります。 システム標準の制限評価関数には下記のものがあります。
=用のeqsel |
<>用のneqsel |
<もしくは<=用のscalarltsel |
>もしくは>=用のscalargtsel |
非常に高いもしくは低い選択性を所有する演算子が、全く等しいか等しくないかに係わらず、eqselまたはneqselを使用しないことも往々にして可能です。 例えば、近似等号用の幾何演算子はテーブルのエントリの小部分にのみに合致すると仮定してeqselを使用します。
範囲比較のために数値スカラに変換することに多少の有意性があるデータ型を比較するために、scalarltselとscalargtselを使用することも可能です。 できればsrc/backend/utils/adt/selfuncs.cのconvert_to_scalar()のルーチンで理解できるところにデータ型を追加してください。 (今後、このルーチンはpg_typeシステムカタログの列で識別された、データ型毎の関数で置き換えられなければなりませんが、まだ行われていません。) これを行わなくても動きますが、オプティマイザは本来の推測機能を十分発揮する事ができません。
幾何演算子に対する追加選択関数areasel、positionsel、contselはsrc/backend/utils/adt/geo_selfuncs.cに書かれています。 本章の執筆時点では、これらは単なるスタブですが、ともかく使いたい(あるいは改良したい)こともあるでしょう。
JOIN句が与えられた場合、それはその演算子用の結合選択評価関数の名前を示します。 (これが演算子名ではなく関数名であることに注意してください。) JOIN句はboolean型を返す二項演算子のみ有効です。 結合選択評価の目的は、現在の演算子について、WHERE句
table1.column1 OP table2.column2
を満たすテーブル内の行の割合を推測することです。 RESTRICT句の使用と同様、これは、いくつかの取り得る結合手順のうち どれが最も仕事量が少ないように考えられるのかをオプティマイザに計算させることで大きなオプティマイザへの援助となります。
以前と同様、本章でも統合選択評価関数の作成方法は説明しません。 適用できるものがあれば、標準の評価関数の使用をお勧めします。
=用のeqjoinsel |
<>用のneqjoinsel |
<もしくは<=用のscalarltjoinsel |
>もしくは>=用のscalargtjoinsel |
2次元面積を基にした比較用のareajoinsel |
2次元位置を基にした比較用のpositionjoinsel |
2次元含有関係を基にした比較用のcontjoinsel |
HASHES句が存在する場合、それはシステムに対して、この演算子に基づいた結合にハッシュ結合方法を使っても問題が無いことを伝えます。 HASHES句はboolean型を返す二項演算子にのみ有効です。 実際には、この演算子は、あるデータ型の等価性を求める演算子であった方が好ましいといえます。
ハッシュ結合の基礎となっている仮定は、結合演算子は左項と右項の値が同じハッシュコードを持つ時にのみ真を返すことができるということです。 2つの値が異なるハッシュの入れ物に置かれた場合、結合演算子の結果が必ず偽であるという仮定を、結合は暗黙的に行ない、それらを比べることをしません。 従って、等価性を表さない演算子にHASHES句を指定することは全く意味がありません。
HASHES印を付けるためには、結合演算子はハッシュインデックスの演算子クラス内になければなりません。 演算子を作成する時には参照する演算子クラスがまだ存在しませんので、演算子の作成時にこれは強制されていません。 しかし、演算子クラスが存在しない場合に、このオペレータをハッシュ結合で使用しようとすると、実行時に失敗します。 システムは、演算子の入力データ型用のデータ型特有のハッシュ関数を検索するために、演算子クラスを必要とします。 もちろん、演算子クラスを作成する前に適切なハッシュ関数を提供しなければなりません。
ハッシュ関数を準備する時には注意が必要です。 マシンに依存することから、ハッシュ結合が適切な処理を行なわずに失敗することがあるからです。 例えば、データ型が不要な部分を埋めるビットを持つ可能性がある構造体である場合、(推奨する戦略である、他の演算子と関数を作成して、不要なビットが常にゼロになることを保証しない限り、)その構造体全体を単にhash_anyに渡すことはできません。 この他の例として、IEEE浮動小数点標準を満たすマシンでは、マイナス0とプラス0は異なる値(異なるビット列)になりますが、この比較は等価と定義されます。 浮動小数点数値がマイナス0を持つ可能性があるのであれば、それがプラス0と同じハッシュコードを確実に生成するような処置が必要です。
注意: ハッシュ結合可能演算子の基となる関数はimmutableもしくはstableでなければなりません。 volatileの場合、システムはその演算子を決してハッシュ結合に使用しません。
注意: ハッシュ結合可能演算子の背後の関数が厳密(strict)な場合、その関数は完全、つまり、2つの非NULL入力に対して、真または偽を返し、決してNULLを返さないものである必要があります。 この規則に従わないと、IN操作におけるハッシュ最適化は間違った結果を生成する可能性があります。 (特に、標準に従うとNULLが正しい答えになるところでINは偽を返すかもしれません。 もしくは、NULLという結果に対する準備をしていないといったエラーを生成するかもしれません。)
MERGES句が存在する場合、それはシステムに対して、この演算子に基づいた結合にマージ結合方法を使っても問題が無いことを伝えます。 MERGES句はboolean型を返す二項演算子にのみ有効です。 実際には、演算子がデータ型またはデータ型のペアの等価性を表すものであることが必要です。
マージ結合は、左側のテーブル、右側のテーブルを順序良くソートし、並列にスキャンするという考えに基づいています。 従って、両データ型には完全な順序付け機能が必要であり、結合演算子はソート順で"同じ場所"にある値の対のみを成功したものとするものである必要があります。 実際問題として、これは、結合演算子は等価性のような振舞いをしなければならないことを意味しています。 左右のデータ型が同じ(または少なくともビット単位での等価)であることが望ましいとされるハッシュ結合とは異なり、マージ結合は論理的な互換性を持つ別の2つのデータ型を取ることができます。 例えば、smallint対integerの等価性演算子はマージ結合が可能です。 両方のデータ型を論理的な互換性を保つ順番にソートする演算子のみが必要です。
マージ結合を実行するためには、マージ結合の等価性演算子に関連する4つの演算子をシステムが識別可能であることが必要です。 その演算子とは、左側オペランドのデータ型を対象とした小なり比較演算子、右側オペランドのデータ型を対象とした小なり比較演算子、2つのデータ型間での小なり比較演算子、および2つのデータ型間での大なり比較演算子の4つです。 (マージ結合可能な演算子が2つの異なった入力データ型を持っている場合は、これらは実際には4つの別個の演算子です。 ただし、入力型が同一の場合には、3つの小なり演算子は、すべて同じ演算子になります。) これらの演算子は、それぞれSORT1、SORT2、LTCMP、およびGTCMPオプションとして、個別に名前で指定することができます。 MERGESが指定された際に、これらのうちいずれかが省略されていた場合は、それぞれ<、<、<、および、>というデフォルトの名前がシステムによって与えられます。 また、これら4つの演算子オプションのいずれかが現われた場合には、MERGESが暗黙的に想定されます。 従って、一部のみを指定して、残りはシステムに指定させることが可能です。
この4つの比較演算子の入力データ型は、マージ結合可能な演算子の入力データ型から推定できます。 従って、COMMUTATORの場合と全く同様に、これらの句で必要とされるのは演算子名のみです。 演算子名に特殊なものを使用していない限り、MERGESとだけ入力すれば十分で、詳細はシステムが指定してくれます。 (COMMUTATORやNEGATORを使用する場合と同様に、他の演算子を定義する前に等価性演算子を定義すると、システムによりダミーの演算子エントリが作成されます。)
演算子をマージ結合可能演算子とする時、制約が追加されます。 これらの制約は現在CREATE OPERATORでは検査されませんが、いずれも真でない場合に演算子を使用するとエラーが起こる可能性があります。
マージ結合が可能な等価性演算子は、マージ結合が可能な交代演算子(2つのデータ型が同じならば演算子自身、同じでなければこれに関連した等価性演算子)を持つ必要があります。
任意のデータ型AとBを関連付けるマージ結合可能な演算子と、Bを3番目のデータ型Cに関連付けるマージ結合可能な演算子とがある場合、AとCにもマージ結合可能演算子が必要になります。 つまり、マージ結合可能な演算子は推移的に使用される必要があります。
命名した4つの比較演算子が互換性を持ってデータ値をソートしなければ、実行時におかしな結果が発生します。
注意: マージ結合可能演算子の背後にある関数はimmutableもしくはstableでなければなりません。 volatileの場合、システムはその演算子を決してマージ結合に使用しようとはしません。
注意: PostgreSQL 7.3より前のバージョンでは、MERGESという略記方法は存在せず、マージ結合可能演算子を作成するためには、SORT1およびSORT2を明示的に指定する必要がありました。 また、LTCMPとGTCMPオプションも以前は存在せず、これらの演算子の名前は個別に<、>として組み込む必要がありました。