Processing new and old variable in Firebird triggers - triggers

This is my pseudocode to access the new and old variable in a Firebird trigger.
create trigger my_trigger for cards
before insert or update
as
declare var_reccord cards;
begin
if (inserting) then
begin
var_reccord = new;
end
else
begin
var_reccord = old;
end
-- USE var_reccord
end
But this does not work for me. How can I do this?

It seems that you want to use something like Oracle's %ROWTYPE but unfortunately current Firebird versions do not support that.
The old and new context variables allow access to columns, not to the entire row. In other words, you must use them like old.columnName. See the documentation for more info.
For example, if table cards has a field foo then you could use it like
create trigger my_trigger for cards
before insert or update
as
declare var_foo TYPE OF COLUMN cards.foo;
begin
if (inserting) then
begin
var_foo = new.foo;
end
else
begin
var_foo = old.foo;
end
-- USE var_foo
end

Related

Track Changes in Source table and load to History Table

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

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.

How to migrate a Firebird's Trigger to PostgreSQL

I'm migrating an entire database from Firebird to PostgreSQL and it's not rocket science. But I'm having serious trouble with triggers. Specially the Firebird's POSITION argument.
Actually, I'm searching about the POSITION behavior. I need it but in PostgreSQL.
Those are the Triggers in Firebird:
This Trigger needs to be executed first:
/* Trigger: TRG_CFE_ESTOQUE_PROCESSADO */
CREATE OR ALTER TRIGGER TRG_CFE_ESTOQUE_PROCESSADO FOR ITENS_CFE
BEFORE UPDATE POSITION 0
AS
BEGIN
IF(NEW.ITE_QTD <> OLD.ITE_QTD)THEN
BEGIN
NEW.ITE_ESTOQUE_PROCESSADO = 'N';
END
END
And this one needs to be executed after:
/* Trigger: TRG_CFE_ESTOQUE_EXCLUIDO */
CREATE OR ALTER TRIGGER TRG_CFE_ESTOQUE_EXCLUIDO FOR ITENS_CFE
BEFORE DELETE POSITION 1
AS
BEGIN
UPDATE ITENS_CFE
SET ITE_ESTOQUE_PROCESSADO = 'N'
WHERE PRO_CODIGO = OLD.PRO_CODIGO
AND CFE_CODIGO = OLD.CFE_CODIGO;
END
For now, I'm not testing it, just searching for a way to reproduce the expected behavior.
Searching again, I've found something in the PostgreSQL Documentation:
If multiple triggers of the same kind are defined for the same event, they will be fired in alphabetical order by name
And I think it will do the magic.
But is this the best way of doing it?
The standard way I've defined trigger would be like the following:
CREATE OR REPLACE FUNCTION func_table_x_after_insert()
RETURNS TRIGGER
AS $$
BEGIN
INSERT INTO table_y
(id)
VALUES
(NEW.id)
;
RETURN NEW;
END;
$$ LANGUAGE PLPGSQL;
CREATE TRIGGER trig_table_x_after_insert
AFTER INSERT ON table_x
FOR EACH ROW EXECUTE PROCEDURE func_table_x_after_insert();
The function you define can handle multiple steps.

Generic string trimming trigger for Postgresql

Problem:
One of the owners of the company that I work for has direct database access. He uses Navicat on a windows notebook. Apparently, it has a feature that he likes where he can import data from Excel. The problem is that text fields often (or maybe always) end up with a \r\n at the end of them. Which can lead to display, reporting and filtering issues. I've been asked to clean this up and to stop him from doing it.
I know I can just add a trigger to each table that will do something like:
NEW.customer_name := regexp_replace(NEW.customer_name, '\r\n', '', 'g');
However, I would prefer to not write a separate trigger function for each table that he has access to (there are over 100). My idea was to just write a generic function and then pass in an array of column names I want corrected via the TG_ARGV[] argument.
Is there a way to update a triggers NEW record dynamically based on the TG_ARGV array?
Details:
I'm using PostgreSQL 9.6.6 on x86_64-pc-linux-gnu
There is no native means to dynamically access the columns of the new record in a plpgsql trigger function. The only way I know is to convert the record to jsonb, modify it and convert it back to record using jsonb_populate_record():
create or replace function a_trigger()
returns trigger language plpgsql as $$
declare
j jsonb = to_jsonb(new);
arg text;
begin
foreach arg in array tg_argv loop
if j->>arg is not null then
j = j || jsonb_build_object(arg, regexp_replace(j->>arg, e'\r\n', '', 'g'));
end if;
end loop;
new = jsonb_populate_record(new, j);
return new;
end;
$$;
The case is much simpler if you can use plpython:
create or replace function a_trigger()
returns trigger language plpython3u as $$
import re
new = TD["new"]
for col in TD["args"]:
new[col] = re.sub(r"\r\n", "", new[col])
return "MODIFY"
$$;

PostgreSQL trigger to replicate changes from one table to another

I have a database named info. In the database I have 4 different tables info1, info2, info3 and info4.
I want to create a stored procedure so that whenever I make changes in table info1 (like INSERT, DELETE or UPDATE) the same changes should appear in the other three tables.
I am using PostgreSQL for this and I don't know how to perform this query.
Please explain it with an example.
Thanks in advance!
Basically you need to read the manual to understand what you are doing. #Michael provided links.
There are many different ways how you can go about this. Here are two typical examples for UPDATE and DELETE:
Create trigger function for UPDATE:
CREATE OR REPLACE FUNCTION trg_info1_upaft()
RETURNS trigger AS
$BODY$
BEGIN
UPDATE info2
SET col1 = NEW.col1
--more?
WHERE info2.info1_id = NEW.info1_id;
UPDATE info3
SET col1 = NEW.col1
--more?
WHERE info3.info1_id = NEW.info1_id;
-- more?
RETURN NULL; -- because trigger is meant for AFTER UPDATE
END;
$BODY$
LANGUAGE plpgsql;
Create the trigger making use of it:
CREATE TRIGGER upaft
AFTER UPDATE
ON info1
FOR EACH ROW
EXECUTE PROCEDURE trg_info1_upaft();
For DELETE:
CREATE OR REPLACE FUNCTION trg_info1_delaft()
RETURNS trigger AS
$BODY$
BEGIN
DELETE FROM info2
WHERE info1_id = OLD.info1_id;
-- more?
RETURN NULL; -- because trigger is meant for AFTER DELETE
END;
$BODY$
LANGUAGE plpgsql;
CREATE TRIGGER delaft
AFTER DELETE
ON info1
FOR EACH ROW
EXECUTE PROCEDURE trg_info1_delaft();
For such changes you should use trigger: http://www.postgresql.org/docs/current/interactive/triggers.html
You will have to create function that inserts/updates/deletes new data into other tables and then show PostgreSQL with CREATE TRIGGER http://www.postgresql.org/docs/current/interactive/sql-createtrigger.html to call that function every time data in source table is changed.