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?

SQL Triggers Overview: Automatic Execution on DML Events Diagram

SQL Trigger Execution Flow: BEFORE vs AFTER Illustration

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:

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?

SQL BEFORE/AFTER Trigger Execution Example with Salary Validation and Audit Log

-- 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:

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.

Syntax (MySQL):

CREATE EVENT event_name
ON SCHEDULE EVERY interval [DAY | HOUR | MINUTE]
DO -- SQL statements

Use 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?

MySQL Event Scheduler Process and Execution Flow Diagram

-- 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:

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:

7. What are common mistakes in triggers and events?

Triggers:

Events/Jobs:

General:

8. What are best practices for triggers and events?

SQL Triggers Best Practices Infographic: Keep Simple, Avoid Recursion

Triggers:

Events/Jobs:

General: