Track Changes in Source table and load to History Table - postgresql

I have created a view which has columns that i need to track for any change and load it into a history table. I created a Trigger as below and when i execute change, it only updates the History but does not add a new updated record. Any idea what im doing wrong?
create or replace function asset_h_fn() returns trigger
LANGUAGE plpgsql
as $$
begin
if (asset = 'Insert') then
insert into asset_history (sys_period,col1,col2,col3,col4,col5,col6)
values (tstzrange(lower(OLD.sys_period), current_timestamp), OLD.col1, OLD.col2, OLD.col3, OLD.col4, OLD.col5);
NEW.sys_period = tstzrange(current_timestamp,null);
return new;
return old;
end if;
end $$ ;

The RETURN OLD; in your code is fortunately unreachable; remove it.
You don't show us the CREATE TRIGGER statement, but it must be an INSTEAD OF trigger.
A view does not hold any data, it is an SQL statement with a name. So if you want a new row to appear in the view, you have to add a second INSERT to the trigger function that inserts a row into the table(s) on which the view is defined.

Well, It depends on how do you define your trigger...
Looking at your code I suppose you should use CREATE TRIGGER <trigger_name> BEFORE INSERT OR UPDATE ON <table_name> FOR EACH ROW EXECUTE PROCEDURE asset_h_fn();
The key here is BEFORE INSERT - if you wish to alter somehow inserted into original table data... If you don't wish to alter it, you should probably use AFTER INSERT.
You may read more about defining triggers and see some examples in official docs

Related

Is FOR EACH STATEMENT Supported in Trigger?

After reading docs, thought For Each Statement covers For Each Row and is more efficient, so I created a generic trigger function and applied to all tables contains column editts (stands for edit-timestamp without timezone, so we know when this record is updated).
CREATE FUNCTION public._tr_updateeditts()
RETURNS trigger
LANGUAGE 'plpgsql'
AS $BODY$
begin
new.editts = now();
return new;
end;
$BODY$;
create trigger _tr_updateeditts_tbl_product before update
on public.product -- table product has column [editts]
for each statement execute procedure public._tr_updateeditts();
But, product.editts is never updated. Changed the above to for each row works. My Postgresql is v12
You cannot modify the data in statement level triggers, and the variable NEW won't be set. To do that, you need a row level trigger.
Statement level triggers may be more efficient since they are not called for each row, but if you need to modify each row, only a row level trigger will do.

Postgresql: create trigger to increment count_column between two tables?

I have a table called car and another table called inventory.
car has a column called needs_oil_change (bool)
inventory has a column called oil_change_due_count INTEGER
I want to create a trigger that will incremented or decrement the inventory.oil_change_due_count whenever a car.oil_change_due_count is changed (or whenever a new car record is inserted, or deleted).
So, in short, I want a trigger to keep the inventory summary count column oil_change_due_count synchronized as car records are created/deleted/updated.
I've tried to follow some docs online like these:
https://w3resource.com/PostgreSQL/postgresql-triggers.php
https://dataegret.com/2017/10/postgresqls-transition-relations/
and others.
I haven't been able to create one that works yet.
How would I write a trigger that could handle that logic?
I just found this:
PostgreSQL: Checking for NEW and OLD in a function for a trigger
not sure if it will answer my question, but I will try to learn from that and see if I can apply to my question.
First of all you need to create a trigger function to handle the logic, in postgresql you can do something like this
CREATE OR REPLACE FUNCTION fn_oil_change() RETURNS TRIGGER AS $$
BEGIN
IF (TG_OP = 'DELETE') THEN
UPDATE inventory SET oil_change_due_count = oil_change_due_count - OLD.oil_change_due_count;
RETURN OLD;
ELSIF (TG_OP = 'UPDATE') THEN
... some logic to hangle update
RETURN NEW
ELSIF (TG_OP = 'INSERT') THEN
... some logic to hangle update
RETURN NEW;
END IF;
RETURN NULL; -- result is ignored since this is an AFTER trigger
END;
$$ LANGUAGE plpgsql;
and then create the trigger itself
CREATE TRIGGER tg_oil_change
AFTER INSERT OR UPDATE OR DELETE ON car
FOR EACH ROW EXECUTE PROCEDURE fn_oil_change();
Something more or less like this, right now I can't test the code, and you'll need to think how to handle the update.

postgresSQL How to apply trigger only on updated row?

