DB2: Insert new rows and ignore duplicates - db2

I have about 100 rows to insert in a table - Some of them have already been inserted before and some of them have not
This is my insert that works fine if the primary key doesn't exist.. I'm going to run this 100 time with different values each time.. However, if the primary key exist, it fails and stop future commands to run.
How to ignore the failure and keep going or simply ignore duplicates?
INSERT INTO MY_TABLE
VALUES( 12342, 'fdbvdfb', 'svsdv', '5019 teR','' , 'saa', 'AL',35005 , 'C', 37, '0',368 , 'P', '2023-02-13', '2023-01-01', '2023-01-10', '2023-01-20','' , 'Test', 'Test', 'Test', 'JFK', '', null, 'Y', 'Y', '', '', '', '', '', '',2385 ,2 , '', 'N', '2023-01-16', '2023-01-20', '', NULL,NULL, NULL, NULL, 'Y', 'Test', 'Test', '', 'N', 'Test', '')
This is the error:
SQL0803N One or more values in the INSERT statement, UPDATE statement, or foreign key update caused by a DELETE statement are not valid because the primary key, unique constraint or unique index identified by "XPS01ME1" constrains
Insert IGNORE into throws:
The use of the reserved word "IGNORE" following "" is not valid.
If it can help I'm using WinSQL 10.0.157.697

You don't mention what platform and version of Db2, so I'll point you to the Linux/Unix/Windows (LUW) documentation for the MERGE statement...
Since I don't know your table or column names, I'll just give you an example with dummy names.
merge into MYTABLE as tgt
using (select *
from table( values(1,1,'CMW',5,1)
) tmp ( tblKey, fld1, fld2, fld3, fld4)
) as src
on src.tblKey = tgt.tblekey
when not matched then
insert ( tblKey, fld1, fld2, fld3, fld4)
values ( src.tblKey, src.fld1, src.fld2, src.fld3, src.fld4);
You're basically building a temporary table on the fly of one row
table( values(1,1,'CMW',5,1) ) tmp ( tblKey, fld1, fld2, fld3, fld4)
Then if there's no matching record via on src.tblKey = tgt.tblekey you do an insert.
Note that while you could do this 100 times, it is a much better performing solution to do all 100 rows at a time.
merge into MYTABLE as tgt
using (select *
from table( values (1,1,'CMW1',5,1)
, (2,11,'CMW2',50,11)
, (3,21,'CMW3',8,21)
-- , <more rows here>
) tmp ( tblKey, fld1, fld2, fld3, fld4)
) as src
on src.tblKey = tgt.tblekey
when not matched then
insert ( tblKey, fld1, fld2, fld3, fld4)
values ( src.tblKey, src.fld1, src.fld2, src.fld3, src.fld4);
Optionally, you could create an actual temporary table, insert the 100 rows (preferably in a single insert) and then use MERGE.

You may do it with a compound statement like below:
--#SET TERMINATOR #
CREATE TABLE MY_TABLE (ID INT NOT NULL PRIMARY KEY)#
BEGIN
DECLARE CONTINUE HANDLER FOR SQLSTATE '23505' BEGIN END;
INSERT INTO MY_TABLE (ID) VALUES (1);
END#
BEGIN
DECLARE CONTINUE HANDLER FOR SQLSTATE '23505' BEGIN END;
INSERT INTO MY_TABLE (ID) VALUES (1), (2), (3);
END#
BEGIN
DECLARE CONTINUE HANDLER FOR SQLSTATE '23505' BEGIN END;
INSERT INTO MY_TABLE (ID) VALUES (4);
END#
SELECT * FROM MY_TABLE#
ID
1
4
fiddle

This is how you ignore an error in db2 --
note, this is not the correct sqlstate for your problem -- replace with the one you need
DECLARE CONTINUE HANDLER FOR SQLSTATE '23505'
BEGIN -- ignore error for duplicate value
END;
Documented here
https://www.ibm.com/docs/en/db2-for-zos/12?topic=procedure-ignoring-condition-in-sql

Related

trigger to set date automatic after update

