トリガーとイベント

トリガーとイベント

実世界のアナロジー

スマートホームシステムを想像してください:

コア概念

トリガーとは

トリガーはテーブルに関連付けられたデータベースオブジェクトで、そのテーブルで特定のイベントが発生したときに自動的に実行されます:

トリガーのタイミング:BEFOREとAFTER

タイミング 説明 典型的な用途
BEFORE 操作の実行前に発火 データ検証、自動値変更
AFTER 操作の実行後に発火 監査ログ、カスケード更新

トリガーイベントタイプ

NEWとOLD参照

トリガー内で操作対象のデータにアクセスできます:

イベント NEW OLD
INSERT 新しく挿入された行 利用不可
UPDATE 更新後の値 更新前の値
DELETE 利用不可 削除された行
SQL
-- SQLiteの例:NEWとOLDにアクセス
-- NEW.column_name:新しいデータを参照
-- OLD.column_name:古いデータを参照

トリガーの典型的なユースケース

  1. 監査ログ:誰がいつどのデータを変更したかを記録
  2. カスケード更新:関連テーブルのデータを自動的に更新
  3. データ検証:データ書き込み前に複雑なビジネスルールの検証を実行
  4. 自動計算:派生フィールドの値を自動的に計算
  5. 冗長データの維持:サマリーやキャッシュテーブルの更新を同期

拡張知識:他のデータベースのイベントスケジューラ

⚠️ 注意: SQLiteはイベントスケジューラをサポートしていません。以下の内容は拡張知識としてMySQLのイベントスケジューラ構文を参照として示しています。

イベントスケジューラにより、時間スケジュールに従ってタスクを自動的に実行できます:

SQL
-- 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は以下の構文でトリガーをサポートしています:

SQL
-- 基本的なトリガー作成構文
CREATE TRIGGER trigger_name
{BEFORE | AFTER} {INSERT | UPDATE | DELETE} ON table_name
[FOR EACH ROW]
[WHEN condition]
BEGIN
    -- トリガーのロジック
END;

MySQLトリガー構文

SQL
-- MySQLトリガー構文
DELIMITER //
CREATE TRIGGER trigger_name
{BEFORE | AFTER} {INSERT | UPDATE | DELETE} ON table_name
FOR EACH ROW
BEGIN
    -- トリガーのロジック
    -- NEWとOLDを使ってデータを参照
END //
DELIMITER ;

PostgreSQLトリガー構文

SQL
-- 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();
💡 ヒント: トリガーはデータベース操作の複雑性を増します。慎重に使用し、過度に複雑なトリガーロジックの作成を避けてください。

例:監査ログトリガーの作成(難易度⭐⭐)

従業員の給与変更履歴を自動的に記録するトリガーを作成します。

SQL
-- 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トリガーでデータを自動入力(難易度⭐⭐)

注文を挿入する際に注文日と初期ステータスを自動的に設定するトリガーを作成します。

SQL
-- 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;
▶ 試してみよう

期待される出力

TEXT
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
💡 SQLiteの制限:SQLiteのトリガー構文はMySQL/PostgreSQLと異なります。MySQLは SET NEW.column = value をサポートし、PostgreSQLでは BEFORE トリガー内で NEW.column := value を使って新しい値を変更します。

応用シナリオ

シナリオ1:注文サマリーテーブルの自動更新

SQL
-- 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:データ検証と自動入力

SQL
-- 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文を使ってカスタムエラーメッセージをスローできます。

📖 まとめ

このレッスンで学んだこと:

📝 演習

  1. 基礎演習:従業員が削除されたときに、その従業員の情報を employees_backup テーブルに自動的にバックアップするトリガーを作成してください。

  2. 中級演習:以下のトリガーシステムを作成してください:

    • 新しい注文が挿入されたとき、商品の在庫を自動的に減算
    • 在庫不足の場合、注文の挿入を阻止してエラーを表示
  3. 思考問題:トリガーとアプリケーションコードの両方でビジネスロジックを実装できます。それぞれどのようなシナリオに最適ですか?どう選択しますか?


次のレッスン → 24-practice-advanced.md

Web-Tutorial.com

Web-Tutorial 技術チーム

複数の開発者によって共同維持されているプログラミングチュートリアルプラットフォーム。各チュートリアルは専門分野の開発者が執筆・レビューしています。正確で信頼性の高いコンテンツを目指しています — 問題を見つけた場合はお知らせください。

100%