PostgreSQL - Trigger on INSERT or UPDATE - postgresql

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;

Related

Trigger function can't update table

Here is my trigger function
CREATE OR REPLACE FUNCTION test_table_insert()
RETURNS TRIGGER
AS $$
BEGIN
IF NEW.id IS NULL THEN
RAISE EXCEPTION 'id is null';
END IF;
UPDATE e_sub_agreement SET ro_id = NEW.id WHERE id = NEW.id;
RETURN NEW;
END;
$$
LANGUAGE plpgsql;
CREATE TRIGGER test_table_insert AFTER INSERT ON e_sub_agreement FOR EACH ROW EXECUTE PROCEDURE test_table_insert();
The problem is that it doesn't update table e_sub_agreement. I checked NEW value and everything is good. It returns with the new id. If I change where statement id = "some existing id in table", then it works. It changes ro_id to the new id. How is it possible? My guess is that data has not been inserted into table and trigger function can't find row with the given id. But it's not how trigger's after insert function works. What's the magic?
An AFTER trigger can not change anything. Running an additional UPDATE is also quite inefficient. Change this to a BEFORE trigger and assign the value you want:
CREATE OR REPLACE FUNCTION test_table_insert()
RETURNS TRIGGER
AS $$
BEGIN
IF NEW.id IS NULL THEN
RAISE EXCEPTION 'id is null';
END IF;
NEW.ro_id := NEW.id;
RETURN NEW;
END;
$$
LANGUAGE plpgsql;
CREATE TRIGGER test_table_insert
BEFORE INSERT ON e_sub_agreement
FOR EACH ROW EXECUTE PROCEDURE test_table_insert();
Note that the NOT NULL check is better done by defining the column as NOT NULL.

How to modify Trigger to update a single attribute in PostgreSQL

Here is my sample table.
CREATE TABLE employee_test(
idTst SERIAL PRIMARY KEY,
monthDownload VARCHAR(6),
changeDate DATE);
I am trying to create a function and trigger that would update changeDate attribute with a current date when monthDownload attribute is updated.
The function I have it works with one problem. It updates all records instead of the one that was updated.
CREATE OR REPLACE FUNCTION downloadMonthChange()
RETURNS TRIGGER AS
$$
BEGIN
IF NEW.monthDownload <> OLD.monthDownload THEN
UPDATE employee_test
SET changeDate = current_date
where OLD.idTst = NEW.idTst;
END IF;
RETURN NEW;
END;
$$
Language plpgsql;
Trigger
Create TRIGGER dataTest
AFTER UPDATE
ON employee_test
FOR EACH ROW
EXECUTE PROCEDURE downloadMonthChange();
When I execute the following Update statement:
UPDATE employee_test SET monthDownload = 'oct12'
WHERE idTst = 1;
All changeDate rows get update with a current date.
Is there a way to have only a row with changed record to have a current date updated.
If you use a before trigger you can write directly to NEW
CREATE OR REPLACE FUNCTION downloadMonthChange()
RETURNS TRIGGER AS
$$
BEGIN
IF NEW.monthDownload <> OLD.monthDownload THEN
NEW.changeDate = current_date;
END IF;
RETURN NEW;
END;
$$
Language plpgsql;
the other option when you must use an after trigger is to include the primary key in the where clause. It appears that you were trying to do this, but you had a spurious OLD in the query. beause of that the where clause was only looking at the record responsible for the trigger call, and not limiting which records were to be updated.
IF NEW.monthDownload <> OLD.monthDownload THEN
UPDATE employee_test
SET changeDate = current_date
where idTst = NEW.idTst;

Postgres insert or update trigger WHEN condition (old)

I need write insert or update trigger, but with WHEN condition with compare OLD and NEW rows.
According documentation OLD is null for insert operation. How i can use OLD in WHEN condition for INSERT AND UPDATE triggers?
Example trigger:
CREATE TRIGGER mytrigger
BEFORE INSERT OR UPDATE ON "mytable"
FOR EACH ROW
WHEN (NEW.score > 0 AND OLD.score <> NEW.score)
EXECUTE PROCEDURE mytrigger();
but for insert OLD is null.
Option A:
You can change the code so that conditions will be in the trigger function rather than the trigger itself. With this approach OLD will be used only in the UPDATE.
Trigger:
CREATE TRIGGER mytrigger
BEFORE INSERT OR UPDATE ON "mytable"
FOR EACH ROW
EXECUTE PROCEDURE mytrigger();
Trigger function:
CREATE OR REPLACE FUNCTION mytrigger()
RETURNS trigger AS
$BODY$
begin
if NEW.score > 0 then
--code for Insert
if (TG_OP = 'INSERT') then
YOUR CODE
end if;
--code for update
if (TG_OP = 'UPDATE') then
if OLD.score <> NEW.score then -- (if score can be null see #voytech comment to this post)
YOUR CODE
end if;
end if;
end if;
return new;
end;
$BODY$
LANGUAGE plpgsql VOLATILE
Option B:
As Thilo suggested write two triggers that share the same trigger function.
Triggers:
CREATE TRIGGER mytrigger1
BEFORE INSERT ON "mytable"
FOR EACH ROW
WHEN NEW.score > 0
EXECUTE PROCEDURE mytrigger();
CREATE TRIGGER mytrigger2
BEFORE UPDATE ON "mytable"
FOR EACH ROW
WHEN (NEW.score > 0 AND OLD.score <> NEW.score)
EXECUTE PROCEDURE mytrigger();
Trigger function:
CREATE OR REPLACE FUNCTION mytrigger()
RETURNS trigger AS
$BODY$
begin
YOUR CODE
return new;
end;
$BODY$
LANGUAGE plpgsql VOLATILE
Trigger.oldMap.keySet(); will give the Id's present in the Trigger.oldMap.
It is a set type collection of all the Id's.
have a look at the following example, change the DML events in the trigger events every time and see the debug logs.
you will understand which trigger context variable is available for which DML event.
CREATE TRIGGER Email_Check_On_Contact
before update{
oldMap<ID,Contact>=new Map<ID,Contact>();
o = trigger.oldMap;
for(Contact newcont: trigger.new)
{
if(newcont.Email != o.get(newcont.Id).Email)
{
newcont.Email.addError('Email cannot be changed');
}
}
}

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

How to apply a update after an inser or update POSTGRESQL Trigger

How to apply an update after an insert or update in POSTGRESQL; I have got a table which has a field lastupdate; I want that field to be set up whenever the row is updated or when it was inserted.
I tried this trigger, but It is not working! HELP!!
CREATE OR REPLACE FUNCTION fn_update_profile()
RETURNS TRIGGER AS $update_profile$
BEGIN
IF (TG_OP = 'INSERT' OR TG_OP = 'UPDATE' ) THEN
UPDATE profile SET lastupdate=now() where oid=OLD.oid;
RETURN NULL;
ELSEIF (TG_OP = 'DELETE') THEN
RETURN NULL;
END IF;
RETURN NULL; -- result is ignored since this is an AFTER trigger
END;
$update_profile$ LANGUAGE plpgsql;
Your trigger function can be a lot easier than you had. Keep in mind that PG will do the update or the insert on the original table, you only have to deal with keeping the profile table up-to-date:
CREATE OR REPLACE FUNCTION fn_update_profile()
RETURNS TRIGGER AS $update_profile$
BEGIN
UPDATE profile SET lastupdate = now() WHERE oid = NEW.oid;
RETURN NEW;
END;
$update_profile$ LANGUAGE plpgsql;
The INSERT and UPDATE trigger functions both use the NEW parameter; the INSERT trigger function does not have the OLD parameter. You should always return NEW from the trigger function if successful (or OLD from a DELETE trigger), even if it is an AFTER INSERT OR UPDATE trigger; the whole operation will be rolled back if NULL is returned. If you then define the actual trigger to fire after the insert or update, you should be good:
CREATE TRIGGER tr_update_profile
AFTER INSERT OR UPDATE ON my_table
FOR EACH ROW EXECUTE PROCEDURE fn_update_profile();