PostgreSQL & division_by_zero exception - postgresql

I can't quite get this right.. my function looks like:
create or replace function myfunc(integer, varchar(25), varchar(25), integer, integer) returns numeric as $$
declare var_return numeric;
begin
select sum(a+ b) / sum(a + b + c)
from mytable
where col1 = $1
and col2 = $2
and col3 = $3
and col4 between $4 AND $5
into var_return;
exception when division_by_zero then return 0.0;
return coalesce(var_return, 0.0);
end;
$$ language plpgsql;
But when I do select myfunc(123, 'foo', 'bar', 1, 10); I see:
ERROR: control reached end of function without RETURN
CONTEXT: PL/pgSQL function "myfunc"
Why does this happen? Clearly I want to catch when the select statement encounters a case where a + b + c equals 0 and return 0.

The EXCEPTION clause pertains to the entire BEGIN block, and runs until the END. That is, Postgres understands you to have written this:
create or replace function myfunc(...) returns ... as $$
declare var_return numeric;
begin
select ...
into var_return; -- but no RETURN statement in this block
exception when division_by_zero then
return 0.0;
return coalesce(var_return, 0.0); -- this is part of the exception handler
end;
$$ language plpgsql;
Move your "RETURN COALESCE..." above the EXCEPTION line, and try again.

You can vastly simplify your function:
CREATE OR REPLACE FUNCTION myfunc(int, varchar(25), varchar(25), int, int)
RETURNS numeric AS
$BODY$
SELECT CASE WHEN abc = 0 THEN 0::numeric ELSE (ab/abc)::numeric END
FROM (
SELECT sum(a + b) AS ab, sum(a + b + c) AS abc
FROM mytable
WHERE col1 = $1
AND col2 = $2
AND col3 = $3
AND col4 BETWEEN $4 AND $5
) x;
$BODY$ language SQL;
Major points
This is language SQL, not a PL/pgSQL function. You can use either, but for a simple case like this you might as well stick with the simpler tool.
#pilcrow diagnosed the cause of your error accurately. The way you have it, the RETURN statement is part of the exception handler and is only ever reached if an exception occurs.
If the data type of a, b, c should be integer or bigint, you probably want to cast to numeric before the division or the results will be coerced to integers.
Use a CASE statement to avoid division by zero. This is much cheaper than exception handling.

Are you sure that you handle the right exception ?
I guess that you have another exception.
See a list of exception you can trap.
I would try first ( I always put exceptions at the end of the block, easier to read.)
create or replace function myfunc(integer, varchar(25), varchar(25), integer, integer) returns numeric as $$
declare var_return numeric;
begin
select sum(a+ b) / sum(a + b + c)
from mytable
where col1 = $1
and col2 = $2
and col3 = $3
and col4 between $4 AND $5
into var_return;
return coalesce(var_return, 0.0);
exception
when division_by_zero then
return 0.0;
when others then
RAISE NOTICE 'caught others';
return 0.0;
end;
$$ language plpgsql;

Related

Raise exception from SQL-only stored function in postgresql [duplicate]

Is there an equivalent (or workaround) for the RAISE EXCEPTION statement for the function written below in LANGUAGE sql?
CREATE OR REPLACE FUNCTION fn_interpolation (p_yearinteger integer,
p_admin_id integer, p_crop_id integer, p_cropparameter integer)
RETURNS TABLE (value double precision, remark text)
AS $$
WITH
yearvalues AS (SELECT yearinteger, value FROM cropvalues WHERE crops_id =
p_crop_id AND admin_id = p_admin_id AND parameter_id = p_cropparameter),
I need the function to abort and to RETURN an error message if the arguments entered into the function do not exist. e.g. IF parameter_id != p_cropparameter THEN RAISE EXCEPTION ‘invalid cropparameter’ END IF
Just define a trivial wrapper function.
CREATE OR REPLACE FUNCTION raise_exception(text) RETURNS text AS $$
BEGIN
RAISE EXCEPTION '%',$1;
END;
$$ LANGUAGE plpgsql VOLATILE;
then use CASE:
SELECT CASE
WHEN parameter_id != p_cropparameter
THEN raise_exception("blah")
ELSE parameter_id
END;
This only works if the CASE otherwise returns text though, e.g. if parameter_id is integer you get:
regress=> SELECT CASE WHEN 1 = 2 THEN raise_exception('blah') ELSE 1 END;
ERROR: CASE types integer and text cannot be matched
LINE 1: SELECT CASE WHEN 1 = 2 THEN raise_exception('blah') ELSE 1 E...
You can work around this with a hack using polymorphic functions. Define:
CREATE OR REPLACE FUNCTION raise_exception(anyelement, text) RETURNS anyelement AS $$
BEGIN
RAISE EXCEPTION '%',$2;
RETURN $1;
END;
$$ LANGUAGE plpgsql VOLATILE;
then pass a fake value of the case type to it so PostgreSQL type-matches it correctly, e.g.
SELECT CASE WHEN 1 = 1 THEN raise_exception(0, 'blah') ELSE 1 END;
or
SELECT CASE WHEN 1 = 1 THEN raise_exception(NULL::integer, 'blah') ELSE 1 END;
All seem too hard? That's because really, this sort of thing is usually just better done in PL/PgSQL.
raise_exception.sql
Function to throwing an error for unhandled/impossible value.
Uses in SQL language.
Wrapper for RAISE command with EXCEPTION level in PL/pgSQL language.
Tests and using examples are included.

