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).
Related
I have a table with a column named priority. I created a trigger function that fires after update. I want the function to change the values of the rest of the rows' priority column. For example I have 10 rows and each row has a single value ranging from 1-10, I then want to change the priority of row 10 to 1, then add 1 to the rest of the rows.
I've tried to change the query in many ways and add more/less logic to the function, but I am stuck.
CREATE FUNCTION reorder_priority() RETURNS TRIGGER AS $$
BEGIN
CASE TG_OP
WHEN 'UPDATE' THEN
UPDATE
link
SET
priority = priority + 1
WHERE
link.priority >= NEW.priority AND
NOT link.priority > OLD.priority;
END CASE;
RETURN NULL;
END;
$$ LANGUAGE plpgsql;
This function was able to do this, but it seems to trigger twice, adding 1 to every single row again, leaving me with 10 rows, but priority ranging from 2-11.
I figured it out..
Just had to change the comparison to exclude updating the value of OLD.priority, return NEW, and also change the trigger to run BEFORE and not AFTER
BEGIN
CASE TG_OP
WHEN 'UPDATE' THEN
UPDATE
link
SET
priority = priority + 1
WHERE
link.priority >= NEW.priority AND
NOT link.priority >= OLD.priority;
END CASE;
RETURN NEW;
END;
I'm copying information from table 1(tmp_subtype) to table 2(subtype_user). I have a test table 1 with 15 registers. I run this function into postgres:
CREATE OR REPLACE FUNCTION VERIFY_AND_INSERT_SUPTYPE()
RETURNS text AS $$
DECLARE
register_subtype RECORD;
existe INT DEFAULT 0;
MESSAGE_EXCEPTION TEXT;
cursor_subtype CURSOR
FOR
SELECT tsd.subtype,tsd.type_id_client,tsd.id_client,tsd.email
FROM tmp_subtype tsd;
BEGIN
OPEN cursor_subtype;
LOOP
FETCH cursor_subtype INTO register_subtype;
EXIT WHEN NOT FOUND;
SELECT COUNT(*) INTO existe FROM (
SELECT sdu.id_client FROM subtype_user sdu
WHERE sdu.id_client = register_subtype.id_client AND sdu.type_id_client = register_subtype.type_id_client
LIMIT 1
) SUB0;
IF existe = 0 THEN
INSERT INTO subtype_user(subtype,type_id_client,id_client,email)
VALUES (register_subtype.subtype,register_subtype.type_id_client,register_subtype.id_client,register_subtype.email);
ELSE
UPDATE subtype_user sdu2 SET subtype=register_subtype.subtype,email=register_subtype.email
WHERE sdu2.id_client = register_subtype.id_client AND sdu2.type_id_client = register_subtype.type_id_client;
END IF;
END LOOP;
CLOSE cursor_subtype;
RETURN 'OK';
EXCEPTION WHEN OTHERS THEN
GET STACKED DIAGNOSTICS MESSAGE_EXCEPTION = MESSAGE_TEXT;
RETURN MESSAGE_EXCEPTION;
END; $$
LANGUAGE plpgsql;
It works, but When I run this function with the real table 1, it is not working. The function finishes but nothing happend. The real table 1 has 1 million of registers.
Row-by-row processing with embedded counting is a recipe for slow and inefficient processing. Additionally your check for existence won't work if the function is invoked from concurrent transactions. As far as I can tell you can replace the whole loop and cursor with a single INSERT statement:
CREATE OR REPLACE FUNCTION VERIFY_AND_INSERT_SUPTYPE()
RETURNS text
AS $$
DECLARE
MESSAGE_EXCEPTION TEXT;
BEGIN
INSERT INTO subtype_user(subtype, type_id_client, id_client, email)
SELECT tsd.subtype, tsd.type_id_client, tsd.id_client, tsd.email
FROM tmp_subtype tsd
ON conflict (id_client, type_id_client) DO UPDATE
SET subtype = excluded.register_subtype,
email = excluded.email;
RETURN 'OK';
EXCEPTION WHEN OTHERS THEN
GET STACKED DIAGNOSTICS MESSAGE_EXCEPTION = MESSAGE_TEXT;
RETURN MESSAGE_EXCEPTION;
END; $$
LANGUAGE plpgsql;
I probable would not add an exception handler to begin with, so that the caller sees the complete exception.
It is hard to say, what is wrong on this code - in this situation RAISE NOTICE is your best friend. I see some issues in your code, but these issues are related to performance. Table with 1 mil rows is nothing.
the code in ISAM programming style can be really slow - instead cycle over cursor use INSERT ON CONFLICT .. statement.
SELECT COUNT(*) ... can be rewritten to little bit faster, but surely more readable form:
IF EXISTS(SELECT ... FROM subtype_user) THEN
UPDATE ...
ELSE
INSERT ...
END IF;
Handling errors from your example is little bit obsolete - catch only exception that you can really solve. Your type of exception handling doesn't solve any, and more, you lose details info about the exception (position, line, ...). Just don't do it.
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.
I have a query which can return 0,1, or more rows.
This is my function:
CREATE OR REPLACE FUNCTION A(v_x integer[])
RETURNS void AS
$BODY$
declare
x_part integer;
x_part2 integer;
x_sum integer;
begin
select a,b,sum(qty) into x_part, x_part2,x_sum
from tablet
where location= any (v_x)
group by a,b
..... more actions....
end;
$BODY$
LANGUAGE plpgsql VOLATILE
I have two problems:
if the query return more than one row I can not save the result into x_part,x_part2,x_sum.
How can I tell the number of rows returned?
Basically what I need is if there is more than 1 row to get out of the function with error msg, if there is 1 or 0 rows.. keep going with the function operation.
How do I do that?
You can do this with the keyword STRICT within the SELECT INTO line as covered here: http://www.postgresql.org/docs/9.1/interactive/plpgsql-statements.html#PLPGSQL-STATEMENTS-SQL-ONEROW in the postgresql manual.
essentially your call becomes:
select a,b,sum(qty) into STRICT x_part, x_part2,x_sum from tablet where location= any (v_x) group by a,b;
... -- Rest of your function code here
-- Exception block added at the end of the code, only called if there is an exception anywhere within your function
EXCEPTION
WHEN TOO_MANY_ROWS THEN
RAISE EXCEPTION 'Too many rows!';
end
$$ LANGUAGE plpgsql;
**Perhaps another solution would be to add a LIMIT 1 to the end of your query to guarantee no more than a single row is returned? although this might mask a problem? ** - leaving this here as referenced by comment, but acknowledge this isn't valid option in this instance
HTH
`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