Triggers

Oct 30, 2025
Updated 3 weeks ago
8 min read

SQL Triggers Explained — With Real MCQs From Accenture Assessments

Database triggers, MySQL trigger syntax, and trigger examples — everything you need to understand automated database actions, written for students preparing for placement assessments at companies like Accenture, TCS, and Wipro.

Most blogs teach you what a trigger is. This one teaches you what a trigger does to you when you write it wrong — and how to get it right the first time.

If you've ever wondered why a row disappeared silently, why a log table kept filling up with duplicate entries, or why your database threw an error you never wrote — there was probably a trigger involved. That's exactly why understanding them matters.

Before diving in, make sure you're comfortable with basic SQL — if not, start with the SQL Quick Recap first, then come back here.


What Is a Trigger, Really?

Think of a trigger as a security guard that wakes up every time something happens to your table.

You don't call the guard. You don't press a button. The moment a row is inserted, updated, or deleted — the guard automatically executes whatever instructions you left.

That's the whole idea. A trigger is automatic, invisible, and attached directly to a table event.

sql
CREATE TRIGGER trigger_name
{BEFORE | AFTER} {INSERT | UPDATE | DELETE}
ON table_name
FOR EACH ROW
BEGIN
    -- what you want to happen automatically
END;

Read that structure once more. Three decisions you make every time:

  • When — BEFORE the change happens, or AFTER?

  • What event — INSERT, UPDATE, or DELETE?

  • What to do — the actual logic inside BEGIN...END

Get those three right and the rest is just SQL you already know.

For a deeper understanding of how triggers fit into database design, the MySQL documentation on trigger syntax is worth bookmarking — it covers edge cases that assessments love to test.


SQL Trigger lifecycle

SQL Triggers : sql trigger lifecycle | Notehub


BEFORE vs AFTER — This Is Where Most People Get Confused

This single decision determines whether your trigger can prevent something or only react to it.

BEFORE

AFTER

Can stop the operation?

✅ Yes — use SIGNAL

❌ No, too late

Can read NEW values?

✅ Yes, and even modify them

✅ Yes, read-only

Best for

Validation, blocking bad data

Logging, auditing, syncing

Risk if query fails

Trigger doesn't run

Trigger already ran

The rule of thumb: if your trigger protects data, use BEFORE. If it records what happened, use AFTER.


OLD and NEW — The Two Keywords You'll Use Constantly

When a trigger fires, it gets access to two special row references:

  • OLD — what the row looked like before the change

  • NEW — what the row looks like after the change

Not every event gives you both:

Event

OLD available?

NEW available?

INSERT

❌ Nothing existed before

✅ Yes

UPDATE

✅ Yes

✅ Yes

DELETE

✅ Yes

❌ Nothing exists after

This is why a backup trigger on DELETE uses OLD.column — the row is already gone, so NEW doesn't exist.


The One Restriction That Trips Everyone Up

You cannot use COMMIT or ROLLBACK inside a trigger.

Triggers run inside the same transaction as the statement that fired them. They don't get their own. If the main query rolls back, the trigger's work rolls back too. If you try to manually commit inside a trigger, MySQL throws an error.

Also: you cannot call a trigger manually. There's no EXEC trigger_name. They only fire when their table event happens. If you need to test one, you have to actually run an INSERT/UPDATE/DELETE.


10 Real MCQs From Accenture Technical Assessments

These aren't made-up examples. These patterns appear directly in company assessments. Understanding why each answer is correct matters more than memorizing the code.


1. Student Record Backup on Deletion

Scenario: When a student is deleted from students, automatically save their record to student_backup with the deletion timestamp.

sql
CREATE TRIGGER after_student_delete
AFTER DELETE ON students
FOR EACH ROW
BEGIN
    INSERT INTO student_backup (student_id, name, department, deleted_on)
    VALUES (OLD.student_id, OLD.name, OLD.department, NOW());