In clause in postgres

Need Output from table with in clause in PostgreSQL
I tried to make loop or ids passed from my code. I did same to update the rows dynamically, but for select I m not getting values from DB
CREATE OR REPLACE FUNCTION dashboard.rspgetpendingdispatchbyaccountgroupidandbranchid(
IN accountgroupIdCol numeric(8,0),
IN branchidcol character varying
)
RETURNS void
AS
$$
DECLARE
ArrayText text[];
i int;
BEGIN
select string_to_array(branchidcol, ',') into ArrayText;
i := 1;
loop
if i > array_upper(ArrayText, 1) then
exit;
else
SELECT
pd.branchid,pd.totallr,pd.totalarticle,pd.totalweight,
pd.totalamount
FROM dashboard.pendingdispatch AS pd
WHERE
pd.accountgroupid = accountgroupIdCol AND pd.branchid IN(ArrayText[i]::numeric);
i := i + 1;
end if;
END LOOP;
END;
$$ LANGUAGE 'plpgsql' VOLATILE;
There is no need for a loop (or PL/pgSQL actually)
You can use the array directly in the query, e.g.:
where pd.branchid = any (string_to_array(branchidcol, ','));
But your function does not return anything, so obviously you won't get a result.
If you want to return the result of that SELECT query, you need to define the function as returns table (...) and then use return query - or even better make it a SQL function:
CREATE OR REPLACE FUNCTION dashboard.rspgetpendingdispatchbyaccountgroupidandbranchid(
IN accountgroupIdCol numeric(8,0),
IN branchidcol character varying )
RETURNS table(branchid integer, totallr integer, totalarticle integer, totalweight numeric, totalamount integer)
AS
$$
SELECT pd.branchid,pd.totallr,pd.totalarticle,pd.totalweight, pd.totalamount
FROM dashboard.pendingdispatch AS pd
WHERE pd.accountgroupid = accountgroupIdCol
AND pd.branchid = any (string_to_array(branchidcol, ',')::numeric[]);
$$
LANGUAGE sql
VOLATILE;
Note that I guessed the data types for the columns of the query based on their names. You have to adjust the line with returns table (...) to match the data types of the select columns.

Check Results Count in Postgres Function Before Returning

I have a postgres function that I'd like to return the result of a query, but I'd like it to return nothing if that query matches more than 1 record.
So, something like:
CREATE OR REPLACE FUNCTION myFunc(_a text, _b text)
RETURNS yy
LANGUAGE plpgsql
STABLE
PARALLEL SAFE
AS $$
BEGIN
RETURN QUERY
SELECT *
FROM yy
WHERE a = x
AND b = y;
END;
$$;
Except, it should return nothing if that query matches more than 1 record.
CREATE OR REPLACE FUNCTION myFunc(_a text, _b text)
RETURNS SETOF yy -- To be able to return "nothing"
LANGUAGE plpgsql
STABLE
PARALLEL SAFE
AS $$
DECLARE
result yy;
BEGIN
SELECT *
INTO STRICT result -- STRICT allows to check that exactly one row returned
FROM yy
WHERE a = x
AND b = y;
RETURN NEXT result; -- RETURN NEXT - return yet another row for "RETURNS SETOF" function
EXCEPTION
WHEN no_data_found OR too_many_rows THEN -- When no data or more then one rows
RETURN; -- Nothing to return, just exit
END;
$$;
i guess this can help you out.
CREATE OR REPLACE FUNCTION database.myFunction(
IN text,IN text)
RETURNS TABLE(firstField, secondField, lastField) AS
$BODY$
--sql string is the variable containing the final sql code
declare sql_string text;
declare regs numeric;
begin
--this is what happens in case count<1
sql_string = 'select 0,0,0';
--now we count them
regs = (select count(firstField) from mytable where a=b)::numeric;
--if >=1, then whe get the whole data
if (regs>=1) then
sql_string = 'select firstField,secondField, lastField from mytable where a=b';
end if;
--and return to you...
return query EXECUTE sql_string;
end;

Equivalent or alternative method for RAISE EXCEPTION statement for function in LANGUAGE sql?

