トリガーとイベント
トリガーとイベント
実世界のアナロジー
スマートホームシステムを想像してください:
- トリガーは「誰かがドアベルを押したら、自動的にポーチのライトを点ける」のようなもの — 特定のイベントが発生したとき、予め設定されたアクションが自動的に実行されます。
- イベントスケジューラは「毎朝7時に自動的にカーテンを開ける」のようなもの — タスクが時間スケジュールに従って定期的に実行されます。
コア概念
トリガーとは
トリガーはテーブルに関連付けられたデータベースオブジェクトで、そのテーブルで特定のイベントが発生したときに自動的に実行されます:
- データの挿入(INSERT)、更新(UPDATE)、削除(DELETE)の前後に発火
- 変更されたデータにアクセス可能(NEWおよびOLD参照)
- 複雑なビジネスルール、監査ログ、データ整合性制約の実装に使用
トリガーのタイミング:BEFOREとAFTER
| タイミング | 説明 | 典型的な用途 |
|---|---|---|
| BEFORE | 操作の実行前に発火 | データ検証、自動値変更 |
| AFTER | 操作の実行後に発火 | 監査ログ、カスケード更新 |
トリガーイベントタイプ
- INSERT:新しいレコードが挿入されたときに発火
- UPDATE:レコードが更新されたときに発火
- DELETE:レコードが削除されたときに発火
NEWとOLD参照
トリガー内で操作対象のデータにアクセスできます:
| イベント | NEW | OLD |
|---|---|---|
| INSERT | 新しく挿入された行 | 利用不可 |
| UPDATE | 更新後の値 | 更新前の値 |
| DELETE | 利用不可 | 削除された行 |
-- SQLiteの例:NEWとOLDにアクセス
-- NEW.column_name:新しいデータを参照
-- OLD.column_name:古いデータを参照
トリガーの典型的なユースケース
- 監査ログ:誰がいつどのデータを変更したかを記録
- カスケード更新:関連テーブルのデータを自動的に更新
- データ検証:データ書き込み前に複雑なビジネスルールの検証を実行
- 自動計算:派生フィールドの値を自動的に計算
- 冗長データの維持:サマリーやキャッシュテーブルの更新を同期
拡張知識:他のデータベースのイベントスケジューラ
イベントスケジューラにより、時間スケジュールに従ってタスクを自動的に実行できます:
-- MySQLイベントスケジューラの例
-- イベントスケジューラを有効化
SET GLOBAL event_scheduler = ON;
-- スケジュールイベントを作成:毎日深夜に期限切れデータをクリーンアップ
CREATE EVENT cleanup_expired_orders
ON SCHEDULE EVERY 1 DAY
STARTS '2024-01-01 00:00:00'
DO
DELETE FROM orders
WHERE status = 'expired'
AND created_at < DATE_SUB(NOW(), INTERVAL 90 DAY);
-- 一回限りのイベントを作成
CREATE EVENT one_time_report
ON SCHEDULE AT '2024-12-31 23:59:59'
DO
INSERT INTO annual_report (year, total_sales)
SELECT YEAR(NOW()), SUM(amount) FROM orders WHERE YEAR(created_at) = YEAR(NOW());
-- すべてのイベントを表示
SHOW EVENTS;
-- イベントを無効化
ALTER EVENT cleanup_expired_orders DISABLE;
-- イベントを削除
DROP EVENT IF EXISTS cleanup_expired_orders;
基本構文
SQLiteトリガー構文
SQLiteは以下の構文でトリガーをサポートしています:
-- 基本的なトリガー作成構文
CREATE TRIGGER trigger_name
{BEFORE | AFTER} {INSERT | UPDATE | DELETE} ON table_name
[FOR EACH ROW]
[WHEN condition]
BEGIN
-- トリガーのロジック
END;
MySQLトリガー構文
-- MySQLトリガー構文
DELIMITER //
CREATE TRIGGER trigger_name
{BEFORE | AFTER} {INSERT | UPDATE | DELETE} ON table_name
FOR EACH ROW
BEGIN
-- トリガーのロジック
-- NEWとOLDを使ってデータを参照
END //
DELIMITER ;
PostgreSQLトリガー構文
-- PostgreSQLではまずトリガー関数を作成する必要がある
CREATE OR REPLACE FUNCTION trigger_function_name()
RETURNS TRIGGER AS $$
BEGIN
-- トリガーのロジック
RETURN NEW; -- または RETURN OLD
END;
$$ LANGUAGE plpgsql;
-- その後トリガーを作成
CREATE TRIGGER trigger_name
{BEFORE | AFTER} {INSERT | UPDATE | DELETE} ON table_name
FOR EACH ROW
EXECUTE FUNCTION trigger_function_name();
例
例:監査ログトリガーの作成(難易度⭐⭐)
従業員の給与変更履歴を自動的に記録するトリガーを作成します。
-- SQLite版
-- まず監査ログテーブルを作成
CREATE TABLE IF NOT EXISTS salary_audit_log (
id INTEGER PRIMARY KEY AUTOINCREMENT,
employee_id INTEGER,
old_salary REAL,
new_salary REAL,
changed_by TEXT,
changed_at TEXT DEFAULT (datetime('now', 'localtime')),
action TEXT
);
-- トリガーを作成:給与更新を記録
CREATE TRIGGER audit_salary_update
AFTER UPDATE ON employees
FOR EACH ROW
WHEN OLD.salary != NEW.salary
BEGIN
INSERT INTO salary_audit_log (
employee_id,
old_salary,
new_salary,
changed_by,
action
)
VALUES (
NEW.id,
OLD.salary,
NEW.salary,
'system', -- 実際には現在のユーザーを使用可能
'UPDATE'
);
END;
-- トリガーをテスト
UPDATE employees SET salary = 12000 WHERE id = 1;
-- 監査ログを表示
SELECT * FROM salary_audit_log;
例:BEFORE INSERTトリガーでデータを自動入力(難易度⭐⭐)
注文を挿入する際に注文日と初期ステータスを自動的に設定するトリガーを作成します。
-- ordersテーブルを作成
CREATE TABLE IF NOT EXISTS orders_new (
id INTEGER PRIMARY KEY AUTOINCREMENT,
customer_name TEXT NOT NULL,
product_id INTEGER,
quantity INTEGER DEFAULT 1,
order_date TEXT,
status TEXT DEFAULT 'pending',
created_at TEXT
);
-- BEFORE INSERTトリガーを作成
CREATE TRIGGER set_order_defaults
BEFORE INSERT ON orders_new
FOR EACH ROW
BEGIN
-- 注文日を自動的に現在の日に設定
SET NEW.order_date = COALESCE(NEW.order_date, date('now', 'localtime'));
-- 作成日時を自動的に設定
SET NEW.created_at = COALESCE(NEW.created_at, datetime('now', 'localtime'));
-- 数量がnullまたは0の場合、デフォルトで1に設定
SET NEW.quantity = CASE WHEN NEW.quantity IS NULL OR NEW.quantity <= 0 THEN 1 ELSE NEW.quantity END;
END;
-- テスト:order_dateとstatusを指定せずに挿入
INSERT INTO orders_new (customer_name, product_id, quantity) VALUES ('John Doe', 1, 3);
-- 自動入力された結果を検証
SELECT * FROM orders_new;
期待される出力:
id customer_name product_id quantity order_date status created_at
1 John Doe 1 3 2026-06-28 pending 2026-06-28 10:30:00
SET NEW.column = value をサポートし、PostgreSQLでは BEFORE トリガー内で NEW.column := value を使って新しい値を変更します。
応用シナリオ
シナリオ1:注文サマリーテーブルの自動更新
-- SQLite版
-- 注文サマリーテーブルを作成
CREATE TABLE IF NOT EXISTS order_summary (
customer_id INTEGER PRIMARY KEY,
total_orders INTEGER DEFAULT 0,
total_amount REAL DEFAULT 0,
last_order_date TEXT
);
-- トリガーを作成:注文挿入時にサマリーを自動更新
CREATE TRIGGER update_order_summary_insert
AFTER INSERT ON orders
FOR EACH ROW
BEGIN
INSERT OR REPLACE INTO order_summary (customer_id, total_orders, total_amount, last_order_date)
SELECT
NEW.customer_id,
COALESCE(os.total_orders, 0) + 1,
COALESCE(os.total_amount, 0) + NEW.amount,
NEW.order_date
FROM (SELECT 1) AS dummy
LEFT JOIN order_summary os ON os.customer_id = NEW.customer_id;
END;
-- トリガーを作成:注文削除時にサマリーを自動更新
CREATE TRIGGER update_order_summary_delete
AFTER DELETE ON orders
FOR EACH ROW
BEGIN
UPDATE order_summary
SET
total_orders = total_orders - 1,
total_amount = total_amount - OLD.amount
WHERE customer_id = OLD.customer_id;
-- 注文数が0になった場合、サマリーレコードを削除
DELETE FROM order_summary
WHERE customer_id = OLD.customer_id AND total_orders <= 0;
END;
シナリオ2:データ検証と自動入力
-- SQLite版:注文合計を自動計算
CREATE TRIGGER calculate_order_total
BEFORE INSERT ON orders
FOR EACH ROW
BEGIN
-- 作成日時を自動設定
SELECT NEW.created_at IS NULL THEN
SET NEW.created_at = datetime('now', 'localtime');
END IF;
-- 金額が正の数であることを検証
IF NEW.amount < 0 THEN
SELECT RAISE(ABORT, '注文金額は負の数にできません');
END IF;
END;
❓ よくある質問
質問:トリガーはデータベースのパフォーマンスに影響しますか? 回答: はい。トリガーの発火ごとに追加のSQL操作が実行されます。トリガーが多すぎたり、ロジックが複雑すぎたりすると、書き込みパフォーマンスが低下します。トリガーロジックをシンプルに保ち、トリガー内で時間のかかる操作を避けてください。
質問:1つのテーブルに複数のトリガーを持たせられますか? 回答: SQLiteはイベントとタイミングごとに1つのトリガーのみ許可します。MySQLとPostgreSQLは複数のトリガーを許可し、FOLLOWS/PRECEDESで実行順序を指定できます。
質問:トリガーは他のテーブルにアクセスできますか? 回答: はい。トリガーは他のテーブルのデータを照会および変更できますが、循環トリガーやデッドロックに注意が必要です。
質問:トリガーはどうデバッグしますか? 回答: トリガー内に一時的なテストデータをログテーブルに挿入して実行フローを観察できます。MySQLではSIGNAL文を使ってカスタムエラーメッセージをスローできます。
📖 まとめ
このレッスンで学んだこと:
- トリガーの概念とメカニズム
- BEFOREとAFTERトリガータイミングの違い
- NEWとOLD参照の使い方
- SQLiteトリガーの構文と例
- トリガーの典型的なユースケース(監査、カスケード更新、データ検証)
- イベントスケジューラの概念(MySQL拡張知識)
📝 演習
-
基礎演習:従業員が削除されたときに、その従業員の情報を
employees_backupテーブルに自動的にバックアップするトリガーを作成してください。 -
中級演習:以下のトリガーシステムを作成してください:
- 新しい注文が挿入されたとき、商品の在庫を自動的に減算
- 在庫不足の場合、注文の挿入を阻止してエラーを表示
-
思考問題:トリガーとアプリケーションコードの両方でビジネスロジックを実装できます。それぞれどのようなシナリオに最適ですか?どう選択しますか?
次のレッスン → 24-practice-advanced.md



