Triggers and Events

Triggers and Events

Real-World Analogy

Imagine a smart home system:

Core Concepts

What Is a Trigger

A Trigger is a database object associated with a table that automatically executes when a specific event occurs on that table:

Trigger Timing: BEFORE vs AFTER

Timing Description Typical Use
BEFORE Fires before the operation executes Data validation, automatic value modification
AFTER Fires after the operation executes Audit logs, cascading updates

Trigger Event Types

NEW and OLD References

You can access the data being operated on within a trigger:

Event NEW OLD
INSERT The newly inserted row Not available
UPDATE The value after update The value before update
DELETE Not available The deleted row
SQL
-- SQLite example: accessing NEW and OLD
-- NEW.column_name: references the new data
-- OLD.column_name: references the old data

Typical Trigger Use Cases

  1. Audit logs: Record who modified what data and when
  2. Cascading updates: Automatically update data in related tables
  3. Data validation: Perform complex business rule validation before data is written
  4. Auto-calculation: Automatically calculate derived field values
  5. Maintaining redundant data: Synchronize updates to summary or cache tables

Extended Knowledge: Event Scheduler in Other Databases

⚠️ Note: SQLite does not support the event scheduler. The following content is extended knowledge showing MySQL's event scheduler syntax as reference.

The event scheduler allows you to execute tasks automatically on a time schedule:

SQL
-- MySQL event scheduler example
-- Enable the event scheduler
SET GLOBAL event_scheduler = ON;

-- Create a scheduled event: clean up expired data daily at midnight
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 a one-time event
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());

-- View all events
SHOW EVENTS;

-- Disable an event
ALTER EVENT cleanup_expired_orders DISABLE;

-- Delete an event
DROP EVENT IF EXISTS cleanup_expired_orders;

Basic Syntax

SQLite Trigger Syntax

SQLite supports triggers with the following syntax:

SQL
-- Basic trigger creation syntax
CREATE TRIGGER trigger_name
{BEFORE | AFTER} {INSERT | UPDATE | DELETE} ON table_name
[FOR EACH ROW]
[WHEN condition]
BEGIN
    -- Trigger logic
END;

MySQL Trigger Syntax

SQL
-- MySQL trigger syntax
DELIMITER //
CREATE TRIGGER trigger_name
{BEFORE | AFTER} {INSERT | UPDATE | DELETE} ON table_name
FOR EACH ROW
BEGIN
    -- Trigger logic
    -- Use NEW and OLD to reference data
END //
DELIMITER ;

PostgreSQL Trigger Syntax

SQL
-- PostgreSQL requires creating a trigger function first
CREATE OR REPLACE FUNCTION trigger_function_name()
RETURNS TRIGGER AS $$
BEGIN
    -- Trigger logic
    RETURN NEW; -- or RETURN OLD
END;
$$ LANGUAGE plpgsql;

-- Then create the trigger
CREATE TRIGGER trigger_name
{BEFORE | AFTER} {INSERT | UPDATE | DELETE} ON table_name
FOR EACH ROW
EXECUTE FUNCTION trigger_function_name();
💡 Tip: Triggers add complexity to database operations. Use them cautiously and avoid creating overly complex trigger logic.

Examples

Example: Creating an Audit Log Trigger

Create a trigger that automatically records the modification history of employee salaries.

SQL
-- SQLite version
-- First create the audit log table
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: record salary updates
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',  -- In practice, you can use the current user
        'UPDATE'
    );
END;

-- Test the trigger
UPDATE employees SET salary = 12000 WHERE id = 1;

-- View the audit log
SELECT * FROM salary_audit_log;
▶ Try it Yourself

Example: BEFORE INSERT Trigger to Auto-Fill Data

Create a trigger that automatically sets the order date and initial status when inserting an order.

SQL
-- Create orders table
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
);

-- Create BEFORE INSERT trigger
CREATE TRIGGER set_order_defaults
BEFORE INSERT ON orders_new
FOR EACH ROW
BEGIN
    -- Automatically set order date to current date
    SET NEW.order_date = COALESCE(NEW.order_date, date('now', 'localtime'));
    -- Automatically set creation time
    SET NEW.created_at = COALESCE(NEW.created_at, datetime('now', 'localtime'));
    -- If quantity is null or 0, default to 1
    SET NEW.quantity = CASE WHEN NEW.quantity IS NULL OR NEW.quantity <= 0 THEN 1 ELSE NEW.quantity END;
END;

-- Test: insert without specifying order_date and status
INSERT INTO orders_new (customer_name, product_id, quantity) VALUES ('John Doe', 1, 3);

-- Verify the auto-filled result
SELECT * FROM orders_new;
▶ Try it Yourself

Expected Output:

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 Limitation: SQLite's trigger syntax differs from MySQL/PostgreSQL. MySQL supports SET NEW.column = value, while PostgreSQL requires using NEW.column := value in BEFORE triggers to modify new values.

Application Scenarios

Scenario 1: Automatically Updating an Order Summary Table

SQL
-- SQLite version
-- Create order summary table
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: auto-update summary on order insertion
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: auto-update summary on order deletion
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;
    
    -- If order count reaches 0, delete the summary record
    DELETE FROM order_summary 
    WHERE customer_id = OLD.customer_id AND total_orders <= 0;
END;

Scenario 2: Data Validation and Auto-Fill

SQL
-- SQLite version: auto-calculate order total
CREATE TRIGGER calculate_order_total
BEFORE INSERT ON orders
FOR EACH ROW
BEGIN
    -- Auto-set creation time
    SELECT NEW.created_at IS NULL THEN
        SET NEW.created_at = datetime('now', 'localtime');
    END IF;
    
    -- Validate that amount must be positive
    IF NEW.amount < 0 THEN
        SELECT RAISE(ABORT, 'Order amount cannot be negative');
    END IF;
END;

❓ FAQ

Q: Do triggers affect database performance? A: Yes. Each trigger activation executes additional SQL operations. Too many triggers or complex logic can degrade write performance. Keep trigger logic simple and avoid time-consuming operations in triggers.

Q: Can a table have multiple triggers? A: SQLite only allows one trigger per event and timing. MySQL and PostgreSQL allow multiple triggers and let you specify execution order with FOLLOWS/PRECEDES.

Q: Can triggers access other tables? A: Yes. Triggers can query and modify data in other tables, but be careful to avoid circular triggers and deadlocks.

Q: How do I debug a trigger? A: You can insert temporary test data into a log table within the trigger to observe the execution flow. MySQL can use the SIGNAL statement to throw custom error messages.

📖 Summary

In this lesson we learned:

📝 Exercises

  1. Basic Exercise: Create a trigger that automatically backs up an employee's information to an employees_backup table when the employee is deleted.

  2. Intermediate Exercise: Create a trigger system that implements the following:

    • When a new order is inserted, automatically reduce the product's stock
    • When stock is insufficient, prevent the order insertion and show an error
  3. Thinking Question: Both triggers and application code can implement business logic. What scenarios is each best suited for? How do you choose?


Next Lesson → 24-practice-advanced.md

Web-Tutorial.com

Web-Tutorial Tech Team

A team of developers maintaining programming tutorials. Each tutorial is written and reviewed by developers with expertise in that field. We work to keep our content accurate and reliable — if you spot an issue, please let us know.

100%

🙏 帮我们做得更好

我们是刚上线的编程教程站,几个人的小团队,精力有限。页面虽经检查,难免还有疏漏——链接失效、排版错乱、内容有误、语言生硬……

如果您发现了,麻烦告诉我们,我们会在收到反馈后第一时间进行修复,再次感谢您的光临 🙏