トリガー(Trigger)は、テーブルやビューへの DML 操作(INSERT・UPDATE・DELETE)、あるいは DDL 操作(CREATE・ALTER・DROP)が実行された際に自動的に起動される PL/SQL のコードブロックです。
監査ログの記録・変更前後の値の保存・派生列の自動計算・整合性チェックなど、アプリケーション側でカバーしきれない処理をデータベース側で強制できます。ただし、トリガーの乱用はデバッグを困難にするため、本当にデータベース側で保証すべき処理に限定して使うのが原則です。
この記事でわかること
- BEFORE / AFTER と行レベル(FOR EACH ROW)/ 文レベルの違い
- :NEW・:OLD 疑似レコードで変更前後の値を取得する方法
- WHEN 句で発火条件を絞る方法
- INSERT / UPDATE / DELETE を1つのトリガーで処理する方法(INSERTING・UPDATING・DELETING)
- ビューへの INSTEAD OF トリガー
- DDL トリガーでスキーマ変更を監査する方法
- トリガーの有効化・無効化・確認方法
トリガーの種類
| 種類 | タイミング | 粒度 | 主な用途 |
|---|---|---|---|
| BEFORE 行レベル | DML の各行処理前 | 1行ごと | :NEW の値を変更する(デフォルト値の設定・変換) |
| AFTER 行レベル | DML の各行処理後 | 1行ごと | 監査ログ記録・他テーブルへの連動更新 |
| BEFORE 文レベル | DML の実行前(1回だけ) | DML文全体 | 権限チェック・ロック取得 |
| AFTER 文レベル | DML の実行後(1回だけ) | DML文全体 | 集計テーブルの更新・ログ記録 |
| INSTEAD OF | DML の代わりに実行 | 1行ごと | 更新不可ビューへの DML を実処理に変換する |
| DDL トリガー | DDL 操作の前後 | スキーマ/DB全体 | CREATE・DROP・ALTER の監査 |
基本的な DML トリガーの作成
BEFORE INSERT トリガー(:NEW の値を設定する)
-- BEFORE INSERT トリガー: INSERT 前に自動的に created_at と updated_at を設定する
CREATE OR REPLACE TRIGGER trg_orders_bi
BEFORE INSERT ON orders
FOR EACH ROW -- 行レベルトリガー(行ごとに発火)
BEGIN
:NEW.created_at := SYSDATE; -- INSERT 直前に created_at を現在日時に設定
:NEW.updated_at := SYSDATE;
:NEW.created_by := SYS_CONTEXT('USERENV', 'SESSION_USER'); -- ログインユーザー
END;
/
-- BEFORE UPDATE トリガー: UPDATE 前に updated_at を更新する
CREATE OR REPLACE TRIGGER trg_orders_bu
BEFORE UPDATE ON orders
FOR EACH ROW
BEGIN
:NEW.updated_at := SYSDATE;
:NEW.updated_by := SYS_CONTEXT('USERENV', 'SESSION_USER');
END;
/
AFTER INSERT / UPDATE / DELETE トリガー(監査ログを記録する)
-- 監査ログテーブルを前提とする
-- CREATE TABLE orders_audit (
-- audit_id NUMBER DEFAULT audit_seq.NEXTVAL PRIMARY KEY,
-- order_id NUMBER, action VARCHAR2(10),
-- old_status VARCHAR2(20), new_status VARCHAR2(20),
-- changed_by VARCHAR2(50), changed_at DATE
-- );
-- AFTER トリガー: INSERT / UPDATE / DELETE を1つで処理する
CREATE OR REPLACE TRIGGER trg_orders_audit
AFTER INSERT OR UPDATE OR DELETE ON orders
FOR EACH ROW
BEGIN
IF INSERTING THEN
INSERT INTO orders_audit (order_id, action, new_status, changed_by, changed_at)
VALUES (:NEW.order_id, 'INSERT', :NEW.status,
SYS_CONTEXT('USERENV', 'SESSION_USER'), SYSDATE);
ELSIF UPDATING THEN
INSERT INTO orders_audit (order_id, action, old_status, new_status, changed_by, changed_at)
VALUES (:NEW.order_id, 'UPDATE', :OLD.status, :NEW.status,
SYS_CONTEXT('USERENV', 'SESSION_USER'), SYSDATE);
ELSIF DELETING THEN
INSERT INTO orders_audit (order_id, action, old_status, changed_by, changed_at)
VALUES (:OLD.order_id, 'DELETE', :OLD.status,
SYS_CONTEXT('USERENV', 'SESSION_USER'), SYSDATE);
END IF;
END;
/
:NEW と :OLD 疑似レコードの制約
| トリガー種類 | :NEW | :OLD |
|---|---|---|
| BEFORE INSERT | 参照・変更可能(INSERT 前に値を書き換えられる) | 常に NULL |
| AFTER INSERT | 参照可能(変更不可) | 常に NULL |
| BEFORE UPDATE | 参照・変更可能 | 参照可能(変更不可) |
| AFTER UPDATE | 参照可能(変更不可) | 参照可能(変更不可) |
| BEFORE DELETE | 常に NULL | 参照可能(変更不可) |
| AFTER DELETE | 常に NULL | 参照可能(変更不可) |
WHEN 句と REFERENCING の使い方
-- WHEN 句: 特定の条件のときだけトリガーを発火させる(行レベルのみ)
-- WHEN 句では ":NEW." ではなく "NEW." と書く(コロンなし)
CREATE OR REPLACE TRIGGER trg_emp_salary_check
BEFORE UPDATE OF salary ON employees
FOR EACH ROW
WHEN (NEW.salary > OLD.salary * 2) -- 給与が2倍以上になる場合のみ発火
BEGIN
RAISE_APPLICATION_ERROR(-20001,
'給与を一度に2倍以上に上げることはできません: ' ||
:OLD.salary || ' → ' || :NEW.salary);
END;
/
-- REFERENCING: :NEW / :OLD の別名を定義する(テーブル名と衝突する場合に使う)
CREATE OR REPLACE TRIGGER trg_new_old_alias
BEFORE INSERT ON orders
REFERENCING NEW AS n OLD AS o -- :n と :o で参照できる
FOR EACH ROW
BEGIN
:n.created_at := SYSDATE;
END;
/
-- UPDATE OF 列名: 特定列が更新された場合のみ発火する
CREATE OR REPLACE TRIGGER trg_emp_email_update
AFTER UPDATE OF email ON employees
FOR EACH ROW
BEGIN
-- email が変更された場合のみ処理する(他の列の UPDATE では発火しない)
DBMS_OUTPUT.PUT_LINE('Email changed: ' || :OLD.email || ' -> ' || :NEW.email);
END;
/
INSTEAD OF トリガー(ビューへの DML)
INSTEAD OF トリガーで更新不可ビューを更新可能にする
-- 結合ビューは通常 INSERT / UPDATE / DELETE できない
CREATE OR REPLACE VIEW emp_dept_v 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;
-- 上記ビューへの INSERT は ORA-01779 が発生する(キー保持でない結合を含む)
-- INSTEAD OF トリガーでビューへの INSERT を実処理に変換する
CREATE OR REPLACE TRIGGER trg_emp_dept_v_ins
INSTEAD OF INSERT ON emp_dept_v
FOR EACH ROW
DECLARE
v_dept_id departments.department_id%TYPE;
BEGIN
-- department_name から department_id を検索する
SELECT department_id INTO v_dept_id
FROM departments
WHERE department_name = :NEW.department_name;
-- employees テーブルに実際に INSERT する
INSERT INTO employees (employee_id, last_name, salary, department_id)
VALUES (:NEW.employee_id, :NEW.last_name, :NEW.salary, v_dept_id);
EXCEPTION
WHEN NO_DATA_FOUND THEN
RAISE_APPLICATION_ERROR(-20002, '部署が見つかりません: ' || :NEW.department_name);
END;
/
-- 上記トリガーにより、ビューに INSERT できるようになる
INSERT INTO emp_dept_v (employee_id, last_name, salary, department_name)
VALUES (300, 'Yamada', 50000, 'IT');
DDL トリガーでスキーマ変更を監査する
DDL トリガーで CREATE / DROP / ALTER を記録する
-- DDL 監査テーブルを前提とする
-- CREATE TABLE ddl_audit (
-- event_time DATE, event_type VARCHAR2(50),
-- object_type VARCHAR2(50), object_name VARCHAR2(128),
-- schema_name VARCHAR2(128), executed_by VARCHAR2(50)
-- );
-- スキーマレベルの DDL トリガー(HR スキーマの DDL を監査)
CREATE OR REPLACE TRIGGER trg_ddl_audit
AFTER DDL ON SCHEMA -- ON SCHEMA: 現在スキーマの DDL に反応
BEGIN
INSERT INTO ddl_audit (event_time, event_type, object_type, object_name, schema_name, executed_by)
VALUES (SYSDATE,
ORA_SYSEVENT, -- 'CREATE'/'DROP'/'ALTER' などのイベント種類
ORA_DICT_OBJ_TYPE, -- 'TABLE'/'INDEX'/'VIEW' などのオブジェクト種類
ORA_DICT_OBJ_NAME, -- オブジェクト名
ORA_DICT_OBJ_OWNER, -- スキーマ名
SYS_CONTEXT('USERENV', 'SESSION_USER'));
COMMIT; -- DDL は自動コミットされるため、監査ログも COMMIT する
END;
/
-- データベースレベルの DDL トリガー(全スキーマの DDL を監査・DBA 権限が必要)
CREATE OR REPLACE TRIGGER trg_ddl_db_audit
AFTER CREATE OR DROP OR ALTER ON DATABASE
BEGIN
INSERT INTO sys.ddl_audit (event_time, event_type, object_type, object_name, schema_name, executed_by)
VALUES (SYSDATE, ORA_SYSEVENT, ORA_DICT_OBJ_TYPE,
ORA_DICT_OBJ_NAME, ORA_DICT_OBJ_OWNER,
SYS_CONTEXT('USERENV', 'SESSION_USER'));
COMMIT;
END;
/
トリガーの有効化・無効化・確認
トリガーの管理コマンドと確認クエリ
-- トリガーを無効化する(テーブルのデータロード時などに一時的に停止する)
ALTER TRIGGER trg_orders_bi DISABLE;
-- トリガーを再度有効化する
ALTER TRIGGER trg_orders_bi ENABLE;
-- テーブルのすべてのトリガーを無効化 / 有効化する
ALTER TABLE orders DISABLE ALL TRIGGERS;
ALTER TABLE orders ENABLE ALL TRIGGERS;
-- トリガーのコンパイルエラーを確認する
SHOW ERRORS TRIGGER trg_orders_audit;
-- または
SELECT line, position, text FROM USER_ERRORS
WHERE name = 'TRG_ORDERS_AUDIT' AND type = 'TRIGGER'
ORDER BY line;
-- トリガーの一覧と状態を確認する
SELECT trigger_name, trigger_type, triggering_event,
table_name, status
FROM USER_TRIGGERS
ORDER BY table_name, trigger_name;
-- トリガーのソースコードを確認する
SELECT text FROM USER_SOURCE
WHERE name = 'TRG_ORDERS_AUDIT' AND type = 'TRIGGER'
ORDER BY line;
-- トリガーを削除する
DROP TRIGGER trg_orders_bi;
まとめ
- BEFORE vs AFTER:BEFORE トリガーは :NEW の値を変更できる(デフォルト値設定・変換に使う)。AFTER トリガーは DML 完了後に処理を行う(監査ログ・他テーブルへの連動に使う)
- :NEW と :OLD:INSERT では :OLD は NULL、DELETE では :NEW は NULL。BEFORE トリガーでは :NEW に値を代入して行の値を変更できるが、AFTER トリガーでは変更不可
- INSERTING / UPDATING / DELETING:複数のイベントを1つのトリガーで処理する場合はこれらの述語で分岐する。UPDATING(‘列名’) で特定列への UPDATE に限定できる
- INSTEAD OF トリガー:結合ビューなど更新不可ビューへの DML を実テーブルへの操作に変換する。ビューの利便性を高める手段として有効
- DDL トリガー:ON SCHEMA(自スキーマ)または ON DATABASE(全スキーマ)でスキーマ変更を監査できる。ORA_SYSEVENT・ORA_DICT_OBJ_NAME などの属性関数でイベント情報を取得する
- 無効化・有効化:大量データのロード時は ALTER TABLE テーブル名 DISABLE ALL TRIGGERS で一時停止してパフォーマンスを向上させる。終了後は ENABLE ALL TRIGGERS で再開する
トリガー内で ORA-04091 ミューテーティングテーブルエラーが発生する場合は Oracle ミューテーティングテーブル完全ガイドを参照してください。INSTEAD OF トリガーとビュー(VIEW)の詳細は Oracle ビュー完全ガイドも参照してください。