Some background info: i have a table named defects which has column named status_id and another column named date_closed ,i want to set date_closed after status_id has been updated
i already try to do this using after update trigger with the following code:
after update on eba_bt_sw_defects
for each row
declare
l_status number(20) := null;
begin
select status_id into l_status from eba_bt_sw_defects D,eba_bt_status S where D.status_id = S.id;
if l_status in ( select id from eba_bt_status where is_open = 'N' and NVL(is_enhancement,'N')='N') then
:NEW.DATE_CLOSED := LOCALTIMESTAMP ;
end if;
end;
but an error occured ( subquery not allowed in this contextCompilation failed)
i want a help
A couple of things that need fixing in your code:
In a trigger do not select from the table the trigger you're on. This will probably raise a ORA-04091: table name is mutating, trigger/function may not see it error.
IF l_variable IN (SELECT ...) is not a valid oracle syntax. It raises PLS-00405: subquery not allowed in this context
I don't have your data so here is a similar example:
drop table todos;
drop table statuses;
-- create tables
create table statuses (
id number generated by default on null as identity
constraint statuses_id_pk primary key,
status varchar2(60 char),
is_open varchar2(1 char) constraint statuses_is_open_ck
check (is_open in ('Y','N'))
)
;
create table todos (
id number generated by default on null as identity
constraint todos_id_pk primary key,
name varchar2(255 char) not null,
close_date timestamp with local time zone,
status_id number
constraint todos_status_id_fk
references statuses on delete cascade
)
;
-- load data
insert into statuses (id, status, is_open ) values (1, 'OPEN', 'Y' );
insert into statuses (id, status, is_open ) values (2, 'COMPLETE', 'N' );
insert into statuses (id, status, is_open ) values (3, 'ON HOLD', 'Y' );
insert into statuses (id, status, is_open ) values (4, 'CANCELLED', 'N' );
commit;
insert into todos (name, close_date, status_id ) values ( 'Y2 Security Review', NULL, 1 );
-- triggers
CREATE OR REPLACE TRIGGER todos_biu BEFORE
INSERT OR UPDATE ON todos
FOR EACH ROW
DECLARE
l_dummy NUMBER;
BEGIN
SELECT
1
INTO l_dummy
FROM
statuses
WHERE
is_open = 'N' AND
id = :new.status_id;
:new.close_date := localtimestamp;
EXCEPTION
WHEN no_data_found THEN
-- I'm assuming you want close_date to NULL if todo is re-opened.
:new.close_date := NULL;
END todos_biu;
/
update todos set status_id = 2;
select * from todos;
id name close_date status_id
1 Y2 Security Review 11-MAY-22 05.27.04.987117000 PM 2

Postgres exclude using gist across different tables

