PostgreSQL trigger function not working properly - sql

I have a trigger function:
CREATE OR REPLACE FUNCTION Day_21_bankTriggerFunction()
RETURNS TRIGGER
LANGUAGE plpgsql
AS
$$
DECLARE
act VARCHAR(30);
BEGIN
SELECT account_number INTO act
DELETE FROM depositor
WHERE depositor.account_number = act;
RETURN act;
END;
$$;
\`
and then I have a trigger:
CREATE TRIGGER Day_21_bankTrigger AFTER DELETE on account FOR EACH ROW EXECUTE PROCEDURE Day_21_bankTriggerFunction()
The thought behind this is that is an account was deleted from the account table then this should trigger the function to run and then delete all records on the depositor table where that account is present.
I can create the trigger function and trigger without an issues but if I attempt to delete an account from the account table...I still see the account # in the depositor table when I shouldn't.
Any thoughts?
above is what I tried. Expected results would be to delete an account from the account table and then the trigger function should kick off and remove that account from the depositor table

Usually, relationships of any mode between tables are created by foreign keys. This is the best way and are standards for DBs. Using foreign keys you can control your data. SQL sample:
CREATE TABLE contacts(
contact_id INT GENERATED ALWAYS AS IDENTITY,
customer_id INT,
contact_name VARCHAR(255) NOT NULL,
email VARCHAR(100),
PRIMARY KEY(contact_id),
CONSTRAINT fk_customer
FOREIGN KEY(customer_id)
REFERENCES customers(customer_id)
ON DELETE SET NULL
ON UPDATE SET NULL
);
Third, specify the parent table and parent key columns referenced by the foreign key columns in the REFERENCES clause.
Finally, specify the delete and update actions in the ON DELETE and ON UPDATE clauses.
The delete and update actions determine the behaviors when the primary key in the parent table is deleted and updated. Since the primary key is rarely updated, the ON UPDATE action is not often used in practice. We’ll focus on the ON DELETE action.
PostgreSQL supports the following actions after updating or deleting:
SET NULL (Set values to NULL if data exists on referencing table)
SET DEFAULT (Set values to DEFAULT VALUES of this field if data exists on referencing table)
RESTRICT (Similar to NO ACTION)
NO ACTION (Can not update or delete data if exists on referencing table)
CASCADE (Delete all data if exists on referencing table)
I wrote for you a sample trigger function:
CREATE OR REPLACE FUNCTION Day_21_bankTriggerFunction()
RETURNS trigger
LANGUAGE plpgsql
AS $function$
declare
act varchar(30);
begin
-- get account_number from deleted record
act = old.account_number;
-- SECTION-1 :: Protect deleting if existing data
if (exists(select 1 from depositor where account_number = act)) then
return null;
end if;
-- SECTION-1 :: END
-- SECTION-2 :: Delete all data in the anothers table if exists */
delete from depositor where account_number = act;
return old;
-- SECTION-2 :: END
end
$function$;
CREATE TRIGGER Day_21_bankTrigger
BEFORE DELETE on account
FOR EACH ROW EXECUTE PROCEDURE Day_21_bankTriggerFunction();
Inside my trigger function, I have written two types of SQL codes. (SECTION-1, SECTION-2). You must choose one of them.

Related

PostgreSQL trigger update average and count when new value in column is added

This is my table:
CREATE TABLE mark (
EID serial,
PID integer,
SID integer,
score integer DEFAULT 5 NOT NULL,
CONSTRAINT PK_EID PRIMARY KEY(EID),
CONSTRAINT "FK_personne_ID"
FOREIGN KEY (PID)
REFERENCES personne (PID)
ON UPDATE RESTRICT ON DELETE RESTRICT,
CONSTRAINT "FK_serie_ID"
FOREIGN KEY (SID)
REFERENCES serie (SID)
ON UPDATE RESTRICT ON DELETE RESTRICT
);
My trigger : when a new row/value is inserted in the column "score", updates the average score and the total count of scores:
I'm not sure if I should implement a function ahead or start with the trigger directly :
CREATE OR REPLACE FUNCTION FunctionUpdateScore(float) RETURNS integer AS
'BEGIN
SELECT COUNT(score) AS nb_score, AVG(score) AS ag_score
FROM mark;
END;'
LANGUAGE 'plpgsql';
--trigger
CREATE or REPLACE TRIGGER TriggerUpdateScore
AFTER INSERT
ON mark
FOR EACH ROW
EXECUTE PROCEDURE FunctionUpdateScore();
UPDATE nb_score
SET nb_score= nb_score+ 1
END;
Properly escape your trigger body. Use dollar quoting
Remove the float argument from your function
Actually perform the update in your function
Update the correct table (you are updating the nb_score table in your example which does not have a trigger on it, therefore it will not fire off the trigger)
Specify a relationship between mark and nb_score. As of now the trigger function below updates all nb_score rows, not just the one that is related to the inserted row in mark.;
Your function return type must be trigger
CREATE OR REPLACE FUNCTION FunctionUpdateScore() RETURNS trigger AS $$
BEGIN
UPDATE nb_score
SET
nb_score=COUNT(score),
ag_score=AVG(score)
FROM mark;
END;
$$ LANGUAGE 'plpgsql';
CREATE TRIGGER TriggerUpdateScore
AFTER INSERT ON mark FOR EACH ROW EXECUTE PROCEDURE FunctionUpdateScore();

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

ORACLE SQL- After delete trigger on parent table with cascade set null, mutating error workaround

I am trying to update columns in my child table service after delete on parent table cars using function choose_ideal_car which uses some selects over cars and service tables.
Here is part of my sql script:
CREATE TABLE cars(car_id INT CONSTRAINT pk_id_cars);
CREATE TABLE service(
service_id INT CONSTRAINT pk_id_service PRIMARY KEY,
car_id INT CONSTRAINT fk_id_car REFERENCES cars(car_id) ON DELETE SET NULL,
period VARCHAR2(5) CONSTRAINT check_period CHECK period IN ('even','odd','every'),
period VARCHAR2(3) CONSTRAINT check_day CHECK day IN ('mon','tue','wed','thu','fri')
);
CREATE OR REPLACE TRIGGER service_set_car_id AFTER DELETE ON cars
DECLARE
CURSOR touched_rows is select * from service where car_id = null;
touched_row service%ROWTYPE;
BEGIN
OPEN touched_rows;
LOOP
FETCH touched_rows INTO touched_row;
EXIT WHEN touched_rows%NOTFOUND;
UPDATE service SET car_id = choose_ideal_car(touched_row.period,touched_row.day) WHERE service_id = touched_row.service_id;
END LOOP;
CLOSE touched_rows;
end;
/
For some reason my trigger is never fired.
I also tried creating triggers like this:
CREATE OR REPLACE TRIGGER service_set_car_id AFTER DELETE FROM cars FOR EACH ROW
CURSOR touched_rows is select * from service where car_id = :old.car_id;
...
Which is fired but throws 'mutating error' because function choose_ideal_car uses selects from both tables. Maybe the solution of this is to create duplicate of my cars table and select from it in my choose_ideal_car function instead of selecting from the cars table on which is my trigger defined, but that does not sounds good to me.
While I am writing this post I realized that even if my first trigger is fired it would not work correctly and throw the same 'mutating error'.
So in the end I have two questions:
1) Why is the first trigger never fired?
2) How to fix this mutating error and get all working correctly?
where car_id = null returns no rows, use where car_id is null instead

How to delete/update records inside trigger based on the updated/deleted row?

I have a table with the following format
id | name | supervisor_id
I made a "BEFORE INSERT" trigger that checks if the supervisor_id exists in the id column and if not, then assign a null value to the supervisor_id.
I am trying to write two more triggers. One that checks if the supervisor_id exists in the id column before each update of the supervisor_id, and one that sets the supervisor_id to NULL for each employee if his supervisor is deleted.
This is my code, of course it's not working, help please.
CREATE OR REPLACE TRIGGER EAP_users_TRG3
AFTER DELETE
ON EAP_users
FOR EACH ROW
DECLARE
d NUMBER;
BEGIN
SELECT id INTO d FROM EAP_users WHERE id = :OLD.id;
UPDATE EAP_users SET supervisor = NULL WHERE supervisor = d;
END;
/
This is the "working" trigger:
CREATE OR REPLACE TRIGGER EAP_users_TRG1
BEFORE INSERT
ON EAP_users
FOR EACH ROW
DECLARE
supervisor EAP_users.supervisor%TYPE;
CURSOR supervisor_CUR IS SELECT idFROM EAP_users;
b BOOLEAN := FALSE;
BEGIN
IF ( :NEW.supervisor IS NOT NULL ) THEN
FOR s IN supervisor_CUR LOOP
IF ( :NEW.supervisor = s.id ) THEN
b := TRUE;
END IF;
END LOOP;
IF (b = FALSE) THEN
:NEW.supervisor := NULL;
END IF;
END IF;
END;
/
According to the definition of your problem, you are trying to enforce referential integrity of your data. In that case, a trigger is probably not the right tool. To quote Oracle's documentation:
You can use both triggers and integrity constraints to define and enforce any type of integrity rule. However, Oracle strongly recommends that you use triggers to constrain data input only in the following situations:
[...]
When a required referential integrity rule cannot be enforced using the following integrity constraints:
NOT NULL, UNIQUE
PRIMARY KEY
FOREIGN KEY
CHECK
DELETE CASCADE
DELETE SET NULL
In that particular case you should use FOREIGN KEY constraint using the DELETE SET NULL modifier. Assuming you have an index on id, all you need is:
ALTER TABLE EAP_users
ADD CONSTRAINT EAP_users_supervisor_cst
FOREIGN KEY (supervisor_id)
REFERENCES EAP_users(id)
ON DELETE SET NULL;
This simple referential integrity constraint will perform probably better the same things as your 3 triggers -- namely:
prevent insert/update with a non existing (non-NULL) supervisor_id
set all supervisor_id to NULL when you delete the supervisor
See http://sqlfiddle.com/#!4/1f8fb/1 for a live example.

sql triggers - want to compare existing row value with value being inserted

I am creating a trigger that should compare of the values being inserted with one that already exists in the table. The old referencer doesn't work here because I am inserting, but how can I reference something that already exists?
Here is my tables and trigger:
create table events(eid char(2) primary key, cid char(2));
create table activities(mid char(2), eid char(2),
primary key (mid, eid),
constraint activities_fk foreign key (eid) references events(eid));
create or replace trigger check_valid
before insert or update on activities
for each row when (old.mid=new.mid)
declare
v_eid char(2);
v_cid char(2);
n_cid char(2);
begin
select eid into v_eid from activities
where mid=:new.mid;
select cid into v_cid from events
where eid=v_eid;
select cid into n_cid from events
where eid=:new.eid;
if v_cid=n_cid then
raise_application_error(-20000, 'Error');
end if;
end check_valid;
/
show errors;
You can't generally select from the table you're inserting into in a trigger. This is the mutating table problem, or as I often call it, the "damn mutating table problem".
Basically, don't do this. It's a bad idea. What happens if you have two sessions operating on the table at once? The trigger fires and neither session sees what the other has done until the commit, which is after the trigger. Then you've got unexpected data in your database.
Tom Kyte says, "when I hit a mutating table error, I've got a serious fatal flaw
in my logic."