Triggers: Automatic Actions
Code that fires by itself on INSERT, UPDATE or DELETE — AFTER, INSTEAD OF and DDL triggers, with a player_log audit example.
The league wants a record of every player ever added or changed. You could ask every developer to remember to write a log row… but someone will forget. A trigger makes the database write the log by itself, every time.
Definition
A trigger is a special stored program that the database executes automatically when a specific event happens on a table, view or database — such as an
INSERT,UPDATEorDELETE(DML triggers) or aCREATE/ALTER/DROP(DDL triggers). Nobody calls a trigger directly; it fires in response to the event.
- Automatic — unlike a stored procedure, there is no
EXEC. - Event — the WHEN: e.g.
AFTER INSERTonplayers. - Action — the WHAT: the SQL inside the trigger body.
How a trigger works
| Step | What happens |
|---|---|
| 1. Event | Someone runs INSERT / UPDATE / DELETE on players |
| 2. Trigger fires | The database starts the trigger automatically |
| 3. Action runs | Your SQL runs, e.g. writes a row into player_log |
| 4. Result | Every change is recorded — nobody can forget |
Trigger types
| Type | When it fires | Typical use | Example clause |
|---|---|---|---|
AFTER (= FOR) | AFTER the change was made | Audit logs, notifications | AFTER INSERT, UPDATE |
| INSTEAD OF | INSTEAD of the original action (the original change does not happen) | Custom rules, soft deletes, updatable views | INSTEAD OF DELETE |
| DDL | On CREATE / ALTER / DROP — structure changes | Guard the schema | ON DATABASE FOR DROP_TABLE |
Syntax (SQL Server)
sql
CREATE TRIGGER trigger_name
ON table_name -- WHICH table is watched
AFTER INSERT, UPDATE, DELETE -- WHEN: the event(s)
AS
BEGIN
-- WHAT: the SQL statements to run
END;The inserted and deleted tables
Inside a SQL Server DML trigger you can read two special, temporary tables:
| Event | inserted contains | deleted contains |
|---|---|---|
INSERT | the new rows | empty |
DELETE | empty | the removed rows |
UPDATE | the rows after the change | the rows before the change |
Note: A SQL Server trigger fires once per statement, not once per row. If one
UPDATEchanges 2 rows,insertedholds 2 rows. Always write set-based code likeINSERT … SELECT … FROM inserted— never assume a single row.
Example — maintaining a log (AFTER INSERT)
sql
CREATE TABLE player_log (
log_id INT IDENTITY(1,1) PRIMARY KEY,
action VARCHAR(10),
player_name VARCHAR(50),
logged_at DATETIME DEFAULT GETDATE()
);
CREATE TRIGGER trg_log_insert
ON players
AFTER INSERT
AS
BEGIN
SET NOCOUNT ON;
INSERT INTO player_log (action, player_name)
SELECT 'INSERT', name
FROM inserted;
END;Now you only write:
sql
INSERT INTO players VALUES (7, 'Hardik Pandya', 'MI', 'India', 300);
SELECT log_id, action, player_name FROM player_log;| log_id | action | player_name |
|---|---|---|
| 1 | INSERT | Hardik Pandya |
Nobody called the trigger — it fired on its own.
Example — audit an UPDATE with old and new values
sql
CREATE TRIGGER trg_log_runs
ON players
AFTER UPDATE
AS
BEGIN
SET NOCOUNT ON;
INSERT INTO runs_log (player_name, old_runs, new_runs)
SELECT i.name, d.runs, i.runs
FROM inserted AS i
JOIN deleted AS d ON i.id = d.id
WHERE i.runs <> d.runs;
END;
UPDATE players SET runs = runs + 50 WHERE team = 'MI';| player_name | old_runs | new_runs |
|---|---|---|
| Rohit Sharma | 500 | 550 |
| Jasprit Bumrah | 20 | 70 |
One UPDATE statement → the trigger fires once → two log rows (because inserted had two rows).
Example — INSTEAD OF DELETE (soft delete)
sql
CREATE TRIGGER trg_no_delete
ON players
INSTEAD OF DELETE
AS
BEGIN
SET NOCOUNT ON;
UPDATE p
SET p.team = NULL -- mark as released instead of deleting
FROM players AS p
JOIN deleted AS d ON p.id = d.id;
END;DELETE FROM players WHERE id = 6; now keeps Bumrah's row and only clears his team.
Example — DDL trigger: guard the schema
sql
CREATE TRIGGER trg_block_drop
ON DATABASE
FOR DROP_TABLE
AS
BEGIN
PRINT 'Dropping tables is not allowed here!';
ROLLBACK;
END;Common mistakes
- ❌ Assuming one row:
DECLARE @name VARCHAR(50) = (SELECT name FROM inserted)→ breaks or logs only one row for multi-row statements. ✅INSERT … SELECT … FROM inserted. - ❌ Putting heavy logic in triggers → every INSERT/UPDATE becomes slow and debugging gets hard ("hidden" code). ✅ keep triggers small; use them for logging and rules.
- ❌ Forgetting a trigger exists and being confused by "extra" rows. ✅ document triggers; list them with
SELECT name FROM sys.triggers. - ❌ Writing SQLite / MySQL style
NEW.namein SQL Server. ✅ SQL Server uses theinserted/deletedtables.
SQL Server vs MySQL / PostgreSQL / SQLite
| SQL Server | MySQL / SQLite | PostgreSQL | |
|---|---|---|---|
| Fires | Once per statement | FOR EACH ROW | Per row or per statement |
| New / old values | inserted / deleted tables | NEW.col / OLD.col | NEW / OLD |
| Body | AS BEGIN … END | BEGIN … END | calls a trigger function |
The playground runs SQLite, so its "try it" example below uses SQLite trigger syntax (NEW.name) — the same idea, but not SQL Server code.
Interview tip
Q: Stored procedure vs trigger?
A stored procedure runs only when someone calls it with EXEC and can take parameters. A trigger cannot be called and takes no parameters — it fires automatically when its event (INSERT / UPDATE / DELETE / DDL) happens.
Key points to remember
- A trigger is stored code that runs automatically when an event (INSERT, UPDATE, DELETE, or DDL) happens — nobody calls it.
- WHEN = the event (AFTER INSERT …); WHAT = the SQL in the trigger body.
- AFTER triggers run after the change (audit logs); INSTEAD OF triggers replace the action; DDL triggers fire on CREATE / ALTER / DROP.
- SQL Server exposes changed rows in the special tables inserted (new values) and deleted (old values).
- SQL Server triggers fire once per statement, so always write set-based code over inserted / deleted.
- Keep triggers small — hidden, heavy trigger logic slows every write and is hard to debug.
- SQLite / MySQL use NEW.col and OLD.col with FOR EACH ROW instead of inserted / deleted.
Try it yourself
player_log audit trigger (SQL Server)
CREATE TABLE player_log (
log_id INT IDENTITY(1,1) PRIMARY KEY,
action VARCHAR(10),
player_name VARCHAR(50),
logged_at DATETIME DEFAULT GETDATE()
);
GO
CREATE TRIGGER trg_log_insert
ON players
AFTER INSERT
AS
BEGIN
SET NOCOUNT ON;
INSERT INTO player_log (action, player_name)
SELECT 'INSERT', name
FROM inserted;
END;
GO
INSERT INTO players VALUES (7, 'Hardik Pandya', 'MI', 'India', 300);
SELECT log_id, action, player_name FROM player_log;
-- 1 | INSERT | Hardik PandyaSQL Server-specific — run this in SQL Server (SSMS / Azure Data Studio).
Try it: same idea in SQLite trigger syntax
Runs on a fresh IPL practice database in your browser · edit the query and press Ctrl + Enter
Try it: log old and new runs on UPDATE (SQLite syntax)
Runs on a fresh IPL practice database in your browser · edit the query and press Ctrl + Enter
Practice: log deletions too
Runs on a fresh IPL practice database in your browser · edit the query and press Ctrl + Enter
Revision Flashcards
Tap a card to flip it and see the answer.
