I have a table that has a contract and columns like starting_date and expiration_date. I need to create a TRIGGER that for a specific category(private) as soon as we are at the expiration date(aka if it expires today), update it by one year.
What I don't really understand is, how could I update the date if I need to do an Update to fire the TRIGGER.
Like if I understand TRIGGERS correctly, I need to execute an Update statement to fire my TRIGGER and let it do its job. But if that's correct then what do I update to fire it?
For the TRIGGER obviously if I do old.expiration_date == current date then I will know if it is going to expire today.
For example let's say that we have a contract with:
starting_date: 1998/02/01
expiration_date: 2021/06/24
And current date: 2021/06/24
So now I need to fire that TRIGGER and update this date but how?
Here is the table:
CREATE TABLE insurance_premiums (
contract_code text NOT NULL,
insurance_team text NOT NULL,
starting_date date NOT NULL,
expiration_date date NOT NULL,
contract_cost float8 NOT NULL,
vehicle_contract text NOT NULL,
customer_contract text NOT NULL,
driver_contract text NOT NULL,
CONSTRAINT insurance_premiums_pkey PRIMARY KEY (contract_code)
);
Here is an INSERT INTO statement for the above example dates:
INSERT INTO insurance_premiums (contract_code, insurance_team, starting_date, expiration_date, contract_cost, vehicle_contract, customer_contract, driver_contract)
VALUES
('FX-30592', 'private', '1998-02-01', '2021-06-24', 3894.68, 'Cavalier', 'Nicoline Vaughn', 'Helen-elizabeth Galiero');
Here is also the TRIGGER:
CREATE TRIGGER update_contract
BEFORE UPDATE ON insurance_premiums
FOR EACH statement
EXECUTE procedure new_date();
And the empty function for the trigger:
CREATE OR REPLACE FUNCTION public.new_date()
RETURNS trigger
LANGUAGE plpgsql
AS $function$
BEGIN
END;
$function$
;
Again the TRIGGER needs to automatically update the date but I don't understand how I "call" it and how it would work
You cannot use a trigger to have an action triggered by time. Triggers can only be fired on data modifications, that is INSERT, UPDATE, DELETE and TRUNCATE statements. That is not what you need here.
You would have to schedule a regular job that expires old entries by running a DELETE statement. Such a job can either be scheduled with operating system tools (like cron on Unix systems) or by a (non-standard) database extension like pg_timetable.
That will work if it is OK to expire data once a day or so. If you need more accuracy, I suggest that you modify your SELECT statements so that they ignore expired data. Then there is no rush expiring them.
If you have a coarser granularity (day/week/month) for data expiry, range partitioning might be the best option. That would allow you to simply drop a partition rather than running a long DELETE.
Related
I have a settings table and a users table. I want to create a trigger to insert a new settings record every time a new user gets created and reference the id of the newly created user in the settings record(settings.user_id in this case). Currently, I've come up with this trigger and function but whenever I try to insert a record into the users table the query fails to execute.
Procedure:
CREATE OR REPLACE FUNCTION create_settings()
RETURNS TRIGGER
LANGUAGE PLPGSQL
AS
$$
BEGIN
INSERT INTO settings (user_id) VALUES (OLD.id);
RETURN OLD;
END;
$$
Trigger:
CREATE TRIGGER create_settings
AFTER INSERT ON users
EXECUTE PROCEDURE create_settings();
and here's the error I get:
ERROR: null value in column "user_id" of relation "settings" violates not-null constraint
DETAIL: Failing row contains (95a8d1ae-cf9d-40be-b5d2-b2d231fb7e1b, null, null, null, null, light, 2022-01-02 15:29:14.290823).
CONTEXT: SQL statement "INSERT INTO settings (user_id) VALUES (OLD.id)"
PL/pgSQL function create_settings() line 3 at SQL statement
The only field that I need is the first null from the error which is the actual reference to the user by ID(most of the values that are null are optional and are NULL by default).
Take a look at the documentation for create trigger. In particular the difference between row level and statement level triggers. You save a statement level trigger. Statement level triggers do not have the pseudo roes Old or New values, They are all null. You need a row level trigger to get values from Old or New. So:
CREATE TRIGGER create_settings
AFTER INSERT ON users
for each row
EXECUTE PROCEDURE create_settings();
It is important to notice that the OLD variable is only available for triggers that execute UPDATE/DELETE in row-level as shown here, so in this case is not possible to get the data you want with an INSERT trigger.
I know it may sound odd but is there any way I can call my trigger on ROLLBACK event in a table? I was going through postgresql triggers documentation, there are events only for CREATE, UPDATE, DELETE and INSERT on table.
My requirement is on transaction ROLLBACK my trigger will select last_id from a table and reset table sequence with value = last_id + 1; in short I want to preserve sequence values on rollback.
Any kind of ideas and feed back will be appreciated guys!
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.
Using Postgres 9.4, I have 2 tables streams and comment_replies. I am trying to do is update the streams.comments count each time a new comment_replies is inserted to keep track of the number of comments a particular stream has. I am not getting any errors but when I try to create a new comment it gets ignored.
This is how I am setting up my trigger. stream_id is a foreign key, so every stream_id will correspond to a streams.id which is the primary key of the streams table. I have been looking at this example: Postgres trigger function, but haven't been able to get it to work.
CREATE TABLE comment_replies (
id serial NOT NULL PRIMARY KEY,
created_on timestamp without time zone,
comments text,
profile_id integer,
stream_id integer
);
The trigger function:
CREATE OR REPLACE FUNCTION "Comment_Updates"()
RETURNS trigger AS
$BODY$BEGIN
update streams set streams.comments=streams.comments+1
where streams.id=comment_replies_streamid;
END$BODY$
LANGUAGE plpgsql VOLATILE
COST 100;
And the trigger:
CREATE TRIGGER comment_add
BEFORE INSERT OR UPDATE
ON comment_replies
FOR EACH ROW
EXECUTE PROCEDURE "Comment_Updates"();
How can I do this?
There are multiple errors. Try instead:
CREATE OR REPLACE FUNCTION comment_update()
RETURNS trigger AS
$func$
BEGIN
UPDATE streams s
SET streams.comments = s.comments + 1
-- SET comments = COALESCE(s.comments, 0) + 1 -- if the column can be NULL
WHERE s.id = NEW.streamid;
RETURN NEW;
END
$func$ LANGUAGE plpgsql;
CREATE TRIGGER comment_add
BEFORE INSERT OR UPDATE ON comment_replies -- on UPDATE, too? Really?
FOR EACH ROW EXECUTE PROCEDURE comment_update();
You need to consider DELETE as well if that is possible. Also if UPDATE can change stream_id. But why increase the count for every UPDATE? This looks like another error to me.
It's a syntax error to table-qualify the target column in the SET clause of UPDATE.
You need to return NEW in a BEFORE trigger unless you want to cancel the INSERT / UPDATE.
Or you make it an AFTER trigger, which would work for this, too.
You need to reference NEW for the stream_id of the current row (which is automatically visible inside the trigger function.
If streams.comments can be NULL, use COALESCE.
And rather use unquoted, legal, lower-case identifiers.
I have a table with a trigger applied to it, that populates an audit table if it changes. I created the original table with just two fields:
CREATE TABLE events(
code serial8 NOT NULL,
event date NOT NULL,
);
And I want a trigger to populate the audit table whenever the event date on this first table is updated:
CREATE TABLE audit(
date_log date,
time_log time,
userid char(20),
event_code int,
action_log text,
old_date date,
new_date date
);
The trigger function:
CREATE OR REPLACE FUNCTION auditing() RETURNS trigger AS $$ BEGIN
INSERT INTO audit(date_log,time_log,userid,event_code,action_log,old_date,new_date)
SELECT current_date,current_time,current_user,code,tg_op,OLD.event, NEW.event FROM events;
RETURN NULL;
END;
$$ LANGUAGE plpgsql;
And I set the trigger like this:
CREATE TRIGGER audit_dates
AFTER UPDATE OF event ON events
FOR EACH ROW EXECUTE PROCEDURE auditing();
The problem I am having is that whenever I run an update function such as:
UPDATE events SET event = '2013-03-01' WHERE code = 1;
The original table updates just fine, but the table that should audit this, generates one audit row for each row that the original table had, so if the original table has 200 rows and I update just one of them, 200 new rows are generated in the audit table.
I am trying to think whether this is because I created the trigger as FOR EACH ROW , but at the same time I have been reading the docs on this and it looks like this is the syntax I need.
What is the right way to get just one new row in the audit table for each time that I update the original table?
You have SELECT ... FROM events in the trigger, so it's selecting all events. You can just use a simple INSERT ... VALUES since NEW and OLD are variables.
CREATE OR REPLACE TRIGGER shares_to_amount
AFTER INSERT OR UPDATE OF issued ON shares_amount
FOR EACH ROW
BEGIN
INSERT INTO shares_amount(
share_issue_id,
share_id,
issued,
date_start,
date_end
) VALUES (
:OLD.share_issue_id,
:OLD.share_id,
:NEW.issued,
:NEW.date_start,
:((NEW.date_start)-1).date_end
);
END;
I want to change the date_end to the date_new date -1 when a new share value is issued into 'issued'. The start date can be today's date but the end date will have to display the day before.
Fist of all your trigger can't work because of mutating table problem. You can't execute DMLs or queries in row-level triggers agains the table which is being changed by triggering DML (if the trigger is not autonomous but this is dangeros and exceptionac case). If I understand your question right you want to keep the history of changes made for shares. Th best way is to create PL/SQL package, incapsulate logic into procedures and provide this interface to end-users or other programms.