Check column value before delete trigger postgreSQL - sql

Can someone tell me how can I check a specific column value before deleting from a table in postgreSQL?
My current code:
CREATE OR REPLACE FUNCTION f_tr_table_row_prohibit_delete() RETURNS TRIGGER AS
$$
BEGIN
IF (OLD.table_state_code=0) THEN
DELETE FROM mytable WHERE table_code=OLD.table_code;
ELSE
RAISE EXCEPTION 'The deletion of a row is allowed only when table state code is 0!';
END IF;
END;
$$
CREATE TRIGGER tr_table_row_prohibit_delete
BEFORE DELETE ON mytable
FOR EACH ROW
EXECUTE PROCEDURE f_tr_laua_rida_prohibit_delete();
LANGUAGE plpgsql SECURITY DEFINER STABLE
SET search_path = public, pg_temp;
I have a table named mytable with columns table_code and table_state_code.
Before deleteing from, mytable I want to check if the table_state_code is 0, if not then it should not allow delete if it is anything else then it should delete the row.
With my current code, it correctly raises the exception when table_state_code is not 0 but when it is then it says :
ERROR: DELETE is not allowed in a non-volatile function, instead of deleteing the row.
Thanks in advance.

In BEFORE DELETE trigger you can cancel the deletion or let it happen.
To cancel deletion you can return NULL or RAISE EXCEPTION.
To let the deletion to be continued return OLD.
The deletion has already started (as it is BEFORE DELETE trigger) so you don't have to execute DELETE FROM mytable WHERE table_code=OLD.table_code;.
See Overview of Trigger Behavior in the documentation.

Related

PostgeSQL trigger before insert unique value

Is there any variant to create a trigger before insert and if the value is a value that exists already in table, just update it. I know about 'ON DUBLICATE KEY' or a 'ON CONFLICT' in PostgreSQL but I need a trigger just because it's a task in my university.
I tried to create it, but I get just an error about duplicate keys.
CREATE or replace FUNCTION trigger_function()
RETURNS TRIGGER
LANGUAGE PLPGSQL
AS $$
BEGIN
IF new.name in (select name from "Test")
then
update "Test" set intt = new.intt where name = new.name;
end if ;
return new;
END;
$$
CREATE TRIGGER trigger_name
BEFORE insert
ON "Test"
for each row
EXECUTE PROCEDURE trigger_function();
Is it possible to create such trigger?
Read the docs plpgsql trigger function:
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).
So RETURN NULL after the UPDATE statement inside the if andRETURN NEW when the if is false. Then the INSERT will happen if the name is unique.

Postgres SQL trigger to update TableA.column with NEW value AFTER INSERT OR UPDATE on TableB

