SQL triggers are like automated routines in a database that execute predefined actions when specific events like INSERT, UPDATE, or DELETE occur in a table. This helps in automating data updation and setting some rules in place. It keeps the data clean and consistent without you having to write extra code every single time. In this article, we will look into what exactly an SQL trigger is and how it works. We will also explore different types of SQL triggers through some examples and understand how they are used differently in MySQL, PostgreSQL, and SQL Server. By the end, you will have a good idea about how and when to actually use triggers in a database setup.
A trigger is like an automatic program that is tied to a database table, and it runs the SQL code automatically when a specific event happens, like inserting, updating or deleting a row. For example, you can use a trigger to automatically set a timestamp on when a new row is created, added or deleted, or new data rules are applied without extra code in your application. In simple terms, we can say that a trigger is a stored set of SQL statements that “fires” in response to table events.
In MySQL, triggers are defined with the CREATE TRIGGER statement and are attached to a specific table and event. Each trigger is row-level, meaning it runs once for each row affected by the event. When you create a trigger, you specify:
For example, a BEFORE INSERT trigger runs just before a new row is added to the table, and an AFTER UPDATE trigger runs right after an existing row is changed. MySQL requires the keyword FOR EACH ROW in a trigger, which makes it execute the trigger body for every row affected by the operation.
Inside a trigger, you refer to the row data using the NEW and OLD aliases. In an INSERT trigger, only NEW.column is available (the incoming data). Similarly, in a DELETE trigger, only OLD.column is available (the data about the row being deleted). However, in an UPDATE trigger, you can use both: OLD.column refers to the row’s values before the update, and NEW.column refers to the values after the update.
Let’s see trigger SQL syntax:
CREATE TRIGGER trigger_name
BEFORE|AFTER {INSERT|UPDATE|DELETE} ON table_name
FOR EACH ROW
BEGIN
-- SQL statements here --
END;
This is the standard SQL form. One thing which needs to be noted is that the trigger bodies often include multiple statements with semicolons; you should usually change the SQL delimiter first, for example to //, so the whole CREATE TRIGGER block is parsed correctly.
Now let’s see how we can create triggers in SQL.
For this, let’s just create a simple users table:
CREATE TABLE users (
id INT AUTO_INCREMENT PRIMARY KEY,
username VARCHAR(50),
created_at DATETIME,
updated_at DATETIME
);
In SQL, you can change the statement delimiter so you can write multi-statement triggers. For example:
DELIMITER //
For instance, we can create a trigger that sets the created_at column to the current time on insertion:
CREATE TRIGGER before_users_insert
BEFORE INSERT ON users
FOR EACH ROW
BEGIN
IF NEW.created_at IS NULL THEN
SET NEW.created_at = NOW();
END IF;
END;
//
So, in the above code, the BEFORE INSERT ON users means the trigger fires before each new row is inserted. The trigger body checks if NEW.created_at is null, and if so, fills it with NOW(). This automates setting a timestamp.
After writing the trigger, you can restore the delimiter if desired so that other codes can execute without any issues.
DELIMITER ;
Now, when you insert without specifying created_at, the trigger will be set automatically.
INSERT INTO users (username) VALUES ('Alice');
SELECT * FROM users;
And the created_at will be filled automatically with the current date/time. A trigger can automate tasks by setting up default values.
There are six types of SQL triggers for each table:
Let’s learn about each of them through examples.
This trigger is activated before a new row is inserted into a table. It is commonly used to validate or modify the data before it is saved.
Example of trigger SQL syntax for BEFORE INSERT:
DELIMITER //
CREATE TRIGGER before_insert_user
BEFORE INSERT ON users
FOR EACH ROW
BEGIN
SET NEW.created_at = NOW();
END;
//
DELIMITER ;
This trigger is automatically set at the created_at timestamp to the current time before a new user record is inserted.
This trigger is executed before an existing row is updated. This allows for validation or modification of data before the update occurs.
Example of trigger SQL syntax for BEFORE UPDATE:
DELIMITER //
CREATE TRIGGER before_update_user
BEFORE UPDATE ON users
FOR EACH ROW
BEGIN
IF NEW.email NOT LIKE '%@%' THEN
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = 'Invalid email address';
END IF;
END;
//
DELIMITER ;
This trigger checks if the new email address is valid before updating the user record. If not, then it raises an error.
This is executed before a row is deleted. And can also be used for enforcing referential integrity or preventing deletion under certain conditions.
Example of trigger SQL syntax for BEFORE DELETE:
DELIMITER //
CREATE TRIGGER before_delete_order
BEFORE DELETE ON orders
FOR EACH ROW
BEGIN
IF OLD.status = 'Shipped' THEN
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = 'Cannot delete shipped orders';
END IF;
END;
//
DELIMITER ;
This trigger prevents deletion of orders that have already been shipped.
This trigger is executed after a new row is inserted and is often used for logging or updating related tables.
Example of trigger SQL syntax for AFTER INSERT
DELIMITER //
CREATE TRIGGER after_insert_user
AFTER INSERT ON users
FOR EACH ROW
BEGIN
INSERT INTO user_logs(user_id, action, log_time
VALUES (NEW.id, 'User created', NOW());
END;
//
DELIMITER ;
This trigger logs the creation of a new user in the user_logs table.
This trigger is executed after a row is updated. And is useful for auditing changes or updating related data.
Example of trigger SQL syntax for AFTER UPDATE
DELIMITER //
CREATE TRIGGER after_update_user
AFTER UPDATE ON users
FOR EACH ROW
BEGIN
INSERT INTO user_logs(user_id, action, log_time)
VALUES (NEW.id, CONCAT('User updated: ', OLD.name, ' to ', NEW.name), NOW());
END;
//
DELIMITER ;
This trigger logs the change in a user’s name after an update.
This trigger is executed after a row is deleted. And is commonly used for logging deletions or cleaning up related data.
Example of trigger SQL syntax for AFTER DELETE
DELIMITER //
CREATE TRIGGER after_delete_user
AFTER DELETE ON users
FOR EACH ROW
BEGIN
INSERT INTO user_logs(user_id, action, log_time)
VALUES (OLD.id, 'User deleted', NOW());
END;
//
DELIMITER ;
This trigger logs the deletion of a user in the user_log table.
Triggers are powerful when you want to automate things that happen when the data changes. Below are some use cases and advantages highlighting when and why you should use SQL triggers.
You must run triggers with care. As triggers run quietly every time data changes, they may sometimes slow things down or make debugging tricky, if you have too many. Still, for things like setting timestamps, checking inputs, or syncing other data, triggers are really useful. They save time and also reduce silly mistakes from writing the same code again and again.
Here are some points to consider before deciding to use SQL triggers:
Let’s now have a look at how triggers differ on different databases such as MySQL, PostgreSQL, and SQL Server.
Feature | MySQL | PostgreSQL | SQL Server |
Trigger Syntax | Defined inline in CREATE TRIGGER, written in SQL. Always includes FOR EACH ROW. | CREATE TRIGGER … EXECUTE FUNCTION function_name(). Allows FOR EACH ROW FOR EACH STATEMENT. | CREATE TRIGGER with AFTER or INSTEAD OF. Always statement-level. Uses BEGIN … END. |
Granularity | Row-level only (FOR EACH ROW). | Row-level (default) or statement-level. | Statement-level only. |
Timing Options | BEFORE, AFTER for INSERT, UPDATE, DELETE. No INSTEAD OF, no triggers on views. | BEFORE, AFTER, INSTEAD OF (on views). | AFTER, INSTEAD OF (views or to override actions). |
Trigger Firing | Fires once per affected row. | Can fire once per row or once per statement. | Fires once per statement. Uses inserted and deleted virtual tables. |
Referencing Rows | Uses NEW.column and OLD.column. | Uses NEW and OLD inside trigger functions. | Uses inserted and deleted virtual tables. Must join them to access the changed rows. |
Language Support | Only SQL (no dynamic SQL in triggers). | PL/pgSQL, PL/Python, others. Supports dynamic SQL, RETURN NEW/OLD. | T-SQL with full language support (transactions, TRY/CATCH, etc.). |
Capabilities | Simple. No dynamic SQL or procedures returning result sets. BEFORE triggers can modify NEW. | Powerful. Can abort or modify actions, return values, and use multiple languages. | Integrated with SQL Server features. Allows TRY/CATCH, transactions, and complex logic. |
Trigger Limits | Before v5.7.2: Only 1 BEFORE and 1 AFTER trigger per table per event (INSERT, UPDATE, DELETE). And after v5.2, you can create multiple triggers for the same event and timing. Use FOLLOWS or PRECEDES to control the order. | No enforced trigger count limits. | Allows up to 16 triggers per table. |
Trigger Ordering | Controlled using FOLLOWS / PRECEDES. | No native ordering of triggers. | No native ordering, but you can manage logic inside triggers. |
Error Handling | No TRY/CATCH. Errors abort the statement. AFTER runs only if BEFORE and the row action succeed. | Uses EXCEPTION blocks in functions. Errors abort the statement. | Supports TRY/CATCH. Trigger errors abort the statement. |
Although SQL triggers might feel a bit tricky at first, you’ll fully understand them and get to know how helpful they are, once you get started. They run on their own when something changes in your tables, which saves time and makes sure the data continues to follow the rules you set. Whether it’s logging changes, stopping unwanted updates, or syncing info across tables, triggers are really useful in SQL. Just make sure to not overuse them and make too many triggers, as that can make things messy and hard to debug later on. Keep it simple, test them properly, and you are good to go.