Обсуждение: Trigger difference in 9.0 and 9.3

Поиск
Список
Период
Сортировка

Trigger difference in 9.0 and 9.3

От
Erwan Tanajaya
Дата:
Hi All,

how i write this trigger in postgresql 9.3 in postgresql 9.0 it is running well

i had transaction header and transaction detail table
I had trigger on after detail table inserted, updated, or deleted it update value on header table
when header is deleted i had trigger that run in before delete event to delete the detail table.

i need header run in before event because when detail is deleted(or update and insert) and the trigger is running i need selecting data from header. 

now in postgresql 9.3 i get this error "tuple to be updated was already modified by an operation triggered by current command", it running well in 9.0

there is hint that said "Consider using an AFTER trigger instead of a before trigger to propagate changes to other row", i cannot use after trigger because i need selecting data from header when detail trigger is fired.

any idea how i should do this on postgresql 9.3

Best Regards,
Erwan Tanajaya



Sorry for my bad english

Re: Trigger difference in 9.0 and 9.3

От
Tom Lane
Дата:
Erwan Tanajaya <erwan.tanajaya@gmail.com> writes:
> how i write this trigger in postgresql 9.3 in postgresql 9.0 it is running
> well

> i had transaction header and transaction detail table
> I had trigger on after detail table inserted, updated, or deleted it update
> value on header table
> when header is deleted i had trigger that run in before delete event to
> delete the detail table.

> i need header run in before event because when detail is deleted(or update
> and insert) and the trigger is running i need selecting data from header.

> now in postgresql 9.3 i get this error "tuple to be updated was already
> modified by an operation triggered by current command", it running well in
> 9.0

I suppose you're running into this 9.3 change:

    Author: Kevin Grittner <kgrittn@postgresql.org>
    Branch: master Release: REL9_3_BR [6868ed749] 2012-10-26 14:55:36 -0500

    Throw error if expiring tuple is again updated or deleted.

    This prevents surprising behavior when a FOR EACH ROW trigger
    BEFORE UPDATE or BEFORE DELETE directly or indirectly updates or
    deletes the the old row.  Prior to this patch the requested action
    on the row could be silently ignored while all triggered actions
    based on the occurence of the requested action could be committed.
    One example of how this could happen is if the BEFORE DELETE
    trigger for a "parent" row deleted "children" which had trigger
    functions to update summary or status data on the parent.

    This also prevents similar surprising problems if the query has a
    volatile function which updates a target row while it is already
    being updated.

    Where the new error messages are generated, in most cases the best
    fix will be to move code from the BEFORE trigger to an AFTER
    trigger.  Where this is not feasible, the trigger can avoid the
    error by re-issuing the triggering statement and returning NULL.

    Kevin Grittner and Tom Lane with input from Florian Pflug and
    Robert Haas, based on problems encountered during conversion of
    Wisconsin Circuit Court trigger logic to plpgsql triggers.


If you're running into this, it's fairly likely that your original
coding didn't actually do what it appears to do.  I'd recommend you
rearrange your trigger code as suggested just for clarity's sake,
even if it somehow was not in fact buggy.  But if you're convinced
it wasn't buggy, you can have the initial trigger return NULL in
cases where the initial update can be skipped (which is what 9.0
was doing silently).

            regards, tom lane


Re: Trigger difference in 9.0 and 9.3

От
Erwan Tanajaya
Дата:
On Mon, Sep 8, 2014 at 9:28 PM, Tom Lane <tgl@sss.pgh.pa.us> wrote:
Erwan Tanajaya <erwan.tanajaya@gmail.com> writes:
> how i write this trigger in postgresql 9.3 in postgresql 9.0 it is running
> well

> i had transaction header and transaction detail table
> I had trigger on after detail table inserted, updated, or deleted it update
> value on header table
> when header is deleted i had trigger that run in before delete event to
> delete the detail table.

> i need header run in before event because when detail is deleted(or update
> and insert) and the trigger is running i need selecting data from header.

> now in postgresql 9.3 i get this error "tuple to be updated was already
> modified by an operation triggered by current command", it running well in
> 9.0

