postgresqlcaseplpgsqldatabase-trigger

update query using `CASE WHEN` not working when trigger fired in PostgreSQL


I try to use a CASE WHEN condition to an update query in PostgreSQL.

The query I am doing is :

if new.bts_trx_gc = 'Terminé' then
        update stt set stt_doe_gc_bts = (case when old.bts_trx_gc <> new.bts_trx_gc then 'Demandé' else stt_doe_gc_bts end) where stt_id = new.bts_id;
        raise info 'test est un test';
    end if;

The trigger fire every time as I can see the raise info in the consol. But the update does not work.

This query is to make sure when a user is updating a field through a web interface, the update fire only if old.bts_trx_gc <> new.bts_trx_gc. Because it may happens the web interface even if the bts_trx_gc value is not modified, a query with bts_trx_gc = Terminé is sent. And I only want to fire the update when this field goes from something to Terminé only.

Considering the query above, What I am trying to do is if the IF condition is true and old.bts_trx_gc <> new.bts_trx_gc then bts_trx_gc = Demandé else keep same value.

Here the how trigger function :

create or replace function after_update_bts_trx_gc() returns trigger
    language plpgsql
as
$$
begin

    if new.bts_trx_gc = 'Terminé' then
        update stt set stt_doe_gc_bts = (case when old.bts_trx_gc <> new.bts_trx_gc then 'Demandé' else stt_doe_gc_bts end) where stt_id = new.bts_id;
        raise info 'test est un test';
    end if;

    return new;

end;
$$;

And here the trigger :

create trigger after_update_bts_trx_gc
    after update
    on etude_bts
    for each row
execute procedure after_update_bts_trx_gc();

I want to learn from this, so, if possible, explain to me what I'm doing wrong here, or if my approach is lacking insight.


Solution

  • I would use an IF to conditionally run the UPDATE, not a CASE expression. This has the added benefit, that you don't run unneeded UPDATEs.

    If bts_trx_gc can contain null values you should use is distinct from rather than <> to properly deal with NULL values.

    create or replace function after_update_bts_trx_gc() returns trigger
        language plpgsql
    as
    $$
    begin
      if new.bts_trx_gc = 'Terminé' and old.bts_trx_gc IS DISTINCT FROM new.bts_trx_gc then
        UPDATE stt 
           set stt_doe_gc_bts = 'Demandé'
        WHERE stt_id = new.bts_id;
    
        raise info 'test est un test';
      end if;
      return new;
    end;
    $$;
    

    This was my original answer where I overlooked the fact that the UPDATE targeted a different table. I am leaving it for reference though.

    Don't use UPDATE to change the row in a trigger. Use a BEFORE UPDATE trigger and assign the value to the new row when the condition is met:

    create or replace function after_update_bts_trx_gc() returns trigger
        language plpgsql
    as
    $$
    begin
      if new.bts_trx_gc = 'Terminé' and old.bts_trx_gc <> new.bts_trx_gc then
        new.stt_doe_gc_bts := 'Demandé';
        raise info 'test est un test';
      end if;
      return new;
    end;
    $$;
    

    But you need a BEFORE trigger to make that work:

    create trigger after_update_bts_trx_gc
        BEFORE update
        on etude_bts
        for each row
    execute procedure after_update_bts_trx_gc();
    

    It would be even more efficient to not fire the trigger at all, if the column doesn't change:

    create trigger after_update_bts_trx_gc
        BEFORE update 
        on etude_bts
        for each row
        when (old.bts_trx_gc <> new.bts_trx_gc)
    execute procedure after_update_bts_trx_gc();