トリガとは、データベースが、ある特定の操作が行われた時に常に自動的に実行しなければならない特定の機能に関する規定です。 トリガはテーブル(パーティション化されているかどうかにかかわらず)、ビュー、外部テーブルに付与することができます。
テーブルおよび外部テーブル上では、トリガをINSERT
、UPDATE
またはDELETE
操作の前後に、行を変更する度、あるいはSQL文ごとに実行するように定義することができます。
INSERT
がON CONFLICT DO UPDATE
句を含む場合、EXCLUDED
列への参照があるなら、BEFORE INSERTトリガとBEFORE UPDATEトリガの両方が適用されることもあります。
さらに、UPDATEトリガについては、特定のカラムがUPDATE文のSET句の対象になった時のみ発動するよう設定することができます。
また、トリガはTRUNCATE文についても発動できます。
トリガイベントが起こると、トリガ関数がそのイベントを扱う適切な時点で呼び出されます。
ビュー上では、トリガをINSERT
、UPDATE
またはDELETE
操作の代わりに実行するものとして定義できます。
そうしたINSTEAD OF
トリガは、ビュー内の変更を行うために必要となる行それぞれに対して一度発行されます。
ビューの元になっている基底テーブルへの必要な変更の実施、そして必要に応じて、ビュー上で見えるであろう変更された行を返却するのは、トリガ関数の責任です。
ビューへのトリガは、SQL文ごとに、INSERT
、UPDATE
またはDELETE
操作の前後で実行させるよう定義することもできます。
しかし、そうしたトリガは、ビューにINSTEAD OF
トリガがあるときにだけ発行されます。
INSTEAD OF
トリガを定義しない場合は、ビューを操作しようとする文は、元になる基底テーブルに影響を与える文に書き換えなければなりません。
その結果、発行されるトリガは、基底テーブルに付けられたトリガとなります。
トリガ関数は、トリガ自体が作成される前までに定義しておく必要があります。
トリガ関数は、引数を取らない、trigger
型を返す関数として宣言される必要があります
(トリガ関数は、通常の関数で使用される引数という形ではなく、TriggerData
構造体で入力を受け取ります)。
適切なトリガ関数が作成されると、CREATE TRIGGERを使用してトリガを構築することができます。 同一のトリガ関数を複数のトリガに使用することができます。
PostgreSQLは、行単位のトリガと文単位のトリガの両方を提供します。
行単位のトリガでは、トリガを発行した文によって影響を受ける行ごとにトリガ関数が呼び出されます。
反対に、文単位のトリガでは、適切な文が実行された時に、その文で何行が影響を受けたかどうかは関係なく、一度だけ呼び出されます。
特に、行に影響を与えない文であっても、適切な文単位のトリガがあれば実行されます。
この2種類のトリガはそれぞれ行レベルトリガと文レベルトリガと呼ばれることがあります。
TRUNCATE
に対するトリガは、行単位ではなく、文レベルにのみに定義することができます。
また、トリガはそれらが操作の前、後または代わりのどれで実行されるかに応じて分けられます。
これらはそれぞれBEFORE
トリガ、AFTER
トリガ、そしてINSTEAD OF
トリガと呼ばれます。
文レベルのBEFORE
トリガは、もちろん文が何かを始める前に発行され、文レベルのAFTER
トリガは文の本当に最後に発行されます。
これらのタイプのトリガはテーブル、ビュー、あるいは外部テーブルに定義できます。
行レベルのBEFORE
トリガは、特定の行が操作される直前に発行され、行レベルのAFTER
トリガは文の終わり(ただし、全ての文レベルのAFTER
トリガの前)に発行されます。
これらのタイプのトリガは、非パーティションテーブル、外部テーブルに定義できますが、ビューには定義できません。
INSTEAD OF
トリガはビューにのみ定義され、行レベルのみが許されます。
つまり、ビュー上のそれぞれの行で処理が必要と判断された場合には、即座に発動します。
継承あるいはパーティション階層において、親テーブルをターゲットとする文は、影響を受けた子テーブルの文レベルトリガを発動しません。 すなわち、親テーブルの文レベルトリガのみが発動します。 しかし、影響を受けた子テーブルの行レベルトリガは発動します。
INSERT
がON CONFLICT DO UPDATE
句を含む場合、EXCLUDED
列が参照されていると、行単位BEFORE
INSERT
トリガおよび行単位BEFORE
UPDATE
トリガの両方の効果が適用され、それが更新後の行の最後の状態から明らかな場合がありえます。
ただし、両方の行レベルのBEFORE
トリガを実行するためにEXCLUDED
の参照が必要なわけではありません。
驚くような結果の可能性について、BEFORE
INSERT
とBEFORE
UPDATE
の両方の文単位トリガーがあり、それらがいずれも挿入あるいは更新対象の行に影響を与える場合に考慮すべきです(これは更新が冪等ではないが、ほぼ同等であるときには、それでも問題になります)。
文単位のUPDATE
トリガはON CONFLICT DO UPDATE
が指定されたとき、そのUPDATE
によって行が影響を受けたかどうかに関わらず(そしてその代替であるUPDATE
部分が実行されたかどうかに関わらず)実行されることに注意してください。
ON CONFLICT DO UPDATE
句のあるINSERT
では、まず文単位のBEFORE
INSERT
トリガ、次に文単位のBEFORE
UPDATE
トリガ、次いで文単位のAFTER
UPDATE
トリガ、最後に文単位のAFTER
INSERT
トリガを実行します。
あるパーティション化されたテーブルに適用されたUPDATE
の結果、行が他のパーティションに移動することになるなら、元のパーティションでDELETE
し、続いて新しいパーティションにINSERT
する操作として実行されます。
この場合、すべての行レベルBEFORE
UPDATE
トリガとBEFORE
DELETE
トリガが元のパーティションで発動します。
そして、すべての行レベルBEFORE
INSERT
トリガが移動先のパーティションで発動します。
これらのトリガが移動対象の行に対して影響を及ぼす際に、驚くべき結果となる可能性を考慮しておくべきでしょう。
AFTER ROW
トリガに関しては、AFTER
DELETE
とAFTER
INSERT
トリガが適用されます。しかし、AFTER
UPDATE
トリガは適用されません。なぜなら、UPDATE
はDELETE
とINSERT
に変換されるからです。
文レベルのトリガに関しては、たとえ行の移動が起こったとしてもDELETE
トリガもINSERT
トリガも発動されません。UPDATE
文中に現れた対象テーブルに定義されたUPDATE
トリガだけが発動されます。
文単位のトリガによって呼び出されるトリガ関数は常にNULL
を返さなければなりません。
行単位のトリガによって呼び出されるトリガ関数は呼び出し元のエクゼキュータにテーブル行(HeapTuple
型の値)を返すように選択することができます。
操作前に発行された行レベルのトリガでは以下の選択肢があります。
NULL
を返して、現在の行への操作を飛ばすことができます。
これは、エクゼキュータにトリガの元になった行レベルの操作(特定のテーブル行の挿入、更新、削除)を行わないよう指示します。
行レベルのINSERT
およびUPDATE
トリガの場合のみ、返される行が挿入される、もしくは実際に更新される行になります。
これにより、トリガ関数で、挿入される行もしくは更新される行を変更することができます。
これらの動作をさせたくない行レベルのBEFORE
トリガについては、渡された行(つまり、INSERT
およびUPDATE
トリガではNEW
行、DELETE
の場合はOLD
行)と同じ行結果を返すように気を付ける必要があります。
行レベルのINSTEAD OF
トリガは、ビューの元となった元テーブルのデータをまったく変更しないことを表すNULL
、または、渡されたビューの行(INSERT
とUPDATE
操作の場合NEW
行、DELETE
操作の場合OLD
行)を返さなければなりません。
非NULLの戻り値は、そのトリガがビューにおいて必要なデータ変更を実行したことを通知するために使用されます。
これにより影響を受けた行数を数えるカウンタは増加されます。
INSERT
とUPDATE
操作では、トリガは戻す前にNEW
行を変更することができます。
これはINSERT RETURNING
またはUPDATE RETURNING
で返されるデータを変更しますので、ビューが提供されたデータと正確に同じ結果を返さない場合に有益です。
操作の後に発生する行レベルトリガでは戻り値は無視されますので、これらはNULL
を返すことができます。
同一リレーション、同一イベントに対して1つ以上のトリガが定義された場合、トリガはその名前のアルファベット順に発生します。
BEFORE
トリガとINSTEAD OF
トリガの場合では、各トリガで返される、変更された可能性がある行が次のトリガの入力となります。
もし、あるBEFORE
トリガやINSTEAD OF
トリガがNULL
を返したら、(いまのところ)操作はその行で中断し、残りのトリガは発生しません。
トリガ定義は、トリガを発動するかどうかをWHEN
句の論理条件で指定することも可能です。行レベルトリガにおいて、WHEN
条件は行の列の古い値と(あるいは)新しい値を検索することができます。(あまり有用ではありませんが、文レベルトリガでもWHEN
条件で同じことができます。)BEFORE
トリガでは、実質的にトリガ関数の開始時と同じ条件で検査できるように、WHEN
条件の評価が関数の実施直前になされます。しかしAFTER
トリガでは、WHEN
条件の評価は行の更新直後に行われ、文の終わり(コミット時)にトリガを発動するためのイベントを待ち行列に入れるかどうかを決めます。そのため、あるAFTER
トリガのWHEN
条件が真を返さなかった場合は、イベントを待ち行列に入れる必要も文の終わりに行を再取得する必要もありません。これは、大量の行の変更が発生するけれども、トリガがその内の少数の行に対してのみ発動させる必要がある、といった文の処理速度を大幅に上げる効果があります。INSTEAD OF
トリガはWHEN
条件をサポートしていません。
通常、行レベルのBEFORE
トリガは、挿入あるいは更新される予定のデータの検査や変更のために使用されます。
例えば、BEFORE
トリガは、timestamp
型の列に現在時刻を挿入するために、あるいは行の2つの要素の整合性を検査するために使用される可能性があります。
行レベルのAFTER
トリガは、ほとんど常識的に他のテーブルに更新を伝播させるために、あるいは他のテーブルとの整合性を検査するために使用されます。
こうした仕事の切り分け理由は、AFTER
トリガは行の最終値を見ることができ、BEFORE
トリガは見ることができないという点です。
トリガをBEFORE
にするかAFTER
にするかを決める時に特別な理由がないのであれば、操作の情報を行が終わるまで保持する必要がない分、BEFORE
を使う方が効率的です。
トリガ関数がSQLコマンドを処理する場合、これらの問い合わせがトリガを再度発行することがあります。
これはカスケードされたトリガと呼ばれます。
カスケードの段数に直接的な制限はありません。
カスケードの場合、同じトリガを再帰的に呼び出すことが可能です。
例えば、INSERT
トリガで同じテーブルに追加の行を挿入する問い合わせが実行された場合、その結果としてINSERT
トリガが再度発行されます。
こうした状況で無限再帰を防ぐのは、トリガプログラマの責任です。
トリガを定義する時、そのトリガ用の引数を指定することができます。
トリガ定義に引数を含めた目的は、似たような要求の異なるトリガに同じ関数を呼び出すことができるようにすることです。
例えば、2つの列名を引数とし、片方に現在のユーザをもう片方に現在のタイムスタンプを取る、汎化トリガ関数があるとします。
適切に作成すれば、この関数が特定のトリガの発行元となるテーブルに依存することはなくなります。
同じ関数を使用して、例えば、トランザクションテーブルに作成記録を自動的に登録させるために、適切な列を持つ任意のテーブルのINSERT
イベントに使用することができます。
また、UPDATE
として定義すれば、最終更新イベントを追跡するために使用することも可能です。
トリガをサポートするプログラミング言語はそれぞれ独自の方法で、トリガ関数で利用できるトリガの入力データを作成します。
この入力データにはトリガイベント種類(例えばINSERT
やUPDATE
など、CREATE TRIGGER
で指定された全ての引数)が含まれます。
行レベルトリガの入力データには、INSERT
およびUPDATE
トリガの場合はNEW
行が、UPDATE
およびDELETE
トリガの場合はOLD
行が含まれます。
デフォルトでは、文レベルトリガには文によって変更された個々の行を検査するための手段がありません。
しかし、トリガがアクセスできる影響を受けた行の集合を作成するために、AFTER STATEMENT
トリガは、遷移テーブル(transition tables)の作成を依頼することができます。
AFTER ROW
トリガも遷移テーブルを依頼できるので、発動中の個々の行における変更だけでなく、テーブル全体におけるすべての変更を見ることができます。
遷移テーブルを検査する方法も使用中のプログラミング言語に依存しますが、典型的な方法は、トリガ関数の中で発行するSQLコマンドでアクセスできる、読み込み専用の一時テーブルのように振る舞う遷移テーブルを作成することです。