postgres INSERT trigger fires even with ON CONFLICT DO NOTHING - postgresql

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.

Related

How to make a PostgreSQL constraint only apply to a new value

I'm new to PostgreSQL and really loving how constraints work with row level security, but I'm confused how to make them do what I want them to.
I have a column and I want add a constraint that creates a minimum length for a text column, this check works for that:
(length((column_name):: text) > 6)
BUT, it also then prevents users updating any rows where column_name is already under 6 characters.
I want to make it so they can't change that value TO that, but can still update a row where that is already happening, so they can change it as needed according to my new policy.
Is this possible?
BUT, it also then prevents users updating any rows where column_name is already under 6 characters.
Well, no. When you try to add that CHECK constraint, all existing rows are checked, and an exception is raised if any violation is found.
You would have to make it NOT VALID. Then yes.
You really need a trigger on INSERT or UPDATE that checks new values. Not as cheap and not as bullet-rpoof, but still pretty solid. Like:
CREATE OR REPLACE FUNCTION trg_col_min_len6()
RETURNS trigger
LANGUAGE plpgsql AS
$func$
BEGIN
IF TG_OP = 'UPDATE'
AND OLD.column_name IS NOT DISTINCT FROM NEW.column_name THEN
-- do nothing
ELSE
RAISE EXCEPTION 'New value for column "note" must have at least 6 characters.';
END IF;
RETURN NEW;
END
$func$;
-- trigger
CREATE TRIGGER tbl1_column_name_min_len6
BEFORE INSERT OR UPDATE ON tbl
FOR EACH ROW
WHEN (length(NEW.column_name) < 7)
EXECUTE FUNCTION trg_col_min_len6();
db<>fiddle here
It should be most efficient to check in a WHEN condition to the trigger directly. Then the trigger function is only ever called for short values and can be super simple.
See:
Trigger with multiple WHEN conditions
Fire trigger on update of columnA or ColumnB or ColumnC
You can create separate triggers for Insert and Update letting each completely define when it should fired. If completely different logic is required for the DML action this technique allows writing dedicated trigger functions. In this case that is not required the trigger function reduces to raise exception .... See Demo
-- Single trigger function for both Insert and Delete
create or replace function trg_col_min_len6()
returns trigger
language plpgsql
as $$
begin
raise exception 'Cannot % val = ''%''. Must have at least 6 characters.'
, tg_op, new.val;
return null;
end;
$$;
-- trigger before insert
create trigger tbl_val_min_len6_bir
before insert
on tbl
for each row
when (length(new.val) < 6)
execute function trg_col_min_len6();
-- trugger before update
create trigger tbl_val_min_len6_bur
before update
on tbl
for each row
when ( length(new.val) < 6
and new.val is distinct from old.val
)
execute function trg_col_min_len6();

Postgresql Trigger Syntax Insert or Update or Delete

I am a newbie and I want to understand:
I understand that New is only executed with Insert or Update.
The OLD ones only with Delete "If I'm not mistaken".
And what I'm trying to do is derive "If 2 values ​​are the same but different numbers" which I insert into a table and leave the default false value, I did, but "I insert" the new data, but it should be the value "OLD" of "driver".
¿Could that request be fulfilled?
db<>fiddle
The trigger:
CREATE FUNCTION TR_DRIVER() RETURNS TRIGGER
AS
$$
BEGIN
IF EXISTS(SELECT number_driver, cod_driver
FROM driver AS con
WHERE EXISTS (SELECT * FROM driver_tmp AS tmp
WHERE con.number_driver<>tmp.number_driver AND con.cod_driver=tmp.cod_driver)) THEN
INSERT INTO driver_false(number_driver, cod_driver, full_name)
VALUES (new.number_driver, new.cod_driver, new.full_name);
ELSE
INSERT INTO driver(number_driver, cod_driver, full_name, active)
VALUES (new.number_driver, new.cod_driver, new.full_name, new.active)
ON CONFLICT (number_driver)
DO UPDATE SET
cod_driver=excluded.cod_driver,
full_name=excluded.full_name,
active=excluded.active;
END IF;
RETURN NEW;
END $$
LANGUAGE plpgsql;
CREATE TRIGGER TR_DRIVER_TMP AFTER INSERT OR UPDATE ON driver_tmp
FOR EACH ROW
EXECUTE PROCEDURE TR_DRIVER() ;
You are mistaken, OLD also exists for UPDATE as update in Postgres is actually INSERT/DELETE. For the details see:
https://www.postgresql.org/docs/current/plpgsql-trigger.html
NEW
Data type RECORD; variable holding the new database row for INSERT/UPDATE operations in row-level triggers. This variable is null in statement-level triggers and for DELETE operations.
OLD
Data type RECORD; variable holding the old database row for UPDATE/DELETE operations in row-level triggers. This variable is null in statement-level triggers and for INSERT operations.

