SQL Triggers & Scheduled Events: Master BEFORE/AFTER Triggers, MySQL Events, SQL Server Jobs with Examples & Best Practices
Table of Contents
1. What is a trigger in SQL?


A trigger is a special type of stored procedure that automatically executes in response to specific database events, such as INSERT, UPDATE, or DELETE operations on a table. Triggers enforce business rules, maintain data integrity, or log changes.
Types:
- BEFORE Triggers: Execute before the triggering event (e.g., to validate or modify data).
- AFTER Triggers: Execute after the triggering event (e.g., to log changes or update related tables).
- INSTEAD OF Triggers (SQL Server, Oracle): Replace the triggering event with custom logic (used for views).
Syntax (varies by RDBMS):
CREATE TRIGGER trigger_name
[BEFORE | AFTER] [INSERT | UPDATE | DELETE] ON table_name
FOR EACH ROW
BEGIN -- Trigger logic
END;Support: MySQL, PostgreSQL, SQL Server, Oracle; SQLite supports triggers but not events.
2. What are BEFORE and AFTER triggers?
BEFORE Triggers: Run before the DML operation is applied, allowing modification of the data (e.g., setting default values) or validation (e.g., rejecting invalid updates).
AFTER Triggers: Run after the DML operation is complete, used for auditing, updating related tables, or enforcing constraints post-operation.
Use Case: A BEFORE trigger might ensure salaries are positive; an AFTER trigger might log salary changes.
3. Can you give an example of BEFORE and AFTER triggers?

-- Create sample tables
CREATE TABLE Employees ( EmployeeID INT PRIMARY KEY, FirstName VARCHAR(50), Salary DECIMAL(10, 2), DepartmentID INT
); CREATE TABLE SalaryAudit ( AuditID INT PRIMARY KEY AUTO_INCREMENT, -- MySQL/PostgreSQL EmployeeID INT, OldSalary DECIMAL(10, 2), NewSalary DECIMAL(10, 2), ChangeDate DATETIME
); -- Insert sample data
INSERT INTO Employees (EmployeeID, FirstName, Salary, DepartmentID)
VALUES (1, 'John', 60000.00, 1), (2, 'Jane', 55000.00, 2); -- MySQL: BEFORE trigger to validate salary
DELIMITER //
CREATE TRIGGER BeforeSalaryUpdate
BEFORE UPDATE ON Employees
FOR EACH ROW
BEGIN IF NEW.Salary < 0 THEN SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = 'Salary cannot be negative.'; END IF;
END //
DELIMITER ; -- MySQL: AFTER trigger to log salary changes
DELIMITER //
CREATE TRIGGER AfterSalaryUpdate
AFTER UPDATE ON Employees
FOR EACH ROW
BEGIN IF OLD.Salary != NEW.Salary THEN INSERT INTO SalaryAudit (EmployeeID, OldSalary, NewSalary, ChangeDate) VALUES (OLD.EmployeeID, OLD.Salary, NEW.Salary, NOW()); END IF;
END //
DELIMITER ; -- SQL Server: BEFORE (INSTEAD OF) and AFTER triggers
CREATE TRIGGER BeforeSalaryUpdateSQLServer
ON Employees
INSTEAD OF UPDATE
AS
BEGIN IF EXISTS (SELECT * FROM inserted WHERE Salary < 0) THROW 50001, 'Salary cannot be negative.', 1; ELSE UPDATE Employees SET Salary = i.Salary, FirstName = i.FirstName, DepartmentID = i.DepartmentID FROM inserted i WHERE Employees.EmployeeID = i.EmployeeID;
END; CREATE TRIGGER AfterSalaryUpdateSQLServer
ON Employees
AFTER UPDATE
AS
BEGIN INSERT INTO SalaryAudit (EmployeeID, OldSalary, NewSalary, ChangeDate) SELECT d.EmployeeID, d.Salary, i.Salary, GETDATE() FROM deleted d JOIN inserted i ON d.EmployeeID = i.EmployeeID WHERE d.Salary != i.Salary;
END; -- Test triggers
UPDATE Employees SET Salary = 65000.00 WHERE EmployeeID = 1;
-- Invalid update (will fail)
UPDATE Employees SET Salary = -1000.00 WHERE EmployeeID = 2; -- Verify data
SELECT * FROM Employees;
SELECT * FROM SalaryAudit; Output (Employees):
EmployeeID | FirstName | Salary | DepartmentID
-----------|-----------|----------|-------------
1 | John | 65000.00 | 1
2 | Jane | 55000.00 | 2 Output (SalaryAudit, MySQL):
AuditID | EmployeeID | OldSalary | NewSalary | ChangeDate
--------|------------|-----------|-----------|----------------
1 | 1 | 60000.00 | 65000.00 | 2025-12-16 10:43 Note:
- BEFORE Trigger: Prevents negative salaries (MySQL:
SIGNAL, SQL Server:THROW). - AFTER Trigger: Logs salary changes to
SalaryAudit. - The invalid update (Salary = -1000.00) is blocked by the BEFORE trigger.
4. What are scheduled events and jobs in SQL?
Scheduled events (MySQL, PostgreSQL with pg_cron) or jobs (SQL Server, Oracle) automate recurring tasks at specified times or intervals.
- Events (MySQL): Database-level tasks defined with
CREATE EVENT, executed by the event scheduler. - Jobs (SQL Server): Managed via SQL Server Agent, allowing complex scheduling.
- Jobs (Oracle): Managed via
DBMS_SCHEDULER.
Syntax (MySQL):
CREATE EVENT event_name
ON SCHEDULE EVERY interval [DAY | HOUR | MINUTE]
DO -- SQL statementsUse Case: Automatically updating statuses, archiving data, or generating reports.
Support: MySQL (events), PostgreSQL (pg_cron), SQL Server (Agent Jobs), Oracle (DBMS_SCHEDULER); SQLite lacks native scheduling.
5. Can you give an example of a scheduled event?

