Oracleのトリガー(Trigger)は、テーブルやビューに対する操作(INSERT / UPDATE / DELETE)が発生したとき、自動的に実行されるPL/SQLプログラムです。監査ログの記録、データ整合性の保証、自動採番など、実務で幅広く使われます。
この記事では、トリガー情報の確認方法を中心に、3つのデータディクショナリビューの使い分けから、作成・有効化/無効化・削除・実務パターン・エラー対処まで、現場で必要な知識を体系的に解説します。
この記事でわかること
USER_TRIGGERS / ALL_TRIGGERS / DBA_TRIGGERS の違いと使い分け
- トリガーの一覧取得・詳細確認のSQL
USER_SOURCE によるソースコード取得
- トリガーの有効/無効の確認と
ALTER TRIGGER ENABLE/DISABLE
CREATE TRIGGER の基本構文(BEFORE/AFTER、行レベル/文レベル)
:NEW / :OLD 疑似レコードの使い方
- INSTEAD OF トリガー(ビュー用)の仕組み
DROP TRIGGER による削除方法
- 他RDBMS比較(MySQL、PostgreSQL、SQL Server)
- よくあるエラーと対処法(ORA-04091 等)
- 実務パターン(監査ログ、自動採番、論理削除フラグ)
トリガーとは(基本概念)
トリガーは、特定のイベント(INSERT / UPDATE / DELETE)がテーブルやビューに対して発生したとき、Oracleが自動的に起動するPL/SQLプログラムです。アプリケーション側でロジックを書かなくても、データベースレベルで処理を強制できるため、データの整合性保証や監査に適しています。
トリガーの動作イメージ
-- ユーザーがINSERTを実行
INSERT INTO employees (emp_id, emp_name) VALUES (100, '山田太郎');
-- ↓ Oracleが自動的にトリガーを起動
-- ↓ 例:監査テーブルに操作ログを自動記録
-- ↓ 例:自動採番カラムに値を自動セット
トリガーの主な用途は以下のとおりです。
| 用途 |
説明 |
例 |
| 監査ログ |
誰が・いつ・何を変更したかを自動記録 |
更新前後の値を監査テーブルに INSERT |
| データ整合性 |
CHECK制約では表現できない複雑な検証 |
他テーブルを参照する整合性チェック |
| 自動採番 |
シーケンスから主キーを自動セット |
INSERT前にシーケンスのNEXTVALを代入 |
| 派生カラム更新 |
他のカラムから計算値を自動設定 |
更新日時の自動セット |
| ビューの更新 |
更新不可ビューに対するDMLを実現 |
INSTEAD OFトリガーで基底テーブルを更新 |
トリガー情報を確認する3つのビュー
Oracleには、トリガー情報を確認するための3つのデータディクショナリビューが用意されています。権限レベルに応じて使い分けます。
| ビュー名 |
表示範囲 |
必要権限 |
主な使用場面 |
USER_TRIGGERS |
自分が所有するトリガーのみ |
不要(自スキーマ) |
開発者が自分のトリガーを確認 |
ALL_TRIGGERS |
アクセス可能な全トリガー |
不要 |
他スキーマのトリガーも含めて確認 |
DBA_TRIGGERS |
データベース上の全トリガー |
SELECT ANY DICTIONARY 等 |
DBAの管理作業・棚卸し |
ポイント
この3ビューの命名規則は、USER_TABLES / ALL_TABLES / DBA_TABLES と同じパターンです。Oracle のデータディクショナリビューは USER_ → ALL_ → DBA_ の順で参照範囲が広がります。
USER_TRIGGERS — 自分のトリガー一覧
SQL
-- 自分が所有するトリガーの一覧を取得
SELECT
trigger_name, -- トリガー名
trigger_type, -- BEFORE EACH ROW / AFTER STATEMENT 等
triggering_event, -- INSERT / UPDATE / DELETE
table_name, -- 対象テーブル名
status -- ENABLED / DISABLED
FROM
user_triggers
ORDER BY
table_name, trigger_name;
実行結果(例)
TRIGGER_NAME TRIGGER_TYPE TRIGGERING_EVENT TABLE_NAME STATUS
--------------------- -------------------- ----------------- ------------ --------
TRG_EMP_AUDIT AFTER EACH ROW INSERT OR UPDATE EMPLOYEES ENABLED
TRG_EMP_SEQ BEFORE EACH ROW INSERT EMPLOYEES ENABLED
TRG_ORDER_CHECK BEFORE EACH ROW INSERT ORDERS DISABLED
USER_TRIGGERS は特別な権限なしで参照できるため、開発者が最もよく使うビューです。OWNER カラムは存在せず、すべて自スキーマのトリガーが返ります。
ALL_TRIGGERS — アクセス可能な全トリガー
SQL
-- アクセス可能な全トリガー(他スキーマ含む)
SELECT
owner, -- 所有者
trigger_name,
trigger_type,
triggering_event,
table_owner, -- 対象テーブルの所有者
table_name,
status
FROM
all_triggers
WHERE
owner = 'HR' -- スキーマ名で絞り込み
ORDER BY
owner, table_name, trigger_name;
ALL_TRIGGERS には OWNER カラムがあり、他ユーザーが所有するトリガーも表示されます。複数スキーマにまたがるシステムで、関連トリガーを横断的に確認する場合に便利です。
DBA_TRIGGERS — 全ユーザーの全トリガー(DBA向け)
SQL
-- データベース上のすべてのトリガー(DBA権限が必要)
SELECT
owner,
trigger_name,
trigger_type,
triggering_event,
table_name,
status
FROM
dba_triggers
WHERE
owner NOT IN ('SYS', 'SYSTEM') -- システムトリガーを除外
ORDER BY
owner, table_name, trigger_name;
注意:DBA_TRIGGERS には SELECT ANY DICTIONARY または SELECT_CATALOG_ROLE が必要です。権限がないと ORA-00942: table or view does not exist が発生します。
特定テーブルのトリガーを確認する
実務では「このテーブルにどんなトリガーが付いているか」を確認するケースが最も多いです。WHERE table_name = 'テーブル名' で絞り込みます。
SQL
-- 特定テーブルのトリガー一覧(詳細情報付き)
SELECT
trigger_name,
trigger_type,
triggering_event,
status,
description -- トリガーの宣言部(CREATE TRIGGER ...)
FROM
user_triggers
WHERE
table_name = 'EMPLOYEES'
ORDER BY
trigger_name;
注意:テーブル名はデータディクショナリ内では大文字で格納されています。WHERE table_name = 'employees' ではヒットしないため、'EMPLOYEES' と大文字で指定するか UPPER() を使ってください。
トリガーのソースコード取得(USER_SOURCE)
トリガーの実装内容(PL/SQLのソースコード)は USER_SOURCE(または ALL_SOURCE / DBA_SOURCE)から取得できます。
SQL
-- トリガーのソースコードを行番号順に取得
SELECT
line, -- 行番号
text -- ソースコード
FROM
user_source
WHERE
name = 'TRG_EMP_AUDIT'
AND type = 'TRIGGER'
ORDER BY
line;
実行結果(例)
LINE TEXT
----- --------------------------------------------------
1 TRG_EMP_AUDIT
2 AFTER INSERT OR UPDATE ON employees
3 FOR EACH ROW
4 BEGIN
5 INSERT INTO emp_audit_log (
6 action_date, action_type, emp_id, old_name, new_name
7 ) VALUES (
8 SYSDATE, 'UPDATE', :NEW.emp_id, :OLD.emp_name, :NEW.emp_name
9 );
10 END;
また、USER_TRIGGERS の TRIGGER_BODY カラムでもソースを取得できますが、LONG型のためSQLで直接扱いにくい場合があります。USER_SOURCE を使う方法がお勧めです。
SQL(DBMS_METADATAを使う方法)
-- DDL全体を取得(CREATE TRIGGER 文を再構成)
SELECT
DBMS_METADATA.GET_DDL('TRIGGER', 'TRG_EMP_AUDIT') AS ddl_text
FROM
dual;
DBMS_METADATA.GET_DDL を使うと、CREATE TRIGGER 文を完全な形で取得できます。別環境への移行やバックアップ用のスクリプト生成に便利です。
トリガーの有効/無効を確認・変更する
ステータスの確認
USER_TRIGGERS の STATUS カラムで、トリガーが ENABLED(有効) か DISABLED(無効) かを確認できます。
SQL
-- 無効化されているトリガーを一覧表示
SELECT
trigger_name,
table_name,
status
FROM
user_triggers
WHERE
status = 'DISABLED'
ORDER BY
table_name;
ALTER TRIGGER による有効化/無効化
大量データのロード時やメンテナンス時に、一時的にトリガーを無効化するケースがあります。
SQL
-- 個別のトリガーを無効化
ALTER TRIGGER trg_emp_audit DISABLE;
-- 個別のトリガーを有効化
ALTER TRIGGER trg_emp_audit ENABLE;
-- テーブルの全トリガーを一括無効化
ALTER TABLE employees DISABLE ALL TRIGGERS;
-- テーブルの全トリガーを一括有効化
ALTER TABLE employees ENABLE ALL TRIGGERS;
注意:トリガーを無効化したまま戻し忘れると、監査ログが記録されない・整合性チェックが効かないなどの問題が起きます。必ず作業後に再有効化してください。
CREATE TRIGGER の基本構文
トリガーの作成には CREATE TRIGGER 文を使います。基本構文は以下のとおりです。
基本構文
CREATE [OR REPLACE] TRIGGER トリガー名
{BEFORE | AFTER} -- 実行タイミング
{INSERT | UPDATE [OF 列名] | DELETE} -- 対象イベント
[OR {INSERT | UPDATE | DELETE}] -- 複数イベント指定可
ON テーブル名
[FOR EACH ROW] -- 行レベルトリガー
[WHEN (条件)] -- 起動条件(オプション)
[DECLARE]
-- 変数宣言(オプション)
BEGIN
-- PL/SQL処理
END;
BEFORE / AFTER の違い
| タイミング |
実行時点 |
:NEW の変更 |
主な用途 |
BEFORE |
DML実行前 |
可能(値を変更できる) |
自動採番、デフォルト値セット、入力値チェック |
AFTER |
DML実行後 |
不可(参照のみ) |
監査ログ記録、他テーブルへの反映 |
BEFOREトリガーの例(自動採番)
SQL
CREATE OR REPLACE TRIGGER trg_emp_seq
BEFORE INSERT ON employees
FOR EACH ROW
BEGIN
-- シーケンスから主キーを自動採番
IF :NEW.emp_id IS NULL THEN
:NEW.emp_id := emp_seq.NEXTVAL;
END IF;
END;
AFTERトリガーの例(監査ログ)
SQL
CREATE OR REPLACE TRIGGER trg_emp_audit
AFTER INSERT OR UPDATE OR DELETE ON employees
FOR EACH ROW
DECLARE
v_action VARCHAR2(10);
BEGIN
CASE
WHEN INSERTING THEN v_action := 'INSERT';
WHEN UPDATING THEN v_action := 'UPDATE';
WHEN DELETING THEN v_action := 'DELETE';
END CASE;
INSERT INTO emp_audit_log (
audit_date, action_type, emp_id,
old_emp_name, new_emp_name, changed_by
) VALUES (
SYSDATE, v_action, NVL(:NEW.emp_id, :OLD.emp_id),
:OLD.emp_name, :NEW.emp_name, USER
);
END;
行レベルトリガー vs 文レベルトリガー
トリガーには行レベル(FOR EACH ROW)と文レベル(FOR EACH ROW なし)の2種類があります。
| 種類 |
実行回数 |
:NEW / :OLD |
主な用途 |
行レベル
FOR EACH ROW |
影響を受ける行ごとに1回 |
使用可能 |
各行の値チェック、監査ログ、自動採番 |
文レベル (FOR EACH ROW なし) |
DML文ごとに1回だけ |
使用不可 |
処理完了通知、集計テーブルの更新 |
文レベルトリガーの例
-- DML文の実行後に1回だけ起動
CREATE OR REPLACE TRIGGER trg_emp_statement
AFTER INSERT ON employees
-- FOR EACH ROW を省略 → 文レベル
BEGIN
-- 例:処理完了ログをテーブルに記録
INSERT INTO operation_log (log_date, message)
VALUES (SYSDATE, 'EMPLOYEESテーブルにINSERTが実行されました');
END;
実行順序の整理
1つのDML文に対して、トリガーは以下の順序で起動します。
BEFORE文レベル → (行ごとに) BEFORE行レベル → DML実行 → AFTER行レベル → AFTER文レベル
:NEW / :OLD 疑似レコード
行レベルトリガーでは、:NEW と :OLD という疑似レコードを使って、変更前後の行データにアクセスできます。
| イベント |
:OLD |
:NEW |
INSERT |
NULL(挿入前はデータなし) |
挿入される値 |
UPDATE |
更新前の値 |
更新後の値 |
DELETE |
削除される値 |
NULL(削除後はデータなし) |
:NEW / :OLD の使用例
CREATE OR REPLACE TRIGGER trg_salary_check
BEFORE UPDATE OF salary ON employees
FOR EACH ROW
BEGIN
-- 給与の減額を禁止する
IF :NEW.salary < :OLD.salary THEN
RAISE_APPLICATION_ERROR(
-20001,
'給与の減額はできません(旧: ' || :OLD.salary ||
' → 新: ' || :NEW.salary || ')'
);
END IF;
END;
ポイント:WHEN 句内では : を付けずに NEW.salary / OLD.salary と記述します。BEGIN ... END 内では :NEW / :OLD とコロンを付けるのがルールです。
INSTEAD OF トリガー(ビュー用)
INSTEAD OF トリガーは、ビューに対する INSERT / UPDATE / DELETE を横取りし、代わりに基底テーブルへの操作を実行するための特殊なトリガーです。複数テーブルを結合したビューなど、通常は更新できないビューに対して DML を可能にします。
SQL
-- 結合ビューの作成
CREATE OR REPLACE VIEW v_emp_dept AS
SELECT
e.emp_id, e.emp_name, e.salary,
d.dept_id, d.dept_name
FROM
employees e
JOIN departments d ON e.dept_id = d.dept_id;
-- INSTEAD OFトリガーでビューへのINSERTを実現
CREATE OR REPLACE TRIGGER trg_v_emp_dept_insert
INSTEAD OF INSERT ON v_emp_dept
FOR EACH ROW
BEGIN
-- 基底テーブル(employees)にINSERT
INSERT INTO employees (emp_id, emp_name, salary, dept_id)
VALUES (:NEW.emp_id, :NEW.emp_name, :NEW.salary, :NEW.dept_id);
END;
INSTEAD OFトリガーの特徴
- ビューにのみ使用可(テーブルには使えない)
- 常に行レベル(FOR EACH ROW)で動作
- BEFORE / AFTER ではなくINSTEAD OFを指定
- ビューへのDMLを「横取り」して、基底テーブルを直接操作する
トリガーの削除(DROP TRIGGER)
不要になったトリガーは DROP TRIGGER で削除できます。
SQL
-- トリガーの削除
DROP TRIGGER trg_emp_audit;
-- 他スキーマのトリガーを削除(DROP ANY TRIGGER 権限が必要)
DROP TRIGGER hr.trg_emp_audit;
注意:テーブルを DROP TABLE すると、そのテーブルに紐づくトリガーも自動的に削除されます。一方、トリガーを DROP してもテーブル本体には影響しません。
削除ではなく一時的に停止したい場合は、前述の ALTER TRIGGER ... DISABLE を使います。削除すると定義自体が失われるため、再作成が必要になります。
他RDBMSとの比較
トリガーの仕様はRDBMSごとに異なります。Oracle経験者が他のDBを扱う場合や、逆にMySQLからOracle移行する場合の参考にしてください。
| 機能 |
Oracle |
MySQL |
PostgreSQL |
SQL Server |
| BEFORE / AFTER |
両方 |
両方 |
両方 |
AFTERのみ(INSTEAD OFあり) |
| INSTEAD OF |
ビューのみ |
非対応 |
ビューのみ(ルールも可) |
テーブル・ビュー両方 |
| 行レベル / 文レベル |
両方 |
行レベルのみ |
両方 |
文レベルのみ |
| 変更前後の参照 |
:NEW / :OLD |
NEW / OLD |
NEW / OLD |
inserted / deleted テーブル |
| 言語 |
PL/SQL |
SQL(制限あり) |
PL/pgSQL(関数経由) |
T-SQL |
| 1テーブルに複数 |
可能 |
イベント+タイミングごとに1つ |
可能 |
可能 |
PostgreSQLのトリガー
PostgreSQLではトリガー本体を直接書けず、まずトリガー関数を作成し、それを CREATE TRIGGER で紐づける2段構えになります。Oracle とは構文が大きく異なるので注意してください。
よくあるエラーと対処法
| エラー |
原因 |
対処法 |
ORA-04091 mutating table |
行レベルトリガー内でトリガーの対象テーブル自体を SELECT / DML した |
コンパウンドトリガー、パッケージ変数、文レベルトリガーで対応 |
ORA-04098 trigger is invalid and failed re-validation |
トリガーが依存するオブジェクト(テーブル、シーケンスなど)が変更・削除された |
ALTER TRIGGER ... COMPILE で再コンパイル |
ORA-04082 NEW or OLD references not allowed in table level triggers |
文レベルトリガーで :NEW / :OLD を使った |
FOR EACH ROW を追加して行レベルにする |
ORA-00942 table or view does not exist |
DBA_TRIGGERS の参照権限がない |
DBA に SELECT ANY DICTIONARY を付与してもらう、または USER_TRIGGERS を使う |
ORA-04084 cannot change NEW values for this trigger type |
AFTER トリガーで :NEW の値を変更しようとした |
BEFORE トリガーに変更する |
ORA-04091 ミューテーティングテーブル の詳細
ORA-04091 はOracleトリガーで最も遭遇しやすいエラーです。行レベルトリガーの中から、そのトリガーが定義されたテーブル自体を参照すると発生します。
エラーが発生するパターン
-- NG: 行レベルトリガー内で自テーブルを参照 → ORA-04091
CREATE OR REPLACE TRIGGER trg_check_count
BEFORE INSERT ON employees
FOR EACH ROW
DECLARE
v_count NUMBER;
BEGIN
-- employees テーブル自体を SELECT → ミューテーティングエラー!
SELECT COUNT(*) INTO v_count FROM employees;
IF v_count >= 1000 THEN
RAISE_APPLICATION_ERROR(-20002, '社員数の上限に達しました');
END IF;
END;
コンパウンドトリガーによる解決
-- OK: コンパウンドトリガーで回避(Oracle 11g以降)
CREATE OR REPLACE TRIGGER trg_check_count
FOR INSERT ON employees
COMPOUND TRIGGER
v_count NUMBER;
-- 文の実行前に件数を取得
BEFORE STATEMENT IS
BEGIN
SELECT COUNT(*) INTO v_count FROM employees;
END BEFORE STATEMENT;
-- 各行のINSERT前にチェック
BEFORE EACH ROW IS
BEGIN
v_count := v_count + 1;
IF v_count > 1000 THEN
RAISE_APPLICATION_ERROR(-20002, '社員数の上限に達しました');
END IF;
END BEFORE EACH ROW;
END trg_check_count;
実務パターン
パターン1:監査ログの自動記録
誰が・いつ・何を変更したかを自動記録する、最も一般的なトリガーパターンです。
監査テーブル + トリガー
-- 監査テーブルの作成
CREATE TABLE emp_audit_log (
audit_id NUMBER GENERATED ALWAYS AS IDENTITY,
audit_date TIMESTAMP DEFAULT SYSTIMESTAMP,
action_type VARCHAR2(10), -- INSERT / UPDATE / DELETE
emp_id NUMBER,
old_salary NUMBER,
new_salary NUMBER,
changed_by VARCHAR2(30) DEFAULT USER
);
-- 監査トリガーの作成
CREATE OR REPLACE TRIGGER trg_emp_salary_audit
AFTER UPDATE OF salary ON employees
FOR EACH ROW
WHEN (OLD.salary <> NEW.salary) -- 変更があった場合のみ
BEGIN
INSERT INTO emp_audit_log (
action_type, emp_id, old_salary, new_salary
) VALUES (
'UPDATE', :NEW.emp_id, :OLD.salary, :NEW.salary
);
END;
パターン2:更新日時の自動セット
SQL
CREATE OR REPLACE TRIGGER trg_emp_updated_at
BEFORE UPDATE ON employees
FOR EACH ROW
BEGIN
:NEW.updated_at := SYSTIMESTAMP;
:NEW.updated_by := USER;
END;
アプリケーション側で updated_at をセットし忘れても、トリガーが自動的に最新のタイムスタンプと更新者を設定します。
パターン3:論理削除フラグの自動セット
SQL
-- 物理削除を論理削除に変換するINSTEAD OFトリガー
CREATE OR REPLACE VIEW v_active_employees AS
SELECT * FROM employees WHERE deleted_flag = 'N';
CREATE OR REPLACE TRIGGER trg_soft_delete
INSTEAD OF DELETE ON v_active_employees
FOR EACH ROW
BEGIN
-- 物理DELETEせず、フラグを更新
UPDATE employees
SET
deleted_flag = 'Y',
deleted_at = SYSTIMESTAMP,
deleted_by = USER
WHERE
emp_id = :OLD.emp_id;
END;
パターン4:自動採番(12c以降はIDENTITY推奨)
SQL
-- シーケンス + トリガーによる自動採番(11g以前の定番パターン)
CREATE SEQUENCE seq_order_id START WITH 1 INCREMENT BY 1;
CREATE OR REPLACE TRIGGER trg_order_seq
BEFORE INSERT ON orders
FOR EACH ROW
BEGIN
:NEW.order_id := seq_order_id.NEXTVAL;
END;
ポイント:Oracle 12c 以降では GENERATED ALWAYS AS IDENTITY カラムが使えるため、自動採番のためだけにトリガーを作る必要はなくなりました。レガシーシステムの保守で見かけるパターンとして覚えておきましょう。
トリガー管理のベストプラクティス
| 項目 |
推奨 |
| 命名規則 |
TRG_テーブル名_目的(例: TRG_EMP_AUDIT)で統一する |
| 処理は最小限に |
重い処理はキューやジョブに委譲し、トリガーは軽量に保つ |
| COMMIT / ROLLBACK禁止 |
DMLトリガー内で COMMIT / ROLLBACK は記述できない(自律型トランザクションを除く) |
| 依存関係の管理 |
テーブル変更後に ALTER TRIGGER ... COMPILE で再コンパイルを忘れない |
| テスト |
INSERT / UPDATE / DELETE それぞれの操作で期待どおり動くか確認する |
| ドキュメント |
トリガーの目的・影響範囲をコメントとして記述する |
逆引きリファレンス
やりたいことからSQLを素早く引けるようにまとめます。
| やりたいこと |
SQL / コマンド |
| 自分のトリガー一覧を見たい |
SELECT * FROM USER_TRIGGERS |
| 特定テーブルのトリガーを確認 |
SELECT * FROM USER_TRIGGERS WHERE table_name = 'テーブル名' |
| ソースコードを表示したい |
SELECT text FROM USER_SOURCE WHERE name = '名前' AND type = 'TRIGGER' ORDER BY line |
| DDL全体を取得したい |
SELECT DBMS_METADATA.GET_DDL('TRIGGER', '名前') FROM dual |
| 無効なトリガーを見つけたい |
SELECT * FROM USER_TRIGGERS WHERE status = 'DISABLED' |
| トリガーを一時停止したい |
ALTER TRIGGER トリガー名 DISABLE |
| テーブルの全トリガーを停止 |
ALTER TABLE テーブル名 DISABLE ALL TRIGGERS |
| トリガーを再有効化したい |
ALTER TRIGGER トリガー名 ENABLE |
| トリガーを削除したい |
DROP TRIGGER トリガー名 |
| コンパイルエラーを再コンパイル |
ALTER TRIGGER トリガー名 COMPILE |
まとめ
Oracleのトリガーは、データベースレベルで自動処理を実行できる強力な機能です。この記事のポイントを整理します。
記事のまとめ
- トリガー情報は
USER_TRIGGERS(自分)→ ALL_TRIGGERS(アクセス可能)→ DBA_TRIGGERS(全体)の3ビューで確認
- ソースコードの取得は
USER_SOURCE または DBMS_METADATA.GET_DDL
BEFORE は値の変更が可能(自動採番等)、AFTER は参照のみ(監査ログ等)
- 行レベル(FOR EACH ROW)は行ごとに起動、文レベルはDML文ごとに1回
:NEW / :OLD で変更前後のデータにアクセス
INSTEAD OF トリガーでビューへのDMLを実現
ORA-04091(ミューテーティングテーブル)はコンパウンドトリガーで回避
- 大量データロード時は
ALTER TABLE ... DISABLE ALL TRIGGERS で一括無効化
- Oracle 12c以降の自動採番には
IDENTITY カラムが推奨