I have 2 tables like this
drop table if exists public.table_1;
drop table if exists public.table_2;
CREATE TABLE public.table_1 (
id serial NOT NULL,
user_id bigint not null,
status varchar(255) not null,
date_start date NOT NULL,
date_end date NULL
);
CREATE TABLE public.table_2 (
id serial NOT NULL,
user_id bigint not null,
status varchar(255) not null,
date_start date NOT NULL,
date_end date NULL
);
alter table public.table_1
add constraint my_constraint_1
EXCLUDE USING gist (user_id with =, daterange(date_start, date_end, '[]') WITH &&)
where (status != 'deleted');
alter table public.table_2
add constraint my_constraint_2
EXCLUDE USING gist (user_id with =, daterange(date_start, date_end, '[]') WITH &&)
where (status != 'deleted');
Every table contains rows which are related to a user, and all the rows of the same user cannot overlap in range. In addition, some rows may be logically deleted, so I added a where condition.
So far it's working w/o problems, but the 2 constraints work separately for each table.
I need to create a constraint which cover the 2 set of tables, so that a single daterange (of the same user and not deleted), may appaer only once across the 2 different tables.
Does the EXCLUDE notation be extended to work with different tables or do I need to check it with a trigger? If the trigger is the answer, which is the simplier way to do this? Create a temporary table with the union of the 2, add the constraint on it and check if fails?
Starting from #Laurenz Albe suggestion, this is what I made
-- #################### SETUP SAMPLE TABLES ####################
drop table if exists public.table_1;
drop table if exists public.table_2;
CREATE TABLE public.table_1 (
id serial NOT NULL,
user_id bigint not null,
status varchar(255) not null,
date_start date NOT NULL,
date_end date NULL
);
CREATE TABLE public.table_2 (
id serial NOT NULL,
user_id bigint not null,
status varchar(255) not null,
date_start date NOT NULL,
date_end date NULL
);
alter table public.table_1
add constraint my_constraint_1
EXCLUDE USING gist (user_id with =, daterange(date_start, date_end, '[]') WITH &&)
where (status != 'deleted');
alter table public.table_2
add constraint my_constraint_2
EXCLUDE USING gist (user_id with =, daterange(date_start, date_end, '[]') WITH &&)
where (status != 'deleted');
-- #################### SETUP TRIGGER ####################
create or REPLACE FUNCTION check_date_overlap_trigger_hook()
RETURNS trigger as
$body$
DECLARE
l_table text;
l_sql text;
l_row record;
begin
l_table := TG_ARGV[0];
l_sql := format('
select *
from public.%s as t
where
t.user_id = %s -- Include only records of the same user
and t.status != ''deleted'' -- Include only records that are active
', l_table, new.user_id);
for l_row in execute l_sql
loop
IF daterange(l_row.date_start, COALESCE(l_row.date_end, 'infinity'::date)) && daterange(new.date_start, COALESCE(new.date_end, 'infinity'::date))
THEN
RAISE EXCEPTION 'Date interval is overlapping with another one in table %', l_table
USING HINT = 'You can''t have the same interval across table1 AND table2';
END IF;
end loop;
RETURN NEW;
end
$body$
LANGUAGE plpgsql;
-- #################### INSTALL TRIGGER ####################
create trigger check_date_overlap
BEFORE insert or update
ON public.table_1
FOR EACH row
EXECUTE PROCEDURE check_date_overlap_trigger_hook('table_2');
create trigger check_date_overlap
BEFORE insert or update
ON public.table_2
FOR EACH row
EXECUTE PROCEDURE check_date_overlap_trigger_hook('table_1');
-- #################### INSERT DEMO ROWS ####################
insert into public.table_1 (user_id, status, date_start, date_end) values (1, 'active', '2020-12-10', '2020-12-20');
insert into public.table_1 (user_id, status, date_start, date_end) values (1, 'deleted', '2020-12-15', '2020-12-25');
insert into public.table_1 (user_id, status, date_start, date_end) values (2, 'active', '2020-12-10', '2020-12-20');
insert into public.table_1 (user_id, status, date_start, date_end) values (2, 'deleted', '2020-12-15', '2020-12-25');
-- This will fail for overlap on the same table
-- insert into public.table_1 (user_id, status, date_start, date_end) values (1, 'active', '2020-12-15', '2020-12-25');
-- This will fail as the user 1 already has an overlapping period on table 1
-- insert into public.table_2 (user_id, status, date_start, date_end) values (1, 'active', '2020-12-15', '2020-12-25');
-- This will fail as the user 1 already has an overlapping period on table 1
insert into public.table_2 (user_id, status, date_start, date_end) values (1, 'deleted', '2020-12-15', '2020-12-25');
update public.table_2 set status = 'active' where id = 1;
select 'table_1' as src_table, * from public.table_1
union
select 'table_2', * from public.table_2
You can probably use a trigger, but triggers are always vulnerable to race conditions (unless you are using SERIALIZABLE isolation).
If your tables really have the same columns, why don't you use a single table (and perhaps add a type column to disambiguate)?

I'm having an issue with this code when I try to input values into the transactions table

So I'm setting up a schema in which I can input transactions of a journal entry independent of each other but also that rely on each other (mainly to ensure that debits = credits). I set up the tables, function, and trigger. Then, when I try to input values into the transactions table, I get the error below. I'm doing all of this in pgAdmin4.
CREATE TABLE transactions (
transactions_id UUID PRIMARY KEY DEFAULT uuid_generate_v1(),
entry_id INTEGER NOT NULL,
post_date DATE NOT NULL,
account_id INTEGER NOT NULL,
contact_id INTEGER NULL,
description TEXT NOT NULL,
reference_id UUID NULL,
document_id UUID NULL,
amount NUMERIC(12,2) NOT NULL
);
CREATE TABLE entries (
id UUID PRIMARY KEY,
test_date DATE NOT NULL,
balance NUMERIC(12,2)
CHECK (balance = 0.00)
);
CREATE OR REPLACE FUNCTION transactions_biut()
RETURNS TRIGGER
LANGUAGE plpgsql
AS $$
BEGIN
EXECUTE 'INSERT INTO entries (id,test_date,balance)
SELECT
entry_id,
post_date,
SUM(amount) AS ''balance''
FROM
transactions
GROUP BY
entry_id;';
END;
$$;
CREATE TRIGGER transactions_biut
BEFORE INSERT OR UPDATE ON transactions
FOR EACH ROW EXECUTE PROCEDURE transactions_biut();
INSERT INTO transactions (
entry_id,
post_date,
account_id,
description,
amount
)
VALUES
(
'1',
'2019-10-01',
'101',
'MISC DEBIT: PAID FOR FACEBOOK ADS',
-200.00
),
(
'1',
'2019-10-01',
'505',
'MISC DEBIT: PAID FOR FACEBOOK ADS',
200.00
);
After I execute this input, I get the following error:
ERROR: column "id" of relation "entries" does not exist
LINE 1: INSERT INTO entries (id,test_date,balance)
^
QUERY: INSERT INTO entries (id,test_date,balance)
SELECT
entry_id,
post_date,
SUM(amount) AS "balance"
FROM
transactions
GROUP BY
entry_id;
CONTEXT: PL/pgSQL function transactions_biut() line 2 at EXECUTE
SQL state: 42703
There are a few problems here:
You're not returning anything from the trigger function => should probably be return NEW or return OLD since you're not modifying anything
Since you're executing the trigger before each row, it's bound to fail for any transaction that isn't 0 => maybe you want a deferred constraint trigger?
You're not grouping by post_date, so your select should fail
You've defined entry_id as INTEGER, but entries.id is of type UUID
Also note that this isn't really going to scale (you're summing up all transactions of all days, so this will get slower and slower...)
#chirs I was able to figure out how to create a functioning solution using statement-level triggers:
CREATE TABLE transactions (
transactions_id UUID PRIMARY KEY DEFAULT uuid_generate_v1(),
entry_id INTEGER NOT NULL,
post_date DATE NOT NULL,
account_id INTEGER NOT NULL,
contact_id INTEGER NULL,
description TEXT NOT NULL,
reference_id UUID NULL,
document_id UUID NULL,
amount NUMERIC(12,2) NOT NULL
);
CREATE TABLE entries (
entry_id INTEGER PRIMARY KEY,
post_date DATE NOT NULL,
balance NUMERIC(12,2),
CHECK (balance = 0.00)
);
CREATE OR REPLACE FUNCTION transactions_entries() RETURNS TRIGGER AS $$
BEGIN
IF (TG_OP = 'DELETE') THEN
INSERT INTO entries
SELECT o.entry_id, o.post_date, SUM(o.amount) FROM old_table o GROUP BY o.entry_id, o.post_date;
ELSIF (TG_OP = 'UPDATE') THEN
INSERT INTO entries
SELECT o.entry_id, n.post_date, SUM(n.amount) FROM new_table n, old_table o GROUP BY o.entry_id, n.post_date;
ELSIF (TG_OP = 'INSERT') THEN
INSERT INTO entries
SELECT n.entry_id,n.post_date, SUM(n.amount) FROM new_table n GROUP BY n.entry_id, n.post_date;
END IF;
RETURN NULL; -- result is ignored since this is an AFTER trigger
END;
$$ LANGUAGE plpgsql;
CREATE TRIGGER transactions_ins
AFTER INSERT ON transactions
REFERENCING NEW TABLE AS new_table
FOR EACH STATEMENT EXECUTE PROCEDURE transactions_entries();
CREATE TRIGGER transactions_upd
AFTER UPDATE ON transactions
REFERENCING OLD TABLE AS old_table NEW TABLE AS new_table
FOR EACH STATEMENT EXECUTE PROCEDURE transactions_entries();
CREATE TRIGGER transactions_del
AFTER DELETE ON transactions
REFERENCING OLD TABLE AS old_table
FOR EACH STATEMENT EXECUTE PROCEDURE transactions_entries();
Any thoughts on optimization?

