Why is trigger not fired on every single row when using "insert select" or "merge" - sql

I defined a BEFORE INSERT trigger for a table and it works as expected for single INSERTstatements, but not for INSERT ... SELECT nor MERGE statements.
These are my database objects (simplified):
CREATE TABLE "COMPANY" (
"ID" NUMBER NOT NULL,
"NAME" VARCHAR(100)
);
CREATE TABLE "EMPLOYEE" (
"ID" NUMBER NOT NULL,
"COMPANY_ID" NUMBER NOT NULL
);
CREATE UNIQUE INDEX "EMPLOYEE_PK" ON "EMPLOYEE" ("ID");
CREATE SEQUENCE "EMPLOYEE_SEQUENCE";
CREATE TRIGGER "BI_EMPLOYEE" BEFORE INSERT ON "EMPLOYEE"
REFERENCING NEW AS newrow FOR EACH ROW BEGIN ATOMIC
IF newrow.id IS NULL THEN
SET newrow.id = NEXT VALUE FOR employee_sequence;
END IF;
END;
If single INSERTstatements are executed, everything works as expected, the ÌD is fetched from the sequence. But if I execute something like
INSERT INTO employee (company_id) SELECT id FROM company;
the I get an error:
integrity constraint violation: unique constraint or index violation: "EMPLOYEE_PK"
which could propably mean that it tries to insert the same key from the sequence twice.
I'm using the latests version 2.3.2 of HSQLDB.

Because triggers are set based, not row based.
See details here

Related

Postgres breaking null constraint on a serial column

I have a table that I create independently, the primary key is set with the serial type and a sequence applied to the table, but when I try to insert a value a NULL CONSTRAINT error is thrown and the return looks like null was passed, am I missing something in the INSERT statement?
SQL for table generation:
DROP TABLE IF EXISTS public."Team" CASCADE;
CREATE TABLE public."Team" (
"IdTeam" serial PRIMARY KEY,
name text NOT null,
CONSTRAINT "pKeyTeamUnique" UNIQUE ("IdTeam")
);
ALTER TABLE public."Team" OWNER TO postgres;
DROP SEQUENCE IF EXISTS public."Team_IdTeam_seq" CASCADE;
CREATE SEQUENCE public."Team_IdTeam_seq"
AS integer
START WITH 1
INCREMENT BY 1
NO MINVALUE
NO MAXVALUE
CACHE 1;
ALTER TABLE public."Team_IdTeam_seq" OWNER TO postgres;
ALTER SEQUENCE public."Team_IdTeam_seq" OWNED BY public."Team"."IdTeam";
SQL for insert :
INSERT INTO public."Team" (name) values ('Manchester Untited');
The returning error:
ERROR: null value in column "IdTeam" violates not-null constraint
DETAIL: Failing row contains (null, Manchester Untited).
SQL state: 23502
I am baffled. Why are you trying to define your own sequence when the column is already defined as serial?
Second, a primary key constraint is already unique. There is no need for a separate unique constraint.
Third, quoting identifiers just makes the code harder to write and to read.
You can just do:
DROP TABLE IF EXISTS public.Team CASCADE;
CREATE TABLE public.Team (
IdTeam serial PRIMARY KEY,
name text NOT null
);
INSERT INTO public.Team (name)
VALUES ('Manchester Untited');
Dropping the sequence causes the default definition for the IdTeam column to be dropped. After recreating the sequence you will have to recreate the default definition.

How to automatically update second table with some same information after insert into first table

