Need help writing a PostgreSQL trigger function - postgresql

I have two tables representing two different types of imagery. I am using PostGIS to represent the boundaries of those images. Here is a simplified example of those tables:
CREATE TABLE img_format_a (
id SERIAL PRIMARY KEY,
file_path VARCHAR(1000),
boundary GEOGRAPHY(POLYGON, 4326)
);
CREATE TABLE img_format_p (
id SERIAL PRIMARY KEY,
file_path VARCHAR(1000),
boundary GEOGRAPHY(POLYGON, 4326)
);
I also have a cross reference table, which I want to contain all the IDs of the images that overlap each other. Whenever an image of type "A" gets inserted into the database, I want to check to see whether it overlaps any of the existing imagery of type "P" (and vice versa) and insert corresponding entries into the img_a_img_p cross reference table. This table should represent a many-to-many relationship.
My first instinct is to write a trigger to manage thisimg_a_img_p table. I've never created a trigger before, so let me know if this is a silly thing to do, but it seems to make sense to me. So I create the following trigger:
CREATE TRIGGER update_a_p_cross_reference
AFTER INSERT OR DELETE OR UPDATE OF boundary
ON img_format_p FOR EACH ROW
EXECUTE PROCEDURE check_p_cross_reference();
The part where I am getting stuck is with writing the trigger function. My code is in Java and I see that there are tools like PL/pgSQL, but I'm not sure if that's what I should use or if I even need one of those special add-ons.
Essentially all I need the trigger to do is update the cross reference table each time a new image gets inserted into either img_format_a or img_format_p. When a new image is inserted, I would like to use a PostGIS function like ST_Intersects to determine whether the new image overlaps with any of the images in the other table. For each image pair where ST_INTERSECTS returns true, I would like to insert a new entry into img_a_img_p with the ID's of both images. Can someone help me figure out how to write this trigger function? Here is some pseudocode:
SELECT * FROM img_format_p P
WHERE ST_Intersects(A.boundary, P.boundary);
for each match in selection {
INSERT INTO img_a_img_p VALUES (A.id, P.id);
}

You could wrap the usual INSERT ... SELECT idiom in a PL/pgSQL function sort of like this:
create function check_p_cross_reference() returns trigger as
$$
begin
insert into img_a_img_p (img_a_id, img_p_id)
select a.id, p.id
from img_format_a, img_format_p
where p.id = NEW.id
and ST_Intersects(a.boundary, p.boundary);
return null;
end;
$$ language plpgsql;
Triggers have two extra variables, NEW and OLD:
NEW
Data type RECORD; variable holding the new database row for INSERT/UPDATE operations in row-level triggers. This variable is NULL in statement-level triggers and for DELETE operations.
OLD
Data type RECORD; variable holding the old database row for UPDATE/DELETE operations in row-level triggers. This variable is NULL in statement-level triggers and for INSERT operations.
So you can use NEW.id to access the new img_format_p value that's going in. You (currently) can't use the plain SQL language for triggers:
It is not currently possible to write a trigger function in the plain SQL function language.
but PL/pgSQL is pretty close. This would make sense as an AFTER INSERT trigger:
CREATE TRIGGER update_a_p_cross_reference
AFTER INSERT
ON img_format_p FOR EACH ROW
EXECUTE PROCEDURE check_p_cross_reference();
Deletes could be handled with a foreign key on img_a_img_p and a cascading delete. You could use your trigger for UPDATEs as well:
CREATE TRIGGER update_a_p_cross_reference
AFTER INSERT OR UPDATE OF boundary
ON img_format_p FOR EACH ROW
EXECUTE PROCEDURE check_p_cross_reference();
but you'd probably want to clear out the old entries before inserting the new ones with something like:
delete from img_a_img_p where img_p_id = NEW.id;
before the INSERT...SELECT statement.

Related

insert values on trigger in temporal tables in postgres