I'm trying to create a function + trigger that will update my "modif" attribut to current date when there is an update or insert on my table called "nada".
the code work well but all the rows are affected.I only want the current date on the rows that were updated.
Any idea ?
This is my code so far:
CREATE OR REPLACE FUNCTION public.maj_modif()
RETURNS "trigger" AS
$BODY$
BEGIN
NEW.modif:= (SELECT current_date);
RETURN NEW;
END;
$BODY$
LANGUAGE plpgsql;
DROP TRIGGER IF EXISTS maj_modif ON public.nada;
CREATE TRIGGER maj_modif BEFORE INSERT OR UPDATE ON public.nada
FOR EACH ROW
EXECUTE PROCEDURE public.maj_modif();
If I try the same code without "FOR EACH ROW" in the trigger I get this erreur: « new » is not affected yet (...) The structure of the registration line is not yet determined.
I assume that you only want the trigger to fire if any columns were actually changed.
That can be done with
CREATE TRIGGER maj_modif BEFORE UPDATE ON public.nada
FOR EACH ROW
WHEN OLD <> NEW
EXECUTE PROCEDURE public.maj_modif();
That only works for UPDATE, because on INSERT OLD is not defined. Define the INSERT trigger without the WHEN clause.

PostgreSQL Trigger Copy New Row into other Table

I have a problem I am stuck on for some time now. So I wanted to reach out for a little help.
I have 2 tables which are holding the same data: transactions and transactions2.
I want to write a Trigger that is triggering every time a new row is added to transactions and insert it into transaction2 in PLSQL.
First I simply duplicated the table with
CREATE TABLE transactions2 (SELECT * FROM transactions WHERE 1=1);
I think I found out how to insert
CREATE OR REPLACE FUNCTION copyRow RETURNS TRIGGER AS $$
DECLARE
BEGIN
INSERT INTO transaction2
VALUES transaction;
END;
I think the syntax with this is also wrong, but how do I say, that the Trigger should start as soon as a new Insert into the first table is made?
Can anyone help me with this?
Thanks
Bobby
The correct syntax for an INSERT is INSERT (<column list>) VALUES (<values list>). The INSERT syntax isn't different in a function compared to "outside". So your trigger function should look something like:
CREATE OR REPLACE FUNCTION t2t2_f ()
RETURNS TRIGGER
AS
$$
BEGIN
INSERT INTO transactions2
(column_1,
...,
column_n)
VALUES (NEW.column_1,
...,
NEW.column_n);
RETURN NEW;
END;
$$
LANGUAGE plpgsql;
Replace the column_is with the actual column names of your table. NEW is a pseudo record with which you can access the values of the new row.
To create the trigger itself use something like:
CREATE TRIGGER t2t2_t
AFTER INSERT
ON transactions
FOR EACH ROW
EXECUTE PROCEDURE t2t2_f();
You may want to use another timing, e.g. BEFORE instead of AFTER.
That should give you something to start with. Please consider studying the comprehensive PostgreSQL Manual for further and more detailed information.

PgSQL log table update time

I've created the following table:
CREATE TABLE updates
(
"table" text,
last_update timestamp without time zone
)
I want to update it whenever any table is updated, the problem is I don't know how, could someone please help me turn this pseudocode into a trigger?
this = current table on whitch operation is performed
ON ALTER,INSERT,DELETE {
IF (SELECT COUNT(*) FROM updates where table = this) = 1
THEN
UPDATE updates SET last_update = timeofday()::timestamp WHERE `table`=this
ELSE
INSERT INTO updates VALUES (this,timeofday()::timestamp);
}
You need a trigger function that is called whenever one of your tables is "updated", assuming that you mean that an INSERT, UPDATE, or DELETE is successfully executed. That trigger function would look like this:
CREATE FUNCTION log_update() RETURNS trigger AS $$
BEGIN
UPDATE updates SET last_update = now() WHERE "table" = TG_TABLE_NAME;
IF NOT FOUND THEN
INSERT INTO updates VALUES (TG_TABLE_NAME, now());
END IF;
IF (TG_OP = 'DELETE') THEN
RETURN OLD;
ELSE
RETURN NEW;
END IF;
END; $$ LANGUAGE PLPGSQL;
Every table that has to be logged this way needs to have a trigger associated with it like this:
CREATE TRIGGER ZZZ_mytable_log_updates
AFTER INSERT OR UPDATE OR DELETE ON mytable
FOR EACH ROW EXECUTE PROCEDURE log_update();
A few comments:
Trigger functions are created with PL/PgSQL; see chapter 40 in the documentation. Trigger functions come with some automatic parameters such as TG_TABLE_NAME.
Don't use reserved words ("table" in your case) as column names. Actually, in this case you are better off using the oid of the table, with the associated TG_RELID automatic parameter. It takes up less storage, it is faster, and it avoids confusion between tables with the same name in different schemas of your database. You can use the pg_tables system catalog table to look up the table name from the oid.
You must return the proper value depending on the operation, or the operation may fail. INSERT and UPDATE operations need to have NEW returned; DELETE needs to have OLD returned.
The name of the trigger starts with "ZZZ" to make sure that it fires after any other triggers on the same table have succeeded (they are fired in alphabetical order). If a prior trigger fails, this trigger function will not be called, which is the proper behaviour because the insert, update or delete will not take place either.