As part of an intro to databases class, we've been told to create a function using PL/pgSQL that gives out a factorial of a number. Now since x! increases rapidly as a function, we've decided to return a bigint due to it being a large range integer.
CREATE OR REPLACE FUNCTION getBigFactorial(fn bigint) RETURNS bigint as $$
DECLARE
product bigint;
minus1 int;
BEGIN
if (fn > 21) then
RAISE EXCEPTION 'Error: computing factorial; numeric value out of range';
return NULL;
elsif (fn < 1) then
RAISE EXCEPTION 'Error: Argument less than 1!';
return NULL;
end if;
minus1 := fn - 1;
if (minus1 > 0) then
product := fn * getBigFactorial(minus1);
return product;
end if;
return fn;
END;
$$ language plpgsql;
For this function, we've been told to create some sort of validation for the number entered.
The one for (fn < 1) worked flawlessly, however, the one which is (fn > 21) is giving some problems.
When the following is entered:
SELECT getBigFactorial(21);
We get:
ERROR: bigint out of range
CONTEXT: PL/pgSQL function getbigfactorial(integer) line 17 at assignment
SQL state: 22003
rather than getting the desired output. Line 17 corresponds to this line:
if (minus1 > 0) then
When we entered 22 instead of 21, we had the desired error output.
Any help in finding out what causes this?
TIA
Your conditional check should be inclusive of 21:
if (fn >= 21) then
-- ...
since for an input of 21, the factorial of 21 (fn * getBigFactorial(minus1), where fn = 21, and minus1 = fn - 1), which is an integer out of range for a bigint, is assigned to product, a bigint.
Related
This question already has an answer here:
pgSQL show an error in its function before the SELECT query
(1 answer)
Closed 1 year ago.
When I work with Microsoft SQL Server databases, I sometimes return multiple result sets from stored procedures. I often return so many that it becomes hard to follow which is which. To solve this problem, I follow a convention I learned from a colleague: The 1st result set is a "map", which defines names for the 2nd and other result sets. It has a single record, where every field name is the name of a result set and the corresponding field value is its index in the returned array of result sets. Client code accesses specific result sets by finding out the index by name first.
The following simple example shows the idea:
create or alter procedure divide
#a int,
#b int
as
begin
declare
#error int = 0
-- Name-to-index map
select
1 as result,
2 as error
-- Result
if #b = 0
begin
set #error = 1
select
null as result
end
else
begin
select
#a / #b as result
end
-- Error
select
#error as error
end
In this example, the first result set (index: 0) gives that there are 2 more result sets: one called "result" (index: 1) and another called "error" (index: 2). Both contain only one record: the result of the division and the error code, respectively.
Example call #1:
exec divide #a = 91, #b = 13
Result sets in JSON format:
[
[{ result: 1, error: 2 }],
[{ result: 7 }],
[{ error: 0 }]
]
Example call #2:
exec divide #a = 91, #b = 0
Result sets in JSON format:
[
[{ result: 1, error: 2 }],
[{ result: null }],
[{ error: 1 }]
]
I tried to port this technique to PostgreSQL 14 using the official documentation and especially this page. I came up with this:
create or replace function divide(
a integer,
b integer
)
returns setof refcursor
language sql as
$$
declare
ref1 refcursor;
ref2 refcursor;
ref3 refcursor;
error int := 0;
begin
-- Name-to-index map
open ref1 for select
1 as result,
2 as error;
return next ref1;
-- Result
if b = 0 then
error := 1;
open ref2 for select
null as result;
else
open ref2 for select
a / b as result;
end if;
return next ref2;
-- Error
open ref3 for select
error;
return next ref3;
end;
$$;
Unfortunately, I get an error: syntax error at or near "refcursor", referring to the refcursor in the 1st line after declare.
You used the wrong language declaration. Your procedure is in plpgsql but you declared it as plain sql through language sql statement at the top.
Replacing
create or replace function divide(
a integer,
b integer
)
returns setof refcursor
language sql as
with
create or replace function divide(
a integer,
b integer
)
returns setof refcursor
language plpgsql as
Solves the problem.
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.
Relatively new to Postgres, and been having trouble subtracting a value from a NUMERIC(4,2) value type in an update statement. The following code:
UPDATE Tickets
SET ticketPrice = ticketPrice-3
FROM Showings
WHERE Showings.priceCode = modTicket
Elicits the following error:
ERROR: numeric field overflow
Detail: A field with precision 4, scale 2 must round to an absolute value less than 10^2.
ticketPrice has the value type of NUMERIC(4,2). How do I make this subtraction? There are no possible values that this subtraction would cause to extend past two decimal points, or in the negatives at all. The only values that this subtraction applies to are 5.00, 3.50, and 8.00.
You could try to find out the error source like this:
do $$
declare r record;
foo numeric(4,2);
begin
for r in (select t.* from Tickets as t, Showings as s where s.priceCode = t.modTicket) loop
foo := r.ticketPrice - 3;
end loop;
exception
when others then raise notice '%', r;
raise;
end $$;
Example:
do $$
declare r record;
foo numeric(1);
begin
for r in (with t(x,y) as (values(1,2),(3,4)) select * from t) loop
foo := r.y + 7;
end loop;
exception
when others then raise notice '%', r;
raise;
end $$;
Output:
NOTICE: (3,4)
ERROR: numeric field overflow
DETAIL: A field with precision 1, scale 0 must round to an absolute value less than 10^1.
CONTEXT: PL/pgSQL function inline_code_block line 1 at assignment
UPDATE Tickets as t
SET ticketPrice = ticketPrice-3
FROM Showings as s
WHERE s.priceCode = t.modTicket
Documentation of UPDATE Query
I have problems with this function and couldn't figure out how to fix it.
Create Function Quy(sdate timestamp)
returns integer as $$
declare
numbmonth integer;
quy integer;
Begin
numbmonth := Date_part('month',sdate);
If numbmonth < 4 then
quy := 1;
else if numbmonth < 7 then
quy := 2;
else if numbmonth < 10 then
quy := 3;
else quy := 4;
return quy;
END;
$$
LANGUAGE plpgsql;
This happens when I try to run the code:
ERROR: syntax error at or near ";"
LINE 16: END;
I really don't understand what is wrong with this.
Multiple syntax errors. The function would work like this:
CREATE OR REPLACE FUNCTION quy(sdate timestamp)
RETURNS integer AS
$func$
DECLARE
numbmonth integer := date_part('month', sdate);
quy integer;
BEGIN
IF numbmonth < 4 THEN
quy := 1;
ELSIF numbmonth < 7 THEN
quy := 2;
ELSIF numbmonth < 10 THEN
quy := 3;
ELSE
quy := 4;
END IF;
RETURN quy;
END
$func$ LANGUAGE plpgsql;
Consult the manual for the basic syntax of IF.
But that's much ado about nothing. To get the quarter of the year use the field specifier QUARTER with date_part() or EXTRACT() in a simple expression:
EXTRACT(QUARTER FROM $timestamp)
EXTRACT is the standard SQL equivalent of date_part().
Either returns double precision, so cast to integer if you need that (::int).
If you still need a function:
CREATE OR REPLACE FUNCTION quy(sdate timestamp)
RETURNS int LANGUAGE sql IMMUTABLE AS
'SELECT EXTRACT(QUARTER FROM $1)::int';
$1 is the reference to the 1st function parameter. Equivalent to sdate in the example. $-notation works in any version of Postgres, while named parameter references in SQL functions were only introduced with Postgres 9.2. See:
PLPGSQL Function to Calculate Bearing
dbfiddle here
Here is the code
CREATE OR REPLACE FUNCTION primes (IN integer) RETURNS TEXT AS $$
DECLARE
counter INTEGER = $1;
primes int [];
mycount int;
BEGIN
WHILE counter != 0 LOOP
mycount := count(primes);
array_append(primes [counter], mycount);
counter := counter - 1;
END LOOP;
RETURN array_to_text(primes[], ',');
END;
$$
LANGUAGE 'plpgsql'
This is me developing the beginnings of a prime generating function. I am trying to simply get it to return the 'count' of the array. So if I pass '7' into the function I should get back [0, 1, 2, 3, 4, 5, 6].
But when I try to create this function I get
SQL Error: ERROR: syntax error at or near "array_append" LINE 1: array_append( $1 [ $2 ], $3 )
^ QUERY: array_append( $1 [ $2 ], $3 ) CONTEXT: SQL statement in PL/PgSQL function "primes" near line 8
I am a newbie with postgres functions. I am not understanding why I cannnot get this array to work properly.
Again all I want is to just fill this array up correctly with the 'current' count of the array. (It's more to just help me understand that it is in fact doing the loop correctly and is counting it correctly).
Thank you for your help.
From the fine manual:
Function: array_append(anyarray, anyelement)
Return Type: anyarray
Description: append an element to the end of an array
So array_append returns an array and you need to assign that return value to something. Also, I think you want array_to_string at the end of your function, not array_to_text. And primes is an array so you want array_append(primes, mycount) rather than trying to append to an entry in primes.
CREATE OR REPLACE FUNCTION primes (IN integer) RETURNS TEXT AS $$
DECLARE
counter INTEGER = $1;
primes int [];
mycount int;
BEGIN
WHILE counter != 0 LOOP
mycount := count(primes);
primes := array_append(primes, mycount);
counter := counter - 1;
END LOOP;
RETURN array_to_string(primes, ',');
END;
$$ LANGUAGE 'plpgsql';
I don't know what you intend mycount := count(primes); to do, perhaps you meant to say mycount := array_length(primes, 1); so that you would get a sequence of consecutive integers in primes.