Trigger on BEFORE INSERT OR UPDATE fired once or twice?

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

Keep default behavior when new trigger is inserted SQL

Let's say I have contraints on a table. When I try to insert something there which doesn't have the correct data, I get an error in the console saying that this and that IC was violated.
ERROR: null value in column "column" violates not-null constraint
DETAIL: Failing row contains (0, null, null, null, null).
Now if I add a this:
CREATE OR REPLACE FUNCTION trigger_function()
RETURNS trigger AS $BODY$
BEGIN
raise notice 'test';
return null;
END;
$BODY$ LANGUAGE plpgsql;
create trigger test_trigger
before update or insert
on tablename
for each row
execute procedure trigger_function();
And execute the same thing as before I instead get this:
NOTICE: test
INSERT 0 0
I know that this trigger isn't really useful but I'm still learning how they work.
Is it possible to keep the previous error messages, and add whatever I did in the trigger_function after it? Why does it replace all the default messages?
Quote from the manual
Row-level triggers fired BEFORE can return null to signal the trigger manager to skip the rest of the operation for this row (i.e., subsequent triggers are not fired, and the INSERT/UPDATE/DELETE does not occur for this row)
(emphasis mine)
Because your trigger returns NULL the insert is never actually done and thus no constraint can be violated.
The output in the psql console
INSERT 0 0
tells you that no rows have been inserted. The second value is the number of rows inserted.
If you want a trigger that "does nothing", you need to use return new
CREATE OR REPLACE FUNCTION trigger_function()
RETURNS trigger
AS $BODY$
BEGIN
raise notice 'test';
return new; --<< here
END;
$BODY$ LANGUAGE plpgsql;

How can I insert the return of DELETE into INSERT in postgresql?

I am trying to delete a row from one table and insert it with some additional data into another. I know this can be done in two separate commands, one to delete and another to insert into the new table. However I am trying to combine them and it is not working, this is my query so far:
insert into b (one,two,num) values delete from a where id = 1 returning one, two, 5;
When running that I get the following error:
ERROR: syntax error at or near "delete"
Can anyone point out how to accomplish this, or is there a better way? or is it not possible?
You cannot do this before PostgreSQL 9.1, which is not yet released. And then the syntax would be
WITH foo AS (DELETE FROM a WHERE id = 1 RETURNING one, two, 5)
INSERT INTO b (one, two, num) SELECT * FROM foo;
Before PostgreSQL 9.1 you can create a volatile function like this (untested):
create function move_from_a_to_b(_id integer, _num integer)
returns void language plpgsql volatile as
$$
declare
_one integer;
_two integer;
begin
delete from a where id = _id returning one, two into strict _one, _two;
insert into b (one,two,num) values (_one, _two, _num);
end;
$$
And then just use select move_from_a_to_b(1, 5). A function has the advantage over two statements that it will always run in single transaction — there's no need to explicitly start and commit transaction in client code.
For all version of PostgreSQL, you can create a trigger function for deleting rows from a table and inserting them to another table. But it seems slower than bulk insert that is released in PostgreSQL 9.1. You just need to move the old data into the another table before it gets deleted. This is done with the OLD data type:
CREATE FUNCTION moveDeleted() RETURNS trigger AS $$
BEGIN
INSERT INTO another_table VALUES(OLD.column1, OLD.column2,...);
RETURN OLD;
END;
$$ LANGUAGE plpgsql;
CREATE TRIGGER moveDeleted
BEFORE DELETE ON table
FOR EACH ROW
EXECUTE PROCEDURE moveDeleted();
As above answer, after PostgreSQL 9.1 you can do this:
WITH tmp AS (DELETE FROM table RETURNING column1, column2, ...)
INSERT INTO another_table (column1, column2, ...) SELECT * FROM tmp;
That syntax you have there isn't valid. 2 statements is the best way to do this. The most intuitive way to do it would be to do the insert first and the delete second.
As "AI W", two statements are certainly the best option for you, but you could also consider writing a trigger for that. Each time something is deleted in your first table, another is filled.