Date Subtraction on PostgreSQL - sql

`I have a table (workers) which has a startdate column(s_date) representing when employee started working. So i want to create a trigger that if it's less than a year (365 days) of working, i m gonna give an exception. But something's wrong with the code. Any help_?
CREATE OR REPLACE FUNCTION control_func() RETURNS TRIGGER AS '
declare
int1 integer;
tt Date;
begin
select now()::date into tt;
select s_date from workers;
if(tt-s_date<365) then
RAISE EXCEPTION ''A message'';
end if;
RETURN NULL;
END;
' LANGUAGE 'plpgsql';

You have several problems. First of all, don't use single quotes to quote function bodies, that just makes a big mess, use dollar quoting instead:
create or replace function f() returns trigger as $$
...
$$ language plpgsql;
Next, this doesn't do anything useful:
select s_date from workers;
That will try to grab all s_date values from workers and then throw them all away. You want to look at the current row for the trigger and that's available in NEW:
NEW
Data type RECORD; variable holding the new database row for INSERT/UPDATE operations in row-level triggers. This variable is NULL in statement-level triggers and for DELETE operations.
So you can look at new.s_date to see the date you're interested in:
select now()::date into tt;
if tt - new.s_date < 365 then
raise exception 'A message';
end if;
This is probably a row-level before insert or update trigger so you don't want return null; here; from the fine manual:
A row-level trigger fired before an operation has the following choices:
It can return NULL to skip the operation for the current row. This instructs the executor to not perform the row-level operation that invoked the trigger (the insertion, modification, or deletion of a particular table row).
For row-level INSERT and UPDATE triggers only, the returned row becomes the row that will be inserted or will replace the row being updated. This allows the trigger function to modify the row being inserted or updated.
So your return null; means "skip this INSERT or UPDATE if the new record is valid" and that's not what you want. You want to return new;.
You also have an unused variable. And you can use current_date instead of your tt.
Your function should look more like this:
create or replace function control_func() returns trigger as $$
begin
if current_date - new.s_date < 365 then
raise exception 'A message';
end if;
return new;
end;
$$ language plpgsql;

EDIT: the initial syntax is supposed to be datediff. However it is a MYSQL syntax. So you are better of with the above detailed and correct answer. ;-)
Ty this this in you code,
Add another variable to save s_date say ss,
If Datediff(days, tt, ss) < 365 then

Related

syntax Error in PostgreSQL when I try to create Trigger

I want to create trigger in PostgreSQL.
Logic is very simple.
I need trigger, if published_at updated and written_at is null, set published_at to written_at.
I wrote this one, but it failed. Does anybody have an idea?
CREATE function setWrittenAt() RETURNS trigger;
AS
DECLARE old_id INTEGER;
BEGIN ;
old_id = OLD.id
IF NEW.published_at IS NOT and NEW.written_at IS null
THEN
UPDATE review SET NEW.written_at = NEW.published_at where id = old_id;
END IF ;
RETURN NEW;
END;
LANGUAGE plpgsql;
CREATE TRIGGER update_written_at
AFTER UPDATE OF published_at ON review
WHEN (OLD.published_at IS DISTINCT FROM NEW.published_at)
EXECUTE PROCEDURE setWrittenAt();
Error:
Syntax error: 7 ERROR: syntax error at or near "DECLARE"
LINE 3: DECLARE old_id INTEGER;
There are multiple errors in your code:
IS NOT is not a valid expression you need IS NOT NULL.
After BEGIN and the returns clause there must be no ;
you forgot to enclose the function body as a string (which is easier to write if you use dollar quoting
you also don't need an unnecessary (additional) UPDATE if you make it a before trigger
CREATE function setwrittenat()
RETURNS trigger
AS
$$
BEGIN
IF NEW.published_at IS NOT NULL and NEW.written_at IS null THEN
NEW.written_at := = NEW.published_at; --<< simply assign the value
END IF;
RETURN NEW;
END;
$$
LANGUAGE plpgsql;
Then use a BEFORE trigger:
CREATE TRIGGER update_written_at
BEFORE UPDATE OF published_at ON review
WHEN (OLD.published_at IS DISTINCT FROM NEW.published_at)
FOR EACH ROW
EXECUTE PROCEDURE setWrittenAt();
this is based on a_horse_with_no_names answer, since it'll throw an error.
ERROR: statement trigger's WHEN condition cannot reference column values
You need to add FOR EACH ROW, else conditional triggers will not function.
If neither is specified, FOR EACH STATEMENT is the default.
Statement-level triggers can also have WHEN conditions, although the feature is not so useful for them since the condition cannot refer to any values in the table.
See here
CREATE TRIGGER update_written_at
BEFORE UPDATE OF published_at ON review
FOR EACH ROW
WHEN (OLD.published_at IS DISTINCT FROM NEW.published_at)
EXECUTE PROCEDURE setWrittenAt();
I can not comment yet, which is why I've posted this as an answer.

Best practices for asserting some condition in SQL when creating a table?

Imagine I create some table:
CREATE TABLE mytable AS
...
Now I want to conduct some sanity check, verify some condition is true for every record of mytable. I could frame this problem as checking whether the result of another query returns zero results.
SELECT count(*)
FROM mytable
WHERE something_horrible_is_true
Is there a standard, recommended way to generate an error here if the count is not equal to zero? To make something happen such that if I'm executing this sanity check query using a java.sql.Statement, a SQLException is triggered?
Is this a reasonable approach? Or is this a better way to enforce that some condition is always true when creating a table? (I use Postgresql.)
Create function to raise exception:
create or replace function raise_error(text) returns varchar as $body$
begin
raise exception '%', $1;
return null;
end; $body$ language plpgsql volatile;
Then you can use it in a regular SQLs:
SELECT case when count(*) > 0 then raise_error('something horrible is true!') end
FROM mytable
WHERE something_horrible_is_true
Here you will get the SQL exception if there are rows that satisfy the something_horrible_is_true condition.
There are also several more complex usage examples:
SELECT
case
when count(*) = 0 then raise_error('something horrible is true!')::int
else count(*)
end
FROM mytable
WHERE something_horrible_is_true
Returns count or rise exception when nothing found.
update mytable set
mydatefield =
case
when mydatefield = current_date then raise_error('Can not update today''s rows')::date
else '1812-10-10'::date
end;
Prevents to update some rows (this is a somewhat contrived example but it shows yet another usage way)
... and so on.
Are you familiar with triggers? Postresql provides good suport for triggers especially using the pgsql laguange.
A trigger is a function (check) that is always run on an event: insert, update,delete. You can call the function before or after the event.
I believe once you know this concept, you can find an online tutorial to help you achieve your goal.
A general approach may look like this:
CREATE FUNCTION trigger_function() RETURN trigger AS
$$
DECLARE c integer;
BEGIN
SELECT count(*) into c FROM mytable WHERE something_horrible_is_true;
IF c>0 then RAISE EXCEPTION 'cannot have a negative salary';
END IF;
return new;
END;
$$ LANGUAGE plpgsql;
And afterwards you execute
CREATE TRIGGER trigger_name BEFORE INSERT
ON table_name
FOR EACH ROW
EXECUTE PROCEDURE trigger_function()
Both code sections are pqsql.

PostgreSQL 1 to many trigger procedure

I wrote this query in PostgreSQL:
CREATE OR REPLACE FUNCTION pippo() RETURNS TRIGGER AS $$
BEGIN
CHECK (NOT EXISTS (SELECT * FROM padre WHERE cod_fis NOT IN (SELECT padre FROM paternita)));
END;
$$ LANGUAGE plpgsql;
It returns:
Syntax error at or near CHECK.
I wrote this code because I have to realize a 1..n link between two tables.
You can't use CHECK here. CHECK is for table and column constraints.
Two further notes:
If this is supposed to be a statement level constraint trigger, I'm guessing you're actually looking for IF ... THEN RAISE EXCEPTION 'message'; END IF;
(If not, you may want to expand and clarify what you're trying to do.)
The function should return NEW, OLD or NULL.

Validate subquery count is not greater than x

I am trying to create a function and a trigger that verify that the count of the wid and rdate columns inside the responsibility table is less than or equal to 10. I need to raise an exception when its greater than 10.
My subquery count is not working. When COUNT() is greater than 10, no exception is thrown.
What am I doing wrong?
CREATE FUNCTION check_10() RETURNS TRIGGER AS $$
BEGIN
IF (SELECT COUNT(CASE WHEN wid = NEW.wid AND rdate = NEW.rdate THEN 1 ELSE 0 END) AS total FROM resposibility) > 10 THEN
RAISE EXCEPTION 'MAXIMUM OF 10 CAGE RESPONSIBILITES FOR EACH WORKER PER DATE';
END IF;
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
/* I've added update to make sure even when "updating" that row it will run the trigger check to verify */
CREATE TRIGGER insert_resp BEFORE INSERT OR UPDATE ON resposibility
FOR EACH ROW EXECUTE PROCEDURE check_10();
As long as you are raising an EXCEPTION (and don't catch it), that will roll back the whole transaction immediately anyway. The RETURN statement simply doesn't matter in this case. Remove it.
It would be an alternative to use RETURN NULL instead of the exception if you want to silently skip the operation on only the current row and otherwise proceed normally.
The only obvious error in your code was > 10 instead of >= 10 as pointed out by #a_horse. And the typo in resposibility. The rest is a matter of efficiency.
Also, assignments are comparatively expensive in plpgsql, there is really no need here. Simplify:
CREATE OR REPLACE FUNCTION check_10()
RETURNS TRIGGER AS
$func$
BEGIN
IF (SELECT count(*) >= 10
FROM responsibility
WHERE wid = NEW.wid
AND rdate = NEW.rdate) THEN
RAISE EXCEPTION 'WORKER % ALREADY HAS MAX. OF 10 RESPONSIBILITES FOR DATE: %'
, NEW.wid, NEW.rdate;
-- no need for *any* RETURN statement here!
END IF;
RETURN NEW;
END
$func$ LANGUAGE plpgsql;
Be sure to have an index on wid and rdate or, ideally, a multicolumn index on (wid, rdate).

Writing an SQL trigger to find if number appears in column more than X times?

I want to write a Postgres SQL trigger that will basically find if a number appears in a column 5 or more times. If it appears a 5th time, I want to throw an exception. Here is how the table looks:
create table tab(
first integer not null constraint pk_part_id primary key,
second integer constraint fk_super_part_id references bom,
price integer);
insert into tab values(1,NULL,100), (2,1,50), (3,1,30), (4,2,20), (5,2,10), (6,3,20);
Above are the original inserts into the table. My trigger will occur upon inserting more values into the table.
Basically if a number appears in the 'second' column more than 4 times after inserting into the table, I want to raise an exception. Here is my attempt at writing the trigger:
create function check() return trigger as '
begin
if(select first, second, price
from tab
where second in (
select second from tab
group by second
having count(second) > 4)
) then
raise exception ''Error, there are more than 5 parts.'';
end if;
return null;
end
'language plpgsql;
create trigger check
after insert or update on tab
for each row execute procedure check();
Could anyone help me out? If so that would be great! Thanks!
CREATE FUNCTION trg_upbef()
RETURN trigger as
$func$
BEGIN
IF (SELECT count(*)
FROM tab
WHERE second = NEW.second ) > 3 THEN
RAISE EXCEPTION 'Error: there are more than 5 parts.';
END IF;
RETURN NEW; -- must be NEW for BEFORE trigger
END
$func$ LANGUAGE plpgsql;
CREATE TRIGGER upbef
BEFORE INSERT OR UPDATE ON tab
FOR EACH ROW EXECUTE procedure trg_upbef();
Major points
Keyword is RETURNS, not RETURN.
Use the special variable NEW to refer to the newly inserted / updated row.
Use a BEFORE trigger. Better skip early in case of an exception.
Don't count everything for your test, just what you need. Much faster.
Use dollar-quoting. Makes your live easier.
Concurrency:
If you want to be absolutely sure, you'll have to take an exclusive lock on the table before counting. Else, concurrent inserts / updates might outfox each other under heavy concurrent load. While this is rather unlikely, it's possible.