How to insert a row into another table during an on conflict - sql

I want to insert in a ManyToMany relationship (between 'fiche' and quality) a line (same principle as a log) when an insertion fails because the key is duplicated.
example:
insert into fiche (content) values ('toto')
on conflict (content) do
insert into fiche_qualites (poi_id, qualites_id) values (idPoi, 'Code error');
My error:
ERROR: syntax error at or near "insert"
I'm in a loop doing mass inserts and I want to be able to keep track when I have a duplicate (detected by uniqueness).

You may use a PL/pgSQL block to catch an unique_violation exception and handle it.
do $$
begin
insert into fiche (content) values ('toto');
exception
when unique_violation then
insert into fiche_qualites (poi_id, qualites_id) values (idPoi, 'Code error');
end;
$$;
The block may be shaped as a reusable function too.
create or replace function insert_or_log(arg_content text)
returns void language plpgsql as
$$
begin
insert into fiche (content) values (arg_content);
exception
when unique_violation then
insert into fiche_qualites (poi_id, qualites_id)
values ((select idPoi from fiche where content = arg_content), 'Code error');
end;
$$;

Triggers are the most intuitive / flexible approach, but since your use case is only about keeping track of the duplicated entries in a separated table you can use a CTE (aka WITH clause) returning the xmax of the new records - new records always have a xmax = 0. Then finally in the outer query you filter only the inserted records to store in the "duplicates" table, e.g.
WITH j AS (
INSERT INTO t VALUES (42,'foo')
ON CONFLICT (id)
DO UPDATE SET txt = EXCLUDED.txt
RETURNING xmax=0 AS inserted,*
)
INSERT INTO t_duplicates
SELECT id,txt FROM j
WHERE NOT inserted -- see boolean expression in the RETURNING clause
Demo: db<>fiddle

Related

After Insert Trigger not working as Expected

I have a scenario
to Insert a record in second table after only when inserting a record in first table on certain conditions.
I have my trigger logic like
CREATE OR REPLACE TRIGGER trgname_TR AFTER INSERT OR UPDATE ON <table1>
DECLARE
v_value NUMBER(12,0);
BEGIN
select br.value into v_value
from table3 BR
where br.value =:NEW.value
and br.category IN (839,23,30,843,1414)
and br.efctv_to_date is null;
IF INSERTING OR UPDATING THEN
INSERT INTO table1(col1,col2,created_date,updated_date)
VALUES
(1,v_value,SYSDATE,:NEW.LAST_UPDATE_DATE);
END IF;
EXCEPTION
WHEN NO_DATA_FOUND THEN
RAISE_APPLICATION_ERROR(-20001,'NO SUCH SOURCES EXISTS');
END;
The problem is when I try inserting record in first table on condition IN (839,23,30,843,1414)
insert working fine and record gets inserted in second table
on when trying insert other than this condition IN (839,23,30,843,1414)
ORA-20001: NO SUCH SOURCES EXISTS
ORA-06512: at "table", line 23
ORA-04088: error during execution of trigger 'table'
There is a Select into command that, when IN list changed, does not fetch any row. Consequence is NO_DATA_FOUND exception.
Check your data and fix the code:
select br.value into v_value
from table3 BR
where br.value =:NEW.value
and br.category IN (839,23,30,843,1414)
and br.efctv_to_date is null;
This command says that you need a SINGLE row from table3 where table3.value is equal to the new value of the trigger table AND table3.efctv_to_date is Null AND table3.category is in the list (this one or changed).
Oracle will return a message and say that there is no such row.
Be careful with this as there could be more than 1 row (because of IN() function) resulting with TOO_MANY_ROWS exception.

Postgres function to create audit table throwing error INSERT has more expressions than target columns

I have a table containing id, status, dateadded. Id and dateadded are defaulting to integer nextval and current_timestamp respectively. I then have a trigger that calls a function on Insert, Update or Delete.
In the function all it does is back up and track the changes in the status table. This method worked successfully when there was more than one field being added to the table. With the status table I am only adding the status field and letting the db take care of the other 2 fields and I receive the error.
It looks as if I might need to do something different in the function?
I'm adding data to these tables using python to automate, and have tried adding a record to the table manually with the same error occurring.
Error while fetching data from PostgreSQL INSERT has more expressions than target columns
LINE 2: (TG_OP, NEW.*)
^
QUERY: INSERT INTO api_audit.d_status_list (id, status, dateadded) VALUES
(TG_OP, NEW.*)
CONTEXT: PL/pgSQL function api_input.d_status_list_func() line 4 at SQL statement
Function code:
create function d_status_list_func() returns trigger
language plpgsql
as
$$
BEGIN
IF TG_OP = 'INSERT' THEN
INSERT INTO api_audit.d_status_list (id, status, dateadded) VALUES
(TG_OP, NEW.*);
RETURN NEW;
ELSIF TG_OP = 'UPDATE' THEN
INSERT INTO api_audit.d_status_list (id, status, dateadded) VALUES
(TG_OP, NEW.*);
RETURN NEW;
ELSIF TG_OP = 'DELETE' THEN
INSERT INTO api_audit.d_status_list (id, status, dateadded) VALUES
(TG_OP, OLD.*);
RETURN OLD;
END IF;
END;
$$;
alter function d_status_list_func() owner to blahblah;
Any assistance is much appreciated.
The insert fails because you are declaring three columns for insert (id, status, dateadded), but you are giving it 4 values: the operation (insert, update, delete), then the 3 original columns.
Presumably, your audit table has (or should have) a column that stores the operation that is being performed.
If so, you should list that column in the insert statement:
INSERT INTO api_audit.d_status_list (operation, id, status, dateadded)
VALUES (TG_OP, NEW.*);
Generally speaking, it is a good practice to avoid * and explicitely list the columns, which makes things easier to track down when they go wrong, so:
INSERT INTO api_audit.d_status_list (operation, id, status, dateadded)
VALUES (TG_OP, NEW.id, NEW.status, NEW.dateadded);

