Triggers
A Trigger is a special type of stored procedure that automatically runs when an event occurs in the database server. Most commonly used for auditing, enforcing complex integrity rules, or synchronizing tables.
Trigger Types
| Type | Timing | Use Case |
|---|---|---|
BEFORE INSERT | Before data is saved | Validation, default values |
AFTER INSERT | After data is saved | Logging, updating aggregates |
BEFORE UPDATE | Before changes are saved | Preventing invalid changes |
AFTER UPDATE | After changes are saved | Auditing changes |
BEFORE DELETE | Before data is removed | Archiving, preventing deletes |
AFTER DELETE | After data is removed | Cleanup, logging |
INSTEAD OF | Replaces the original operation | Making views updatable |
Trigger Example (Audit Log)
CREATE TRIGGER LogSalaryChange
AFTER UPDATE ON Employees
FOR EACH ROW
BEGIN
IF OLD.salary <> NEW.salary THEN
INSERT INTO SalaryAudit(emp_id, old_sal, new_sal, changed_at)
VALUES (OLD.id, OLD.salary, NEW.salary, NOW());
END IF;
END;
Trigger to Update Timestamp
CREATE TRIGGER UpdateModifiedAt
BEFORE UPDATE ON Users
FOR EACH ROW
BEGIN
SET NEW.modified_at = NOW();
END;
Trigger to Prevent Deletion
CREATE TRIGGER PreventAdminDelete
BEFORE DELETE ON Users
FOR EACH ROW
BEGIN
IF OLD.role = 'admin' THEN
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = 'Cannot delete admin users';
END IF;
END;
Cursors
A Cursor is a database object used to retrieve data from a result set one row at a time. SQL is fundamentally set-based, meaning it’s designed to work on all rows at once. Cursors are row-based and are generally much slower.
Cursor Example
DECLARE emp_cursor CURSOR FOR
SELECT id, salary FROM employees WHERE department_id = 5;
OPEN emp_cursor;
FETCH NEXT FROM emp_cursor INTO @emp_id, @salary;
WHILE @@FETCH_STATUS = 0
BEGIN
-- Process each row
UPDATE employees SET salary = @salary * 1.1 WHERE id = @emp_id;
FETCH NEXT FROM emp_cursor INTO @emp_id, @salary;
END;
CLOSE emp_cursor;
DEALLOCATE emp_cursor;
Why Cursors Are Slow
- Row-by-row processing: Each row requires a separate fetch, which means many round-trips within the database engine.
- Lock overhead: Cursors often hold locks longer than set-based operations.
- No optimisation: The database cannot parallelise or optimise row-by-row operations.
- Memory: Cursors may materialise the entire result set in memory.
Replace Cursors with Set-Based Operations
-- BAD: Cursor loop applying 10% raise
-- DECLARE cursor... FETCH... UPDATE... LOOP
-- GOOD: Single set-based UPDATE
UPDATE employees SET salary = salary * 1.1 WHERE department_id = 5;
Most common cursor use cases can be replaced with:
UPDATEwithCASEexpressions- Window functions (
LAG,LEAD,ROW_NUMBER) - Recursive CTEs for hierarchical processing
Q: BEFORE vs AFTER Triggers?
A:
BEFORE:Runs before the data is saved. Good for validation or modifying data before it hits the disk.AFTER:Runs after the data is saved. Good for logging or updating other related tables.
Q: Why are Cursors often avoided?
A: Cursors process data row-by-row, which is highly inefficient in a relational database. It leads to more locking, higher memory usage, and slower performance compared to “Set-based” operations (like a single UPDATE statement).
Q: What is an INSTEAD OF trigger?
A: It bypasses the standard action (INSERT/UPDATE/DELETE) and executes the trigger logic instead. Often used to make complex views updatable.
Q: Can triggers be recursive?
A: Some databases allow recursive triggers (a trigger that causes another trigger to fire). This can lead to infinite loops and is usually disabled by default. Use RECURSIVE TRIGGER settings with caution.
1. Audit salary changes using trigger.
CREATE TRIGGER LogSalaryChange
AFTER UPDATE ON Employees
FOR EACH ROW
BEGIN
IF OLD.salary <> NEW.salary THEN
INSERT INTO SalaryAudit(emp_id, old_sal, new_sal, changed_at)
VALUES (OLD.id, OLD.salary, NEW.salary, NOW());
END IF;
END;2. Prevent deletion of admin users.
CREATE TRIGGER PreventAdminDelete
BEFORE DELETE ON Users
FOR EACH ROW
BEGIN
IF OLD.role = 'admin' THEN
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = 'Cannot delete admin';
END IF;
END;3. Replace cursor with Set-based query.
Instead of looping through rows to apply a 10% raise:
-- BAD (Cursor)
-- Loop { UPDATE employees SET salary = salary * 1.1 WHERE id = @current_id }
-- GOOD (Set-based)
UPDATE employees SET salary = salary * 1.1 WHERE dept = 'Sales';4. Log deleted rows using trigger.
CREATE TRIGGER ArchiveDeletes
BEFORE DELETE ON users
FOR EACH ROW
BEGIN
INSERT INTO deleted_users_log(id, email, deleted_at)
VALUES (OLD.id, OLD.email, NOW());
END;5. Auto-update timestamp on row change.
CREATE TRIGGER UpdateTimestamp
BEFORE UPDATE ON Products
FOR EACH ROW
BEGIN
SET NEW.updated_at = NOW();
END;Premium Content
Unlock Triggers & Cursors and all premium lessons with a subscription.
From ₹199.99/year — See plans