もし大量データの更新時に各行ごとに何か処理したいとか、テーブル全体に対して一度だけ処理したいって状況に出くわしたことがあるなら、「どうやって実装するのがベストなんだろう?」って悩んだことあるかも。PostgreSQLだと、行レベルトリガーとステートメントレベルトリガーの2つがあるんだ。どっちを使うべきか理解しておくのは、DB設計をちゃんとやるためにも、パフォーマンス最適化やミス防止のためにも超大事。じゃあ詳しく見ていこう!
行レベルのトリガー(FOR EACH ROW)は、操作で影響を受けた各行ごとに発動するよ。INSERT、UPDATE、DELETEのたびにね。つまり、SQLクエリで100行が対象なら、トリガーも100回実行されるってこと。
どんな時に使う?
行レベルトリガーは、変更された各行を個別に処理したいときに便利。例えば:
- 各行の変更をログに残したいとき。
- 各行ごとに関連データを自動更新したいとき。
例:各行の変更をログに残す
例えば、学生のテーブルがあるとする:
CREATE TABLE students (
id SERIAL PRIMARY KEY,
name VARCHAR(100),
age INT,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
このテーブルで更新された各行を、別のテーブルstudents_logに記録したいとする:
CREATE TABLE students_log (
log_id SERIAL PRIMARY KEY,
student_id INT,
old_name VARCHAR(100),
new_name VARCHAR(100),
changed_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
変更を記録するための関数:
CREATE OR REPLACE FUNCTION log_student_update()
RETURNS TRIGGER AS $$
BEGIN
INSERT INTO students_log(student_id, old_name, new_name, changed_at)
VALUES (OLD.id, OLD.name, NEW.name, CURRENT_TIMESTAMP);
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
FOR EACH ROWトリガーの作成:
CREATE TRIGGER student_update_logger
AFTER UPDATE ON students
FOR EACH ROW
EXECUTE FUNCTION log_student_update();
テスト:
UPDATE students
SET name = 'イワン イワノフ'
WHERE id = 1;
このクエリを実行すると、students_logテーブルに変更内容の詳細が記録されるよ。
ステートメントレベルのトリガー(FOR EACH STATEMENT)
ステートメントレベルのトリガー(FOR EACH STATEMENT)は、SQLクエリ全体に対して一度だけ発動する。影響を受ける行数に関係なくね。例えば100行更新しても、トリガーは1回だけ実行される。
ステートメントレベルのトリガーは、こんな時に便利:
- 操作全体に対して一度だけ何かしたいとき。
- 集計データを扱ったり、テーブル全体の計算をしたいとき。
例:変更回数カウンターの更新
例えば、studentsテーブルの変更回数を記録するカウンターテーブルがあるとする:
CREATE TABLE students_changes_log (
total_changes INT DEFAULT 0
);
INSERT INTO students_changes_log(total_changes) VALUES (0);
studentsテーブルでUPDATE操作があるたびに、このカウンターを増やしたい。
カウンターを更新する関数:
CREATE OR REPLACE FUNCTION increment_changes_counter()
RETURNS TRIGGER AS $$
BEGIN
UPDATE students_changes_log
SET total_changes = total_changes + 1;
RETURN NULL; -- ステートメントレベルのトリガーは行を返さない
END;
$$ LANGUAGE plpgsql;
FOR EACH STATEMENTトリガーの作成:
CREATE TRIGGER update_changes_counter
AFTER UPDATE ON students
FOR EACH STATEMENT
EXECUTE FUNCTION increment_changes_counter();
テスト:
UPDATE students
SET age = age + 1
WHERE age < 20;
このクエリを実行すると、トリガーは一度だけ発動して、変更カウンターが1増えるよ。
FOR EACH ROWとFOR EACH STATEMENTの比較
| 基準 | FOR EACH ROW | FOR EACH STATEMENT |
|---|---|---|
| 実行レベル | 影響を受けた各行ごと | 操作全体で一度だけ |
| 呼び出し頻度 | 1行ごとに1回 | SQLクエリごとに1回 |
| 用途 | 個別変更のログ、行ごとの処理 | 集計、メタ情報の更新 |
| 例 | 各行の変更をログに残す | 変更カウンターの更新 |
| パフォーマンス | 大量操作時はコスト高め | 大量操作時でもコスト低め |
FOR EACH ROWとFOR EACH STATEMENTはいつ使う?
FOR EACH ROWを使うべきなのは:
- 各行ごとにトリガーを実行したいとき。
- 処理ロジックを特定の行の変更に紐付けたいとき。
- 各行の
OLDやNEWデータにアクセスしたいとき。
例:テーブルの変更ログを残す、関連レコードを自動作成するなど。
FOR EACH STATEMENTを使うべきなのは:
- 操作全体に対して一度だけ処理したいとき。
- トリガーロジックが特定の行の変更に依存しないとき。
- パフォーマンスが超重要で、トリガーの大量呼び出しを避けたいとき。
例:カウンターの更新、テーブルのメタデータ計算など。
ミスしやすいポイント・注意点
正しいトリガータイプの選択は意外と難しい。ここは要注意:
FOR EACH STATEMENTトリガーでOLDやNEWデータを使おうとするとエラーになる。これらは行レベルトリガーでしか使えない。- 行レベルトリガー(
FOR EACH ROW)は、たくさんの行を処理するときにパフォーマンスが大きく落ちることがある。パフォーマンスには常に気をつけて! - トリガーの再帰に注意。例えば、トリガーが同じテーブルのデータを変更すると、無限ループになることもある。
GO TO FULL VERSION