Creating BEFORE UPDATE and AFTER UPDATE Triggers in MySQL
Course: MySQL Training Course
Topic: Creating BEFORE UPDATE and AFTER UPDATE Triggers
Course Link: MySQL Training Course at JustAcademy
Demo Link: Register for a Course Demo
General Website: JustAcademy
1. Introduction to MySQL UPDATE Triggers
MySQL triggers are database objects that execute automatically when a specified event occurs on a table. An UPDATE trigger runs when an existing row is modified using an SQL UPDATE statement. Triggers are useful for enforcing data rules, maintaining audit records, updating timestamps, tracking changes, and preserving historical information.
MySQL supports two main trigger timings for UPDATE operations:
- BEFORE UPDATE: Executes before the database modifies the row. It is commonly used to validate or adjust the incoming values.
- AFTER UPDATE: Executes after the row has been updated successfully. It is commonly used to record the change in an audit or history table.
For example, when an employee's salary changes, a BEFORE UPDATE trigger can validate the new salary, while an AFTER UPDATE trigger can save the previous and new salary in a history table.
2. Learning Objectives
After completing this topic, you should be able to:
- Understand the purpose and execution timing of MySQL triggers.
- Differentiate BEFORE UPDATE and AFTER UPDATE triggers.
- Understand the OLD and NEW keywords.
- Create triggers using CREATE TRIGGER.
- Validate and modify incoming values using BEFORE UPDATE.
- Record changes using AFTER UPDATE and audit tables.
- Use DELIMITER correctly in MySQL command-line clients.
- Inspect, test, troubleshoot, and drop triggers.
- Apply triggers to employee, product, customer, and order management systems.
3. What Is a MySQL Trigger?
A trigger is a named database object associated with a table. MySQL automatically invokes it when the specified table event occurs. A trigger is not normally called directly by a user like a stored procedure; it runs as part of the triggering SQL statement.
3.1 Main Trigger Events
| Event |
Purpose |
Example |
| INSERT |
Runs when a new row is inserted. |
Set a default status or record a new entry. |
| UPDATE |
Runs when an existing row is updated. |
Track a salary or address change. |
| DELETE |
Runs when a row is deleted. |
Record deleted data in an audit table. |
3.2 Main Components of a Trigger
- Trigger name: The identifier used to refer to the trigger.
- Timing: BEFORE or AFTER the triggering event.
- Event: UPDATE, INSERT, or DELETE.
- Table: The table associated with the trigger.
- FOR EACH ROW: Specifies that the trigger executes once for each affected row.
- Trigger body: The SQL statements executed by the trigger.
4. Understanding BEFORE UPDATE Triggers
A BEFORE UPDATE trigger executes before each affected row is updated. It can inspect the existing values and the proposed new values. It can also assign values to fields through the NEW pseudorecord before the row is stored.
4.1 Common Uses
- Reject invalid values before they are saved.
- Prevent a salary from becoming negative.
- Normalize incoming text values.
- Set or adjust a value based on business rules.
- Update a modified timestamp.
- Check whether a proposed change is allowed.
4.2 Basic Syntax
CREATE TRIGGER trigger_name
BEFORE UPDATE ON table_name
FOR EACH ROW
BEGIN
-- Validation or value adjustment statements
END;
The exact body depends on the database rules you want to enforce. Use a BEFORE UPDATE trigger when you need to examine or adjust the proposed row before the update is stored.
5. Understanding AFTER UPDATE Triggers
An AFTER UPDATE trigger executes after the row has been updated successfully. It is appropriate for actions that depend on the completed change, such as writing an audit entry or storing a previous value in a history table.
5.1 Common Uses
- Maintain audit logs.
- Record old and new values for important fields.
- Track employee salary or department history.
- Record when a product price changes.
- Maintain related database records when appropriate.
5.2 Basic Syntax
CREATE TRIGGER trigger_name
AFTER UPDATE ON table_name
FOR EACH ROW
BEGIN
-- Audit or related database statements
END;
An AFTER UPDATE trigger cannot use NEW to modify the row that has just been updated. If a value must be adjusted before it is stored, use a BEFORE UPDATE trigger instead.
6. Difference Between BEFORE UPDATE and AFTER UPDATE
| Feature |
BEFORE UPDATE |
AFTER UPDATE |
| Execution time |
Before the row is updated. |
After the row is updated successfully. |
| Read old values |
Yes, using OLD. |
Yes, using OLD. |
| Read proposed new values |
Yes, using NEW. |
Yes, using NEW. |
| Change the row's NEW values |
Yes, by assigning to NEW columns where permitted. |
No, the updated row cannot be modified through NEW. |
| Typical purpose |
Validation and adjustment. |
Audit logging and history recording. |
| Example |
Set updated_at before saving. |
Store old and new salary values. |
7. Understanding OLD and NEW Keywords
In a row-level trigger, OLD and NEW refer to the row's values before and after the triggering operation. For UPDATE triggers, both are available.
| Keyword |
Meaning in UPDATE Trigger |
Example |
| OLD.column_name |
The column value before the update. |
OLD.salary |
| NEW.column_name |
The proposed or resulting column value. |
NEW.salary |
Suppose an employee's salary changes from 40000 to 45000. In the trigger, OLD.salary represents 40000 and NEW.salary represents 45000.
IF OLD.salary <> NEW.salary THEN
-- The salary value has changed
END IF;
Be careful when comparing nullable values. If either value may be NULL, a comparison using <> can evaluate to NULL instead of TRUE or FALSE. MySQL's NULL-safe equality operator, <=>, can help:
IF NOT (OLD.salary <=> NEW.salary) THEN
-- The values differ, including NULL-to-value changes
END IF;
8. Preparing a Sample Database
Before creating triggers, create a database and a sample employees table. The following example uses an employee ID, name, salary, department, and modification timestamp.
CREATE DATABASE company_db;
USE company_db;
CREATE TABLE employees (
employee_id INT PRIMARY KEY,
employee_name VARCHAR(100) NOT NULL,
salary DECIMAL(10,2) NOT NULL,
department VARCHAR(50),
updated_at TIMESTAMP NULL DEFAULT NULL
);
INSERT INTO employees
(employee_id, employee_name, salary, department, updated_at)
VALUES
(1, 'Aarav Sharma', 40000.00, 'IT', NULL),
(2, 'Priya Verma', 50000.00, 'HR', NULL),
(3, 'Rahul Mehta', 45000.00, 'Finance', NULL);
SELECT * FROM employees;
The table provides the starting data for the examples in the following sections.
9. Creating a BEFORE UPDATE Trigger
Let's create a trigger that sets the updated_at column to the current date and time whenever an employee record is updated. This timestamp is assigned before the row is stored.
9.1 Create the Trigger
In MySQL command-line clients, change the statement delimiter temporarily because the trigger body contains semicolons.
DELIMITER $$
CREATE TRIGGER before_employee_update
BEFORE UPDATE ON employees
FOR EACH ROW
BEGIN
SET NEW.updated_at = CURRENT_TIMESTAMP;
END$$
DELIMITER ;
9.2 Explanation
DELIMITER $$ changes the command-line client's statement terminator so semicolons inside the trigger body are not treated as the end of the CREATE TRIGGER statement.
CREATE TRIGGER before_employee_update creates a trigger with this name.
BEFORE UPDATE ON employees specifies when and where the trigger runs.
FOR EACH ROW means the trigger executes for each row affected by an UPDATE statement.
SET NEW.updated_at = CURRENT_TIMESTAMP assigns the current timestamp to the proposed row.
DELIMITER ; restores the usual delimiter in the command-line client.
DELIMITER is a client command used by tools such as the MySQL command-line client. It is not part of the SQL statement sent to the MySQL server in the same way as CREATE TRIGGER.
9.3 Test the BEFORE UPDATE Trigger
UPDATE employees
SET salary = 42000.00
WHERE employee_id = 1;
SELECT employee_id, employee_name, salary, updated_at
FROM employees
WHERE employee_id = 1;
The employee's salary should be updated to 42000.00, and updated_at should contain the timestamp assigned by the trigger.
10. Validating Data with BEFORE UPDATE
BEFORE UPDATE triggers can also enforce selected business rules. For example, an organization may want to prevent an employee salary from being set to zero or a negative value.
10.1 Create a Salary Validation Trigger
DELIMITER $$
CREATE TRIGGER before_salary_validation
BEFORE UPDATE ON employees
FOR EACH ROW
BEGIN
IF NEW.salary <= 0 THEN
SIGNAL SQLSTATE '45000'
SET MESSAGE_TEXT = 'Salary must be greater than zero';
END IF;
END$$
DELIMITER ;
The trigger raises a custom error when the proposed salary is zero or negative. The UPDATE statement then fails rather than storing that invalid salary.
10.2 Test Valid and Invalid Values
-- Valid update
UPDATE employees
SET salary = 46000.00
WHERE employee_id = 1;
-- Invalid update: expected to raise an error
UPDATE employees
SET salary = -1000.00
WHERE employee_id = 1;
The first statement should succeed if no other constraint or trigger prevents it. The second should be rejected by the validation trigger.
10.3 Important Design Note
Validation triggers are useful when rules must be enforced for updates from multiple applications. However, database constraints such as CHECK, NOT NULL, UNIQUE, and foreign key constraints should also be used where suitable. For straightforward rules such as requiring salary to be positive, a CHECK constraint can often be simpler and more explicit.
11. Creating an Audit Table for AFTER UPDATE
An audit table stores a record of changes made to important data. It may contain the employee ID, previous salary, new salary, previous department, new department, and the time of the change.
CREATE TABLE employee_audit (
audit_id BIGINT AUTO_INCREMENT PRIMARY KEY,
employee_id INT NOT NULL,
old_salary DECIMAL(10,2),
new_salary DECIMAL(10,2),
old_department VARCHAR(50),
new_department VARCHAR(50),
changed_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
);
The audit_id column uniquely identifies each audit entry. The employee_id identifies the employee whose record changed. The old and new columns preserve the values before and after the update.
12. Creating an AFTER UPDATE Trigger
Now create an AFTER UPDATE trigger that records an audit row when an employee's salary or department changes. The trigger compares OLD and NEW values and inserts a record only when one of those fields differs.
DELIMITER $$
CREATE TRIGGER after_employee_update
AFTER UPDATE ON employees
FOR EACH ROW
BEGIN
IF NOT (OLD.salary <=> NEW.salary)
OR NOT (OLD.department <=> NEW.department) THEN
INSERT INTO employee_audit (
employee_id,
old_salary,
new_salary,
old_department,
new_department,
changed_at
)
VALUES (
OLD.employee_id,
OLD.salary,
NEW.salary,
OLD.department,
NEW.department,
CURRENT_TIMESTAMP
);
END IF;
END$$
DELIMITER ;
12.1 Explanation of the Trigger
- The trigger runs after each employee row has been updated successfully.
- OLD.salary and OLD.department contain the values before the update.
- NEW.salary and NEW.department contain the values after the update.
- The NULL-safe comparison detects differences even when a nullable department changes from NULL to a value or from a value to NULL.
- The INSERT statement creates an audit record when salary or department has changed.
- The trigger does not create an audit row when neither of those two fields changes.
12.2 Test the AFTER UPDATE Trigger
UPDATE employees
SET salary = 48000.00,
department = 'Technology'
WHERE employee_id = 1;
SELECT *
FROM employee_audit
WHERE employee_id = 1
ORDER BY audit_id;
The audit table should contain a record of the previous and new salary and department for the update. If the row is updated again with different salary or department values, another audit record should be added.
13. Complete Example: Employee Change Tracking
This example combines a BEFORE UPDATE trigger that sets a timestamp with an AFTER UPDATE trigger that records changes in an audit table.
13.1 Create the Required Tables
CREATE TABLE employees_demo (
employee_id INT PRIMARY KEY,
employee_name VARCHAR(100) NOT NULL,
salary DECIMAL(10,2) NOT NULL,
department VARCHAR(50),
updated_at TIMESTAMP NULL DEFAULT NULL
);
CREATE TABLE employee_demo_audit (
audit_id BIGINT AUTO_INCREMENT PRIMARY KEY,
employee_id INT NOT NULL,
old_salary DECIMAL(10,2),
new_salary DECIMAL(10,2),
old_department VARCHAR(50),
new_department VARCHAR(50),
changed_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
);
INSERT INTO employees_demo
(employee_id, employee_name, salary, department)
VALUES
(101, 'Neha Kapoor', 55000.00, 'Marketing'),
(102, 'Vikram Singh', 60000.00, 'Operations');
13.2 Create the BEFORE UPDATE Trigger
DELIMITER $$
CREATE TRIGGER before_employees_demo_update
BEFORE UPDATE ON employees_demo
FOR EACH ROW
BEGIN
SET NEW.updated_at = CURRENT_TIMESTAMP;
END$$
DELIMITER ;
13.3 Create the AFTER UPDATE Trigger
DELIMITER $$
CREATE TRIGGER after_employees_demo_update
AFTER UPDATE ON employees_demo
FOR EACH ROW
BEGIN
IF NOT (OLD.salary <=> NEW.salary)
OR NOT (OLD.department <=> NEW.department) THEN
INSERT INTO employee_demo_audit (
employee_id,
old_salary,
new_salary,
old_department,
new_department,
changed_at
)
VALUES (
OLD.employee_id,
OLD.salary,
NEW.salary,
OLD.department,
NEW.department,
CURRENT_TIMESTAMP
);
END IF;
END$$
DELIMITER ;
13.4 Perform an Update
UPDATE employees_demo
SET salary = 62000.00,
department = 'Business Operations'
WHERE employee_id = 101;
SELECT * FROM employees_demo
WHERE employee_id = 101;
SELECT * FROM employee_demo_audit
WHERE employee_id = 101
ORDER BY audit_id;
The first SELECT shows the updated employee record, including the modification timestamp. The second SELECT shows the audit entry created for the salary or department change.
14. Understanding Trigger Execution for Multiple Rows
MySQL triggers are row-level triggers. If one UPDATE statement affects multiple rows, the trigger executes separately for each affected row. For example:
UPDATE employees
SET department = 'General'
WHERE salary < 50000.00;
If three rows match the WHERE condition, the BEFORE UPDATE trigger runs for each of those rows, and the AFTER UPDATE trigger also runs for each successfully updated row. The audit trigger can therefore insert multiple history records.
Always test UPDATE statements carefully. An UPDATE statement without a WHERE clause may affect every row in the table, which can result in many trigger executions and audit entries.
15. Viewing and Managing Triggers
15.1 List Triggers in the Current Database
SHOW TRIGGERS;
15.2 Show Triggers for a Specific Table
SHOW TRIGGERS LIKE 'employees';
The LIKE pattern filters trigger names, so it is not a reliable way to filter by the table name in every situation. To inspect triggers associated with a particular table, query INFORMATION_SCHEMA:
SELECT
TRIGGER_NAME,
EVENT_MANIPULATION,
ACTION_TIMING,
EVENT_OBJECT_TABLE,
ACTION_STATEMENT
FROM INFORMATION_SCHEMA.TRIGGERS
WHERE TRIGGER_SCHEMA = DATABASE()
AND EVENT_OBJECT_TABLE = 'employees';
15.3 View the Trigger Definition
SHOW CREATE TRIGGER before_employee_update;
This statement displays the definition of a particular trigger, subject to the user's database privileges.
15.4 Drop a Trigger
DROP TRIGGER IF EXISTS before_employee_update;
DROP TRIGGER IF EXISTS before_salary_validation;
DROP TRIGGER IF EXISTS after_employee_update;
Dropping a trigger removes that trigger definition. It does not automatically delete existing records from the associated table or audit table.
16. Common Errors and Troubleshooting
| Problem |
Possible Cause |
Solution |
| Syntax error near END |
Incorrect delimiter handling or missing semicolon. |
Use DELIMITER in a supported client and check BEGIN/END syntax. |
| Trigger already exists |
A trigger with the same name is already defined. |
Choose another name or drop the existing trigger before recreating it. |
| Invalid salary is stored |
No validation rule exists or the trigger is not attached to the expected table. |
Inspect the trigger definition and test the rule with invalid input. |
| Audit entry is missing |
The audited columns did not change, the trigger was not created, or the update failed. |
Check trigger status, compare OLD and NEW values, and inspect the UPDATE result. |
| Unexpected duplicate audit entries |
Multiple updates or multiple triggers are writing audit records. |
Review application statements and inspect all triggers attached to the table. |
| Cannot modify NEW in AFTER trigger |
AFTER triggers cannot assign to the updated row through NEW. |
Move the adjustment to a BEFORE trigger. |
| NULL comparison behaves unexpectedly |
Ordinary comparisons with NULL can return NULL. |
Use NULL-safe equality with <=> when appropriate. |
| Trigger creation fails due to permissions |
The database user lacks required privileges. |
Ask the database administrator to grant the appropriate privileges. |
17. Best Practices for MySQL UPDATE Triggers
- Keep trigger logic focused: Each trigger should have a clear, limited responsibility.
- Choose the right timing: Use BEFORE UPDATE for validation and value adjustment; use AFTER UPDATE for recording completed changes.
- Use meaningful names: Names such as before_employee_update and after_employee_update are easier to maintain.
- Handle NULL values correctly: Use NULL-safe comparisons when columns can contain NULL.
- Test multiple-row updates: A single UPDATE statement can invoke a trigger for many rows.
- Protect audit records: Limit permissions on audit tables and define appropriate retention policies.
- Avoid unnecessary work: Insert audit rows only when relevant values change.
- Be careful with transactions: For transactional tables such as InnoDB, trigger operations generally participate in the same transaction as the triggering statement. If the statement or transaction rolls back, the trigger's transactional changes roll back as well.
- Do not rely only on application validation: Database-level rules can protect data when multiple applications write to the same tables.
- Document triggers: Explain why each trigger exists and which business rule it enforces.
- Review side effects: A trigger can cause additional database writes and affect performance.
- Check execution order: If multiple triggers share the same timing and event for a table, inspect their ordering and interactions carefully.
18. Security and Performance Considerations
Triggers execute automatically, so their behavior can be less visible to developers who only inspect the application code. Keep trigger definitions documented and include them in database change management.
18.1 Performance
Every affected row can cause trigger logic to run. Complex calculations, unnecessary queries, and large audit inserts can slow down bulk updates. Keep the trigger body efficient and index audit tables according to how records will be searched.
18.2 Audit Reliability
An audit table can help explain what changed, but the exact audit information depends on the trigger's design. The example above records only salary and department changes; it does not record every column or identify the application user. If user identity is required, design a trustworthy approach for passing and validating that information.
18.3 Transaction Behavior
With transactional tables such as InnoDB, an error in a trigger can cause the triggering statement to fail, and the changes made by the statement and its triggers are rolled back together. Trigger behavior also depends on the storage engines involved. Avoid mixing transactional assumptions with nontransactional tables without testing the consequences.
19. Practical Exercises
- Create a products table with product_id, product_name, price, and updated_at columns.
- Create a BEFORE UPDATE trigger that assigns CURRENT_TIMESTAMP to updated_at.
- Create a BEFORE UPDATE validation rule that rejects a price less than or equal to zero.
- Create a product_price_history table containing the product ID, old price, new price, and change time.
- Create an AFTER UPDATE trigger that records a history row only when the price changes.
- Update one product's price and verify the history record.
- Update multiple products in a single statement and count the resulting history rows.
- Test a nullable column and confirm that your comparison logic handles NULL correctly.
- Use SHOW TRIGGERS and SHOW CREATE TRIGGER to inspect the definitions.
- Drop a test trigger and verify that it no longer executes.
20. Interview Questions and Answers
Q1. What is a MySQL trigger?
A MySQL trigger is a database object that executes automatically when a configured event occurs on a table.
Q2. What is a BEFORE UPDATE trigger?
It executes before an affected row is updated and can validate or adjust proposed values through NEW.
Q3. What is an AFTER UPDATE trigger?
It executes after an affected row has been updated successfully and is often used to record audit information.
Q4. What is the difference between OLD and NEW?
OLD refers to the previous row values, while NEW refers to the proposed or resulting row values in the trigger.
Q5. Can we assign a value to NEW.column in a BEFORE UPDATE trigger?
Yes. A BEFORE UPDATE trigger can assign a value to an eligible NEW column before the row is stored.
Q6. Can we assign a value to NEW.column in an AFTER UPDATE trigger?
No. An AFTER trigger cannot change the row through NEW because the update has already taken place.
Q7. Why is FOR EACH ROW used?
It specifies that the trigger body runs once for each row affected by the triggering statement.
Q8. Why do we use DELIMITER?
In clients such as the MySQL command-line client, DELIMITER temporarily changes the statement terminator so the semicolons inside a multi-statement trigger body do not prematurely end the CREATE TRIGGER command.
Q9. How can a trigger reject an invalid value?
A trigger can use SIGNAL SQLSTATE '45000' to raise a user-defined error when a business rule is violated.
Q10. How can an AFTER UPDATE trigger maintain an audit table?
It can insert OLD values, NEW values, a record identifier, and a timestamp into a separate audit table after the update succeeds.
Q11. What happens if an UPDATE statement changes several rows?
The trigger runs separately for each affected row, so an audit trigger may create one audit record per qualifying row.
Q12. How do you see existing triggers?
Use SHOW TRIGGERS or query INFORMATION_SCHEMA.TRIGGERS for detailed metadata.
Q13. How do you delete a trigger?
Use DROP TRIGGER IF EXISTS trigger_name.
Q14. Why should nullable values be compared carefully?
Comparisons involving NULL can return NULL rather than TRUE or FALSE. The NULL-safe equality operator <=> is useful when checking whether two values differ.
Q15. Can triggers replace all database constraints?
No. Use database constraints for appropriate integrity rules and triggers for logic that genuinely requires trigger behavior, such as custom auditing or more involved validation.
21. Quick Revision Checklist
- BEFORE UPDATE executes before the row is stored.
- AFTER UPDATE executes after the row update succeeds.
- OLD represents previous values.
- NEW represents proposed or resulting values.
- Use BEFORE UPDATE to validate or adjust eligible NEW values.
- Use AFTER UPDATE to record completed changes.
- Use FOR EACH ROW for row-level trigger execution.
- Use DELIMITER appropriately in the MySQL command-line client.
- Use NULL-safe comparisons for nullable columns when needed.
- Use SHOW TRIGGERS and INFORMATION_SCHEMA.TRIGGERS to inspect trigger definitions.
- Use DROP TRIGGER IF EXISTS to remove a trigger safely.
- Test invalid values, unchanged values, multiple-row updates, and transaction rollback behavior.
22. Conclusion
BEFORE UPDATE and AFTER UPDATE triggers help enforce database rules and maintain reliable records of changes. BEFORE UPDATE triggers are best suited to validating or adjusting values before they are stored. AFTER UPDATE triggers are useful for recording old and new values in audit or history tables after an update succeeds.
By understanding OLD and NEW, using NULL-safe comparisons where necessary, keeping trigger logic focused, and testing both single-row and multi-row updates, you can build maintainable MySQL solutions for employee management, inventory systems, customer records, and other applications.
Explore the course: MySQL Training Course
Book a demo: Register for a Course Demo
Visit JustAcademy: www.justacademy.co