Q&A
What are triggers in SQL?
Overview
A trigger is "event-driven" SQL: you attach logic to a table that the database runs whenever a specified change happens. Because the database enforces it, the logic can't be bypassed by forgetful application code. Triggers are powerful for auditing and integrity but should be used judiciously - they execute invisibly, which can make systems harder to reason about.
graph TD
A[INSERT / UPDATE / DELETE on table] --> B{Trigger timing}
B --> C[BEFORE: validate / modify incoming row]
B --> D[AFTER: audit / cascade / update other tables]
C & D --> E[Trigger body runs automatically]
style E fill:#1e1b4b,color:#fff
Trigger Components
- Event:
INSERT,UPDATE, orDELETE. - Timing:
BEFORE(runs before the change is applied - can validate/alter the row) orAFTER(runs after - for auditing, cascading). Some databases also haveINSTEAD OFtriggers (mainly on views). - Level:
FOR EACH ROW(row-level, fires once per affected row) or statement-level (once per statement). - Body: the SQL/procedural code to run, with access to the old/new row values.
Example (MySQL): Auditing changes
CREATE TRIGGER audit_salary_change
AFTER UPDATE ON employees
FOR EACH ROW
BEGIN
INSERT INTO salary_audit (employee_id, old_salary, new_salary, changed_at)
VALUES (OLD.id, OLD.salary, NEW.salary, NOW());
END;
Whenever a row in employees is updated, this automatically logs the old and new salary. OLD and NEW are pseudo-records exposing the row values before/after the change.
OLD and NEW
Inside row-level triggers you reference the affected row's values:
| Event | OLD available |
NEW available |
|---|---|---|
| INSERT | No | Yes (the inserted row) |
| UPDATE | Yes (before) | Yes (after) |
| DELETE | Yes (the deleted row) | No |
Example (BEFORE trigger): Validation / normalization
CREATE TRIGGER normalize_email
BEFORE INSERT ON users
FOR EACH ROW
BEGIN
SET NEW.email = LOWER(TRIM(NEW.email)); -- normalize before storing
END;
A BEFORE trigger can modify NEW values before they're written, or reject invalid data (e.g. raise an error).
Common Use Cases
- Auditing / history - log who changed what and when.
- Enforcing complex business rules that constraints can't express.
- Maintaining derived/denormalized data - e.g. update a running total or counter.
- Validation / normalization before insert/update.
- Cascading actions beyond what foreign keys provide.
Syntax Varies by Database
The concept is standard but syntax differs: MySQL uses OLD/NEW; PostgreSQL triggers call a separate trigger function (CREATE FUNCTION ... RETURNS TRIGGER then CREATE TRIGGER); SQL Server uses inserted/deleted pseudo-tables and is statement-level by default. Know your target database's flavor.
Drawbacks and Cautions
- Hidden logic - triggers run invisibly, making behavior hard to trace and debug ("why did this row change?").
- Performance - they add work to every affected DML statement; row-level triggers on bulk operations can be costly.
- Cascading/recursion - a trigger that modifies another table can fire more triggers, risking loops.
- Testing difficulty - side effects are easy to overlook.
For these reasons, many teams prefer handling such logic in the application or in explicit stored procedures, reserving triggers for auditing and integrity that must be guaranteed at the data layer.
Why This Matters in Interviews
Database interviews use triggers to test knowledge of event-driven SQL and integrity. Cover the event types (INSERT/UPDATE/DELETE), timing (BEFORE/AFTER, plus INSTEAD OF for views), row-vs-statement level, and OLD/NEW access. Equally important is judgment: name the legitimate uses (auditing, validation, derived data) and the downsides (hidden logic, performance, recursion). Acknowledging database-specific syntax differences shows real-world experience.
Common Pitfalls
- Putting heavy logic in row-level triggers that fire on large bulk updates (performance hit).
- Creating trigger loops (trigger A updates table B whose trigger updates table A).
- Overusing triggers for logic better placed in the application, creating hidden behavior.
- Assuming one database's syntax (
OLD/NEW) works everywhere (PostgreSQL/SQL Server differ). - Forgetting that
AFTERtriggers can't change the row being inserted (useBEFORE).
References
- MySQL Triggers: https://dev.mysql.com/doc/refman/8.0/en/triggers.html
- PostgreSQL CREATE TRIGGER: https://www.postgresql.org/docs/current/sql-createtrigger.html