New to Postgres and PL/pgSQL here.
How do I go about writing a PL/pgSQL function to perform different actions based on the type of update (insert,delete,etc) made to the table/record in a postgres database.
You seem to be looking for a trigger.
In SQL, triggers are procedures that are called (fired) when a specific event happens on an object, for example when a table is updated, deleted from or insterted into. Triggers can respond to many use cases such as implementing business integrity rules, cleaning data, auditing, security, ...
In Postgres, you should first define a PL/pgSQL function, and then reference it in the trigger declaration.
CREATE OR REPLACE FUNCTION my_table_function() RETURNS TRIGGER AS $my_table_trigger$
BEGIN
...
END
$my_table_trigger$ LANGUAGE plpgsql;
CREATE TRIGGER my_table_trigger
AFTER INSERT OR UPDATE OR DELETE ON mytable
FOR EACH ROW EXECUTE PROCEDURE my_table_function();
From within the trigger code, you have access a set of special variables such as :
NEW, OLD : pseudo records that contain new/old database records affected by the query
TG_OP : operation that fired the trigger (INSERT, UPDATE, DELETE, ...)
Using these variables and other triggers mechanisms, you can analyze or alter the on-going operation, or even abort it by raising an exception.
I would recommend reading Postgres documentation for the CREATE TRIGGER statement and Trigger Procedure (the latest gives lots of examples).
Related
I am trying to understand how transaction works in Postgres and what happens when multiple commands try to work on the same table. My doubt is related to a small experiment that I carried out.
Consider a table called experiment with a trigger (experiment_log) on it that is fired after every update, delete, or insert.
Now consider this function.
CREATE OR REPLACE FUNCTION test_func() RETURNS void AS $body$
DECLARE
_q_txt text;
version_var integer;
BEGIN
EXECUTE 'DROP TRIGGER IF EXISTS experiment_log ON experiment';
SELECT version INTO version_var FROM experiment;
RAISE NOTICE 'VERSION AFTER DROPPING TRIGGER: %', version_var;
EXECUTE 'SELECT pg_sleep(20);';
SELECT version INTO version_var FROM experiment;
RAISE NOTICE 'VERSION BEFORE RECREATING TRIGGER: %', version_var;
EXECUTE 'CREATE TRIGGER experiment_log AFTER INSERT OR UPDATE OR DELETE ON experiment FOR EACH ROW EXECUTE PROCEDURE experiment_log_trigger_func();';
END;
$body$
language 'plpgsql';
So, this function drops the trigger and waits for 20 seconds before re-creating this trigger. Now any update operation performed during the time when function is sleeping, the update operation blocks. It means that I can not update the experiment table until the function test_func has executed completely.
Can anyone explain this behaviour? It seems I am missing something out to reason this behaviour.
That is because DROP TRIGGER places an ACCESS EXCLUSIVE lock on the table, and the lock is held until the transaction ends, that is, for the whole duration of the function call.
If you want to disable a trigger temporarily, use
ALTER TABLE experiment DISABLE TRIGGER experiment_log;
I would like to give you a reference from the documentation, but the lock level of DROP TRIGGER is not documented. However, it is documented that the SQL statement takes the lock:
Also, most PostgreSQL commands automatically acquire locks of appropriate modes to ensure that referenced tables are not dropped or modified in incompatible ways while the command executes.
There you can also find how long a lock is held:
Once acquired, a lock is normally held until the end of the transaction.
To find the lock taken by DROP TRIGGER, try this simple experiment:
CREATE TABLE t();
CREATE TRIGGER whatever BEFORE UPDATE ON t
FOR EACH ROW EXECUTE FUNCTION suppress_redundant_updates_trigger();
BEGIN; -- start a transaction
DROP TRIGGER whatever ON t;
SELECT mode FROM pg_locks
WHERE pid = pg_backend_pid() -- only locks for the current session
AND relation = 't'::regclass; -- only locks on "t"
mode
═════════════════════
AccessShareLock
AccessExclusiveLock
(2 rows)
COMMIT;
You see that an ACCESS SHARE lock and an ACCESS EXCLUSIVE lock are held on the table.
I am doing a simple exercise that given a Mission view i have to recalculate the data on new insertions to do this i need to make a trigger this is what i have done at the moment:
CREATE VIEW Missioni AS
SELECT d.Codice, SUM(v.Chilometri) AS KmTotali, SUM(v.Chilometri) * a.CostoKm AS CostoTotale
FROM Dipendente d join Viaggio v on v.Dipendente = d.Codice join Auto a on a.Targa = v.Auto
GROUP BY d.Codice, a.CostoKm;
CREATE OR REPLACE FUNCTION CALCULATE_MISSION()
RETURNS trigger AS $CALCULATE_MISSION$
BEGIN
DELETE FROM Missioni;
INSERT INTO Missioni SELECT Dipendente, SUM(Chilometri), SUM(Chilometri * CostoKm) FROM Viaggio v JOIN AUTO a ON a.targa = v.auto GROUP BY Dipendente;
END;
$CALCULATE_MISSION$ LANGUAGE plpgsql;
CREATE TRIGGER CalcolaVistaOneShot AFTER INSERT ON Viaggio
FOR EACH STATEMENT
EXECUTE FUNCTION CALCULATE_MISSION();
At the moment I am doing this on pgAdmin 4 in the query editor and it is giving me the following error:
ERROR: it is not possible to delete from the "missions" view
DETAIL: Views containing GROUP BY are not auto-updatable.
HINT: To allow deletions from the view, either an INSTEAD OF DELETE trigger or an ON DELETE DO INSTEAD rule without conditions must be provided.
CONTEXT: SQL statement "DELETE FROM Missions"
PL / pgSQL calculate_mission () function line 3 to SQL statement
Regardless of which type you want, views cannot be directly updated. With standard views there is no dml applied - they are defined by the query and that is run on each time the view is referenced. In this case there would be no triggering at all as there is nothing to do. A materialized view is refreshed (see documentation previously referenced) with the REFRESH MATERIALIZED VIEW ddl statement. In this case your trigger consists of a single execute statement and return null.
create or replace function calculate_mission()
returns trigger
language plpgsql
as $$
begin
execute 'refresh materialized view missioni';
return null;
end; $$;
A couple of notes: It seems you are attempting to maintain a live "as of now" value for KmTotali, and CostoTotale. If the result set is small a standard view will likely perform sufficiently. If the result set is large the a materialized view likely performs better on select, but DML operations of the tables involved must absorb the entire refresh time; especially problematic when dml activity is heavy on the underlying tables.
Additionally, you are refreshing only on insert, what happens if I update the values in the calculation of when rows are delete or updated. I have put together a small fictitious demo.
Finally, spend some time and familiarize yourself with the Postgres Documentation. It is quite complete, although sometimes it can be difficult to navigate to exactly what you need.
Have two triggers on a table. One trigger is executed when there is a insert or update for each row in the table. Second trigger is executed when there is a update for each row in the table. Which trigger gets executed first in ORACLE 10G when there is a update statement on a row in the table. Is there any order of execution for triggers in oracle? If so how can i set it?
The order in which the triggers will fire is arbitrary and not something that you can control in 10g. I believe, technically, it goes in the order that the triggers happened to be created but that's certainly not something that you'd want to count on.
In 11g, you can control the firing order of triggers. However you are almost always better off replacing the two triggers with one trigger that calls two stored procedures. So rather than
CREATE TRIGGER trg_1
BEFORE UPDATE ON t
FOR EACH ROW
BEGIN
<<do thing 1>>
END;
CREATE TRIGGER trg_2
BEFORE UPDATE ON t
FOR EACH ROW
BEGIN
<<do thing 2>>
END;
you would be much better served with something like
CREATE PROCEDURE p1( <<arguments>> )
AS
BEGIN
<<do thing 1>>
END;
CREATE PROCEDURE p2( <<arguments>> )
AS
BEGIN
<<do thing 2>>
END;
CREATE TRIGGER trg
BEFORE UPDATE ON t
FOR EACH ROW
BEGIN
p1( <<list of arguments>> );
p2( <<list of arguments>> );
END;
For versions before 11g, no, the order is unspecified. From 10g Release 2 docs:
For enabled triggers, Oracle automatically performs the following actions:
Oracle runs triggers of each type in a planned firing sequence when more than one trigger is fired by a single SQL statement. First, statement level triggers are fired, and then row level triggers are fired.
Oracle performs integrity constraint checking at a set point in time with respect to the different types of triggers and guarantees that triggers cannot compromise integrity constraints.
Oracle provides read-consistent views for queries and constraints.
Oracle manages the dependencies among triggers and schema objects referenced in the code of the trigger action
Oracle uses two-phase commit if a trigger updates remote tables in a distributed database.
Oracle fires multiple triggers in an unspecified, random order, if more than one trigger of the same type exists for a given statement; that is, triggers of the same type for the same statement are not guaranteed to fire in any specific order.
No order to trigger firing can be relied upon in 10g beyond the normal before statement, before row, after row, after statement order. In 11g a new FOLLOWS clause was added to the CREATE TRIGGER statement.
In Oracle 10g we do not control the triggers that are created on same timing. It is executed randomly. So we cannot say which trigger is fired first. To overcome this problem, Oracle 11g introduced FOLLOWS CLAUSE. Using this we can control the execution order.
I am in the process of creating a sql server 2008 database table for auditing users actions.
Is it possible to create a database table which can only inserted in to - no truncates, deletes or updates allowed on the data in the table. One option I know of is to use a different user with limited rights, but this isnt option for me. So looking at other options?
You need to create a TRIGGER that fires on UPDATE and DELETE and throws an error:
CREATE TRIGGER user_action_update on UserActions FOR UPDATE, DELETE AS
BEGIN
RAISERROR ('Cannot modify or delete user actions', 16, 1)
ROLLBACK TRAN
RETURN
END
GO
Source: http://msdn.microsoft.com/en-us/magazine/cc164047.aspx
Another way to do that is to Write a trigger creation script for the table and set the action to " INSTEAD OF " which will override the triggering action (unwanted action in your case ) for some other code, or null code.
INSTEAD OF Property
Specifies that the DML trigger is executed instead of the triggering SQL statement, therefore, overriding the actions of the triggering statements.
Here is a link in how to Write the SQL statement for the trigger creation:
http://technet.microsoft.com/en-us/library/ms189799.aspx
Good luck
Adrian
Hey, I'm trying to create a trigger in my Oracle database that changes all other records except the one that has just been changed and launched the trigger to 0. Because I am updating records in the same table as the one that launched the trigger I got the mutating table error. To solve this, I put the code as an anonymous transaction, however this causes a deadlock.
Trigger code:
CREATE OR REPLACE TRIGGER check_thumbnail AFTER INSERT OR UPDATE OF thumbnail ON photograph
FOR EACH ROW
BEGIN
IF :new.thumbnail = 1 THEN
check_thumbnail_set_others(:new.url);
END IF;
END;
Procedure code:
CREATE OR REPLACE PROCEDURE check_thumbnail_set_others(p_url IN VARCHAR2)
IS PRAGMA AUTONOMOUS_TRANSACTION;
BEGIN
UPDATE photograph SET thumbnail = 0 WHERE url <> p_url;
COMMIT;
END;
I assume I'm causing a deadlock because the trigger is launching itself within itself. Any ideas?
Using an autonomous transaction for this sort of thing is almost certainly a mistake. What happens if the transaction that inserted the new thumbnail needs to rollback? You've already committed the change to the other rows in the table.
If you want the data to be transactionally consistent, you would need multiple triggers and some way of storing state. The simplest option would be to create a package with a collection of thumbnail.url%type then create three triggers on the table. A before statement trigger would clear out the collection. A row-level trigger would insert the :new.url value into the collection. An after statement trigger would then read the values from the collection and call the check_thumbnail_set_others procedure (which would not be an autonomous transaction).