PostgreSQL insert current sequence value to another field with condition

the issue:
i need to do something like this
drop table if exists tt_t;
create temp table tt_t(id serial primary key, main_id int, external_id int);
insert into tt_t(main_id, external_id)
select currval(pg_get_serial_sequence('tt_t', 'id')), 1
where not exists (select from tt_t where external_id = 1);
but execution raises an error
SQL Error [55000]: ERROR: currval of sequence "tt_t_id_seq" is not yet defined in this session
solution:
there is a way to solve this with anonymous code block
do
$$
begin
if not exists(select from tt_t where external_id = 1)
then
insert into tt_t(external_id, main_id)
values(1, currval(pg_get_serial_sequence('tt_t', 'id')));
end if;
end;
$$
;
but anonymous blocks has some restrictions e.g. Dapper parameters not working with PostgreSQL through npgsql connection, is postgres anonymous function parameterization supported?
how do i fix it without anonymous code block (UPD: and without any DDL changes)?
probable solution:
insert into tt_t(id, main_id, external_id)
select nextval(pg_get_serial_sequence('tt_t', 'id')), currval(pg_get_serial_sequence('tt_t', 'id')), 1
where not exists (select from tt_t where external_id = 1);
shorter code has been proposed to me
insert into tt_t(id, main_id, external_id)
select nextval(pg_get_serial_sequence('tt_t', 'id')), lastval(), 1
where not exists (select from tt_t where external_id = 1);
but i'm not sure if nextval will be calculated first
What about using a default value:
drop table if exists tt_t;
create temp table tt_t(id serial primary key, main_id int default lastval(), external_id int);
insert into tt_t(external_id)
select 1
where not exists (select * from tt_t where external_id = 1);
In theory it shouldn't be possible that another nextval() is called between the one for the id and the lastval(). However I am not 100% sure if there are some corner cases that I don't know of.
The following works as well (even if one or more of the external_id values already exist).
insert into tt_t(external_id)
select *
from (values (1),(2),(3)) x (external_id)
where not exists (select *
from tt_t
where external_id = x.external_id);

