INSTEAD OF DELETE trigger (Postgresql) - sql

I would like to disable the DELETE statement on a table.
What I need to do is a SET a field value instead of removing the respective record.
So far I have tried the following:
CREATE TRIGGER delete_trg
INSTEAD OF DELETE
ON schema.tbl
FOR EACH ROW
EXECUTE PROCEDURE schema.tbl_delete_fn();
My schema.tbl_delete_fn() function is as follows:
CREATE OR REPLACE FUNCTION schema.tbl_delete_fn()
RETURNS trigger AS
BEGIN
NEW.deleted := true;
RETURN NEW;
END;
So far this doesn't seem to work... any ideas?

You want a BEFORE DELETE trigger whose function returns NULL and the row variable is OLD, not NEW.
CREATE TRIGGER delete_trg
BEFORE DELETE
ON schema.tbl
FOR EACH ROW
EXECUTE PROCEDURE schema.tbl_delete_fn();
CREATE OR REPLACE FUNCTION schema.tbl_delete_fn()
RETURNS trigger AS '
BEGIN
UPDATE schema.tbl SET deleted=true WHERE ctid=OLD.ctid;
RETURN NULL;
END; ' language plpgsql;

Or...
CREATE RULE delete_rule
AS ON DELETE TO schema.tbl
DO INSTEAD NOTHING;
Pros: Clearer, no code is called for each row visited, and no SP required.
Cons: Less standard than the trigger solution.

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.

Move Data from One table to other table by using trigger in postgreSQL

I have the requirement to move data from one table to another table when the value of one of the columns is updated. And I just want to move the updated row to the new table.
Below is my trigger that I have written. The issue with my code is, that it is moving all the data and not just the row which was updated. Can anyone give a suggestion?
create or replace function moveToAC1ControlHist()
returns trigger as $$
begin if NEW.file_status='CO'
then
insert into ac1_control_hist (file_status,identitifier)
(
select file_status,identitifier
from
ac1_control where new.FILE_STATUS = 'CO'
);
end if;
return new;
end;
$$ language plpgsql;
create TRIGGER AC1_CONTROL_TRIGGER AFTER update of file_status ON AC1_CONTROL
FOR EACH ROW when (new.file_status ='CO')EXECUTE PROCEDURE moveToAC1ControlHist();
I think the logic you want is:
create or replace function moveToAC1ControlHist()
returns trigger as
$$
begin
insert into ac1_control_hist (file_status,identitifier)
values (new.file_status, new.identitifier);
return null;
end;
$$ language plpgsql;
create trigger ac1_control_trigger
after update of file_status on ac1_control
for each row
when (new.file_status ='co')
execute function movetoac1controlhist()
;
Rationale:
you just want to copy (part of) the row being updated, so there is no need to select; you can access the values of the current row with new in a row-level trigger
the trigger definition filters on new file_status that is equal to 'CO', so there is no need for a if construct in the function
this is an after trigger, so you can just return null - the result is discarded anyway

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.

Getting "invalid NEW or OLD specification" message when I try to run this

