PostgreSQL: how to catch exception in a function? - postgresql

I have a table and 3 functions
fntrans-calls->fntrans2-calls->fntrans3
In this example I have removed call of fntrans3();
After this call
SELECT public.fntrans2();
the table t2 contains records, ok, it is clear, rollback works to the savepoint inside of function
But when I do call
SELECT public.fntrans();
the table does not contain any rows and output shows notice about exception in fntrans;
Why in the 2nd case exception throwed to the 1st function but when I call fntrans2 only it caught inside this function.
create table t2(ID SERIAL PRIMARY KEY, f1 INTeger);
CREATE OR REPLACE FUNCTION "public"."fntrans" (
)
RETURNS integer AS
$body$
declare d double precision;
BEGIN
raise notice 'fntrans';
INSERT INTO t2 (f1) VALUES (1);
INSERT INTO t2 (f1) VALUES (2);
INSERT INTO t2 (f1) VALUES (3);
select fntrans2();
INSERT INTO t2 (f1) VALUES (4);
RETURN 1;
EXCEPTION
WHEN OTHERS THEN
BEGIN
raise notice 'fntrans exception';
RETURN 0;
END;
END;
$body$
LANGUAGE 'plpgsql'
VOLATILE
CALLED ON NULL INPUT
SECURITY INVOKER
COST 100;
CREATE OR REPLACE FUNCTION public.fntrans2 (
)
RETURNS integer AS
$body$
declare d double precision;
BEGIN
raise notice 'fntrans2';
INSERT INTO t2 (f1) VALUES (10);
INSERT INTO t2 (f1) VALUES (22);
INSERT INTO t2 (f1) VALUES (30);
BEGIN
raise exception 'Oooooppsss 2!';
INSERT INTO t2 (f1) VALUES (40);
RETURN 1;
EXCEPTION
WHEN OTHERS THEN RETURN 0;
END;
END;
$body$
LANGUAGE 'plpgsql' VOLATILE;
CREATE OR REPLACE FUNCTION public.fntrans3 (
)
RETURNS integer AS
$body$
declare d double precision;
BEGIN
raise notice 'fntrans3';
INSERT INTO t2 (f1) VALUES (100);
INSERT INTO t2 (f1) VALUES (200);
INSERT INTO t2 (f1) VALUES (300);
raise exception 'Oooooppsss 3!';
INSERT INTO t2 (f1) VALUES (400);
RETURN 1;
EXCEPTION
WHEN OTHERS THEN RETURN 0;
END;
$body$
LANGUAGE 'plpgsql'
VOLATILE
CALLED ON NULL INPUT
SECURITY INVOKER
COST 100;

Your problem is the line
select fntrans2();
in fntrans. You cannot use SELECT without an INTO clause in PL/pgSQL.
Without the EXCEPTION block you'll get the following message:
CONTEXT: SQL statement "select fntrans2()"
PL/pgSQL function fntrans() line 8 at SQL statement
ERROR: query has no destination for result data
HINT: If you want to discard the results of a SELECT, use PERFORM instead.
CONTEXT: PL/pgSQL function fntrans() line 8 at SQL statement
That is pretty self-explanatory.
It also explains why you don't see any results from the function fntrans2 – it doesn't get called.
You could change the offending line to
PERFORM fntrans2();

Related

function not returning any value and not raising exception