When I enter a new record in one table, I need to have some of the information from the first table be automatically added to the second table. I unsuccessfully tried triggers to do this.
My primary table looks like this:
CREATE TABLE demographics (
person_local_id BIGSERIAL UNIQUE PRIMARY KEY,
first_name VARCHAR(50)...[other lines]
);
I set up the child table like this:
CREATE TABLE pedigree (
pedigree_id BIGSERIAL PRIMARY KEY NOT NULL,
person_local_id BIGSERIAL NOT NULL,
person_sex VARCHAR(10),
father VARCHAR(10) DEFAULT 0,
mother VARCHAR(10) DEFAULT 0,
FOREIGN KEY (person_local_id) REFERENCES demographics (person_local_id)
ON DELETE CASCADE ON UPDATE CASCADE
);
My approach was to create a trigger on the demographics primary table such that any time a record was added to it, a corresponding record would be added to the pedigree table consisting of just the person_local_id. I added a foreign key on the pedigree table that referenced the column in the demographics that I need to copy over to the pedigree table in that column.
Then I created a trigger, but it doesn't work. I tried this with and without the word "EXECUTE".
CREATE TRIGGER into_pedigree AFTER INSERT OR UPDATE ON demographics
FOR EACH ROW EXECUTE INSERT INTO pedigree (person_local_id) SELECT (NEW.person_local_id) FROM NEW;
I keep getting syntax errors but I can't identify the error:
ERROR: syntax error at or near "INSERT"
LINE 2: FOR EACH ROW EXECUTE INSERT INTO pedigree (person_local_id) ...
^
I also tried this, adding the name:
CREATE TRIGGER into_pedigree ON identify_relatives_database.demographics
AFTER INSERT
AS
BEGIN
INSERT INTO pedigree (person_local_id) VALUES (INSERTED.person_local_id)
END;
But I get the error message:
ERROR: syntax error at or near "ON"
LINE 1: CREATE TRIGGER into_pedigree ON demographics
^
I appreciate your assistance.
You may try this.
CREATE TRIGGER [dbo].[Customer_INSERT]
ON [dbo].[demographics]
AFTER INSERT, UPDATE
AS
BEGIN
SET NOCOUNT ON;
DECLARE #CustomerId INT
SELECT #CustomerId = INSERTED.person_local_id FROM INSERTED
IF EXISTS ( SELECT * FROM PEDIGREE WHERE person_local_id = #CustomerId)
BEGIN
--- here col is the required column name need to be modified,
--- since you are inserting person_local_id from base table which is auto generated and not suppose to be change in any condition
UPDATE PEDIGREE SET COL = INSERTED.COL WHERE person_local_id = #CustomerId
END
ELSE
BEGIN
INSERT INTO pedigree (person_local_id)
VALUES(#CustomerId)
END
END
Although I don't find anything related to your update part. Since you are inserting primary key from base table as foreign key in child table, so for normalization it is not going to changed in any condition. So i don't think you need update part in your trigger Hence your required trigger will be:
CREATE TRIGGER [dbo].[Customer_INSERT]
ON [dbo].[demographics]
AFTER INSERT
AS
BEGIN
SET NOCOUNT ON;
INSERT INTO pedigree (person_local_id)
SELECT INSERTED.person_local_id FROM INSERTED
END
You should try this :
CREATE TRIGGER into_pedigree ON demographics
FOR INSERT
AS
insert into pedigree (person_local_id)
values(inserted.person_local_id);
PRINT 'AFTER INSERT trigger fired.'
GO

How to insert a newly generated id into another table with a trigger in postgresql?

Basically, users when they create a new record in mytable1, there is an id field that needs to be the same across multiple tables. I achieve this by having mytable2 with the s_id as primary key
My current function looks like
CREATE OR REPLACE FUNCTION test.new_record()
RETURNS trigger
LANGUAGE plpgsql
AS $function$
BEGIN
case when new.s_id in (select s_id from mytable1) then
insert into mytable2 (sprn, date_created) select max(s_id) +1, now() from mytable2 ;
update mytable1 set new.s_id = (select max(b.s_id) from mytable2 b);
end case;
RETURN new;
END;
$function$;
Intended was when the s_id is replicated then it would create a new entry on mytable2. This new entry would then be updated onto mytable1
Problem with this function is that right now it does not recognise the new on the update part of the function.
How to keep the s_id take the value on every new insert ?
If you want to have one "generator" across multiple tables, create one sequence that is used across all those tables for the default value:
create sequence the_id_sequence;
create table one
(
id integer primary key default nextval('the_id_sequence')
.... other columns
);
create table two
(
id integer primary key default nextval('the_id_sequence')
.... other columns ...
);
If you want to replicate an ID from one table to another during insert, you only need one sequence:
create table one
(
-- using identity is the preferred over "serial" to auto-generate PK values
id integer primary key generated always as identity
);
create table two
(
id integer primary key
);
create or replace function insert_two()
returns trigger
as
$$
begin
insert into two (id) values (new.id);
return new;
end;
$$
language plpgsql;
create trigger replicate_id
before insert on one
for each row
execute procedure insert_two();
Then if you run:
insert into one (id) values (default);
A row with exactly the same id value will be inserted into table two.
If you don't have a generated ID column so far, use the following syntax:
alter table one
add testidcolumn bigint generated always as identity;

How to create a conditional trigger

I have a table with an id as auto incremented primary key and another id.
CREATE TABLE tester (
"id" integer PRIMARY KEY AUTOINCREMENT,
"refId" integer DEFAULT 0
);
refId should be able to either be 0 (the default) or reference id if refId > 0 (i.e. act as foreign key).
Now I need two constraints:
A row should only be deletable if its id is not used (referenced?) by any other row's refId
A row should only be deletable if its refId is 0.
From what I have understood, I need to create a trigger that checks for these constraints before a DELETE event happens. And depending on refId's value either abort the delete action or allow it.
However, I have a hard time understanding the syntax for this and how to do a conditional check. But what I have so far (in mind!) is concerning 1.):
CREATE TRIGGER no_delete_if_inuse
BEFORE DELETE ON tester
FOR EACH ROW BEGIN
SELECT RAISE(ABORT, 'cannot delete because of foreign key violation')
WHERE (SELECT "refId" FROM tester WHERE "refId" = OLD."id") IS NOT NULL;
END;
And concerning 2.)
CREATE TRIGGER no_delete_if_ref
BEFORE DELETE ON tester
FOR EACH ROW BEGIN
IF OLD."refId" > 0 THEN RAISE(ABORT, "cannot delete tester because it refers to an existing tester");
END;
Does this make sense and is valid?
I am totally not sure, to me it does but well, I am all noob.
Also as a last question, can I alternatively combine this into a single trigger? For example would this be a valid query:
CREATE TRIGGER no_delete_if_inuse
BEFORE DELETE ON tester
FOR EACH ROW BEGIN
SELECT RAISE(ABORT, 'cannot delete because of foreign key violation')
WHERE (SELECT "refId" FROM tester WHERE ("refId" = OLD."id" OR "refId" > 0) ) IS NOT NULL;
END;
You can define a foreign key referring to the same table. Use null instead of 0 for rows without a reference:
create table tester(
id int primary key,
refid int references tester,
check (id <> refid)
);
insert into tester values
(1, null),
(2, null),
(3, 1),
(4, 3);
You need a trigger to ensure that a row which references another one cannot be deleted.
create or replace function before_delete_on_tester()
returns trigger language plpgsql as $$
begin
if old.refid is not null then
raise exception
'Cannot delete: (id)=(%) references (id)=(%)', old.id, old.refid;
end if;
return old;
end $$;
create trigger before_delete_on_tester
before delete on tester
for row execute procedure before_delete_on_tester();
Test:
delete from tester where id = 1;
ERROR: update or delete on table "tester" violates foreign key constraint "tester_refid_fkey" on table "tester"
DETAIL: Key (id)=(1) is still referenced from table "tester".
delete from tester where id = 4;
ERROR: Cannot delete from tester. (id)=(4) references (id)=(3)
CONTEXT: PL/pgSQL function before_delete_on_tester() line 4 at RAISE
In Postgres you have to define a trigger function. Read more:
Overview of Trigger Behavior
Trigger Procedures
Create Trigger

how to check Composite Primary key before inserting new values

I have a table with multiple columns which consists of a composite primary key for three of them (columns customer_id,system_origin,policy_number).
The table is created as below :
Create table LOST_MEMBER_ACCESS_LOG (
Customer_id varchar2(20) NOT NULL,
System_Origin varchar2(20) NOT NULL,
Policy_Number varchar2(20) NOT_NULL,
PRIMARY_KEY(Customer_id,System_Origin, Policy_Number)
);
I have a requirement to write a stored proc which firsts checks if a row already exists for unique combination for customer_id,system_origin,policy_number then it updates the already existing row with the new values or else it inserts a new row.
What you're saying you want to do is:
if row exists then
update ...
else
insert ...
end if;
But there is easier way. Just do the update and check if any row was touched
update lost_member_access_log
set ...
where ... ;
if sql%rowcount = 0 then
insert into lost_member_access_log ...
end if;
MERGE is of course also an option.