Not null constraint using triggers in SQL - 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.

Related

syntax Error in PostgreSQL when I try to create Trigger

I want to create trigger in PostgreSQL.
Logic is very simple.
I need trigger, if published_at updated and written_at is null, set published_at to written_at.
I wrote this one, but it failed. Does anybody have an idea?
CREATE function setWrittenAt() RETURNS trigger;
AS
DECLARE old_id INTEGER;
BEGIN ;
old_id = OLD.id
IF NEW.published_at IS NOT and NEW.written_at IS null
THEN
UPDATE review SET NEW.written_at = NEW.published_at where id = old_id;
END IF ;
RETURN NEW;
END;
LANGUAGE plpgsql;
CREATE TRIGGER update_written_at
AFTER UPDATE OF published_at ON review
WHEN (OLD.published_at IS DISTINCT FROM NEW.published_at)
EXECUTE PROCEDURE setWrittenAt();
Error:
Syntax error: 7 ERROR: syntax error at or near "DECLARE"
LINE 3: DECLARE old_id INTEGER;
There are multiple errors in your code:
IS NOT is not a valid expression you need IS NOT NULL.
After BEGIN and the returns clause there must be no ;
you forgot to enclose the function body as a string (which is easier to write if you use dollar quoting
you also don't need an unnecessary (additional) UPDATE if you make it a before trigger
CREATE function setwrittenat()
RETURNS trigger
AS
$$
BEGIN
IF NEW.published_at IS NOT NULL and NEW.written_at IS null THEN
NEW.written_at := = NEW.published_at; --<< simply assign the value
END IF;
RETURN NEW;
END;
$$
LANGUAGE plpgsql;
Then use a BEFORE trigger:
CREATE TRIGGER update_written_at
BEFORE UPDATE OF published_at ON review
WHEN (OLD.published_at IS DISTINCT FROM NEW.published_at)
FOR EACH ROW
EXECUTE PROCEDURE setWrittenAt();
this is based on a_horse_with_no_names answer, since it'll throw an error.
ERROR: statement trigger's WHEN condition cannot reference column values
You need to add FOR EACH ROW, else conditional triggers will not function.
If neither is specified, FOR EACH STATEMENT is the default.
Statement-level triggers can also have WHEN conditions, although the feature is not so useful for them since the condition cannot refer to any values in the table.
See here
CREATE TRIGGER update_written_at
BEFORE UPDATE OF published_at ON review
FOR EACH ROW
WHEN (OLD.published_at IS DISTINCT FROM NEW.published_at)
EXECUTE PROCEDURE setWrittenAt();
I can not comment yet, which is why I've posted this as an answer.

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.

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.

SQL RAISE EXCEPTION not working

I'm not very good with SQL and I have a small problem with my code.
CREATE OR REPLACE FUNCTION cancelBooking() RETURNS TRIGGER AS $cancelBooking$
BEGIN
IF (NEW.bookingid not in(SELECT bookingid FROM flightbooking)) THEN
RAISE EXCEPTION 'ID NOT FOUND';
END IF;
END;
$cancelBooking$ LANGUAGE plpgsql;
CREATE TRIGGER cancelBooking BEFORE UPDATE ON flightbooking
FOR EACH ROW EXECUTE PROCEDURE cancelBooking();
UPDATE flightbooking
SET status = 'C'
WHERE bookingid=11;
After I update flightbooking with non existing ID it still says UPDATE 0 which didn't do anything of course but I want it to be an error not successfull query.
Any ideas? I tried to look for a solution on the internet but it didn't help.
Obviously the stated question is why it is not working (which is due to the problem discussed in the other answers. Obviously this will never work since the only case of the trigger being fired never can have be in a snapshot where a row with the same bookingid as NEW will be visible in the same snapshot.
Also I am not 100% sure but I am worried about performance in your function. (PLPGSQL is a bit funny at times).
Try this instead as it is clearer what is going on under the hood and therefore makes clearer what can be optimized.
CREATE OR REPLACE FUNCTION cancelBooking() RETURNS TRIGGER AS
$cancelBooking$
BEGIN
PERFORM * FROM flightbooking WHERE bookingid = NEW.bookingid;
IF NOT FOUND THEN
RAISE EXCEPTION 'ID NOT FOUND';
END IF;
END;
$cancelBooking$ LANGUAGE plpgsql;
I am guessing in most cases that that the performance difference will be very minimal but the performance implications and caveats are clearer so the opportunities to shoot yourself in the foot are less.
On to a real solution rather than a critique and diagnosis
This will never work as you have done it. You could do as follows instead:
CREATE OR REPLACE FUNCTION cancelBooling(_bookingid int) returns void
LANGUAGE PLPGSQL AS
$$
BEGIN
DELETE FROM flightbooking WHERE bookingid = _bookingid;
IF NOT FOUND THEN
RAISE EXCEPTION 'NOT FOUND';
END IF;
END;
$$;
Your trigger fires for each row being updated. Because there are no rows to update (the WHERE clause in the UPDATE doesn't find any), the trigger is never fired.

PostgreSQL 1 to many trigger procedure

I wrote this query in PostgreSQL:
CREATE OR REPLACE FUNCTION pippo() RETURNS TRIGGER AS $$
BEGIN
CHECK (NOT EXISTS (SELECT * FROM padre WHERE cod_fis NOT IN (SELECT padre FROM paternita)));
END;
$$ LANGUAGE plpgsql;
It returns:
Syntax error at or near CHECK.
I wrote this code because I have to realize a 1..n link between two tables.
You can't use CHECK here. CHECK is for table and column constraints.
Two further notes:
If this is supposed to be a statement level constraint trigger, I'm guessing you're actually looking for IF ... THEN RAISE EXCEPTION 'message'; END IF;
(If not, you may want to expand and clarify what you're trying to do.)
The function should return NEW, OLD or NULL.