I suppose you're running into this 9.3 change:

    Author: Kevin Grittner <kgrittn@postgresql.org>
    Branch: master Release: REL9_3_BR [6868ed749] 2012-10-26 14:55:36 -0500

    Throw error if expiring tuple is again updated or deleted.

    This prevents surprising behavior when a FOR EACH ROW trigger
    BEFORE UPDATE or BEFORE DELETE directly or indirectly updates or
    deletes the the old row.  Prior to this patch the requested action
    on the row could be silently ignored while all triggered actions
    based on the occurence of the requested action could be committed.
    One example of how this could happen is if the BEFORE DELETE
    trigger for a "parent" row deleted "children" which had trigger
    functions to update summary or status data on the parent.

    This also prevents similar surprising problems if the query has a
    volatile function which updates a target row while it is already
    being updated.

    Where the new error messages are generated, in most cases the best
    fix will be to move code from the BEFORE trigger to an AFTER
    trigger.  Where this is not feasible, the trigger can avoid the
    error by re-issuing the triggering statement and returning NULL.

    Kevin Grittner and Tom Lane with input from Florian Pflug and
    Robert Haas, based on problems encountered during conversion of
    Wisconsin Circuit Court trigger logic to plpgsql triggers.


If you're running into this, it's fairly likely that your original
coding didn't actually do what it appears to do.  I'd recommend you
rearrange your trigger code as suggested just for clarity's sake,
even if it somehow was not in fact buggy.  But if you're convinced
it wasn't buggy, you can have the initial trigger return NULL in
cases where the initial update can be skipped (which is what 9.0
was doing silently).

                        regards, tom lane

hi Tom Lane,

i do as you suggested, i returning null on the initial before delete trigger(parrent trigger), but when i returning null no delete action is accured.
am i doing wrong ?

"the trigger can avoid the error by re-issuing the triggering statement and returning NULL"
how i re-issuing the triggering statement ?

it wasn't buggy in postgresql 9.0 and other database i use, i got the expected result that i want.

thank you in advance
i really need solution for this simple case

Best Regards,
Erwan Tanajaya

Re: Trigger difference in 9.0 and 9.3

От
David G Johnston
Дата:
Erwan Tanajaya-2 wrote
> i do as you suggested, i returning null on the initial before delete
> trigger(parrent trigger), but when i returning null no delete action is
> accured.
> am i doing wrong ?
>
> "the trigger can avoid the error by re-issuing the triggering statement
> and
> returning NULL"
> how i re-issuing the triggering statement ?

If you are inside a DELETE trigger you would do something like:

DELETE FROM table_this_trigger_is_on WHERE pk_for_the_table =
OLD.pk_for_the_table;
RETURN NULL;

The returning of NULL prevents the original DELETE action but since you took
care of it manually the final result is that the record in question is still
gone.

That said you need to see if you can introduce logic somewhere that says:
"since my parent record is going to go away anyway I should just avoid
updating it in the first place".

Aside from that a self-contain and reasonably faithful reproduction of your
exact problem might help you either obtain advice or make Kevin et. al.
reconsider the solution put in place to avoid the problem they were facing.

Note that in the two years this patch has been in place this is the first
time I recall seeing a report of this nature...which suggests there is a
correct solution that you are missing.  Though I am also thinking 9.3 hasn't
received as much adoption in the past two years as we would have liked...

David J.




--
View this message in context: http://postgresql.nabble.com/Trigger-difference-in-9-0-and-9-3-tp5818162p5826449.html
Sent from the PostgreSQL - novice mailing list archive at Nabble.com.


Re: Trigger difference in 9.0 and 9.3

От
Erwan Tanajaya
Дата:
On Tue, Nov 11, 2014 at 10:04 AM, David G Johnston <david.g.johnston@gmail.com> wrote:
Erwan Tanajaya-2 wrote
> i do as you suggested, i returning null on the initial before delete
> trigger(parrent trigger), but when i returning null no delete action is
> accured.
> am i doing wrong ?
>
> "the trigger can avoid the error by re-issuing the triggering statement
> and
> returning NULL"
> how i re-issuing the triggering statement ?

If you are inside a DELETE trigger you would do something like:

DELETE FROM table_this_trigger_is_on WHERE pk_for_the_table =
OLD.pk_for_the_table;
RETURN NULL;

The returning of NULL prevents the original DELETE action but since you took
care of it manually the final result is that the record in question is still
gone.


Thank you very much David for your help  :)
i will try immediately


 
That said you need to see if you can introduce logic somewhere that says:
"since my parent record is going to go away anyway I should just avoid
updating it in the first place".

Aside from that a self-contain and reasonably faithful reproduction of your
exact problem might help you either obtain advice or make Kevin et. al.
reconsider the solution put in place to avoid the problem they were facing.

Note that in the two years this patch has been in place this is the first
time I recall seeing a report of this nature...which suggests there is a
correct solution that you are missing.  Though I am also thinking 9.3 hasn't
received as much adoption in the past two years as we would have liked...

i am using 9.0 or 9.1 for long time and statisfied with this version, until ubuntu 14.04 and hosting provider force me to use newer version.

