CodeGym /Courses /SQL SELF /Analyzing Common Mistakes When Developing Triggers

Analyzing Common Mistakes When Developing Triggers

SQL SELF
Level 58 , Lesson 4
Available

Alright, folks, by now you already know what triggers are, their types, how they work, and you’ve even learned how to create them for different tasks. But, as is often the case in programming, knowing what you can do is important, but knowing what you shouldn’t do is just as crucial. Today, we’re gonna dig into the typical mistakes developers make when working with triggers, so you can dodge them and save yourself a couple hours—or maybe even days—of debugging.

Trigger Recursion: When a Trigger Calls Itself

This is probably the most popular rookie mistake. Imagine you created a trigger that updates a value in one of your table’s columns, like last_modified. But as soon as this change happens, the update operation itself fires the trigger again. That’s an infinite loop, and your server crashes with a stack overflow error.

Example:

CREATE OR REPLACE FUNCTION update_last_modified()
RETURNS TRIGGER AS $$
BEGIN
    -- Update the last_modified field
    UPDATE my_table
    SET last_modified = NOW()
    WHERE id = NEW.id;
    RETURN NEW;
END;
$$ LANGUAGE plpgsql;

CREATE TRIGGER after_update
AFTER UPDATE ON my_table
FOR EACH ROW
EXECUTE FUNCTION update_last_modified();

What’s going wrong here? The UPDATE operation inside the function triggers the same trigger that created it. Voilà, infinite loop.

How to avoid it:

Use the OLD variable and compare values before making changes:

CREATE OR REPLACE FUNCTION update_last_modified_safe()
RETURNS TRIGGER AS $$
BEGIN
    -- Check if the value changed
    IF NEW.last_modified IS DISTINCT FROM OLD.last_modified THEN
        NEW.last_modified = NOW();
    END IF;
    RETURN NEW;
END;
$$ LANGUAGE plpgsql;

Make sure you’re not calling unnecessary operations inside your trigger.

Incorrect Use of OLD and NEW

These variables are your real buddies when working with triggers, but in inexperienced hands, they can be a real headache. OLD holds the data before the row was changed, and NEW is the data that will be saved after the change.

The mistake often happens when you misinterpret or try to use these variables where they’re not available. For example, if you’re working with a BEFORE INSERT trigger, OLD won’t be available—the row is just being created, after all.

Example mistake:

-- This will throw an error because OLD doesn’t exist on insert
CREATE OR REPLACE FUNCTION log_inserts()
RETURNS TRIGGER AS $$
BEGIN
    INSERT INTO audit_log (old_data, new_data)
    VALUES (OLD.my_column, NEW.my_column);
    RETURN NEW;
END;
$$ LANGUAGE plpgsql;

How to avoid it:

Be careful where you use OLD and NEW:

  • OLD is available for UPDATE and DELETE operations.
  • NEW is available for INSERT and UPDATE operations.

Multiple Triggers on One Operation

In PostgreSQL, you can create several triggers for the same operation and table. Sounds handy, but in reality, this can lead to chaos if triggers start conflicting with each other or changing the same data.

Example:

-- Trigger 1
CREATE OR REPLACE FUNCTION trigger_one()
RETURNS TRIGGER AS $$
BEGIN
    -- Trigger 1 logic
    RETURN NEW;
END;
$$ LANGUAGE plpgsql;

-- Trigger 2
CREATE OR REPLACE FUNCTION trigger_two()
RETURNS TRIGGER AS $$
BEGIN
    -- Trigger 2 logic
    RETURN NEW;
END;
$$ LANGUAGE plpgsql;

-- Creating two triggers
CREATE TRIGGER trigger_one AFTER INSERT ON my_table EXECUTE FUNCTION trigger_one();
CREATE TRIGGER trigger_two AFTER INSERT ON my_table EXECUTE FUNCTION trigger_two();

Both triggers will fire on row insert into my_table. If their logic isn’t synced up, you might get unpredictable results.

How to avoid it:

  • Plan your trigger architecture ahead of time.
  • If triggers deal with the same logic, combine them into one trigger.

Performance Issues

Triggers add extra computation to every operation they’re attached to. If you use triggers on tables with lots of operations or records, this can seriously hurt performance.

Bad example:

CREATE OR REPLACE FUNCTION heavy_trigger_function()
RETURNS TRIGGER AS $$
BEGIN
    -- Heavy operation that runs on every row update
    PERFORM some_heavy_query();
    RETURN NEW;
END;
$$ LANGUAGE plpgsql;

CREATE TRIGGER performance_killer AFTER UPDATE ON huge_table EXECUTE FUNCTION heavy_trigger_function();

How to avoid it:

  • Keep trigger logic as light as possible. If you need to run a heavy operation, consider moving it to a background job.
  • Use trigger execution conditions with the WHEN clause:
CREATE TRIGGER optimized_trigger
AFTER UPDATE ON my_table
WHEN (OLD.column_name IS DISTINCT FROM NEW.column_name)
EXECUTE FUNCTION light_function();

Triggers and Transactions

Triggers run inside the transaction started by your query. If an error happens inside the trigger, the whole transaction gets rolled back. This can be useful in some scenarios, but can also cause unexpected problems if you haven’t thought through error handling.

Example mistake:

CREATE OR REPLACE FUNCTION error_prone_trigger()
RETURNS TRIGGER AS $$
BEGIN
    -- Throwing an error
    RAISE EXCEPTION 'Something went wrong!';
    RETURN NEW;
END;
$$ LANGUAGE plpgsql;

If this trigger fires, your main query’s transaction will be rolled back.

How to avoid it:

Add error handling to your triggers to minimize the impact on the main transaction:

CREATE OR REPLACE FUNCTION safe_trigger()
RETURNS TRIGGER AS $$
BEGIN
    BEGIN
        -- Code that might throw an error
        INSERT INTO another_table VALUES (NEW.data);
    EXCEPTION
        WHEN OTHERS THEN
            RAISE NOTICE 'An error occurred, but we handled it gracefully.';
    END;
    RETURN NEW;
END;
$$ LANGUAGE plpgsql;

Practical Tips

  1. Keep triggers as simple as possible. If your trigger feels too big or complicated, you probably need to split it into separate functions or rethink the logic.

  2. Always test triggers on small data sets. Before hooking a trigger up to a critical table, try it out on test data first.

  3. Document your triggers. A few months down the line, you or your teammates might forget why a trigger was created in the first place. Good documentation saves a lot of headaches.

  4. Try to avoid triggers for stuff you can handle at the app level. Triggers are great for automating tasks that need to happen instantly, but using them for complex business logic can cause problems later.

  5. Keep an eye on performance. Always monitor how triggers affect your database performance, especially as your data or load grows.

With these tips and the knowledge you’ve picked up today, you’re ready not just to create triggers, but to write ones that work right, efficiently, and without nasty surprises.

2
Task
SQL SELF, level 58, lesson 4
Locked
Creating a Safe Trigger for Updating a Column
Creating a Safe Trigger for Updating a Column
1
Survey/quiz
Row-level and Table-level Triggers, level 58, lesson 4
Unavailable
Row-level and Table-level Triggers
Row-level and Table-level Triggers
Comments
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION