Delete command not work after create trigger - postgresql

I create a simple function and trigger to delete all rows from tb_users when a row from tb_city is deleted:
create or replace function delete_user()
returns trigger as
$$
begin
delete from rl_user_city where user_id in (select distinct user_id from rl_user_city where city_id = old.id) and city_id = old.id;
return new;
end;
$$
language plpgsql;
drop trigger if exists t_delete_user on tb_city;
create trigger t_delete_user
before delete on tb_city
for each row
execute procedure delete_user();
After I create this trigger, the command delete is not working: delete from tb_city where id = 385;
My tables:
tb_city:
id: int
city: string
tb_user_city:
user_id: int
city_id: int
I run, but the row is not deleted...
Any idea?

In a before delete trigger the value NEW is null. A null return value will block the deletion from happening. You need to RETURN OLD from a delete trigger instead.
If I am not mistaken, if you simply want to delete all users that are registered in a city you are deleting the DELETE command in the trigger function can be much simpler:
DELETE FROM rl_user_city WHERE city_id = OLD.id

Related

How to DELETE/INSERT rows in the same table using a UPDATE Trigger?

I want to create a trigger function, which copies certain columns of an recent updated row and deletes the old data. After that I want to insert the copied columns in exact the same table in the same row (overwrite). I need the data to be INSERTED because this function will be embedded in an existing program, with predefined Triggers.
That's what I have so far:
CREATE OR REPLACE FUNCTION update_table()
RETURNS TRIGGER AS
$func$
BEGIN
WITH tmp AS (DELETE FROM table
WHERE table.id = NEW.id
RETURNING id, geom )
INSERT INTO table (id, geom) SELECT * FROM tmp;
END;
$func$ language plpgsql;
CREATE TRIGGER T_update
AFTER UPDATE OF geom ON table
EXECUTE PROCEDURE update_table();
But I get the Error message:
ERROR: cannot perform DELETE RETURNING on relation "table"
HINT: You need an unconditional ON DELETE DO INSTEAD rule with a RETURNING clause.
Why I should use a rule here?
I'm using PostgreSQL 9.6
UPDATE:
A little bit of clarification. When I have two columns in my table (id, geom), after I updated geom I want to make a copy of this (new)row and insert it into the same table, while overwriting the updated row. (I'm not interested in any value before the update) I know that this is odd but I need this row to be inserted again because the program i embed this function in, listens to a INSERT statement and cannot be changed by me.
Right after you update a row, its old values will no longer be available. So, if you simply want to preserve the old row in case of an update you need to create a BEFORE UPDATE trigger, so that you can still access the OLD values and create a new row, e.g.
CREATE TABLE t (id int, geom geometry(point,4326));
CREATE OR REPLACE FUNCTION update_table() RETURNS TRIGGER AS $$
BEGIN
INSERT INTO t (id, geom) VALUES (OLD.id,OLD.geom);
RETURN NEW;
END; $$ LANGUAGE plpgsql;
CREATE TRIGGER t_update
BEFORE UPDATE OF geom ON t FOR EACH ROW EXECUTE PROCEDURE update_table();
INSERT INTO t VALUES (1,'SRID=4326;POINT(1 1)');
If you update the record 1 ..
UPDATE t SET geom = 'SRID=4326;POINT(2 2)', id = 2 WHERE id = 1;
UPDATE t SET geom = 'SRID=4326;POINT(3 3)', id = 3 WHERE id = 2;
.. you get a new record in the same table as you wished
SELECT id, ST_AsText(geom) FROM t;
id | st_astext
----+------------
1 | POINT(1 1)
2 | POINT(2 2)
3 | POINT(3 3)
Demo: db<>fiddle
Unrelated note: consider upgrading your PostgreSQL version! 9.6 will reach EOL in November, 2021.
First thanks to #JimJones for the answer. I´d like to post his answer modified for this purpose. This code "overwrites" the updated row by inserting a copy of itself and then deleting the old duplicate. That way I can Trigger on INSERT.
CREATE TABLE t (Unique_id SERIAL,id int, geom geometry(point,4326));
CREATE OR REPLACE FUNCTION update_table() RETURNS TRIGGER AS $$
BEGIN
INSERT INTO t (id, geom) VALUES (NEW.id,NEW.geom);
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
CREATE TRIGGER t_update
BEFORE UPDATE OF geom ON t FOR EACH ROW EXECUTE PROCEDURE update_table();
CREATE OR REPLACE FUNCTION delete_table() RETURNS TRIGGER AS $$
BEGIN
DELETE FROM t a
USING t b
WHERE a.Unique_id < b.Unique_id
AND a.geom = b.geom;
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
CREATE TRIGGER t_delete
AFTER UPDATE OF geom ON t FOR EACH ROW EXECUTE PROCEDURE delete_table();
INSERT INTO t VALUES (1,1,'SRID=4326;POINT(1 1)');
UPDATE t SET geom = 'SRID=4326;POINT(2 2)' WHERE id = 1;

PostgreSQL - Trigger on INSERT or UPDATE

I wan't to add a trigger in a PostgreSQL database. This trigger is used to concatenate values of 2 columns to update a 3rd one. I wan't to run it when a row is inserted or updated in the table.
Table
CREATE TABLE IF NOT EXISTS FILE(ID INT NOT NULL PRIMARY KEY, FOLDER TEXT, NAME TEXT, URL TEXT);
Function
CREATE OR REPLACE FUNCTION LINK() RETURNS trigger AS
$$
BEGIN
IF (TG_OP = 'UPDATE') THEN
UPDATE FILE
SET URL = CONCAT(FOLDER, NAME)
WHERE ID = OLD.ID;
ELSIF (TG_OP = 'INSERT') THEN
UPDATE FILE
SET URL = CONCAT(FOLDER, NAME)
WHERE ID = NEW.ID;
END IF;
RETURN NULL;
END
$$
LANGUAGE PLPGSQL;
Trigger
CREATE TRIGGER TRIGGER_LINK
BEFORE INSERT OR UPDATE
ON FILE
FOR EACH ROW
EXECUTE PROCEDURE LINK();
When I insert a value in table like
INSERT INTO FILE VALUES (1, 'C:\', 'doc.pdf');
I have an error message list index out of range because ID number is not yet created and UPDATE query on INSERT can't execute. But if I make an AFTER UPDATE it will run infinitely.
How to run a trigger function on INSERT or UPDATE with a WHERE clause on ID to target only inserted or updated row ? I'm using PostgreSQL 10.14.
Don't use UPDATE to do that. Just assign the value. Also, a BEFORE trigger should not return null because that will abort the operation.
CREATE OR REPLACE FUNCTION LINK() RETURNS trigger AS
$$
BEGIN
new.url := CONCAT(new.FOLDER, new.NAME);
RETURN new; --<< important!
END
$$
LANGUAGE PLPGSQL;

Record old is not assigned on Update Statement

I created a Trigger on Update which should insert in my table preishist.
//preishist
CREATE TABLE preishist (
updatenr varchar(4),
preis numeric(8,2),
pnr varchar(4),
gueltigab date,
PRIMARY KEY(updatenr));
//produkt
CREATE TABLE produkt(
pnr varchar(4),
name varchar(4),
preis numeric(8,2),
gultigab date,
PRIMARY KEY(pnr));
//Trigger
CREATE TRIGGER trigger AFTER UPDATE ON produkt FOR EACH STATEMENT EXECUTE
PROCEDURE triggerfunc();
//Triggerfunc
CREATE FUNCTION triggerfunc RETURNS TRIGGER AS '
begin
IF TG_OP = 'UPDATE' THEN
IF OLD.preis <> NEW.preis AND OLD.gueltigab < NEW.gueltigab THEN
INSERT INTO preishist (preis,pnr,gultigab) VALUES (NEW.preis, NEW.pnr,NEW.gueltigab);
end IF;
end IF;
end;
When I try to update produkt I get the error message
Record old is not assigned yet.
I'm happy for all help.
OLD and NEW are only available in row level trigger. You defined your trigger as a statement level trigger.
To be able to use OLD and NEW use:
CREATE TRIGGER trigger
AFTER UPDATE ON produkt
FOR EACH ROW --<< here
EXECUTE PROCEDURE triggerfunc();

Postgres triggers - adding old and new values

I am trying to learn Postgres triggers, using some simple examples. I have created a simple table:
create table emp (empname text, salary integer, last_user text);
My goal is to replace the old salary with a new salary computed as the salary inserted (new) + the old salary. I could not get them to sum even when I did not put a condition (i.e. empname is the same)
Here is my code:
-- this table returns a new row instead of summing
CREATE OR REPLACE FUNCTION emp_stamp() RETURNS trigger
AS $emp_stamp$
BEGIN
new.salary = new.salary + old.salary ;
RETURN NEW;
END;
$emp_stamp$ LANGUAGE plpgsql;
CREATE TRIGGER emp_stamp
BEFORE UPDATE on emp
FOR EACH ROW
EXECUTE PROCEDURE emp_stamp();
When I insert into the table, I get a new row added and no existing rows follow the formula:
INSERT INTO EMP VALUES('BR',39970,'BR')
I have also unsuccessfully tried the UPDATE command.
Your CREATE TRIGGER script says BEFORE UPDATE. So this trigger is not fired at all for INSERT commands.
Moreover, the same trigger function would raise an error for INSERT commands anyway because, obviously, there is no "old" version for newly inserted rows.
It should work just fine as is for UPDATE, though. I just cleaned it up a bit:
CREATE OR REPLACE FUNCTION emp_stamp()
RETURNS trigger AS
$func$
BEGIN
NEW.salary := NEW.salary + OLD.salary;
RETURN NEW;
END
$func$ LANGUAGE plpgsql;
CREATE TRIGGER emp_stamp
BEFORE UPDATE ON emp
FOR EACH ROW EXECUTE PROCEDURE emp_stamp();
Just a proof of concept, I fail to see the point of adding up old an new value.
For starters, your table needs a proper PRIMARY KEY, a serial column for instance (empname is hardly unique):
CREATE TABLE emp (
emp_id serial PRIMARY KEY
, empname text
, salary integer
, last_user text);
Then the UPDATE could work reliably:
UPDATE EMP
SET salary = 39970
WHERE emp_id = 123;
I still don't see how the mentioned trigger would make sense. You could increase an existing salary like this, no trigger involved:
UPDATE EMP
SET salary = salary + 39970
WHERE emp_id = 123;

Dynamic Change Column Values with NEW in Trigger Function

Is it possible to dynamically change column values in NEW with a Trigger Function?
Triggers:
CREATE TRIGGER lowercase_username_on_insert_trigger BEFORE INSERT OR UPDATE ON users
FOR EACH ROW EXECUTE PROCEDURE lowercase_on_insert('username');
CREATE TRIGGER lowercase_email_on_insert_trigger BEFORE INSERT OR UPDATE ON users
FOR EACH ROW EXECUTE PROCEDURE lowercase_on_insert('email');
Trigger Function:
CREATE OR REPLACE FUNCTION lowercase_on_insert()
RETURNS trigger
AS $lowercase_on_insert$
BEGIN
-- e.g., NEW.username = LOWER(NEW.username)
-- -OR- NEW.email = LOWER(NEW.email)
EXECUTE FORMAT('NEW.%s = LOWER(NEW.%s);', TG_ARGV[0], TG_ARGV[0]);
RETURN NEW;
END
$lowercase_on_insert$ LANGUAGE plpgsql;
I get an ERROR: syntax error at or new "NEW" whenever I run an INSERT.
This is my table:
CREATE TABLE users (
id BIGSERIAL PRIMARY KEY,
username VARCHAR(55) NOT NULL UNIQUE,
email VARCHAR(120) NOT NULL UNIQUE
);
Change your Trigger function as below
CREATE OR REPLACE FUNCTION lowercase() RETURNS trigger AS $$
BEGIN
NEW.username = LOWER(NEW.username);
NEW.email = LOWER(NEW.email);
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
and assign
CREATE TRIGGER tgrr BEFORE INSERT OR UPDATE ON users
FOR EACH ROW EXECUTE PROCEDURE lowercase();
OR
You can directly apply lower() function in Insert/Update like this
insert into users(username,email) values (lower('Jerry'),lower('JeRRY#mail.COM'));
update users set username=lower('JERRY'),email=lower('JERRY#MAIL.COM') where id =1