So I am new to using procedures and triggers and it is really confusing me
I have used temporal tables and want to basically create a history table of records inserted,updated or deleted.
Infact I have created my history table and works fine when I use this trigger sql
DROP TRIGGER if exists versioning_trigger on mytable;
CREATE TRIGGER versioning_trigger BEFORE INSERT OR UPDATE OR DELETE ON mytable FOR EACH ROW EXECUTE PROCEDURE versioning('sys_period', 'table_history', true);
This creates records of the rows updated or deleted,precisely copies the old row record from mytable into table_history table and updates the record in mytable.But I want to insert the updated record from mytable to table_history also so that it has records of all types('current active record'and 'record before updation').Also insert some other fields in table_history when the trigger is executed.
I want to ask
How is it possible to have different trigger events(BEFORE or AFTER) together in one CREATE TRIGGER query in temporal_tables?
Is it possible to insert new field values in table_history on trigger execution? How can I accomplish this?
https://www.postgresql.org/docs/current/static/plpgsql-trigger.html
A trigger procedure is created with the CREATE FUNCTION command,
declaring it as a function with no arguments and a return type of
trigger
and also
same trigger can't fire both before and after event - just create two triggers if you really need it
https://www.postgresql.org/docs/current/static/sql-createtrigger.html
Determines whether the function is called before, after, or instead of
the event.
use NEW instead of OLD for new values
https://www.postgresql.org/docs/current/static/plpgsql-trigger.html
NEW
Data type RECORD; variable holding the new database row for
INSERT/UPDATE operations in row-level triggers. This variable is
unassigned in statement-level triggers and for DELETE operations.

Before and After trigger on the same event? Fill a child table PostgreSQL

Situation
I have a database in PostgreSQL 9.5 used to store object locations by time.
I have a main table named "position" with the columns (only relevant):
position_id
position_timestamp
object_id
It is partitioned into 100 child tables on object_id with the condition:
CREATE TABLE position_object_id_00
( CHECK object_id%100 = 0 )
INHERITS ( position );
And so on for the others children. I partitioned with a modulus relation to distribute equally the objects.
Each child is indexed on position_id and object_id (two different indexes).
The trigger to redirect inserts on children is:
CREATE TRIGGER insert_position_trigger
BEFORE INSERT ON position
FOR EACH ROW EXECUTE PROCEDURE insert_position();
And the procedure insert_position() looks for the right child table to insert the data, inserts it and then return the NEW object:
CREATE OR REPLACE FUNCTION insert_position() RETURNS TRIGGER AS $insert_position$
DECLARE
BEGIN
--Look for child table
[...]
--Insert data in right child table
[...]
RETURN NEW;
END;
$insert_position$ LANGUAGE plpgsql;
I have a summary table object_last_known_position with the same columns that is updated with the trigger:
CREATE TRIGGER update_object_last_known_position
AFTER INSERT OR UPDATE ON position
FOR EACH ROW
EXECUTE PROCEDURE update_object_last_known_position();
The procedure update_object_last_known_position() basically checks if the position_timestamp is more recent, then delete the older entry and create a new entry with the data passed on the INSERT or UPDATE query (NEW).
Issue
So these two triggers react to the same event: insert on position, one is before, the other is after
Returning new for insert_position() allows me to use NEW in the trigger update_object_last_known_position(), and this is absolutely necessary. But doing that, it also insert the data on the master table position. So my data is now duplicated.
I tried to put the two triggers before, they both execute when I insert data if I let it like that, but if I remove the "return new" from the procedure insert_position(), update_object_last_known_position() is not executed.
I am stuck with this issue and I didn't find a way to execute both of these triggers without filling the master table position when I insert data.
So if you have any ideas, I will really appreciate :)
Thank you for your help!
EDIT
Solution
Thanks to the answer
I "merged" my two triggers: insert_position() now calls update_object_last_known_position directly. For that, I modified update_object_last_known_position to a stored procedure with a parameter. The parameter is the id of the position insert_position() just created, so I am able to find it and retrieve information.
(Calling update_object_last_known_position inside the other trigger means we cannot use NEW anymore)
And obviously return type for insert_position() is now NULL, and everything works fine :)
If I understand you correctly you are trying to:
Stop the insert, and replace it with an insert into another table (determined by the trigger)
Update a summary table (delete/insert) to point to the new row.
Your problem is that 1 stops 2 from happening? That's logical because you've stopped the insert so you've stopped any processing on the insert as well.
So to solve this you have a couple of options (options 1 and 2 are similar)
Call update_object_last_known_position() from insert_position() and only have one trigger
Create a wrapper method for both insert_position() and update_object_last_known_position() and have only one trigger.
Put the trigger for update_object_last_known_position() on all of the tables that insert_position() might insert into.