Is there an equivalent (or workaround) for the RAISE EXCEPTION statement for the function written below in LANGUAGE sql?
CREATE OR REPLACE FUNCTION fn_interpolation (p_yearinteger integer,
p_admin_id integer, p_crop_id integer, p_cropparameter integer)
RETURNS TABLE (value double precision, remark text)
AS $$
WITH
yearvalues AS (SELECT yearinteger, value FROM cropvalues WHERE crops_id =
p_crop_id AND admin_id = p_admin_id AND parameter_id = p_cropparameter),
I need the function to abort and to RETURN an error message if the arguments entered into the function do not exist. e.g. IF parameter_id != p_cropparameter THEN RAISE EXCEPTION ‘invalid cropparameter’ END IF
Just define a trivial wrapper function.
CREATE OR REPLACE FUNCTION raise_exception(text) RETURNS text AS $$
BEGIN
RAISE EXCEPTION '%',$1;
END;
$$ LANGUAGE plpgsql VOLATILE;
then use CASE:
SELECT CASE
WHEN parameter_id != p_cropparameter
THEN raise_exception("blah")
ELSE parameter_id
END;
This only works if the CASE otherwise returns text though, e.g. if parameter_id is integer you get:
regress=> SELECT CASE WHEN 1 = 2 THEN raise_exception('blah') ELSE 1 END;
ERROR: CASE types integer and text cannot be matched
LINE 1: SELECT CASE WHEN 1 = 2 THEN raise_exception('blah') ELSE 1 E...
You can work around this with a hack using polymorphic functions. Define:
CREATE OR REPLACE FUNCTION raise_exception(anyelement, text) RETURNS anyelement AS $$
BEGIN
RAISE EXCEPTION '%',$2;
RETURN $1;
END;
$$ LANGUAGE plpgsql VOLATILE;
then pass a fake value of the case type to it so PostgreSQL type-matches it correctly, e.g.
SELECT CASE WHEN 1 = 1 THEN raise_exception(0, 'blah') ELSE 1 END;
or
SELECT CASE WHEN 1 = 1 THEN raise_exception(NULL::integer, 'blah') ELSE 1 END;
All seem too hard? That's because really, this sort of thing is usually just better done in PL/PgSQL.
raise_exception.sql
Function to throwing an error for unhandled/impossible value.
Uses in SQL language.
Wrapper for RAISE command with EXCEPTION level in PL/pgSQL language.
Tests and using examples are included.

Can I make a plpgsql function return an integer without using a variable?

Something like this:
CREATE OR REPLACE FUNCTION get(param_id integer)
RETURNS integer AS
$BODY$
BEGIN
SELECT col1 FROM TABLE WHERE id = param_id;
END;
$BODY$
LANGUAGE plpgsql;
I would like to avoid a DECLARE just for this.
Yes you can. There are a number of ways.
1) RETURN (SELECT ...)
CREATE OR REPLACE FUNCTION get_1(_param_id integer)
RETURNS integer
LANGUAGE plpgsql AS
$func$
BEGIN
RETURN _param_id;
-- Or:
-- RETURN (SELECT col1 FROM tbl WHERE id = _param_id);
END
$func$;
2) Use an OUT or INOUT parameter
CREATE OR REPLACE FUNCTION get_2(_param_id integer, OUT _col1 integer)
-- RETURNS integer -- is optional noise in this case
LANGUAGE plpgsql AS
$func$
BEGIN
SELECT INTO _col1 col1 FROM tbl WHERE id = _param_id;
-- also valid, but discouraged:
-- _col1 := col1 FROM tbl WHERE id = _param_id;
END
$func$;
More in the manual here.
3) (Ab)use IN parameter
Since Postgres 9.0 you can also use input parameters as variables. The release notes for 9.0:
An input parameter now acts like a local variable initialized to the passed-in value.
CREATE OR REPLACE FUNCTION get_3(_param_id integer)
RETURNS integer
LANGUAGE plpgsql AS
$func$
BEGIN
SELECT INTO _param_id col1 FROM tbl WHERE id = _param_id;
RETURN _param_id;
-- Also vlaid, but discouraged:
-- $1 := col1 FROM tbl WHERE id = $1;
-- RETURN $1;
END
$func$;
Variants 2) and 3) do use a variable implicitly, but you don't have to DECLARE one explicitly (as requested).
4) Use a DEFAULT value with an INOUT parameter
This is a bit of a special case. The function body can be empty.
CREATE OR REPLACE FUNCTION get_4(_param_id integer, INOUT _col1 integer = 123)
RETURNS integer
LANGUAGE plpgsql AS
$func$
BEGIN
-- You can assign some (other) value to _col1:
-- SELECT INTO _col1 col1 FROM tbl WHERE id = _param_id;
-- If you don't, the DEFAULT 123 will be returned.
END
$func$;
INOUT _col1 integer = 123 is short notation for INOUT _col1 integer DEFAULT 123. See:
The forgotten assignment operator "=" and the commonplace ":="
5) Use a plain SQL function instead
CREATE OR REPLACE FUNCTION get_5(_param_id integer)
RETURNS integer
LANGUAGE sql AS
'SELECT col1 FROM tbl WHERE id = _param_id';
Or use use param reference $1 instead of param name.
Variant 5) one uses plain single quotes for the function body. All the same. See:
What are '$$' used for in PL/pgSQL
Insert text with single quotes in PostgreSQL
db<>fiddle here - demonstrating all (incl. call)