【Oracle】トリガーを作成する方法|BEFORE/AFTER・行/文レベル・:NEW/:OLD・INSTEAD OF・有効化/無効化まで解説

【Oracle】トリガーを作成する方法|BEFORE/AFTER・行/文レベル・:NEW/:OLD・INSTEAD OF・有効化/無効化まで解説 Oracle

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 の基本構文

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」と覚えてください。

行レベルトリガーと文レベルトリガー

項目 行レベル(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)にはアクセスできません。行ごとの値を参照したい場合は 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)では :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)で動作します。

トリガーの有効化 / 無効化 / 削除 / 確認

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 でトリガーが毎行発火するとパフォーマンスが大幅に低下します。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トリガーを一時的に無効化するには?
AALTER 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文」、特定列の更新トリガーは「特定の項目が更新された場合のトリガー作成方法」も併せて参照してください。