Postgresql - Constraints on Ranges - Two tables

With constraints on ranges, we prevent adding any overlapping values in an existing table. For example, in the following room_reservation table, we make sure that no room is reserved in a conflicting time.
CREATE EXTENSION btree_gist;
CREATE TABLE room_reservation (
room text,
during tsrange,
EXCLUDE USING GIST (room WITH =, during WITH &&)
);
What we need here is to also consider another table (rooms) also having room and during fields and consider the records within that table while making a reservation?
Our specific scenario is exam management. We have an invigilation table (room reservation) and also a time table of classes. Once we are adding an invigilation record, we need to make sure that it does not coincide with any other invigilation record and make sure that there is no lecture at that time in that room.
You cannot do that with a single exclusion constraint. Instead, you should use the exclusion constraint on one table, say invigilation, and then use a BEFORE INSERT trigger on that same table that checks if there is a conflict in the second table, say rooms. The trigger function on the first table would do a simple range check on the second table:
CREATE FUNCTION check_no_class() RETURNS trigger AS $$
BEGIN
PERFORM * FROM rooms
WHERE room = NEW.room
AND during && NEW.during;
IF FOUND THEN
RETURN NULL;
ELSE
RETURN NEW;
END IF;
END; $$ LANGUAGE plpgsql;
CREATE TRIGGER check_rooms
BEFORE INSERT ON invigilation
FOR EACH ROW EXECUTE PROCEDURE check_no_class();
If a class is scheduled in a room then the insert on invigilation will fail.

How to refer to the new inserted row in TSQL Trigger

I know that in plpgsql if one would want to refer to the new inserted row, you can use "NEW".
How can I do this in T-SQL (transact sql)?
The following is the trigger I am trying to create:
CREATE Trigger setAlertId on rules_table
FOR INSERT AS
DECLARE #max_id integer
SELECT #max_id = (select max(AlertId) from rules_table)
NEW.AlertId = #max_id+1
END
GO
I get the error message:
Incorrect syntax near 'NEW'
Thanks.
inserted and deleted pseudo tables:
DML trigger statements use two special tables: the deleted table and the inserted tables. SQL Server automatically creates and manages these tables. You can use these temporary, memory-resident tables to test the effects of certain data modifications and to set conditions for DML trigger actions. You cannot directly modify the data in the tables
In your case why dont you use an identity on the alertid field that increments itself?
If you want to do it in your trigger you will need to select your primary key from inserted and then do an update on rules tables.

Manipulate rows automatically before the `INSERT` statement

I'm looking for a way to manipulate rows automatically before adding them to a table in postgreSQL. Say for instance we have the following table:
CREATE TABLE foo (
id serial NOT NULL,
value integer NOT NULL,
CONSTRAINT "Foo_pkey" PRIMARY KEY (id),
CONSTRAINT "Foo_value_check" CHECK (value >= 0)
)
Now one can insert rows:
INSERT INTO foo (id,value) VALUES ('0','2')
And when one enters:
INSERT INTO foo (id,value) VALUES ('1','-2')
An error will occur. Is it possible to define a "rewrite rule" that given the value column contains a value less than zero, zero is used (for instance)?
Yes, it is possible. One way is to use triggers. A trigger causes a procedure to be run on particular actions, which can allow you to modify the data to be inserted (amongst other things).
To set up a trigger, you first create a function that will perform the checks and modifications you want. The variable new in your function will be implicitly declared and contain the new row to be inserted / updated so you can check and modify the values before they reach the table.
You then specify that this function is to be called before insert or update on one or more tables.
Example:
CREATE FUNCTION validate_foo_row()
RETURNS TRIGGER AS $$
BEGIN
IF new.value<0 THEN
new.value=0;
END IF;
RETURN NEW;
END
$$ LANGUAGE 'plpgsql';
CREATE TRIGGER trig_validate_foo BEFORE INSERT ON foo
FOR EACH ROW EXECUTE PROCEDURE validate_foo_row();
SqlFiddle Here
The above simplistic example only triggers for inserts, you might want to have it trigger for updates as well.
You can read more about triggers in the postgresql manual. They are powerful and are capable of a lot more than this simple example shows.