I have a very simple trigger, that updates a column in table_a whenever table_b is updated:
create or replace function trigger_update_status()
returns trigger as
$body$
begin
-- UPDATE TABLE A
update table_a
set a_status = new.b_status,
date_updated = now()
from table_b
where table_a.serial_number = table_b.serial_number;
return new;
end;
$body$
language plpgsql;
create trigger "currentStatus" after update or insert on table_b
for each row
execute procedure trigger_update_status();
However, I am getting the error that there is no RETURN value:
ERROR: control reached end of trigger procedure without RETURN
I'm confused on whether or not NEW is appropriate here as I've been reading conflicting information.
On the one hand, the answer here (Postgres trigger after insert accessing NEW) makes it clear that: "The return value of a row-level trigger fired AFTER or a statement-level trigger fired BEFORE or AFTER is always ignored; it might as well be null. However, any of these types of triggers might still abort the entire operation by raising an error."
On the other hand, my trigger here essentially matches the one here (https://dba.stackexchange.com/questions/182678/postgresql-trigger-to-update-a-column-in-a-table-when-another-table-gets-inserte), which calls NEW & AFTER together. So I am not sure why mine is not working. Any help is very greatly appreciated!
Future answer for anybody with same issue: it was a second (similarly named, which is why I didn't catch it until much later) trigger that was called AFTER this one that was causing the issue. The second trigger could not RETURN NEW because in this case, there was no new value. I fixed this by adding an IF/ELSE statement to my second trigger:
IF NEW.current_location <> OLD.current_location
THEN
INSERT INTO table_x(serial_number, foo, bar)
VALUES(OLD.serial_number, OLD.foo, old.bar);
return new;
-- ADDED THIS LINE:
else return null;
END IF;
Lesson learned thanks to #sticky bit - if the trigger works in an isolated dbfiddle, it's something else causing the issue.

updating object inside BEFORE DELETE trigger has no effect

I need to clean up a deleted object within another object which uses it as a foreign key, and I use a BEFORE DELETE trigger for that. I have no idea why the code below does not work.
Even more strange is that if I perform sequentially the UPDATE query and then DELETE query, the row is deleted properly.
CREATE OR REPLACE FUNCTION cleanNoteEventConnections() RETURNS TRIGGER AS $$
DECLARE
BEGIN
EXECUTE 'update invoice set "noteEvent"=null where "noteEvent"=' || OLD.id;
RETURN NULL;
END;
$$ LANGUAGE 'plpgsql';
CREATE TRIGGER cleanNoteEventConnections BEFORE DELETE ON note_event
FOR EACH ROW EXECUTE PROCEDURE cleanNoteEventConnections();
This is what I see in the pgAdmin console after the delete query:
delete from note_event where id=34
result: Query returned successfully: 0 rows affected, 11 msec execution time.
And the note_event with id 34 still exists.
This behavior is described in the documentation:
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). If a nonnull value is returned then the operation proceeds with that row value.
Use RETURN OLD; instead of RETURN NULL;.

Not null constraint using triggers in SQL

I want to implement a not-null constraint on an attribute using a trigger.
Here's my code:
create table mytable2(id int);
create or replace function p_fn() returns trigger as $prim_key$
begin
if (tg_op='insert') then
if (id is null) then
raise notice 'ID cannot be null';
return null;
end if;
return new;
end if;
end;
$prim_key$ language plpgsql;
create trigger prim_key
before insert on mytable2
for each row execute procedure p_fn();
But I get an error saying "control reached end of trigger procedure without RETURN" whenever I try to insert a null value. I tried placing the "return new" statement in the inner IF, but it still gave me the same error. What am I doing wrong?
The immediate cause of the problem is that PostgreSQL string comparisons are case sensitive. INSERT is not the same as insert. Try:
IF tg_op = 'INSERT' THEN
Advice
You're only raising a notice. This allows flow of control to continue to the next line in the procedure. You should generally RAISE EXCEPTION to abort execution and roll the transaction back. See RAISE. As it stands, the trigger will cause inserts that do not satisfy the requirement to silently fail, which is generally not what you want.
Additionally, your triggers should usually end with a RAISE EXCEPTION if they're always supposed to return before end of function. That would've helped you see what was going wrong sooner.
I'd add just a couple of things to the good suggestions already made:
make sure you handle the UPDATE case as well
RAISE using the proper error condition. I've given a basic example below - for more formatting options see the docs here.
for clarity, I like to include at least the table name in my trigger name
CREATE OR REPLACE FUNCTION fn_validate_id_trigger() RETURNS TRIGGER AS
$BODY$
BEGIN
IF (TG_OP IN ('INSERT', 'UPDATE')) THEN
IF (NEW.id IS NULL) THEN
RAISE not_null_violation;
END IF;
END IF;
RETURN NULL;
END;
$BODY$
LANGUAGE plpgsql;
CREATE CONSTRAINT TRIGGER tr_mytable_validate_id
AFTER INSERT OR UPDATE
ON mytable2
FOR EACH ROW EXECUTE PROCEDURE fn_validate_id_trigger();
Update: this is now a CONSTRAINT trigger and fires AFTER insert or update. In first edit, I presented a column-specific trigger (UPDATE OF id). This was problematic as it would not fire if another trigger executed on the table changed column 'id' to null.
Again, this isn't the most efficient way to handle constraints but it's good to know.

Postgres: checking value before conditionally running an update or delete

I've got a fairly simple table which stores the records' authors in a text field as shown here:
CREATE TABLE "public"."test_tbl" (
"index" SERIAL,
"testdate" DATE,
"pfr_author" TEXT DEFAULT "current_user"(),
CONSTRAINT "test_tbl_pkey" PRIMARY KEY("index");
The user will never see the index or pfr_author fields, but I'd like them to be able to UPDATE the testdate field or DELETE whole records if they have permission and if they are the author. i.e. if test_tbl.pfr_author = CURRENT_USER THEN permit the UPDATE OR DELETE, but if not then raise an error message such as "Sorry, you do not have permission to edit this record.".
I have not gone down the route of using a trigger as I figure that even if it is executed before row update the user-requested update will still take place afterwards regardless.
I've tried doing this through a rule, but end up with infinite recursion as I put an update command inside the rule. Is there some way to do this using rules alone or a combination of a rule and trigger?
Thanks very much for any help!
Use a row level BEFORE trigger on UPDATE and DELETE to do this. Just have it return NULL when the operation is not permitted and the operation will be skipped.
http://www.postgresql.org/docs/9.0/interactive/trigger-definition.html
the trigger function have some problem,resulting recursive loop update.You should do like this:
CREATE OR REPLACE FUNCTION "public"."test_tbl_trig_func" () RETURNS trigger AS $body$
BEGIN
IF not (old.pfr_author = "current_user"() OR "current_user"() = 'postgres') THEN
NULL;
END IF;
RETURN new;
END;
$body$ LANGUAGE 'plpgsql' VOLATILE CALLED ON NULL INPUT SECURITY INVOKER COST 100;
I have a test like this,it does well;
UPDATE test_tbl SET testdate = CURRENT_DATE WHERE test_tbl."index" = 2;