The entire question I'm trying to answer is:
Utilize the travel anywhere database to create a database trigger "hotel_kids_rule" to enforce a business rule. when inserting a hotel_reservation, if the num_kids value is more than zero, then assign the bed_type DQ to the reservation. save the trigger source as a script file.
For example, the trigger will change the bed_type for the following insert statement.
insert into hotel_reservation (reserve_no,reserve_date,arrival_date,dep_date,num_adults,num_kids,customer_id,hotel_id,bed_type,rooms)
values(hotel_reserve_sequence.nextval,sysdate,sysdate+5,sysdate+7,1,2,101,19,'DT',1);
For example, the trigger will not change the bed_type for the following insert statement.
insert into hotel_reservation (reserve_no,reserve_date,arrival_date,dep_date,num_adults,num_kids,customer_id,hotel_id,bed_type,rooms)
values(hotel_reserve_sequence.nextval,sysdate,sysdate+5,sysdate+7,2,0,102,20,'DT',1);
This is what I have so far:
create or replace trigger hotel_kids_rule
after insert or update on hotel_reservation
for each row
when (num_kids > 0)
declare
new_bed_type varchar2(20);
new_bed_type='DQ'
begin
new.bed_type=new_bed_type;
end;
Any help would be appreciated! Thanks!
I think you want a before trigger, so you can set the new value before it is written. Also, the way you access new is not ok: within the trigger code, you need :new.bed_type; in the when condition, you want new.num_kids.
This should work:
create or replace trigger hotel_kids_rule
before insert or update on hotel_reservation
for each row
when (new.num_kids > 0)
begin
:new.bed_type := 'DQ';
end;
/
Here is a small demo.
The assignment of your variable needs to be inside the actual code block. And the assignment operator is := in PL/SQL, not =. If you want the trigger to change anything you also need to create it as a BEFORE trigger:
create or replace trigger hotel_kids_rule
before insert or update on hotel_reservation
for each row
when (new.num_kids > 0)
declare
new_bed_type varchar2(20);
begin
new_bed_type := 'DQ';
:new.bed_type := new_bed_type;
end;
Note that the new record in the when condition is used without the colon : while inside the code block it needs to be referenced with the colon.
Alternatively initialize the variable when declaring it:
declare
new_bed_type varchar2(20) := 'DQ';
begin
:new.bed_type := new_bed_type;
end;
Or even simpler without any intermediate variable.
begin
:new.bed_type := 'DQ';
end;

PostgreSQL Update trigger

I have a table:
CREATE TABLE annotations
(
gid serial NOT NULL,
annotation character varying(250),
the_geom geometry,
"rotationAngle" character varying(3) DEFAULT 0,
CONSTRAINT annotations_pkey PRIMARY KEY (gid),
CONSTRAINT enforce_dims_the_geom CHECK (st_ndims(the_geom) = 2),
CONSTRAINT enforce_srid_the_geom CHECK (st_srid(the_geom) = 4326)
)
And trigger:
CREATE TRIGGER set_angle
AFTER INSERT OR UPDATE
ON annotations
FOR EACH ROW
EXECUTE PROCEDURE setangle();
And function:
CREATE OR REPLACE FUNCTION setAngle() RETURNS TRIGGER AS $$
BEGIN
IF TG_OP = 'INSERT' THEN
UPDATE annotations SET "rotationAngle" = degrees( ST_Azimuth( ST_StartPoint(NEW.the_geom), ST_EndPoint(NEW.the_geom) ) )-90 WHERE gid = NEW.gid;
RETURN NEW;
ELSIF TG_OP = 'UPDATE' THEN
UPDATE annotations SET "rotationAngle" = degrees( ST_Azimuth( ST_StartPoint(NEW.the_geom), ST_EndPoint(NEW.the_geom) ) )-90 WHERE gid = NEW.gid;
RETURN NEW;
END IF;
END;
$$ LANGUAGE plpgsql;
And when new row inserted in table or row edited i want to field rotationAngle setted with function result.
But when i inserting a new row in table function not work. I mean thath rotationAngle value not changed.
What can be wrong?
You are triggering an endless loop. Simplify the trigger function:
CREATE OR REPLACE FUNCTION set_angle()
RETURNS trigger
LANGUAGE plpgsql AS
$func$
BEGIN
NEW."rotationAngle" := degrees(
ST_Azimuth(
ST_StartPoint(NEW.the_geom)
, ST_EndPoint(NEW.the_geom)
)
) - 90;
RETURN NEW;
END
$func$;
Assign to NEW directly. No WHERE in this case.
You must double-quote illegal column names. Better not to use such names to begin with.
Recent related answer.
Code for insert & upgrade is the same. I folded into one code path.
Use a BEFORE trigger. This way you can edit columns of the triggering row directly before they are saved:
CREATE TRIGGER set_angle
BEFORE INSERT OR UPDATE ON annotations
FOR EACH ROW EXECUTE PROCEDURE set_angle();
However
If you are just trying to persist a functionally dependent value in the table (and there are no other considerations): Don't. Use a view or a generated column instead:
Store common query as column?
Then you don't need any of this.
There are multiple things wrong here.
1) When you insert a row 'A' the function setAngle() is called. But in the function you are calling another update within the function which will trigger the function again, and again, and so on...To fix this don't issue a update! Just update the NEW records value independently and return it.