Postgres Function not working when I have a large result - sql

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.

Related

How to check if an sequence is above a certain number and if not change it in Postgres

I have a problem where my SQL sequence has to be above a certain number to fix a unique constraint error. Now I started to write an if-statement that checks for a certain number and if it's below it should be increased to a certain number. The statement is for Postgres.
I got the separate parts running but the connection over if is throwing an error and I don't know why.
First for selecting the current number:
SELECT nextval('mySequence')
Then to update the number:
SELECT setval('mySequence', targetNumber, true)
The full statement looks something like this in my tries:
IF (SELECT nextval('mySequence') < targetNumber)
THEN (SELECT setval('mySequence', targetNumber, true))
END IF;
and the error is
ERROR: syntax error at »IF«
Can someone explain to me what I did wrong there because the error message isn't giving me much to work with? I would appreciate your help.
Try this:
SELECT setval('mySequence', targetNumber, true)
WHERE (SELECT nextval('mySequence') < targetNumber) is true;
You can use postgres functions if you want to use IF statement.
You can try something like this:
CREATE SEQUENCE seq_test_id_seq;
CREATE TABLE seq_test(
id integer NOT NULL DEFAULT nextval('seq_test_id_seq'),
name VARCHAR
);
CREATE OR REPLACE FUNCTION seq_test_function(target_number bigint)
RETURNS void
LANGUAGE 'plpgsql'
VOLATILE
PARALLEL UNSAFE
COST 100
AS $BODY$
DECLARE
seq_val INTEGER;
BEGIN
SELECT nextval('seq_test_id_seq') INTO seq_val;
RAISE NOTICE 'NEXT SEQUENCE [%]', seq_val;
IF (seq_val < target_number) THEN
SELECT setval('seq_test_id_seq', target_number, true) INTO seq_val;
RAISE NOTICE 'SEQUENCE VALUE MODIFIED [%]', seq_val;
END IF;
END;
$BODY$;
Then call the procedure:
select seq_test_function(10);

SQL RAISE EXCEPTION not working

I'm not very good with SQL and I have a small problem with my code.
CREATE OR REPLACE FUNCTION cancelBooking() RETURNS TRIGGER AS $cancelBooking$
BEGIN
IF (NEW.bookingid not in(SELECT bookingid FROM flightbooking)) THEN
RAISE EXCEPTION 'ID NOT FOUND';
END IF;
END;
$cancelBooking$ LANGUAGE plpgsql;
CREATE TRIGGER cancelBooking BEFORE UPDATE ON flightbooking
FOR EACH ROW EXECUTE PROCEDURE cancelBooking();
UPDATE flightbooking
SET status = 'C'
WHERE bookingid=11;
After I update flightbooking with non existing ID it still says UPDATE 0 which didn't do anything of course but I want it to be an error not successfull query.
Any ideas? I tried to look for a solution on the internet but it didn't help.
Obviously the stated question is why it is not working (which is due to the problem discussed in the other answers. Obviously this will never work since the only case of the trigger being fired never can have be in a snapshot where a row with the same bookingid as NEW will be visible in the same snapshot.
Also I am not 100% sure but I am worried about performance in your function. (PLPGSQL is a bit funny at times).
Try this instead as it is clearer what is going on under the hood and therefore makes clearer what can be optimized.
CREATE OR REPLACE FUNCTION cancelBooking() RETURNS TRIGGER AS
$cancelBooking$
BEGIN
PERFORM * FROM flightbooking WHERE bookingid = NEW.bookingid;
IF NOT FOUND THEN
RAISE EXCEPTION 'ID NOT FOUND';
END IF;
END;
$cancelBooking$ LANGUAGE plpgsql;
I am guessing in most cases that that the performance difference will be very minimal but the performance implications and caveats are clearer so the opportunities to shoot yourself in the foot are less.
On to a real solution rather than a critique and diagnosis
This will never work as you have done it. You could do as follows instead:
CREATE OR REPLACE FUNCTION cancelBooling(_bookingid int) returns void
LANGUAGE PLPGSQL AS
$$
BEGIN
DELETE FROM flightbooking WHERE bookingid = _bookingid;
IF NOT FOUND THEN
RAISE EXCEPTION 'NOT FOUND';
END IF;
END;
$$;
Your trigger fires for each row being updated. Because there are no rows to update (the WHERE clause in the UPDATE doesn't find any), the trigger is never fired.

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.

In postgresql. How can I know if query returned more than 1 row?

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

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).