END;

Why AFTER DELETE? The student is already gone from the main table — we're reacting, not preventing. We use OLD because NEW doesn't exist after a delete.

Why not BEFORE? We want the deletion to succeed first. If we put this in a BEFORE trigger and the INSERT somehow failed, it could block the delete.


2. Salary Update Log

Scenario: Every time an employee's salary changes, log the old amount, new amount, and when it happened.

sql
CREATE TRIGGER after_salary_update
AFTER UPDATE ON employees
FOR EACH ROW
BEGIN
    INSERT INTO salary_log (emp_id, old_salary, new_salary, updated_on)
    VALUES (OLD.emp_id, OLD.salary, NEW.salary, NOW());
END;

The key insight here: OLD.salary and NEW.salary exist simultaneously inside an UPDATE trigger. This is the only event where you get both — use it whenever you need a before/after comparison in your log.


3. New Employee Registration Log

Scenario: When a new employee is added, automatically log their ID and join date.

sql
CREATE TRIGGER after_employee_insert
AFTER INSERT ON employees
FOR EACH ROW
BEGIN
    INSERT INTO register_log (emp_id, joined_on)
    VALUES (NEW.emp_id, NOW());
END;

Notice: No OLD here — on an INSERT, the row didn't exist before, so OLD is undefined. Always NEW on inserts.


4. Product Deletion Tracker

Scenario: Log the product ID and exact time whenever a product is deleted.

sql
CREATE TRIGGER after_product_delete
AFTER DELETE ON products
FOR EACH ROW
BEGIN
    INSERT INTO product_log (prod_id, deleted_at)
    VALUES (OLD.prod_id, NOW());
END;

Same pattern as #1 — AFTER DELETE, always OLD. If you remember nothing else from this section, remember: delete = OLD, insert = NEW, update = both.


5. Validate Order Total Before Insert

Scenario: Reject any order where total_amount is negative, before it ever hits the table.

sql
CREATE TRIGGER before_order_insert
BEFORE INSERT ON orders
FOR EACH ROW
BEGIN
    IF NEW.total_amount < 0 THEN
        SIGNAL SQLSTATE '45000'
        SET MESSAGE_TEXT = 'Total amount cannot be negative';
    END IF;
END;

This is the validation pattern — and it's the most important one in any assessment. SIGNAL SQLSTATE '45000' is how MySQL throws a custom error from inside a trigger. The 45000 code means "user-defined exception." The INSERT never completes, the row never reaches the table.

Why BEFORE? Because we want to stop bad data from entering at all. AFTER would be too late.


6. Attendance Update Monitor

Scenario: Log old and new attendance percentage whenever it changes.

sql
CREATE TRIGGER after_attendance_update
AFTER UPDATE ON attendance
FOR EACH ROW
BEGIN
    INSERT INTO attendance_log (stud_id, old_percent, new_percent, updated_on)
    VALUES (OLD.stud_id, OLD.percentage, NEW.percentage, NOW());
END;

Straightforward AFTER UPDATE. The interesting production question here: what if someone updates the row but the percentage doesn't actually change? You'd still log it. See #8 for how to fix that.


7. Prevent Deletion of Admin Users

Scenario: Admin users cannot be deleted. If someone tries, throw an error and block it.

sql
CREATE TRIGGER before_user_delete
BEFORE DELETE ON users
FOR EACH ROW
BEGIN
    IF OLD.role = 'Admin' THEN
        SIGNAL SQLSTATE '45000'
        SET MESSAGE_TEXT = 'Cannot delete Admin users';
    END IF;
END;

BEFORE DELETE with SIGNAL — the protection pattern. This is used heavily in production for protecting critical records. Notice we check OLD.role because that's the row being deleted.


8. Log Department Change Only When It Actually Changed

Scenario: Only write to the log if the department actually changed — not on every UPDATE.