I really hope Kevin and other dev have a better solution for this case(applying those patch for update case only maybe), i don't have enough faith that i correct or something for now but if there are another person encounter the same problem that i had(and i hope they reporting it on the mailing list) maybe dev should really consider this problem.


David J.


Best Regards,
Erwan Tanajaya
 

Re: Trigger difference in 9.0 and 9.3

От
Kevin Grittner
Дата:
Erwan Tanajaya <erwan.tanajaya@gmail.com> wrote:

>> Aside from that a self-contain and reasonably faithful
>> reproduction of your exact problem might help you either obtain
>> advice or make Kevin et. al. reconsider the solution put in
>> place to avoid the problem they were facing.

> I really hope Kevin and other dev have a better solution for
> this case

You really need to provide a self-contained test case to
demonstrate what you think the problem is.  For example, you might
think something like this works:

------------------------------------------------------------

create table tran_header
  (
    tran_id int not null,
    total_amt int not null default (0),
    primary key (tran_id)
  );
create table tran_detail
  (
    tran_id int not null,
    tran_det_id int not null,
    amt int not null,
    primary key (tran_id, tran_det_id)
  );

create function tran_detail_insert_func()
  returns trigger
  language plpgsql
as $$
begin
  update tran_header
    set total_amt = total_amt + new.amt
    where tran_header.tran_id = new.tran_id;
  return new;
end;
$$;
create function tran_detail_update_func()
  returns trigger
  language plpgsql
as $$
begin
  if old.tran_id = new.tran_id then
    update tran_header
      set total_amt = total_amt + new.amt - old.amt
      where tran_header.tran_id = old.tran_id;
  else
    update tran_header
      set total_amt = total_amt - old.amt
      where tran_header.tran_id = old.tran_id;
    update tran_header
      set total_amt = total_amt + new.amt
      where tran_header.tran_id = new.tran_id;
  end if;
  return new;
end;
$$;
create function tran_detail_delete_func()
  returns trigger
  language plpgsql
as $$
begin
  update tran_header
    set total_amt = total_amt - old.amt
    where tran_header.tran_id = old.tran_id;
  return old;
end;
$$;
create function tran_header_delete_func()
  returns trigger
  language plpgsql
as $$
begin
  delete from tran_detail where tran_id = old.tran_id;
  return old;
end;
$$;

create trigger tran_detail_insert_trig
  after insert on tran_detail
  for each row execute procedure tran_detail_insert_func();
create trigger tran_detail_update_trig
  after update on tran_detail
  for each row execute procedure tran_detail_update_func();
create trigger tran_detail_delete_trig
  after delete on tran_detail
  for each row execute procedure tran_detail_delete_func();
create trigger tran_header_delete_trig
  before delete on tran_header
  for each row execute procedure tran_header_delete_func();

insert into tran_header (tran_id) values (1), (2), (3);
insert into tran_detail (tran_id, tran_det_id, amt) values
  (1,1,100),(1,2,200),(2,1,100),(2,2,200),(2,3,300);

select * from tran_header order by tran_id;

delete from tran_header where tran_id = 2;

------------------------------------------------------------

The above probably doesn't do what you expect or intend on 9.0.
Try a select from tran_header and you will notice that the row you
probably *thought* you deleted is still there.  It is silently
doing the wrong thing.  You can fix it like this:

------------------------------------------------------------

create or replace function tran_header_delete_func()
  returns trigger
  language plpgsql
as $$
begin
  delete from tran_detail where tran_id = old.tran_id;
  if found then
    delete from tran_header where tran_id = old.tran_id;
    return null;
  end if;
  return old;
end;
$$;

------------------------------------------------------------

You could also have fixed it by deleting the header row *twice*,
since (unless there is some other process adding detail in between)
there won't be any detail the second time to update the header row
and cause a problem -- what the above fix does it to do that second
delete automatically.

What happens in more recent versions is that rather than silently
failing to delete the specified master row, it gives an error so
you can fix your broken code.

Now, if you can provide a self-contained test case (similar to the
above -- something that can run from an empty database to
demonstrate your issue), we can offer other advice or consider some
adjustment to the code if it seems warranted.  I'm pretty sure we
will not go back to the type of failure shown above.

--
Kevin Grittner
EDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company


Re: Trigger difference in 9.0 and 9.3

От
Erwan Tanajaya
Дата:
Thank you Kevin,

i already testing your test case in 9.0 (with some modification) and you right i did not get result that i want, i am really sorry for this issue.

i will re-test my script on other database i use also to find different behaviour between database that i use to port my application.

Thank you all for your help


Best Regards,
Erwan Tanajaya