Triggers and Events
Triggers and Events
Real-World Analogy
Imagine a smart home system:
- Trigger is like "when someone rings the doorbell, automatically turn on the porch light" — when a certain event occurs, a preset action is executed automatically.
- Event scheduler is like "every day at 7 AM, automatically open the curtains" — tasks are executed periodically according to a time schedule.
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:
- Fires before or after data insertion (INSERT), update (UPDATE), or deletion (DELETE)
- Can access the modified data (NEW and OLD references)
- Used to implement complex business rules, audit logs, and data integrity constraints
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
- INSERT: Fires when a new record is inserted
- UPDATE: Fires when a record is updated
- DELETE: Fires when a record is deleted
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 |
-- SQLite example: accessing NEW and OLD
-- NEW.column_name: references the new data
-- OLD.column_name: references the old data
Typical Trigger Use Cases
- Audit logs: Record who modified what data and when
- Cascading updates: Automatically update data in related tables
- Data validation: Perform complex business rule validation before data is written
- Auto-calculation: Automatically calculate derived field values
- Maintaining redundant data: Synchronize updates to summary or cache tables
Extended Knowledge: Event Scheduler in Other Databases
The event scheduler allows you to execute tasks automatically on a time schedule:
-- 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:
-- 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
-- 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
-- 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();
Examples
Example: Creating an Audit Log Trigger
Create a trigger that automatically records the modification history of employee salaries.
-- 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;
Example: BEFORE INSERT Trigger to Auto-Fill Data
Create a trigger that automatically sets the order date and initial status when inserting an order.
-- 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;
Expected Output:
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, while PostgreSQL requires using NEW.column := value in BEFORE triggers to modify new values.
Application Scenarios
Scenario 1: Automatically Updating an Order Summary Table
-- 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
-- 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:
- The concept and mechanism of triggers
- The difference between BEFORE and AFTER trigger timing
- How to use NEW and OLD references
- SQLite trigger syntax and examples
- Typical trigger use cases (audit, cascading updates, data validation)
- The event scheduler concept (MySQL extended knowledge)
📝 Exercises
-
Basic Exercise: Create a trigger that automatically backs up an employee's information to an
employees_backuptable when the employee is deleted. -
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
-
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



