Oracle のトリガー(Trigger)は、テーブルに対する INSERT / UPDATE / DELETE が実行されたときに自動的に処理を起動する仕組みです。監査ログの自動記録、タイムスタンプの自動更新、データの整合性チェックなど、アプリケーションのコードに手を加えずにビジネスロジックを実装できます。
本記事では、CREATE TRIGGER の基本構文から、BEFORE / AFTER の使い分け、行レベル / 文レベルの違い、:NEW / :OLD 疑似レコードの動作、INSTEAD OF トリガー、そして有効化 / 無効化 / 削除の管理方法まで基礎から実践レベルで解説します。
この記事でわかること
・CREATE TRIGGER の基本構文
・BEFORE トリガーと AFTER トリガーの違いと使い分け
・行レベルトリガー(FOR EACH ROW)と文レベルトリガーの違い
・:NEW / :OLD 疑似レコードの使い方(INSERT / UPDATE / DELETE 別)
・複数イベントに対応するトリガー(INSERT OR UPDATE OR DELETE)
・INSTEAD OF トリガーでビューを更新可能にする方法
・トリガーの有効化(ENABLE)/ 無効化(DISABLE)/ 削除(DROP)
・監査ログ・自動タイムスタンプ・データ検証の実務パターン
・CREATE TRIGGER の基本構文
・BEFORE トリガーと AFTER トリガーの違いと使い分け
・行レベルトリガー(FOR EACH ROW)と文レベルトリガーの違い
・:NEW / :OLD 疑似レコードの使い方(INSERT / UPDATE / DELETE 別)
・複数イベントに対応するトリガー(INSERT OR UPDATE OR DELETE)
・INSTEAD OF トリガーでビューを更新可能にする方法
・トリガーの有効化(ENABLE)/ 無効化(DISABLE)/ 削除(DROP)
・監査ログ・自動タイムスタンプ・データ検証の実務パターン
CREATE TRIGGER の基本構文
SQL(基本構文)
CREATE [OR REPLACE] TRIGGER trigger_name
{BEFORE | AFTER | INSTEAD OF}
{INSERT | UPDATE [OF column_name] | DELETE}
[OR {INSERT | UPDATE | DELETE}]
ON table_name
[FOR EACH ROW]
[WHEN (condition)]
[DECLARE
-- variables]
BEGIN
-- trigger body
END;
/
SQL(最もシンプルなトリガー例)
-- employees テーブルに INSERT されたらログに記録
CREATE OR REPLACE TRIGGER trg_emp_insert_log
AFTER INSERT ON employees
FOR EACH ROW
BEGIN
INSERT INTO audit_log (table_name, action, key_value, action_date, action_user)
VALUES ('EMPLOYEES', 'INSERT', :NEW.employee_id, SYSDATE, USER);
END;
/
BEFORE トリガーと AFTER トリガーの違い
| 項目 | BEFORE トリガー | AFTER トリガー |
|---|---|---|
| 実行タイミング | DML 実行前(書き込み前) | DML 実行後(書き込み後) |
| :NEW の変更 | 可能(値を上書きできる) | 不可(既に書き込み済み) |
| 主な用途 | データの自動補正・デフォルト値設定・入力検証 | 監査ログの記録・通知・後処理 |
| ROLLBACK 時 | DML ごとロールバックされる | DML ごとロールバックされる |
SQL(BEFORE: 値を自動補正)
-- INSERT / UPDATE 前に created_at / updated_at を自動設定
CREATE OR REPLACE TRIGGER trg_emp_timestamp
BEFORE INSERT OR UPDATE ON employees
FOR EACH ROW
BEGIN
IF INSERTING THEN
:NEW.created_at := SYSDATE;
END IF;
:NEW.updated_at := SYSDATE;
END;
/
SQL(AFTER: 監査ログを記録)
-- UPDATE 後に変更内容を監査テーブルに記録
CREATE OR REPLACE TRIGGER trg_emp_audit
AFTER UPDATE ON employees
FOR EACH ROW
BEGIN
INSERT INTO salary_audit (employee_id, old_salary, new_salary, changed_at, changed_by)
VALUES (:OLD.employee_id, :OLD.salary, :NEW.salary, SYSDATE, USER);
END;
/
BEFORE と AFTER の使い分けルール
・:NEW の値を変更したい(自動補正・デフォルト値設定)→ BEFORE
・変更後のデータを他テーブルに記録したい(監査ログ・通知)→ AFTER
迷ったら「データを変えるなら BEFORE、記録するなら AFTER」と覚えてください。
・:NEW の値を変更したい(自動補正・デフォルト値設定)→ BEFORE
・変更後のデータを他テーブルに記録したい(監査ログ・通知)→ AFTER
迷ったら「データを変えるなら BEFORE、記録するなら AFTER」と覚えてください。
行レベルトリガーと文レベルトリガー
| 項目 | 行レベル(FOR EACH ROW) | 文レベル(FOR EACH ROW なし) |
|---|---|---|
| 発火回数 | 影響を受ける行ごとに 1 回 | DML 文 1 つにつき1 回だけ |
| :NEW / :OLD | 使用可能 | 使用不可 |
| WHEN 句 | 使用可能 | 使用不可 |
| 主な用途 | 行単位のデータ操作・値の自動設定 | DML 完了後の集計処理や通知 |
SQL(文レベルトリガーの例)
-- employees テーブルへの DML が完了した後に 1 回だけ実行
CREATE OR REPLACE TRIGGER trg_emp_statement
AFTER INSERT OR UPDATE OR DELETE ON employees
-- FOR EACH ROW なし = 文レベル
BEGIN
INSERT INTO change_notifications (table_name, notified_at)
VALUES ('EMPLOYEES', SYSDATE);
END;
/
文レベルトリガーでは :NEW / :OLD は使えない
文レベルトリガーは「DML 文全体に対して 1 回」発火するため、個々の行の値(:NEW / :OLD)にはアクセスできません。行ごとの値を参照したい場合は
文レベルトリガーは「DML 文全体に対して 1 回」発火するため、個々の行の値(:NEW / :OLD)にはアクセスできません。行ごとの値を参照したい場合は
FOR EACH ROW を付けてください。:NEW と :OLD 疑似レコード
行レベルトリガーでは、:NEW(変更後の値)と :OLD(変更前の値)で各列にアクセスできます。DML の種類によって使える疑似レコードが異なります。
| DML | :OLD | :NEW | 説明 |
|---|---|---|---|
| INSERT | 全列 NULL | 挿入される値 | 新しい行のデータ。BEFORE で :NEW を変更可能 |
| UPDATE | 変更前の値 | 変更後の値 | 両方アクセス可能。BEFORE で :NEW を変更可能 |
| DELETE | 削除される値 | 全列 NULL | 削除前のデータを参照できる |
SQL(:NEW / :OLD の使い方)
-- UPDATE トリガー: 給与変更の監査(:OLD と :NEW の比較)
CREATE OR REPLACE TRIGGER trg_salary_change
BEFORE UPDATE OF salary ON employees
FOR EACH ROW
WHEN (NEW.salary <> OLD.salary) -- WHEN 句ではコロンを付けない
BEGIN
INSERT INTO salary_history
(employee_id, old_salary, new_salary, changed_at)
VALUES
(:OLD.employee_id, :OLD.salary, :NEW.salary, SYSDATE);
END;
/
WHEN 句ではコロンを付けない
PL/SQL ブロック内(BEGIN〜END)では
PL/SQL ブロック内(BEGIN〜END)では
:NEW.salary とコロンを付けますが、WHEN 句では NEW.salary とコロンなしで記述します。よくあるコンパイルエラーの原因なので注意してください。複数イベントに対応するトリガー
SQL(INSERT / UPDATE / DELETE を 1 つのトリガーで処理)
CREATE OR REPLACE TRIGGER trg_emp_all_dml
AFTER INSERT OR UPDATE OR DELETE ON employees
FOR EACH ROW
DECLARE
v_action VARCHAR2(10);
v_key NUMBER;
BEGIN
IF INSERTING THEN
v_action := 'INSERT';
v_key := :NEW.employee_id;
ELSIF UPDATING THEN
v_action := 'UPDATE';
v_key := :OLD.employee_id;
ELSIF DELETING THEN
v_action := 'DELETE';
v_key := :OLD.employee_id;
END IF;
INSERT INTO audit_log (table_name, action, key_value, action_date, action_user)
VALUES ('EMPLOYEES', v_action, v_key, SYSDATE, USER);
END;
/
| 条件述語 | 意味 |
|---|---|
| INSERTING | INSERT 文で発火した場合に TRUE |
| UPDATING | UPDATE 文で発火した場合に TRUE |
| UPDATING(‘column_name’) | 指定した列が UPDATE された場合に TRUE |
| DELETING | DELETE 文で発火した場合に TRUE |
INSTEAD OF トリガー:ビューを更新可能にする
複数テーブルを結合したビューは通常 UPDATE / INSERT / DELETE ができませんが、INSTEAD OF トリガーを定義することで DML を受け付けるようにできます。
SQL(INSTEAD OF トリガーの例)
-- 結合ビュー
CREATE OR REPLACE VIEW emp_dept_view AS
SELECT e.employee_id, e.last_name, e.salary, d.department_name
FROM employees e JOIN departments d ON e.department_id = d.department_id;
-- ビューへの UPDATE を可能にする INSTEAD OF トリガー
CREATE OR REPLACE TRIGGER trg_emp_dept_view_update
INSTEAD OF UPDATE ON emp_dept_view
FOR EACH ROW
BEGIN
UPDATE employees
SET last_name = :NEW.last_name,
salary = :NEW.salary
WHERE employee_id = :OLD.employee_id;
UPDATE departments
SET department_name = :NEW.department_name
WHERE department_name = :OLD.department_name;
END;
/
-- これでビュー経由の UPDATE が可能になる
UPDATE emp_dept_view SET salary = 5000 WHERE employee_id = 100;
INSTEAD OF はビュー専用
INSTEAD OF トリガーはテーブルには定義できません。テーブルに対しては BEFORE / AFTER のみ使用可能です。また、INSTEAD OF トリガーは常に行レベル(FOR EACH ROW)で動作します。
INSTEAD OF トリガーはテーブルには定義できません。テーブルに対しては BEFORE / AFTER のみ使用可能です。また、INSTEAD OF トリガーは常に行レベル(FOR EACH ROW)で動作します。
トリガーの有効化 / 無効化 / 削除 / 確認
SQL(トリガーの管理操作)
-- トリガーを無効化(一時停止) ALTER TRIGGER trg_emp_insert_log DISABLE; -- トリガーを有効化(再開) ALTER TRIGGER trg_emp_insert_log ENABLE; -- テーブル上の全トリガーを一括無効化(大量データ投入時に便利) ALTER TABLE employees DISABLE ALL TRIGGERS; -- テーブル上の全トリガーを一括有効化 ALTER TABLE employees ENABLE ALL TRIGGERS; -- トリガーを削除 DROP TRIGGER trg_emp_insert_log;
SQL(トリガーの一覧と状態を確認)
-- 自分のスキーマのトリガー一覧
SELECT trigger_name, trigger_type, triggering_event,
table_name, status
FROM user_triggers
ORDER BY table_name, trigger_name;
-- トリガーのソースコードを確認
SELECT trigger_body FROM user_triggers WHERE trigger_name = 'TRG_EMP_INSERT_LOG';
大量データ投入時はトリガーを無効化する
Data Pump インポートやバッチ INSERT でトリガーが毎行発火するとパフォーマンスが大幅に低下します。
Data Pump インポートやバッチ INSERT でトリガーが毎行発火するとパフォーマンスが大幅に低下します。
ALTER TABLE ... DISABLE ALL TRIGGERS で一括無効化し、投入完了後に ENABLE ALL TRIGGERS で戻すのが定番の手法です。トリガー情報の詳細な取得方法は「トリガー情報を取得するSQL文」を参照してください。
実務パターン集
パターン(1):自動タイムスタンプ(created_at / updated_at)
SQL(タイムスタンプ自動設定)
CREATE OR REPLACE TRIGGER trg_orders_timestamp
BEFORE INSERT OR UPDATE ON orders
FOR EACH ROW
BEGIN
IF INSERTING THEN
:NEW.created_at := SYSTIMESTAMP;
:NEW.created_by := USER;
END IF;
:NEW.updated_at := SYSTIMESTAMP;
:NEW.updated_by := USER;
END;
/
パターン(2):データ検証(BEFORE トリガーでチェック)
SQL(入力値の検証)
-- salary が 0 未満にならないようにチェック
CREATE OR REPLACE TRIGGER trg_emp_salary_check
BEFORE INSERT OR UPDATE OF salary ON employees
FOR EACH ROW
BEGIN
IF :NEW.salary < 0 THEN
RAISE_APPLICATION_ERROR(-20001, '給与は 0 以上で入力してください');
END IF;
IF UPDATING AND :NEW.salary > :OLD.salary * 2 THEN
RAISE_APPLICATION_ERROR(-20002, '給与の増加率が上限(200%)を超えています');
END IF;
END;
/
パターン(3):論理削除(DELETE を禁止)
SQL(論理削除トリガー)
-- DELETE 操作を禁止する
CREATE OR REPLACE TRIGGER trg_emp_soft_delete
BEFORE DELETE ON employees
FOR EACH ROW
BEGIN
RAISE_APPLICATION_ERROR(-20003,
'物理削除は禁止です。UPDATE SET is_deleted=1 を使ってください');
END;
/
パターン(4):自動採番(シーケンス連携)
SQL(シーケンスによる自動採番)
-- Oracle 11g 以前(12c 以降は IDENTITY 列を推奨)
CREATE SEQUENCE seq_emp_id START WITH 1 INCREMENT BY 1;
CREATE OR REPLACE TRIGGER trg_emp_auto_id
BEFORE INSERT ON employees
FOR EACH ROW
BEGIN
IF :NEW.employee_id IS NULL THEN
:NEW.employee_id := seq_emp_id.NEXTVAL;
END IF;
END;
/
シーケンスの詳細は「シーケンスを作成する方法完全ガイド」を参照してください。
よくあるエラー
| エラー | 原因 | 対処 |
|---|---|---|
| ORA-04091(ミューティングテーブル) | トリガー内で同じテーブルを SELECT / DML した | 複合トリガーまたは自律トランザクションで回避。詳細は「トリガー高度活用ガイド」を参照 |
| ORA-04098(トリガーが無効) | トリガーのコンパイルエラーが残っている | SHOW ERRORS TRIGGER trigger_name でエラー内容を確認し修正 |
| PLS-00049(バインド変数が不正) | WHEN 句で :NEW / :OLD にコロンを付けた | WHEN 句ではコロンなし(NEW.col / OLD.col)にする |
| ORA-25004 | テーブルに INSTEAD OF トリガーを定義しようとした | INSTEAD OF はビュー専用。テーブルには BEFORE / AFTER を使う |
よくある質問
QBEFORE と AFTER はどちらを使うべきですか?
A:NEW の値を変更する必要がある場合は BEFORE を使います(タイムスタンプ自動設定、デフォルト値補正、入力検証など)。変更結果を記録する場合は AFTER を使います(監査ログ、通知など)。迷ったら「データを変えるなら BEFORE、記録するなら AFTER」が基本ルールです。
QFOR EACH ROW を付けないとどうなりますか?
A文レベルトリガーになり、DML 文 1 つにつき 1 回だけ発火します。1 つの UPDATE 文で 100 行更新しても、トリガーは 1 回しか実行されません。また、
:NEW / :OLD は使えなくなります。行ごとの値にアクセスしたい場合は必ず FOR EACH ROW を付けてください。Qトリガー内で同じテーブルを SELECT すると ORA-04091 が出ます
Aこれはミューティングテーブルエラーです。行レベルトリガー内で、そのトリガーが定義されたテーブル自体を SELECT / DML すると発生します。対処法は複合トリガー(COMPOUND TRIGGER)を使うのが最も一般的です。詳細は「トリガー高度活用ガイド」を参照してください。
Qトリガーを一時的に無効化するには?
A
ALTER TRIGGER trigger_name DISABLE で個別に無効化できます。テーブル上の全トリガーを一括無効化するにはALTER TABLE table_name DISABLE ALL TRIGGERS を使います。大量データの投入前に無効化し、完了後に ENABLE に戻すのが一般的なパターンです。Q1 つのテーブルに複数のトリガーを定義できますか?
Aできます。同じタイミング(例: BEFORE INSERT)に複数のトリガーを定義可能です。ただし実行順序は保証されません(Oracle 11g 以降では FOLLOWS 句で順序を指定可能)。トリガー間に依存関係がある場合は、1 つのトリガーにまとめるか FOLLOWS で順序を明示してください。
QINSTEAD OF トリガーはどんなときに使いますか?
A複数テーブルを結合したビューに対して INSERT / UPDATE / DELETE を実行したい場合に使います。結合ビューは通常 DML できませんが、INSTEAD OF トリガーで「ビューへの DML を受け取って、実テーブルへの操作に変換する」ことで更新可能になります。テーブルには使えず、ビュー専用の機能です。
まとめ
Oracle トリガー作成の要点をまとめます。
| やりたいこと | 設定 |
|---|---|
| INSERT/UPDATE 前に値を自動設定 | BEFORE INSERT OR UPDATE … FOR EACH ROW |
| DML 後に監査ログを記録 | AFTER INSERT OR UPDATE OR DELETE … FOR EACH ROW |
| 特定の列が更新されたときだけ発火 | AFTER UPDATE OF salary ON … FOR EACH ROW |
| 結合ビューを更新可能にする | INSTEAD OF UPDATE ON view_name FOR EACH ROW |
| DML 文全体で 1 回だけ処理 | AFTER INSERT OR UPDATE … (FOR EACH ROW なし) |
| 値の変更前後を比較 | :OLD.col と :NEW.col を参照(FOR EACH ROW 必須) |
| トリガーを一時停止 | ALTER TRIGGER trigger_name DISABLE |
| テーブルの全トリガーを一括無効化 | ALTER TABLE table_name DISABLE ALL TRIGGERS |
複合トリガー・DDL トリガー・自律トランザクションなど高度なトピックは「トリガー高度活用完全ガイド」、トリガー情報の取得は「トリガー情報を取得するSQL文」、特定列の更新トリガーは「特定の項目が更新された場合のトリガー作成方法」も併せて参照してください。