How to stop the "insert or update on table ...violates foreign key constraint"?

How to construct an INSERT statement so that it would not generate the error "insert or update on table ... violates foreign key constraint" in case if the foreign key value does not exist in the reference table?
I just need no record created in this case and success response.
Thank you
Use a query as the source for the INSERT statement:
insert into the_table (id, some_data, some_fk_column
select *
from (
values (42, 'foobar', 100)
) as x(id, some_data, some_fk_column)
where exists (select *
from referenced_table rt
where rt.primary_key_column = x.some_fk_column);
This can also be extended to a multi-row insert:
insert into the_table (id, some_data, some_fk_column
select *
from (
values
(42, 'foobar', 100),
(24, 'barfoo', 101)
) as x(id, some_data, some_fk_column)
where exists (select *
from referenced_table rt
where rt.primary_key_column = x.some_fk_column);
You didn't show us your table definitions so I had to make up the table and column names. You will have to translate that to your names.
You could create a function with plpgsql, which inserts a row and catches the exception:
CREATE FUNCTION customInsert(int,varchar) RETURNS VOID
AS $$
BEGIN
INSERT INTO foo VALUES ($1,$2);
EXCEPTION
WHEN foreign_key_violation THEN --do nothing
END;
$$ LANGUAGE plpgsql
You can then call this function by this:
SELECT customInsert(1,'hello');
This function tries to insert the given parameters into the table foo and catches the foreign_key_violation error if occurs.
Of course you can generalise the function more, to be able to insert in more than one table, but your question sounded like this was only needed for one specific table.