sql
CREATE TRIGGER after_dept_update
AFTER UPDATE ON employees
FOR EACH ROW
BEGIN
    IF OLD.department <> NEW.department THEN
        INSERT INTO dept_change_log (emp_id, old_dept, new_dept, updated_on)
        VALUES (OLD.emp_id, OLD.department, NEW.department, NOW());
    END IF;
END;

This is the conditional log pattern — and it's smarter than #6. In real systems, UPDATE statements fire even when nothing meaningful changed (batch jobs, ORM quirks). Comparing OLD vs NEW before logging keeps your audit table clean and meaningful.


9. Customer Insertion Tracker

Scenario: Log when a new customer was created.

sql
CREATE TRIGGER after_customer_insert
AFTER INSERT ON customers
FOR EACH ROW
BEGIN
    INSERT INTO customer_log (cust_id, created_on)
    VALUES (NEW.cust_id, NOW());
END;

AFTER INSERT, NEW only. This is the cleanest trigger pattern — react to a successful insert, log it. Nothing can go wrong here because you're not blocking anything.


10. Prevent Negative Price on Product Update

Scenario: Before any price update, check that the new price is not negative.

sql
CREATE TRIGGER before_price_update
BEFORE UPDATE ON products
FOR EACH ROW
BEGIN
    IF NEW.price < 0 THEN
        SIGNAL SQLSTATE '45000'
        SET MESSAGE_TEXT = 'Price cannot be negative';
    END IF;
END;

BEFORE UPDATE — validation on write. The new value is available via NEW.price even before it's saved, which is what makes BEFORE triggers so powerful for data integrity.


Pattern Recognition — The Cheat Sheet

Once you see the pattern, assessments become easy:

Scenario type

Timing

Event

Keyword

Log what was deleted

AFTER

DELETE

OLD

Log what was inserted

AFTER

INSERT

NEW

Log before/after a change

AFTER

UPDATE

OLD + NEW

Block bad data on insert

BEFORE

INSERT

NEW + SIGNAL

Block bad data on update

BEFORE

UPDATE

NEW + SIGNAL

Protect rows from deletion

BEFORE

DELETE

OLD + SIGNAL

Log only real changes

AFTER

UPDATE

IF OLD <> NEW


Common Mistakes to Avoid

Using AFTER when you need BEFORE for validation. Once the row hits the table, you can't un-insert it from inside a trigger. Always validate in BEFORE.

Forgetting that DELETE has no NEW. Writing NEW.column inside a DELETE trigger causes an error. The row is gone — only OLD exists.

Not handling NULL in comparisons. IF OLD.department <> NEW.department fails silently when either value is NULL. In production, use COALESCE or IS DISTINCT FROM to handle NULLs safely.

Writing triggers that call each other. If Trigger A updates Table B, and Table B has Trigger B that updates Table A — you get an infinite loop. MySQL limits recursive trigger depth, but hitting that limit crashes the query.

Forgetting DELIMITER in MySQL CLI. The semicolon inside your BEGIN...END block terminates the CREATE TRIGGER statement early. Use DELIMITER // before and DELIMITER ; after when running triggers from the command line.

GeeksforGeeks has a solid reference on MySQL trigger restrictions that's worth reading before any assessment — it covers the COMMIT/ROLLBACK restriction and recursive trigger limits in detail.


What's Next

Triggers are one part of database automation. Once you're comfortable here, the natural progression is:

  • Views — virtual tables built from queries, great for simplifying complex joins

  • Stored Procedures — reusable logic blocks you call explicitly (unlike triggers)

  • Transactions — manual COMMIT/ROLLBACK control that triggers run inside of

If the joins and subqueries post felt comfortable, triggers will click quickly. The mental model is the same — you're still writing SELECT, INSERT, and conditionals. You're just telling the database when to run them automatically.


Continue your SQL journey with these notes from NoteHub:

SQL Series

DBMS Foundations

Placement Prep