Postgres sql exception handling for batch insert

I have two tables and i am inserting the data from one table to other.
insert into a (id1, value1)
select id, value from b
id1 is unique and when I have repeating id in table b. How can I catch the exception for each row in PostgreSQL without halting the execution.
If you can't do as #a_horse_with_no_name suggests and avoid the exception then a PL/PgSQL procedure that loops over a query and does a BEGIN ... EXCEPTION ... block is the way to go.
This is massively less efficient than filtering out the problem rows with a WHERE clause and (if needed) join, so it should be avoided if possible.
The main time it's necessary is if, say, an exception is being thrown by validation code you can't run to produce a boolean for a where clause. Unfortunately most of PostgreSQL's data-type input functions don't have a "test" mode where you can get a NULL result or error flag for invalid input, so this is usually the case for things like date/time parsing.
You want to do something like:
DO
LANGUAGE plpgsql
$$
DECLARE
r record;
BEGIN
FOR r IN SELECT a, b FROM mytable
LOOP
BEGIN
INSERT INTO newtable (x, y)
VALUES (r.a, r.b);
EXCEPTION
WHEN check_violation THEN
RAISE NOTICE 'Skipped row %', r.a;
END;
END LOOP;
END;
$$;
For details, see the PL/PgSQL manual.
Note that this does a subtransaction for every loop iteration and also requires executor state setup for every iteration, so it's way slower than doing it with an INSERT INTO ... SELECT ... WHERE ....
Just don't insert those that would cause an error:
insert into a (id1, value1)
select id, value
from b
where not exists (select 1
from a
where a.id1 = b.id);
Or just select the unique ones if a is empty:
insert into a (id1, value1)
select distinct on (id) id, value
from b
order by id;

Trigger function to delete certain rows from the same table

I'm trying to create a Trigger/Function in Postgres that will check, upon an insert to a table, whether or not there is already another post by a different member with the same content. If there is a post, this function will not insert the new one and leave the table unchanged. Otherwise, it will be added.
So far, the trigger and function look like:
Trigger:
CREATE TRIGGER isPostUnique
AFTER INSERT ON posts
FOR EACH ROW
EXECUTE PROCEDURE deletePost();
Function:
CREATE FUNCTION deletePost() RETURNS TRIGGER AS $isPostUnique$
BEGIN
IF (EXISTS (SELECT * FROM posts p1, posts p2
WHERE (p1.userID <> p2.userID)
AND (p1.content LIKE p2.content)))
THEN
DELETE FROM NEW WHERE (posts.postID = NEW.postID);
RETURN NEW;
END IF;
END;
$isPostUnique$ LANGUAGE plpgsql;
Adding the function and trigger works without any errors, but when I try to run the following query to test it: INSERT INTO posts VALUES (7, 3, 'test redundant post', 10, 1); I get this error
ERROR: relation "new" does not exist
LINE 1: DELETE FROM NEW WHERE (posts.postID = NEW.postID)
^
QUERY: DELETE FROM NEW WHERE (posts.postID = NEW.postID)
CONTEXT: PL/pgSQL function dp() line 7 at SQL statement
I am aware that you can't use 'NEW' in FOR EACH ROW inserts, but I have no other idea of how to accomplish this.
Updated answer for updated question
Of course you can use NEW in FOR EACH ROW trigger function. You just can't direct a DELETE statement at it. It's a row type (data type HeapTuple to be precise), not a table.
To abort the INSERT silently (no exception raised) if the same content is already there ...
CREATE FUNCTION deletePost()
RETURNS TRIGGER AS
$func$
BEGIN
IF EXISTS (
SELECT 1
FROM posts p
WHERE p.content = NEW.content
-- AND p.userID <> NEW.userID -- I doubt you need this, too?
) THEN
RETURN NULL; -- cancel INSERT
ELSE
RETURN NEW; -- go ahead
END IF;
END
$func$ LANGUAGE plpgsql;
Of course this only works for a trigger ...
...
BEFORE INSERT ON posts
...
Unique index
A UNIQUE constraint or a unique index (almost the same effect) might be a superior solution:
CREATE UNIQUE INDEX posts_content_uni_idx (content);
Would raise an exception at the attempt to insert a duplicate value. No trigger necessary.
It also provides the very well needed index to speed up things.

Oracle DB insert and do nothing on duplicate key

I have to insert some data in oracle DB, without previously checking if it already exist.
Does exist any way, transiction on oracle to catch the exception inside the query and handle it to don't return any exception?
It would be perfect something in mysql's style like
insert .... on duplicate key a=a
You can use MERGE. The syntax is a bit different from a regular insert though;
MERGE INTO test USING (
SELECT 1 AS id, 'Test#1' AS value FROM DUAL -- your row to insert here
) t ON (test.id = t.id) -- duplicate check
WHEN NOT MATCHED THEN
INSERT (id, value) VALUES (t.id, t.value); -- insert if no duplicate
An SQLfiddle to test with.
If you can use PL/SQL, and you have a unique index on the columns where you don't want any duplicates, then you can catch the exception and ignore it:
begin
insert into your_table (your_col) values (your_value);
exception
when dup_val_on_index then null;
end;
Since 11g there is the ignore_row_on_dupkey_index hint that ignores Unique Constraint-exceptions and let the script go on with the next row if there is any, see Link. The exception is not logged. It needs two arguments, the table name and the index name.
INSERT /*+ ignore_row_on_dupkey_index(my_table, my_table_idx) */
INTO my_table(id,name,phone)
VALUES (24,'Joe','+49 19450704');