-- Enable event scheduler in MySQL
SET GLOBAL event_scheduler = ON; -- Create table for archiving old employees
CREATE TABLE ArchivedEmployees ( EmployeeID INT, FirstName VARCHAR(50), ArchiveDate DATETIME
); -- Create event to archive employees with low salary
DELIMITER //
CREATE EVENT ArchiveLowSalaryEmployees
ON SCHEDULE EVERY 1 DAY
STARTS '2025-12-17 00:00:00'
DO
BEGIN INSERT INTO ArchivedEmployees (EmployeeID, FirstName, ArchiveDate) SELECT EmployeeID, FirstName, NOW() FROM Employees WHERE Salary < 60000.00; DELETE FROM Employees WHERE Salary < 60000.00;
END //
DELIMITER ; -- SQL Server: Create a job using SQL Server Agent (T-SQL to define job)
-- Note: Requires SQL Server Agent setup, shown for completeness
EXEC msdb.dbo.sp_add_job @job_name = 'ArchiveLowSalaryEmployeesJob';
EXEC msdb.dbo.sp_add_jobstep @job_name = 'ArchiveLowSalaryEmployeesJob', @step_name = 'ArchiveStep', @subsystem = 'TSQL', @command = ' INSERT INTO ArchivedEmployees (EmployeeID, FirstName, ArchiveDate) SELECT EmployeeID, FirstName, GETDATE() FROM Employees WHERE Salary < 60000.00; DELETE FROM Employees WHERE Salary < 60000.00; ';
EXEC msdb.dbo.sp_add_jobschedule @job_name = 'ArchiveLowSalaryEmployeesJob', @name = 'DailySchedule', @freq_type = 4, -- Daily @freq_interval = 1, @active_start_time = 000000; -- 00:00:00
EXEC msdb.dbo.sp_add_jobserver @job_name = 'ArchiveLowSalaryEmployeesJob'; -- Verify data after event/job (assuming it runs)
SELECT * FROM Employees;
SELECT * FROM ArchivedEmployees; Output (after event runs):
Employees:
EmployeeID | FirstName | Salary | DepartmentID
-----------|-----------|----------|-------------
1 | John | 65000.00 | 1
3 | kristal | 65000.00 | 1 ArchivedEmployees:
EmployeeID | FirstName | ArchiveDate
-----------|-----------|----------------
2 | Jane | 2025-12-17 00:00 Note:
- MySQL event archives employees with salaries < 60000 daily.
- SQL Server job performs the same task via SQL Server Agent.
- Jane's record is moved to
ArchivedEmployeesand deleted fromEmployees.
6. Can you provide a comprehensive example using BEFORE/AFTER triggers and scheduled events?
-- Create sample tables
CREATE TABLE Employees ( EmployeeID INT PRIMARY KEY, FirstName VARCHAR(50), Salary DECIMAL(10, 2), DepartmentID INT, LastUpdated DATETIME
); CREATE TABLE SalaryAudit ( AuditID INT PRIMARY KEY AUTO_INCREMENT, -- MySQL/PostgreSQL EmployeeID INT, OldSalary DECIMAL(10, 2), NewSalary DECIMAL(10, 2), ChangeDate DATETIME
); CREATE TABLE ArchivedEmployees ( EmployeeID INT, FirstName VARCHAR(50), ArchiveDate DATETIME
); -- Insert sample data
INSERT INTO Employees (EmployeeID, FirstName, Salary, DepartmentID, LastUpdated)
VALUES (1, 'John', 60000.00, 1, NOW()), (2, 'Jane', 55000.00, 2, NOW()), (3, 'kristal', 65000.00, 1, NOW()); -- MySQL: BEFORE trigger to validate and set LastUpdated
DELIMITER //
CREATE TRIGGER BeforeEmployeeUpdate
BEFORE UPDATE ON Employees
FOR EACH ROW
BEGIN IF NEW.Salary < 0 THEN SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = 'Salary cannot be negative.'; END IF; SET NEW.LastUpdated = NOW();
END //
DELIMITER ; -- MySQL: AFTER trigger to log salary changes
DELIMITER //
CREATE TRIGGER AfterEmployeeUpdate
AFTER UPDATE ON Employees
FOR EACH ROW
BEGIN IF OLD.Salary != NEW.Salary THEN INSERT INTO SalaryAudit (EmployeeID, OldSalary, NewSalary, ChangeDate) VALUES (OLD.EmployeeID, OLD.Salary, NEW.Salary, NOW()); END IF;
END //
DELIMITER ; -- MySQL: Scheduled event to archive low-salary employees
SET GLOBAL event_scheduler = ON;
DELIMITER //
CREATE EVENT ArchiveLowSalaryEmployees
ON SCHEDULE EVERY 1 DAY
STARTS '2025-12-17 00:00:00'
DO
BEGIN INSERT INTO ArchivedEmployees (EmployeeID, FirstName, ArchiveDate) SELECT EmployeeID, FirstName, NOW() FROM Employees WHERE Salary < 60000.00; DELETE FROM Employees WHERE Salary < 60000.00;
END //
DELIMITER ; -- Test triggers
UPDATE Employees SET Salary = 62000.00 WHERE EmployeeID = 1;
-- Invalid update (will fail)
UPDATE Employees SET Salary = -1000.00 WHERE EmployeeID = 2; -- Verify data before event
SELECT * FROM Employees;
SELECT * FROM SalaryAudit; -- Simulate event execution
INSERT INTO ArchivedEmployees (EmployeeID, FirstName, ArchiveDate)
SELECT EmployeeID, FirstName, NOW()
FROM Employees
WHERE Salary < 60000.00;
DELETE FROM Employees
WHERE Salary < 60000.00; -- Verify data after event
SELECT * FROM Employees;
SELECT * FROM ArchivedEmployees; Output (Employees after UPDATE):
EmployeeID | FirstName | Salary | DepartmentID | LastUpdated
-----------|-----------|----------|-------------|--------------------
1 | John | 62000.00 | 1 | 2025-12-16 10:43
2 | Jane | 55000.00 | 2 | 2025-12-16 10:43
3 | kristal | 65000.00 | 1 | 2025-12-16 10:43 Output (SalaryAudit after UPDATE):
AuditID | EmployeeID | OldSalary | NewSalary | ChangeDate
--------|------------|-----------|-----------|----------------
1 | 1 | 60000.00 | 62000.00 | 2025-12-16 10:43 Output (Employees after Event):
EmployeeID | FirstName | Salary | DepartmentID | LastUpdated
-----------|-----------|----------|-------------|--------------------
1 | John | 62000.00 | 1 | 2025-12-16 10:43
3 | kristal | 65000.00 | 1 | 2025-12-16 10:43 Output (ArchivedEmployees after Event):
EmployeeID | FirstName | ArchiveDate
-----------|-----------|----------------
2 | Jane | 2025-12-17 00:00 Description:
- BEFORE Trigger: Validates salary and updates
LastUpdated. - AFTER Trigger: Logs salary changes to
SalaryAudit. - Event: Archives employees with salaries < 60000 daily.
- Integrates prior concepts (
UPDATE,INSERT,DELETE, transactions).
7. What are common mistakes in triggers and events?
Triggers:
- Creating recursive triggers (e.g., an
UPDATEtrigger causing anotherUPDATE), leading to infinite loops. - Not handling errors, causing silent failures.
- Overusing triggers for complex logic better suited to stored procedures.
Events/Jobs:
- Forgetting to enable the event scheduler (
event_scheduler=ONin MySQL). - Scheduling overly frequent or heavy tasks, impacting performance.
- Not logging event outcomes, making debugging difficult.
General:
- Ignoring transaction boundaries, risking inconsistent data.
- Not testing triggers/events with edge cases (e.g., NULL values, large datasets).
- Overcomplicating triggers/events, reducing maintainability.
8. What are best practices for triggers and events?

Triggers:
- Use
BEFOREtriggers for validation or data modification,AFTERfor auditing or cascading updates. - Keep trigger logic simple to avoid performance issues and maintainability problems.
- Disable recursive triggers if not needed (e.g., MySQL:
SET SESSION max_sp_recursion_depth = 0).
Events/Jobs:
- Enable and verify the scheduler (MySQL:
SET GLOBAL event_scheduler = ON). - Log event actions to a table for auditing and debugging.
- Schedule events during low-traffic periods to minimize performance impact.
General:
- Use transactions in triggers/events for data consistency.
- Comment trigger/event logic for clarity (e.g.,
-- Log salary changes). - Test triggers/events with small datasets and edge cases.
- Use
EXPLAINorEXPLAIN ANALYZEto optimize queries within triggers/events. - Index columns used in trigger/event conditions or joins.
- Monitor trigger/event performance and disable unused ones.