Run triggered postgresql function on hsqldb - postgresql

I can't find solution about transfering my function.
Lets manage working function and trigger on postgresql as below:
CREATE FUNCTION func_check_minutes() RETURNS trigger AS
$$
BEGIN
IF (SELECT 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();
Is it even possible to run this function on hslqdb?
Because when I try to run it (obviously without language command) there is an error:
DatabaseException: unexpected token: TRIGGER
I have syntax error, so I dont know if it's even possible. I was reading about functions and triggers in hsqldb from documentation, but did'nt notice any example about triggered functions in hsqldb.
With help from #fredt I created query:
<sql dbms="hsqldb">
DROP TRIGGER IF EXISTS tr_check_minutes
CREATE TRIGGER tr_check_minutes
BEFORE INSERT ON hours_worked
FOR EACH ROW
BEGIN ATOMIC
IF (SELECT sum(minutes) + NEW.minutes FROM hours_worked WHERE date = NEW.date) > 1440
THEN RETURN NULL;
END IF;
RETURN NEW;
END
</sql>
But it prints an error:
user lacks privilege or object not found: NEW.DATE

If you want the INSERT to fail when too many hours are worked, you can throw an exception:
CREATE TRIGGER tr_check_minutes
BEFORE INSERT ON hours_worked
REFERENCING NEW ROW AS NEW
FOR EACH ROW
BEGIN ATOMIC
IF (SELECT sum(minutes) + NEW.minutes FROM hours_worked WHERE date = NEW.date) > 1440
THEN
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = 'too many hours';
END IF;
END

Related

postgresql trigger by using 2 tables

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

How To Restrict Delete using PL/pgSQL trigger?

If the client user is trying to delete more than 5 records from a Table i want to restrict that using a trigger. I have a basic idea to do that but i don't know how to implement the Idea. I appreciate any HELP.
Basic Idea : In Trigger IF TG_OP = Delete and the count of records to be deleted are more than 5 then Restrict.
CREATE TRIGGER adjust_count_trigger BEFORE DELETE ON schemaname.tablename
FOR EACH ROW EXECUTE PROCEDURE public.adjust_count();
CREATE OR REPLACE FUNCTION adjust_count()
RETURNS TRIGGER AS
$$
DECLARE
num_rows int;
num_rows1 int;
BEGIN
IF TG_OP = 'DELETE' THEN
EXECUTE 'select count(*) from '||TG_TABLE_SCHEMA ||'.'||TG_RELNAME ||' where oid = old.oid ' into num_rows ;
IF num_rows > 5 Then
RAISE NOTICE 'Cannot Delete More than 5 Records , % ', num_rows ;
END IF ;
END IF ;
RETURN OLD;
END;
$$
LANGUAGE 'plpgsql';
In earlier versions of Postgres you can simulate a transition table introduced in Postgres 10. You need two triggers.
create trigger before_delete
before delete on my_table
for each row execute procedure before_delete();
create trigger after_delete
after delete on my_table
for each statement execute procedure after_delete();
In the first trigger create a temp table and insert a row into it:
create or replace function before_delete()
returns trigger language plpgsql as $$
begin
create temp table if not exists deleted_rows_of_my_table (dummy int);
insert into deleted_rows_of_my_table values (1);
return old;
end $$;
In the other trigger count rows of the temp table and drop it:
create or replace function after_delete()
returns trigger language plpgsql as $$
declare
num_rows bigint;
begin
select count(*) from deleted_rows_of_my_table into num_rows;
drop table deleted_rows_of_my_table;
if num_rows > 5 then
raise exception 'Cannot Delete More than 5 Records , % ', num_rows;
end if;
return null;
end $$;
The above solution may seem a bit hacky but it is safe if only the temp table does not exist before delete (do not use the same name of the temp table for multiple tables).
Test it in rextester.
You can easily do that with the new transition relation feature from PostgreSQL v10:
CREATE OR REPLACE FUNCTION forbid_more_than() RETURNS trigger
LANGUAGE plpgsql AS
$$DECLARE
n bigint := TG_ARGV[0];
BEGIN
IF (SELECT count(*) FROM deleted_rows) <= n IS NOT TRUE
THEN
RAISE EXCEPTION 'More than % rows deleted', n;
END IF;
RETURN OLD;
END;$$;
CREATE TRIGGER forbid_more_than_5
AFTER DELETE ON mytable
REFERENCING OLD TABLE AS deleted_rows
FOR EACH STATEMENT
EXECUTE PROCEDURE forbid_more_than(5);

How to modify Trigger to update a single attribute in PostgreSQL

Here is my sample table.
CREATE TABLE employee_test(
idTst SERIAL PRIMARY KEY,
monthDownload VARCHAR(6),
changeDate DATE);
I am trying to create a function and trigger that would update changeDate attribute with a current date when monthDownload attribute is updated.
The function I have it works with one problem. It updates all records instead of the one that was updated.
CREATE OR REPLACE FUNCTION downloadMonthChange()
RETURNS TRIGGER AS
$$
BEGIN
IF NEW.monthDownload <> OLD.monthDownload THEN
UPDATE employee_test
SET changeDate = current_date
where OLD.idTst = NEW.idTst;
END IF;
RETURN NEW;
END;
$$
Language plpgsql;
Trigger
Create TRIGGER dataTest
AFTER UPDATE
ON employee_test
FOR EACH ROW
EXECUTE PROCEDURE downloadMonthChange();
When I execute the following Update statement:
UPDATE employee_test SET monthDownload = 'oct12'
WHERE idTst = 1;
All changeDate rows get update with a current date.
Is there a way to have only a row with changed record to have a current date updated.
If you use a before trigger you can write directly to NEW
CREATE OR REPLACE FUNCTION downloadMonthChange()
RETURNS TRIGGER AS
$$
BEGIN
IF NEW.monthDownload <> OLD.monthDownload THEN
NEW.changeDate = current_date;
END IF;
RETURN NEW;
END;
$$
Language plpgsql;
the other option when you must use an after trigger is to include the primary key in the where clause. It appears that you were trying to do this, but you had a spurious OLD in the query. beause of that the where clause was only looking at the record responsible for the trigger call, and not limiting which records were to be updated.
IF NEW.monthDownload <> OLD.monthDownload THEN
UPDATE employee_test
SET changeDate = current_date
where idTst = NEW.idTst;

PostgreSQL AFTER INSERT trigger prevents insert

I have a PostgreSQL 9.3 database. I use a log4net configuration to insert the errors in a table: log_messages.
I have a webpage that shows the errors in a nice way with charts and such.
Because I use a rather complex view this webpage is very slow, so I moved to a materialized view. My page is fast again.
Now I need to keep my materialized view in sync with my table/view. So I created a AFTER INSERT trigger on the table:
CREATE TRIGGER refresh_mv_insert
AFTER INSERT
ON log_messages
FOR EACH ROW
EXECUTE PROCEDURE refresh_mv();
My refresh_mv() is more complicated but even this simplified version doesn't work:
CREATE OR REPLACE FUNCTION refresh_mv()
RETURNS trigger AS
$BODY$
DECLARE
l_view_name character varying := 'mv_log_messages';
begin
EXECUTE 'REFRESH MATERIALIZED VIEW ' || l_view_name;
RETURN NEW;
end;
$BODY$
LANGUAGE plpgsql VOLATILE
COST 100;
When I change it to an anonymous procedure the full and simplified version do work. So it seems I have an error in the Trigger part.
I've been reading documentation and similar Q&A for two days now but I can't get it to work.
Any help is much appreciated.
Edit: Clarification
In my full trigger procedure I use a config table to store the refresh timestamp and I don't refresh within an hour.
When I enable the trigger the record is not saved into the log_messages table.
I don't know how to read any trigger errors. Where can I find them?
Here's my full code:
CREATE OR REPLACE FUNCTION refresh_mv()
RETURNS trigger AS
$BODY$
DECLARE
l_last_refresh timestamp;
l_view_name character varying := 'mv_log_messages';
l_num_new smallint;
l_refresh boolean := false;
begin
l_refresh := false;
-- check the last time:
select last_refresh, num_new into l_last_refresh, l_num_new from config where view_name = l_view_name;
-- refresh every hour
if (l_last_refresh + interval '1 hour' < current_timestamp) then
l_refresh := true;
end if;
-- refresh every 10 inserts, but not more often than every 10 minutes:
if (l_num_new > 9 and l_last_refresh + interval '10 minutes' < current_timestamp) then
l_refresh := true;
end if;
if l_refresh then
-- Reset config and do refresh:
update config set last_refresh = current_timestamp, num_new = 0 where view_name = l_view_name;
-- this line prevents the insertion of the record EXECUTE 'REFRESH MATERIALIZED VIEW ' || l_view_name;
else
-- Update counter:
update config set num_new = l_num_new + 1 where view_name = l_view_name;
end if;
RETURN NULL;
end;
$BODY$
LANGUAGE plpgsql VOLATILE
COST 100;
This trigger works because I commented the line EXECUTE 'REFRESH MATERIALIZED VIEW ' || l_view_name;

How to create a trigger that checks values?

I need help with my trigger. For example i have Query like this
INSERT INTO test(id_users,id_type,id_color) VALUES(1,3,4);
in my data base i have table with name: test
ghost, id, id_users, id_type, id_color
and i need before insert or update to check
Create trigger
Begin
select id from test where ghost = false and id_users = 26 and id_type = 3
if NO execute INSERT
if YEST exit with no action
END
How can i creat this trigger ?
There are two ways, depending on how you want to manage the problem.
If you wish to silence it, use a before trigger and return null:
create function ignore_invalid_row() returns trigger as $$
begin
if not exists(
select 1
from test
where not ghost
and id_users = new.id_users
and id_type = new.id_type
)
then
return null;
end if;
return new;
end;
$$ language plpgsql;
create trigger ignore_invalid_row_tg before insert on test
for each row execute procedure ignore_invalid_row();
If you wish to raise it, use a constraint trigger and raise an exception:
create function reject_invalid_row() returns trigger as $$
begin
if not exists(
select 1
from test
where not ghost
and id_users = new.id_users
and id_type = new.id_type
)
then
raise exception 'message';
end if;
return null;
end;
$$ language plpgsql;
create constraint trigger reject_invalid_row_tg after insert on test
for each row execute procedure reject_invalid_row();
http://www.postgresql.org/docs/current/static/sql-createtrigger.html
First, you need to create a trigger function and then the trigger, based on it:
CREATE OR REPLACE FUNCTION my_trigger_function() RETURNS TRIGGER AS $BODY$ BEGIN
IF EXISTS (SELECT id FROM test WHERE ghost = false AND id_users = 26 AND id_type = 3) THEN
return NEW;
ELSE
return NULL;
END IF; END; $BODY$ LANGUAGE 'plpgsql';
Then, you create trigger based on this function:
CREATE TRIGGER t_my_trigger BEFORE INSERT ON test FOR EACH ROW EXECUTE PROCEDURE my_trigger_function();
For more on triggers, see postgres docs.