I'm reading a lot of articles how to create triggers, but I really have no clue how to create trigger depending on date.
I want to add a trigger that:
1. check before add or update if the sum of minutes is higher than 50 on a specific day.
So in example entity:
CREATE TABLE employees(
id int serial primary key,
minutes int NOT NULL,
date date NOT NULL
);
this is a typical data in db:
(1, 40, '2018-01-1')
(2, 30, '2018-01-2')
(3, 20, '2018-01-3')
(4, 10, '2018-01-4')
now if I add:
(6, 20, '2018-01-1')
(7, 40, '2018-01-2')
(8, 20, '2018-01-3')
(9, 20, '2018-01-4')
the final result would've be:
(1, 40, '2018-01-1')
(2, 30, '2018-01-2')
(3, 20, '2018-01-3')
(4, 10, '2018-01-4')
(8, 20, '2018-01-3')
(9, 20, '2018-01-4')
id with 6 and 7 are omitted (not added, because they are higher than 50)
I really appreciate a help with it :)
update: with help from #Laurenz Albe I created function and trigger as below:
CREATE FUNCTION func_check_minutes() RETURNS trigger AS
$$
BEGIN
IF (SELECT sum(minutes) + NEW.minutes FROM employees WHERE date = NEW.date) > 50
THEN RETURN NULL;
END IF;
END;
$$
LANGUAGE 'plpgsql';
CREATE TRIGGER tr_check_minutes
BEFORE INSERT ON employees
FOR EACH ROW
EXECUTE PROCEDURE func_check_minutes();
But this solution fails without a clear reason.
As you have not mentioned what fails, i am assuming that nothing is being written to the table. You will have to add RETURN NEW after the END IF
I have updated the trigger
CREATE FUNCTION func_check_minutes() RETURNS trigger AS
$$
BEGIN
IF (SELECT sum(minutes) + NEW.minutes FROM employees WHERE date = NEW.date) > 50
THEN RETURN NULL;
END IF;
RETURN NEW;
END;
$$
LANGUAGE 'plpgsql';
CREATE TRIGGER tr_check_minutes
BEFORE INSERT ON employees
FOR EACH ROW
EXECUTE PROCEDURE func_check_minutes();
The central piece of such a trigger would be:
IF (SELECT sum(minutes) + NEW.minutes
FROM employees
WHERE date = NEW.date) > 50
THEN
<throw an error or return NULL>
END IF;
But you should be aware that there is a race condition that could cause the condition to be violated:
If the triggers from two inserts run concurrently, each of them won't see the effects of the other transaction, and both could report success while the final result after both transactions are committed could exceed the limit.
The only way to get around that would be using SERIALIZABLE transactions.
Related
I'm trying to keey track of a clients database with which we sync. I need to record records_added (INSERTs) and records_updated (UPDATEs) to our table.
I'm using an UPSERT to handle the sync, and a trigger to update a table keeping track of insert/updates.
The issue is counting records that have are updated. I have 40+ columns to check, do I have to put all these in my check logic? Is there a more elegant way?
Section of code in question:
select
case
when old.uuid = new.uuid
and (
old.another_field != new.another_field,
old.and_another_field != new.and_another_field,
-- many more columns here << This is particularly painful
) then 1
else 0
end into update_count;
Reproducible example:
-- create tables
CREATE TABLE IF NOT EXISTS example (uuid serial primary key, another_field int, and_another_field int);
CREATE TABLE IF NOT EXISTS tracker_table (
records_added integer DEFAULT 0,
records_updated integer DEFAULT 0,
created_at date unique
);
-- create function
CREATE OR REPLACE FUNCTION update_records_inserted () RETURNS TRIGGER AS $body$
DECLARE update_count INT;
DECLARE insert_count INT;
BEGIN
-- ---------------- START OF BLOCK IN QUESTION -----------------
select
case
when old.uuid = new.uuid
and (
old.another_field != new.another_field
-- many more columns here
) then 1
else 0
end into update_count;
-- ------------------ END OF BLOCK IN QUESTION ------------------
-- count INSERTs
select
case
when old.uuid is null
and new.uuid is not null then 1
else 0
end into insert_count;
-- --log the counts
-- raise notice 'update %', update_count;
-- raise notice 'insert %', insert_count;
-- insert or update count to tracker table
insert into
tracker_table(
created_at,
records_added,
records_updated
)
VALUES
(CURRENT_DATE, insert_count, update_count) ON CONFLICT (created_at) DO
UPDATE
SET
records_added = tracker_table.records_added + insert_count,
records_updated = tracker_table.records_updated + update_count;
RETURN NEW;
END;
$body$ LANGUAGE plpgsql;
-- Trigger
DROP TRIGGER IF EXISTS example_trigger ON example;
CREATE TRIGGER example_trigger
AFTER
INSERT
OR
UPDATE
ON example FOR EACH ROW EXECUTE PROCEDURE update_records_inserted ();
-- A query to insert, then update when number of uses > 1
insert into example(whatever) values (2, 3) ON CONFLICT(uuid) DO UPDATE SET another_field=excluded.another_field+1;
I have 3 tables:
Employee(eid, ename, salary, did, classification)
Project(pid, pname, did, budget, duedate)
Onproject(pid, eid, fromdate)
I try to write trigger that checks on insert of a new employee to Onproject, that the duedate is greater than one month from fromdate, if not return a error message, and the record won't be added to the table.
I tried
CREATE TRIGGER T1
BEFORE INSERT
ON Onproject
FOR EACH ROW
EXECUTE PROCEDURE trigf1();
create or replace function trigf1() returns trigger as
$BODY$ BEGIN
IF (DATE_PART('day', NEW.fdate::date) - DATE_PART('day', duedate::date) > 30)
THEN insert into Onproject values (NEW.pid, NEW.eid, NEW.fdate)
else
rais notice 'adding employee to the project failed, less then one month to due date.'
end if
end
$BODY$
LANGUAGE PLPGSQL VOLATILE
but the trigger doesn't know the duedate field and the Project table.
How can I create the trigger by using the Project and Onproject tables?
You are not returning anything from the trigger. If you want the INSERT to proceed simply return the new row, if you want it to fail, you need to raise an error.
You also need to run a select on the project table to retrieve the duedate, you can't just take it out of thin air:
create or replace function trigf1() returns trigger as
$BODY$
declare
l_duedate date;
BEGIN
-- get the duedate for the project
select duedate
into l_duedate
from project
where pid = new.pid;
IF l_duedate > NEW.fromdate::date + interval '1 month'
THEN
-- everything OK, proceed with the insert
return new;
end if;
-- using raise error, aborts the transaction
raise error 'adding employee to the project failed, less then one month to due date.'
end;
$BODY$
LANGUAGE PLPGSQL VOLATILE
I'm trying to capture a count after TRUNCATE on a table, and am a bit stuck. Postgres 11.x on RDS. The context isn't complicated, a single-level, statement-level trigger (not a cascade.) All I need is the count of truncated records. For an UPDATE trigger, I can get the count of deleted rows off of a transition table, but TRUNCATE doesn't support transitions tables.
It seems like GET DIAGNOSTICS ROW_COUNT should do the trick. I get a new row in the deletion_log, but the deleted record count is 0. Here's my unsuccessful code:
CREATE OR REPLACE FUNCTION trigger_truncate_statement_log_count()
RETURNS trigger
AS $$
DECLARE
deleted_count int := 0;
BEGIN
GET DIAGNOSTICS deleted_count = ROW_COUNT;
insert into deletion_log
(schema_name, table_name, deleted_count)
select TG_TABLE_SCHEMA, TG_TABLE_NAME, deleted_count;
return null;
END;
$$ LANGUAGE plpgsql;
Below is the code for the deletion_log table the trigger is meant to insert into:
BEGIN;
CREATE TABLE IF NOT EXISTS deletion_log (
id uuid DEFAULT gen_random_uuid() NOT NULL,
deletion_dts timestamp with time zone DEFAULT now() NOT NULL,
client_address inet DEFAULT inet_client_addr(),
schema_name text DEFAULT get_current_user(),
table_name text NOT NULL,
deleted_count integer DEFAULT 0 NOT NULL
);
COMMIT;
I've got the DELETE trigger firing correctly, but not the TRUNCATE. As an example, I'm testing it out on a scratch table named goals (from Postgres Weekly this morning):
CREATE TABLE goals (
team TEXT, year INT, goals INT);
INSERT INTO goals VALUES
('USA', 2010, 5), ('USA', 2011, 3),
('USA', 2012, 12), ('ENG', 2010, 17),
('ENG', 2011, 9), ('ENG', 2012, 11);
Here's the trigger setup:
DROP TRIGGER IF EXISTS trigger_delete_log_count_goals ON goals;
CREATE TRIGGER trigger_delete_log_count_goals AFTER DELETE ON goals
REFERENCING OLD TABLE AS deleted_rows FOR EACH STATEMENT
EXECUTE PROCEDURE trigger_delete_log_count();
DROP TRIGGER IF EXISTS trigger_truncate_log_count_goals ON goals;
CREATE TRIGGER trigger_truncate_log_count_goals AFTER TRUNCATE ON goals
EXECUTE PROCEDURE trigger_truncate_log_count();
I'm guessing that I've missed something basic. What is the easiest way to get the number of rows affected in an AFTER TRUNCATE trigger?
Thanks for any help!
Jeremy's comment got me thinking, and I tried a count(*). This seems to work in a BEFORE TRUNCATE statement trigger.
CREATE OR REPLACE FUNCTION trigger_truncate_log_count()
RETURNS trigger
AS $$
DECLARE
deleted_count int := 0;
BEGIN
EXECUTE 'select count(1) from ' || TG_TABLE_NAME into deleted_count;
insert into deletion_log
(operation_name,
schema_name,
table_name,
deleted_count)
select TG_OP,
TG_TABLE_SCHEMA,
TG_TABLE_NAME,
deleted_count;
return null;
END;
$$ LANGUAGE plpgsql;
I added TG_OP to my deletion_log table to see DELETE or TRUNCATE in the log.
Thanks for the help, and comments on gotchas or details I might not have thought of appreciated.
I am getting acquainted with Triggers in (Postgre)sql.
What I have now is a table Verine (which is teams in german).
Vereine :{[team:string, punkte:int, serie:int]}
This is a very very small thing I wrote just to understand how creating tables, sorting stuff and views work and now I'm using it for triggers. Anyway, team is team obviously the name of the team and primary key, punkte means points and serie refers to the division of the team (just so you understand what the different domains mean).
Problem starts here:
So assume I have a team, let's say "Juventus", already in my table "Vereine". If I then want to insert another row/tuple that has the same key "Juventus", instead of wanting two entries for them, what I'd like is to update the values for key "Juventus" (replacing new values with old ones). In the example below I try to do that with points.
create table vereine(
team varchar(20) primary key,
punkte int not null,
serie int not null
)
--beispiel was die Aufgabe verlangt
create trigger prevent_redundancy
before insert on vereine
for each row
execute procedure update_points()
create or replace function update_points()
returns trigger as
$BODY$
begin
if (new.team in (old.team)) then
update vereine
set punkte = new.punkte
where team = new.team;
else
end if;
end;
$BODY$
LANGUAGE plpgsql;
--Was die aufgabe verlangt ist, dass keine bereits existierende ID eingefügt wird,
--sondern der entsprechende Modellname dann umgeändert wird
insert into vereine values('JuventusFC', 50, 1);
insert into vereine values('AS Roma', 30, 1);
insert into vereine values('ParmaCalcio1913', 25, 1);
insert into vereine values('Palermo', 37, 2);
insert into vereine values('Pescara', 32, 2);
insert into vereine values('Spezia', 26, 2);
insert into vereine values('Carrarese Calcio', 34, 3);
insert into vereine values('Virtus Entella', 31, 3);
insert into vereine values('Juventus U-23', 50, 3);
select *
from vereine
insert into vereine values('JuventusFC', 53, 1);
Here are my problems:
First of all: How would I check if a key already exists in the table? In queries, I would use things like case when, where in or just approach the problem differently. Do I need if statements here? In other words, how would you rewrite
if (new.team in (old.team)) then
so that it checks if it already exists in there?
Secondly: This may be related to the first problem: I get this when trying to insert any tuple:
Query execution failed
Reason:
SQL Error [54001]: ERROR: stack depth limit exceeded
Hint: Increase the configuration parameter "max_stack_depth" (currently 2048kB), after ensuring the platform's stack depth limit is adequate.
Where: SQL statement "insert into vereine values(new.team, new.punkte, new.serie)"
How can I fix my code so that it does what I want it to do?
Sorry for being too wordy. I just want to make sure you understand how this is supposed to work. Yes, Ive asked a question before related to the same assignment but this is a whole different problem. Sorry for that as well.
What you basically want is an UPSERT syntax, as shown in this answer using a UNIQUE CONSTRAINT on team column. I would suggest you to use this for your insert operation in the relevant function, instead of a trigger.
If you still insist on using a trigger, you could write something like this.
create or replace function update_points()
returns trigger as
$BODY$
begin
if EXISTS (select 1 FROM vereine WHERE team = new.team ) then
update vereine
set punkte = new.punkte
where team = new.team;
RETURN NULL;
else
RETURN NEW;
end if;
end;
$BODY$
LANGUAGE plpgsql;
The difference between RETURN NULL; and RETURN NEW; in a Trigger returning procedure and a Before Insert Trigger is that RETURN NULL won't execute the triggering statement( i.e the main INSERT operation ), whereas RETURN NEW; continues with the intended INSERT statement execution normally.
Demo
Now, coming to your error SQL Error [54001]: ERROR: stack depth limit exceeded,
it appears that you have another trigger already created on the table that is triggered by the update operation or you have written another insert and returning NEW from the Trigger. You have to take a call on how to handle if it's another trigger( either to drop it or modify it, but that's beyond the scope of this question, and you should ask it separately if you have further issues)
You can check the existence of Triggers on the table by simply querying the information_schema.triggers or pg_trigger , Refer to this answer for more details.
This is my first time using triggers in green plum environment. I think I have most of it setup but I am facing some issues when I insert data. Here is my trigger
CREATE TRIGGER insert_trigger
BEFORE INSERT ON leads.abhi_temp
FOR EACH ROW EXECUTE PROCEDURE leads.my_trigger();
Here is the definition of the trigger
CREATE OR REPLACE FUNCTION leads.my_trigger()
RETURNS TRIGGER AS $$
BEGIN
IF ( NEW.date >= DATE '2003-01-01' AND
NEW.date < DATE '2003-12-31' ) THEN
INSERT INTO leads.abhi_temp_y2003 VALUES (NEW.*);
ELSIF ( NEW.date >= DATE '2004-01-01' AND
NEW.date < DATE '2004-12-31' ) THEN
INSERT INTO leads.abhi_temp_y2004 VALUES (NEW.*);
END IF;
RETURN NULL;
END;
$$
LANGUAGE plpgsql;
Now to insert data into my table I use
insert into leads.myData (select column1, column2 from leads.someOtherDara where column1 = '1');
But this gives me an error
ERROR: function cannot execute on segment because it issues a non-SELECT statement (functions.c:133)
I think the error is because I am using nested queries to insert data. Not sure how to fix this. Any recommendation. Thanks in advance for your help
I am aware, There is very limited support for triggers in Greenplum, It does not support DML operations.
May i know how do you achieve this, i mean how the rules can be applied as u said in your previous comments