The below function neither returning the value nor raising the exception.
create or replace function get_custid(p_customerNum varchar2)
RETURNS text AS $$
DECLARE
cust_id customer.customer_num%TYPE;
begin
raise notice '%', message_text;
select customer_num into cust_id
from customer
where customer_num = p_customerNum;
return cust_id;
exception
when OTHERS then
raise notice '%', message_text;
raise;
end $$ language plpgsql;
select get_custid('Ab12345') from dual;
-- the customer number is existed but not returning any rows.
select get_custid('DDDDDDD') from dual;
-- the customer number is not existed but not going to exception block
I think that is you really use postgresql this code is more likely what you need (or at list it's running...)
create table customer (cust_id int, customer_num varchar);
insert into customer values (1, 'Ab12345');
drop function get_custid(varchar);
create or replace function get_custid(p_customerNum varchar)
RETURNS int AS $$
DECLARE
out_cust_id int;
begin
--raise notice '%', message_text;
select cust_id into out_cust_id
from customer
where customer_num = p_customerNum;
if out_cust_id is null
then raise exception 'your exception';
end if;
return out_cust_id;
end $$ language plpgsql;
select get_custid('Ab12345');
In PL/pgSQL, SELECT INTO only throws an exception on the wrong number of rows if STRICT is specified.
create or replace function get_custid(p_customerNum varchar)
RETURNS text AS $$
DECLARE
cust_id customer.customer_num%TYPE;
begin
raise notice '%', 'message_text';
select customer_num into strict cust_id
from customer
where customer_num = p_customerNum;
return cust_id;
exception
when OTHERS then
raise notice '%', 'message_text';
raise;
end $$ language plpgsql;

query has no destination for result data error

I am having the following function in `
CREATE OR REPLACE FUNCTION public.get_avalable_providers(
start_day_id integer,
end_day_id integer,
number_of_days integer,
requested integer)
RETURNS SETOF provider AS
$BODY$declare
required integer;
available_product integer;
p provider;
p_id integer;
noa integer;
begin
FOR p IN SELECT * FROM provider
loop
FOR p_id, noa IN SELECT id, number_of_availables FROM product
WHERE provider_id = p.id
LOOP
required = requested/noa;
select available_product =
public.get_available_products_biggerthan(
start_day_id, end_day_id, number_of_days, required, p_id);
if available_product = number_of_days then
return next p;
exit;
end if;
END LOOP;
end loop;
return;
end;
$BODY$
LANGUAGE plpgsql VOLATILE
COST 100
ROWS 1000;
ALTER FUNCTION public.get_avalable_providers(integer, integer, integer,
integer, integer)
OWNER TO postgres;
The above functions is supposed to return some providers that have sufficient amount of the requested product in a days range
it is taking advantage anther function get_available_products_biggerthan and I am getting the following error:
ERROR: query has no destination for result data
HINT: If you want to discard the results of a SELECT, use PERFORM instead.
CONTEXT: PL/pgSQL function
get_avalable_products(integer,integer,integer,integer,integer) line 15 at
SQL statement
Question
where am I making mistake?
https://www.postgresql.org/docs/current/static/plpgsql-statements.html#PLPGSQL-STATEMENTS-SQL-ONEROW
The result of a SQL command yielding a single row (possibly of
multiple columns) can be assigned to a record variable, row-type
variable, or list of scalar variables. This is done by writing the
base SQL command and adding an INTO clause
try below (I only fixed obvious syntax):
CREATE OR REPLACE FUNCTION public.get_avalable_providers(
start_day_id integer,
end_day_id integer,
number_of_days integer,
requested integer)
RETURNS SETOF provider AS
$BODY$declare
required integer;
available_product integer;
p provider;
p_id integer;
noa integer;
begin
FOR p IN (SELECT * FROM provider)
loop
FOR p_id, noa IN (SELECT id, number_of_availables FROM product
WHERE provider_id = p.id)
LOOP
required = requested/noa;
select public.get_available_products_biggerthan(
start_day_id, end_day_id, number_of_days, required, p_id) INTO available_product ;
if available_product = number_of_days then
return next p;
exit;
end if;
END LOOP;
end loop;
return;
end;
$BODY$
LANGUAGE plpgsql VOLATILE
COST 100
ROWS 1000;

postgresql: converting CTE column to array

I am trying to convert one of the columns of CTE to array. I keep on getting "syntax error at or near" followed by "ret := array(".
My objective is that the table I am returning from a_function() in example below is stored as a variable to be referred later in function. But I could not find a syntax to do so. So, instead of using CTE, if I can use something else, that would work just as nicely.
Note: I am trying this in pgAdmin III.
create or replace function a_function()
--returns int[][] as
returns table(column1 int, column2 int) as
$body$
begin
return query
select 1,2;
end;
$body$
language 'plpgsql'
;
--select * from a_function();
create or replace function test_a_function()
returns void as
$body$
declare ret int[];
begin
with ret_cte(column1, column2) as (
select * from a_function()
)
ret := array(
select column1 from ret_cte
)
;
--raise notice '%', array_to_string(ret, ',');
end;
$body$
language 'plpgsql'
;
--select test_a_function();
I am trying to convert one of the columns of CTE to array. I keep on getting "syntax error at or near" followed by "ret := array("
You could use:
ret :=array(with ret_cte(column1, column2) as (
select * from a_function()
)
select column1 from ret_cte
);
Rextester Demo
Arrays can be built and returned using the array_agg() function. For example, your second function could be written using a SQL language function that returns the array as follows:
create or replace function test_a_function()
returns int[] as
$body$
select array_agg(column1) from a_function();
$body$
language 'sql';
Alternatively, you can assign the array to a variable like this:
create or replace function test_a_function()
returns void as
$body$
declare
ret int[];
begin
select array_agg(column1)
into ret
from a_function();
raise info '%', ret;
end;
$body$
language 'plpgsql';

Select statement within function blank results

I'm using Postgresql and trying to have a select statement in a function to call out. At the moment the call gives me zero results
CREATE OR REPLACE FUNCTION f_all_male_borrowers
(
OUT p_given_names varchar(60),
OUT p_family_name varchar(60),
OUT p_gender_code integer
)
RETURNS SETOF record as $body$
declare body text;
BEGIN
SELECT into p_given_names,p_family_name, p_gender_code
borrower.given_names, borrower.family_name, gender.gender_code
FROM BORROWER
INNER join gender on borrower.gender_code=gender.gender_code
WHERE borrower.gender_code = '1';
RETURN ;
END;
$body$ LANGUAGE plpgsql;
Call to function:
select * from f_all_male_borrowers()
What is missing, or what am I doing wrong here?
Thank you
CREATE OR REPLACE FUNCTION f_all_male_borrowers
(
OUT p_given_names varchar(60),
OUT p_family_name varchar(60),
OUT p_gender_code integer
)
RETURNS SETOF record as
$body$
SELECT into p_given_names,p_family_name, p_gender_code
borrower.given_names, borrower.family_name, gender.gender_code
FROM BORROWER
INNER join gender on borrower.gender_code=gender.gender_code
WHERE borrower.gender_code = '1';
$body$
LANGUAGE sql VOLATILE
COST 100
ROWS 1000;
ALTER FUNCTION f_all_male_borrowers()
OWNER TO postgres;
Try this and then call :
select * from f_all_male_borrowers();
Before doing that you need to check whether your query have result or not !!
After creating function then:
Got to Functions->Right click your function(ie,f_all_male_borrowers())-> Scripts->Select Script ->Then run it.
If it returns result then your procedure is correct.

How to return no records found from a stored procedure

Is it possible to have a stored procedure behave exactly like a regular select query when no records are found, or is this a driver issue.
For example, with go, a query that returns no rows will return an sql.ErrNoRows error. However, this will not:
create table emptytable(id int);
create function selectany() returns emptytable as $$
DECLARE
_out emptytable;
BEGIN
SELECT * INTO emptytable FROM emptytable limit 1;
RETURN _out;
END;
$$ LANGUAGE PLPGSQL;
I have tried SELECT INTO STRICT, and while that raises a "query returned no rows" error, it is not the same as a non-stored procedure query. Neither is raising NO_DATA_FOUND.
If I understand your requirements correctly:
Return one or no row from a function and allow to do more with the returned row (if any).
Test table:
CREATE TABLE emptytable(id int, txt text); -- multiple columns
To return one or no complete table row:
CREATE OR REPLACE FUNCTION selectany_all()
RETURNS SETOF emptytable AS
$func$
DECLARE
_out emptytable;
BEGIN
FOR _out IN
SELECT * FROM emptytable LIMIT 1
LOOP
-- do something with _out before returning
RAISE NOTICE 'before: %', _out;
RETURN NEXT _out;
-- or do something with _out after returning row
RAISE NOTICE 'after: %', _out;
END LOOP;
END
$func$ LANGUAGE plpgsql;
For a more flexible approach: return arbitrary columns:
CREATE OR REPLACE FUNCTION selectany_any()
RETURNS TABLE (id int, txt text) AS
$func$
BEGIN
FOR id, txt IN
SELECT e.id, e.txt FROM emptytable e LIMIT 1
LOOP
-- do something with id and text before returning
RAISE NOTICE 'before: %, %', id, txt;
RETURN NEXT;
-- or do something with id and text after returning row
RAISE NOTICE 'after: %, %', id, txt;
END LOOP;
END
$func$ LANGUAGE plpgsql;
Note, the LOOP is never entered if there is no row. Accordingly you will get no NOTICE from my test code.
Both functions work for n rows returned as well, LIMIT 1 is just for this particular request.
Closely related, wtih more explanation:
Return multiple fields as a record in PostgreSQL with PL/pgSQL
2.5 options:
1a) If you just need to return a query, you can use SETOF and RETURN QUERY
1b) or just use language SQL as #ClodoaldoNeto, which returns a query natively using sql's SELECT stmt
2) If you need to process the result in the procedure, you must use SETOF and RETURN NEXT, ensuring you check IF FOUND THEN RETURN; (note lack of NEXT, which if given will act as a single blank row is returned)
Ideally, I'd like to not use SETOF for procedures known to return exactly none or 1 rows, but it seems SETOF is required to get a procedure to query like an sql statement from the app and have drivers recognize NO ROWS RETURNED
Examples below:
create table emptytable(id int);
create function selectany() returns setof emptytable as $$
DECLARE
_out emptytable;
BEGIN
SELECT * INTO _out FROM emptytable limit 1;
IF FOUND THEN
RETURN _out;
END IF;
RETURN;
END;
$$ LANGUAGE PLPGSQL;
create function selectany_rq() returns setof emptytable as $$
BEGIN
RETURN QUERY SELECT * INTO _out FROM emptytable limit 1;
END;
$$ LANGUAGE PLPGSQL;
As suggested in the comments do return setof emptytable
create function selectany()
returns setof emptytable as $$
select *
from emptytable
limit 1
;
$$ language sql;
Plain sql can do that