ROLLBACK event triggers in postgresql
You can't use a sequence for this. You need a single serialization point through which all inserts have to go - otherwise the "gapless" attribute can not be guaranteed. You also need to make sure that no rows will ever be deleted from that table.
The serialization also means that only a single transaction can insert rows into that table - all other inserts have to wait until the "previous" insert has been committed or rolled back.
One pattern how this can be implemented is to have a table where the the "sequence" numbers are stored. Let's assume we need this for invoice numbers which have to be gapless for legal reasons.
So we first create the table to hold the "current value":
create table slow_sequence
(
seq_name varchar(100) not null primary key,
current_value integer not null default 0
);
-- create a "sequence" for invoices
insert into slow_sequence values ('invoice');
Now we need a function that will generate the next number but that guarantees that no two transactions can obtain the next number at the same time.
create or replace function next_number(p_seq_name text)
returns integer
as
$$
update slow_sequence
set current_value = current_value + 1
where seq_name = p_seq_name
returning current_value;
$$
language sql;
The function will increment the counter and return the incremented value as a result. Due to the update
the row for the sequence is now locked and no other transaction can update that value. If the calling transaction is rolled back, so is the update to the sequence counter. If it is committed, the new value is persisted.
To ensure that every transaction uses the function, a trigger should be created.
Create the table in question:
create table invoice
(
invoice_number integer not null primary key,
customer_id integer not null,
due_date date not null
);
Now create the trigger function and the trigger:
create or replace function f_invoice_trigger()
returns trigger
as
$$
begin
-- the number is assigned unconditionally so that this can't
-- be prevented by supplying a specific number
new.invoice_number := next_number('invoice');
return new;
end;
$$
language plpgsql;
create trigger invoice_trigger
before insert on invoice
for each row
execute procedure f_invoice_trigger();
Now if one transaction does this:
insert into invoice (customer_id, due_date)
values (42, date '2015-12-01');
The new number is generated. A second transaction then needs to wait until the first insert is committed or rolled back.
As I said: this solution is not scalable. Not at all. It will slow down your application massively if there are a lot of inserts into that table. But you can't have both: a scalable and correct implementation of a gapless sequence.
I'm also pretty sure that there are edge case that are not covered by the above code. So it's pretty likely that you can still wind up with gaps.
Trigger order of execution
From the docs:
Statement-level BEFORE triggers naturally fire before the statement starts to do anything, while statement-level AFTER triggers fire at the very end of the statement.
http://www.postgresql.org/docs/9.4/interactive/trigger-definition.html
In other words, a before statement trigger will be run just before the statement executes, and an after statement trigger will be run just after the statement finishes executing.
Statement level triggers can't be constraint triggers, and only constraint triggers are deferrable, so there's no way to postpone the trigger until the end of the transaction.
If more than one trigger needs to run at a given time, they are run
in the alphabetical order of their name.
Creating multiple event triggers inside one trigger postgres
Yes, you can do that.
If you write your trigger in PL/pgSQL, you will have the special variable TG_OP
defined. The values are INSERT
, UPDATE
, DELETE
or TRUNCATE
, based on what operation caused the trigger to run.
See the documentation for details.
AFTER INSERT trigger in separate transaction?
All PostgreSQL triggers execute in the same transaction as the transaction that has triggered them.
Edit: You can also use LISTEN
+ NOTIFY
to send a message from your trigger to a code that executes outside of the transaction. In that case, the message will only be delivered at the point of a successful commit. Errors in listeners will not roll back the triggering transaction.
Perform action before exception call in Postgres trigger function
You can trap errors / catch exceptions.
In the EXCEPTION
block you can do anything else, like INSERT into another table. Afterwards you could re-raise the exception to propagate out, but that would roll back the whole transaction including the INSERT to the log table (unless the exception is wrapped and caught in an outer function).
You could:
use tricks like a dblink call to emulate an autonomous transaction, which isn't undone, when the wrapping transaction is rolled back. Related:
- How to use (install) dblink in PostgreSQL?
- dblink can't update a table on the same database in an after UPDATE trigger
- Does Postgres support nested or autonomous transactions?
RAISE
aNOTICE
orWARNING
additionally, which also isn't undone byROOLBACK
.RAISE
a differentEXCEPTION
with your own text.
Alternatively, you can just cancel the row that triggered the trigger function and not raise an exception. Everything else in the transaction goes through normally.
Assuming this is a trigger ON UPDATE
and you have another table with identical structure to write failed INSERTs to:
CREATE OR REPLACE FUNCTION emp_stamp()
RETURNS trigger AS
$func$
BEGIN
-- Check that empname and salary are given
IF NEW.empname IS NULL THEN
RAISE EXCEPTION 'empname cannot be null';
END IF;
IF ...
RETURN NEW; -- regular end
EXCEPTION WHEN others THEN -- or be more specific
INSERT INTO log_tbl VALUES (NEW.*); -- identical table structure
RETURN NULL; -- cancel row
END
$func$ LANGUAGE plpgsql;
Note that NEW
contains the state of the row right before the exception occurred, including previous successful statements in the same function.
Trigger:
CREATE TRIGGER emp_stamp
BEFORE INSERT OR UPDATE ON tbl
FOR EACH ROW EXECUTE PROCEDURE emp_stamp();
Related Topics
SQL Server Max Statement Returns Multiple Results
Query the Two Cities in Station with the Shortest and Longest City Names,
Transfer Data Between Databases with Postgresql
How to Use a SQL for Loop to Insert Rows into Database
In MySQL, How to Copy the Content of One Table to Another Table Within the Same Database
Select Top N Rows for Each Group
How to Make a Sum Without Group By
Postgresql - Repeating Rows from Limit Offset
Basic Oracle Trigger Audit Table
Select * from Table1 That Does Not Exist in Table2 with Conditional
SQL Server Error: Column Name or Number of Supplied Values Does Not Match Table Definition
Sqlite Create Pre-Populated Fts Table
Please Correct Me with the SQL Query
Difference Between Inner Join and Left Semi Join
How to Create a One-Time-Use Function in a Script or Stored Procedure