Search code examples
postgresqltriggers

Postgres if condition comparing timestamps in trigger not working


I'm trying to implement soft delete with triggers. So that when a record in a table is soft deleted, a trigger handles soft deleting all dependent rows in another table. That is, table B has a foreign key to table A. When I soft delete a record in table A, I want all records in table B with a foreign key to the affected record in table A to also get soft deleted.

The main implementation of the trigger works. But I want to make sure that I don't update any records unless I actually need to. So if a soft deleted record in table A would be updated again for some reason, the records in table B should not be updated.

This is the first implementation that works:

CREATE OR REPLACE FUNCTION TableB_SoftDelete_WhenTableASoftDeleted() RETURNS TRIGGER AS
    $$
    BEGIN
        RAISE NOTICE 'OLD.deleted_at: %', OLD.deleted_at;
        RAISE NOTICE 'NEW.deleted_at: %', NEW.deleted_at;
        IF NEW.deleted_at IS NOT NULL THEN
            RAISE NOTICE 'Will update Table B with a_id %', NEW.id;
            UPDATE table_b
                SET deleted_at = NEW.deleted_at
                WHERE a_id = NEW.id;
        ELSE
            RAISE NOTICE 'IF didnt match...';
        END IF;
        RETURN NEW;
    END;
    $$
    LANGUAGE plpgsql;

CREATE OR REPLACE TRIGGER TR_TableA_AU AFTER UPDATE ON table_a
    FOR EACH ROW EXECUTE PROCEDURE TableB_SoftDelete_WhenTableASoftDeleted();

So when I do UPDATE table_a SET deleted_at = CURRENT_TIMESTAMP WHERE id = 1; I get the following log:

NOTICE:  OLD.deleted_at: <NULL>
NOTICE:  NEW.deleted_at: 2023-02-07 07:51:15.869723
NOTICE:  Will update Table B with a_id 1

But this would obviously update Table B any time an already soft deleted record in Table A is updated again. So then I try to change my condition into IF NEW.deleted_at IS NOT NULL AND NEW.deleted_at != OLD.deleted_at THEN, but then my IF isn't hit anymore, and I don't understand why. I see this in the log:

NOTICE:  OLD.deleted_at: <NULL>
NOTICE:  NEW.deleted_at: 2023-02-07 07:52:31.22866
NOTICE:  IF didnt match...

deleted_at is differing in the OLD and NEW records. deleted_at in both tables are of the type timestamp(6) without time zone.


Solution

  • On many databases, logical operations work like this:

    null != '2023-02-07' Returns = false
    

    So it's better if you write like this:

    RAISE NOTICE 'OLD.deleted_at: %', OLD.deleted_at;
    RAISE NOTICE 'NEW.deleted_at: %', NEW.deleted_at;
    IF ((OLD.deleted_at is null) and (NEW.deleted_at IS NOT null)) or (OLD.deleted_at != NEW.deleted_at) THEN
        RAISE NOTICE 'Will update Table B with a_id %', NEW.id;
        UPDATE table_b
            SET deleted_at = NEW.deleted_at
            WHERE a_id = NEW.id;
    ELSE
        RAISE NOTICE 'IF didnt match...';
    END IF;
    RETURN NEW;