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

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.

Related

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;.

Check column value before delete trigger postgreSQL

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.

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.

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;