Trigger on BEFORE INSERT OR UPDATE fired once or twice? - postgresql

I have created the following trigger;
CREATE TRIGGER material_trigger
BEFORE INSERT OR UPDATE ON materials
FOR EACH ROW
EXECUTE PROCEDURE my_proc ();
I'm struggling to understand when my_proc will be run on an upsert query, such as the below:
--UPSERT
INSERT INTO materials (id, col)
VALUES (1, 1)
ON CONFLICT (id) DO UPDATE SET
col='x'
If I'm running the upsert, am I right to think that:
in the case of an INSERT it will be run once
in the case of an UPDATE it will be run twice
Then logically if I ran this AFTER the upsert I would ensure that the my_proc was triggered only once?

The trigger is, infact fired twice in the case that the UPDATE is carried out. This can be demonstrated using the follwing:
-- create table
CREATE TABLE IF NOT EXISTS example (uuid int unique);
-- create function
CREATE OR REPLACE FUNCTION print_function ()
RETURNS TRIGGER
AS $body$
BEGIN
RAISE NOTICE 'Starting';
RAISE NOTICE 'METHOD: %' , TG_OP;
RETURN NEW;
END;
$body$
LANGUAGE plpgsql;
-- create trigger function
CREATE TRIGGER example_trigger
BEFORE INSERT OR UPDATE ON example
FOR EACH ROW
EXECUTE PROCEDURE print_function ();
-- upserts
INSERT INTO example(uuid) VALUES (1) ON CONFLICT(uuid) DO UPDATE SET uuid=5;
INSERT INTO example(uuid) VALUES (1) ON CONFLICT(uuid) DO UPDATE SET uuid=5;
which gives output:
NOTICE: Starting
NOTICE: METHOD: INSERT -- Populated with inital value
NOTICE: Starting
NOTICE: METHOD: INSERT -- Fired a first time for insert (can't insert as exists)
NOTICE: Starting
NOTICE: METHOD: UPDATE -- Fired a second time for update
INSERT 0 1

Related

Retrieving Postgresql autoinc value from view trigger with JSDBC

I have a view with a trigger that executes on each insert. This trigger actually updates several other tables. Bellow is the pseudo code:
CREATE OR REPLACE FUNCTION insert_proc() RETURNS trigger AS $$
DECLARE
_id int4;
BEGIN
INSERT INTO table1 VALUES (NEW.key) RETURNING id INTO _id;
INSERT INTO table2 VALUES (DEFAULT,_id,NEW.field1,NEW.field2);
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
CREATE TRIGGER insert_trg INSTEAD OF INSERT ON myview FOR EACH ROW EXECUTE PROCEDURE insert_proc();
table1 is actually having a column of type SERIAL that generates new ids on each insert. This id is used to create a row in table2.
Now, I'd like to retrieve the generated id from Java code. With a "regular" table, I'd use Statement.getGeneratedKeys(). But I can't get this to work with the view/trigger, as the returned ResultSet is empty.
Does anyone have an idea?

postgres INSERT trigger fires even with ON CONFLICT DO NOTHING

In a couple of SO answers (1, 2), it's suggested that INSERT triggers shouldn't fire if there's a conflict and ON CONFLICT DO NOTHING is in the triggering statement. Perhaps I've misunderstood, but it does not seem to be true in my experiments.
Here's my SQL, run on Postgres 9.6.
CREATE TABLE t (
n text PRIMARY KEY
);
CREATE FUNCTION def() RETURNS trigger AS $$
BEGIN
RAISE NOTICE 'Called def()';
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
CREATE TRIGGER deftrig BEFORE INSERT ON t FOR EACH ROW EXECUTE PROCEDURE def();
If I then run a couple of inserts:
testdb=> insert into t (n) values ('dummy') on conflict do nothing;
NOTICE: Called def()
INSERT 0 1
testdb=> insert into t (n) values ('dummy') on conflict do nothing;
NOTICE: Called def()
INSERT 0 0
I would have expected to see Called def() the first time, but not the next.
What am I getting wrong?
A BEFORE INSERT trigger runs before the conflict check. The trigger has an opportunity to change the inserted values, and it wouldn't make sense to check for collisions before this happens. Per the documentation:
Note that the effects of all per-row BEFORE INSERT triggers are reflected in excluded values, since those effects may have contributed to the row being excluded from insertion.
An AFTER INSERT trigger will behave as you expect.

Having multiple trigger events when redirecting insertions to partition tables

I am trying to set up triggers for insert and update events for the master table of some partition tables in PostgreSQL. Each time an insertion is made into the master table, the insert trigger event will redirect it into the correct partition table. Consequently, I will need to return NULL from this function call, since I don't want the master table to be populated as well. If the master table receives an update event, it will update a timestamp before making the change in the table. The problem is that the update trigger is never fired. I am using PostgreSQL version 9.6.
I have tried to combine the trigger functions into one, and merged the called trigger procedures into one as well, but the results are the same. The update trigger is only triggered if I return NEW from the insertion trigger function (which populates the master table), or if I comment out the insertion trigger function altogether.
DROP SCHEMA IF EXISTS test CASCADE;
CREATE SCHEMA test;
SET SCHEMA 'test';
CREATE TYPE test_type AS ENUM ('unit', 'performance');
CREATE TABLE test (
type test_type NOT NULL,
score INTEGER NOT NULL CHECK (score > 0),
id SERIAL PRIMARY KEY,
updated_at TIMESTAMP DEFAULT current_timestamp
);
CREATE TABLE performance_test (
CHECK (type = 'performance')
) INHERITS (test);
CREATE FUNCTION insert_test()
RETURNS trigger AS
$$
BEGIN
INSERT INTO performance_test VALUES (NEW.*);
RETURN NULL;
END;
$$ LANGUAGE plpgsql;
CREATE FUNCTION update_timestamp()
RETURNS trigger AS
$$
BEGIN
RAISE NOTICE 'This is never reached.';
UPDATE performance_test
SET updated_at = current_timestamp
WHERE id = NEW.id;
RETURN NULL;
END;
$$ LANGUAGE plpgsql;
CREATE TRIGGER test_insertion BEFORE INSERT ON test
FOR EACH ROW EXECUTE PROCEDURE insert_test();
CREATE TRIGGER test_update BEFORE UPDATE ON test
FOR EACH ROW EXECUTE PROCEDURE update_timestamp();
---------------------------------------------------------------------------
INSERT INTO test VALUES ('performance', 10);
SELECT * FROM performance_test;
UPDATE test SET score = 20 WHERE id = 1;
SELECT * FROM performance_test;
I am not sure if it is possible to achieve what I want with this method, so I'm reaching out here for any advice. Thanks in advance!
/ Hampus
Row triggers must be defined on individual partitions, not the partitioned table. See https://www.postgresql.org/docs/10/ddl-partitioning.html#DDL-PARTITIONING-DECLARATIVE-LIMITATIONS
I don't know why the documentation for 9.6 doesn't mention this
working update trigger:
CREATE FUNCTION update_timestamp()
RETURNS trigger AS
$$
BEGIN
NEW.updated_at = now();
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
CREATE TRIGGER test_update BEFORE UPDATE ON performance_test
FOR EACH ROW EXECUTE PROCEDURE update_timestamp();
if you do UPDATE test SET score = 30, updated_at=DEFAULT; or UPDATE test SET score = 30, updated_at=current_timestamp; you might not need the update trigger.
Partitioning is not a free lunch because it has non-obvious effects on both behavior and performance, as you noticed by the trigger not behaving as you expected. If you make a mistake it can easily lead to failing queries and even bad data.
If you are really sure you need it you should make sure you understand it in detail and otherwise I'd recommend you to avoid it, most issues with slow queries can be solved by making sure the table statistics is up to date, using the right indexes, optimizing queries, changing Postgres configuration or adding more hardware.

PostgreSQL triggers and temporary table

I have created an before update and after update trigger on a postgresql db table.
There is a requirement to preserve historical record and at the same time create a new record for the said data. Old record is to be marked as archived.
I was planning on using temporary table to keep track of the NEW values and reset the NEW values such that it is marked as archived.
In my after update trigger I would read the data from the temporary table, and create a brand new active record.
My problem is temporary table created in before update trigger is not visible to after update trigger. Moreover I cannot even pass on any argument (of type record) to the after update trigger as it is not allowed.
I have already achieved the desired result in Oracle db, using Global Temporary table, but having a tough time in PostgreSQL.
Here is the sample code for before update trigger function:
CREATE OR REPLACE FUNCTION trigger_fct_trig_trk_beforeupdate()
RETURNS trigger AS
$BODY$
DECLARE
some variable declarations;
BEGIN
Drop table IF EXISTS track_tmp_test;
CREATE TEMPORARY TABLE track_tmp_test(
...
);
Insert into track_tmp_test (........)
values(NEW., NEW..., NEW.., NEW...);
NEW... := OLD...;
NEW... := OLD.... ;
NEW... := OLD...;
Mark the NEW.status : = 'archived';
RETURN NEW;
END
$BODY$
LANGUAGE plpgsql VOLATILE
COST 100;
CREATE TRIGGER trig_trk_test_beforeupdate
BEFORE UPDATE ON test
FOR EACH ROW EXECUTE PROCEDURE trigger_fct_trig_trk_beforeupdate() ;
NOW the after UPDATE trigger function:
CREATE OR REPLACE FUNCTION trigger_fct_trg_trk_afterupdate()
RETURNS trigger AS
$BODY$
DECLARE
some variables;
-- insert into original table the data from temporary that was inserted in before update trigger
INSERT into TEST (....)
select ....
from track_tmp_test ;
-- delete data from temporary table after insert
delete from track_tmp_test ;
EXCEPTION
WHEN OTHERS THEN
-- Consider logging the error and then re-raise
RAISE;
RETURN NEW;
END
$BODY$
LANGUAGE plpgsql VOLATILE
COST 100;
Is there a way that after update trigger can access the temporary table created in before update trigger function?
I cannot have a permanent table hold he values, as trigger can be fired by many users updating the data in the table.
There is no problem with access to temporary table from triggers, and following code working without issue (on PostgreSQL 9.4):
CREATE OR REPLACE FUNCTION public.f1()
RETURNS trigger
LANGUAGE plpgsql
AS $function$
begin
drop table if exists bubu;
create temp table bubu(a int);
insert into bubu values(10);
return new;
end
$function$
CREATE OR REPLACE FUNCTION public.f2()
RETURNS trigger
LANGUAGE plpgsql
AS $function$
declare r record;
begin
for r in select * from bubu
loop
raise notice '%', r;
end loop;
return null;
end
$function$
create trigger xx
before insert on omega
for each row execute procedure f1();
create trigger yy
after insert on omega
for each row execute procedure f2();
postgres=# insert into omega values(333);
NOTICE: (10)
INSERT 0 1
So I am sure, so your problem will not be in access to temporary tables. It works well. There can be a issue on some 8.2, 8.3 and older with invalid plans due reference on dropped objects. Isn't it your problem?
I can say, so your design is wrong - there is not any reason, why you have to use a temp table. Same job you can do in after trigger. Any operations inside triggers should be fast, pretty fast. Dropping or creating temporary table is not fast operation.
If you have a older PostgreSQL release, you have not to drop temp table every. You should to delete content only. See a article http://postgres.cz/wiki/Automatic_execution_plan_caching_in_PL/pgSQL
The temporary table should be visible as #Pavel explains, but that's not the main issue here.
Your approach might make sense in Oracle with a global temporary table. But the posted Postgres code does not.
The trigger is fired for each row. You would (drop and) create a temp table for every row, and call another trigger, just to do what you could easily do in one trigger directly.
Instead, to keep the old row and set it to archived, plus INSERT a copy of the NEW row:
Demo table:
CREATE TEMP TABLE test (id int, txt text, archived bool DEFAULT FALSE);
Trigger func:
CREATE OR REPLACE FUNCTION trg_test_beforeupdate()
RETURNS trigger AS
$func$
BEGIN
INSERT INTO test SELECT (NEW).*; -- insert a copy of the NEW row
SELECT (OLD).* INTO NEW; -- revert row to previous state
NEW.archived = TRUE; -- just set it to "archived"
RETURN NEW;
END
$func$ LANGUAGE plpgsql;
Trigger:
CREATE TRIGGER beforeupdate
BEFORE UPDATE ON test
FOR EACH ROW EXECUTE PROCEDURE trg_test_beforeupdate();
Test:
INSERT INTO test VALUES (1, 'foo'), (2, 'bar');
UPDATE test SET txt = 'baz' WHERE id = 1;
SELECT * FROM test;
Works.

Applying trigger on sqlite database in objective-c

How can I apply a trigger on my sqlite database using objective-c.
I want to apply trigger to check if a table has performed an insert operation.
Run a CREATE TRIGGER statement on it. You'll need to find some way to check the trigger fires, so if you e.g. make the trigger insert rows into another table, you can regularly check the table to see if it has new rows.
If you have a table named CSAppointment, the you can write:
ALTER TABLE "CSAppointment" ADD COLUMN "tableUid" INTEGER;
CREATE TRIGGER log_insert AFTER INSERT ON CSAppointment
BEGIN
INSERT INTO CSRowChanges(tableUId, rowUid, deleteFlag ) VALUES (NEW.tableUid, NEW.uid, 0 );
END;
CREATE TRIGGER log_update AFTER UPDATE ON CSAppointment
BEGIN
INSERT INTO CSRowChanges(tableUId, rowUid, deleteFlag) VALUES (NEW.tableUid, NEW.uid, 0) ;
END;
CREATE TRIGGER log_delete AFTER DELETE ON CSAppointment
BEGIN
INSERT INTO CSRowChanges(tableUId, rowUid, deleteFlag) VALUES (OLD.tableUid, OLD.uid, 1 ) ;
END;
This way you are adding triggers for insert, update and delete operations.
More information on here.