PostgreSQL Math Function Error - sql

I'm getting an error from a deterministic PG function that seems dependent based on when I call it in a query.
The function is:
CREATE OR REPLACE FUNCTION ircm(starting_money numeric, value numeric, days numeric)
RETURNS numeric
AS $$
BEGIN
-- Calculate effective interest rate, compounded monthly.
RETURN 12*(pow((value/starting_money),(1./(12.*(days/365.)))) - 1);
END;
$$ LANGUAGE plpgsql;
If I call it in a simple SELECT statement, it works fine:
SELECT ircm(100.00,60.427500643787215,30)
Result: -4.79925436505569765596
However, when I run that exact same call from a different SELECT statement using a subquery:
SELECT
ircm(100.00,m.v::numeric,30) AS result
FROM(
SELECT 60.427500643787215 AS v
) m
I get the error:
ERROR: a negative number raised to a non-integer power yields a complex result
CONTEXT: PL/pgSQL function "ircm" line 6 at RETURN
Since the calls are logically equivalent, how can PG be returning an error for one call, but not the other? I've made sure there's only one definition of the function "ircm" in my database. I've tried dropping/adding it again to make sure PG isn't somehow caching a corrupted definition.

Which version and on which platform is your database running?
Have you tried rewriting the function in SQL:
CREATE OR REPLACE FUNCTION ircm(numeric, numeric, numeric)
RETURNS numeric AS $$
select 12 * (pow(($2 / $1), (1. / (12.* ($3 / 365.)))) - 1);
$$ LANGUAGE sql immutable;
I'm running v8.4.4 on Solaris, Linux, and Mac OS X and achieve the same results from your plpgsql (and my SQL) version called directly, and from within the subquery.

Related

PostgreSQL: Why calling function (NUMERIC -> NUMERIC) in SELECT is too expensive?

I have a query with a long select part with many cases when statement and I tried to put them to function for clarity, but the query start running longer than before.
So, for testing, I create one simple table
CREATE TABLE t1 AS
select random()::numeric from generate_series(1, 5000000, 1)
and one very simple function
create funtion f1 (i NUMERIC)
returns NUMERIC
LANGUAGE plpgsql
AS
$body$
begin
return i+1
end
$body$
and running two queries:
1: SELECT random, random+1 from T1
2: SELECT random, f1(random) from T1
and I don't know why the first is running two times faster than the second, so please help.
Sorry for my English :(
That is because the function f1 has to be called for every row, and calling a PL/pgSQL function is quite expensive.
If you define it as an SQL function, it could be inlined, which would be much cheaper:
FREATE FUNCTION f1(i NUMERIC) RETURNS numeric
LANGUAGE sql AS
'SELECT i + 1';

I'm trying to cross database with insert query in plpgsql, please help me

CREATE OR REPLACE FUNCTION "public"."cross_insert"("p_name" varchar, "p_detail" varchar)
RETURNS SETOF "pg_catalog"."varchar" AS $BODY$
BEGIN
SELECT * FROM public.dblink(
'
host=10.10.10.53
port=5432
user=sassuperuser
password=password10
dbname=blog2
',
'
SELECT * FROM public.funct_insert2(
'''||p_name||''',
'''||p_detail||'''
);
'
);
RETURN query
SELECT ('SUKSES')::character varying;
END$BODY$
LANGUAGE plpgsql VOLATILE
COST 100
ROWS 1000
Your code is little bit messy. Try start with reading documentation to PLpgSQL. There are three issues:
PL/pgSQL doesn't allow implicit throwing result of any query. You should to iterate over result (FOR IN SELECT) or you have to save result to variable (SELECT INTO). If you want to do it, then you should to use PERFORM command. So you query should to look like:
PERFORM public.dblink(' .....');
There is not any sense why your function is declared like RETURNS SETOF varchar. Then you have to use RETURN QUERY SELECT 'success'. It is absolutely useless. There is relatively high overhead of this functionality. This should be classic scalar function that returns text or better, that returns void type. The exception is raised on error. So you don't need to returns anything.
Second, don't do this - PostgreSQL has FDW interface. You can use foreign table instead. It will be faster and safer.

Function parameter anyelement, PostgreSQL bug?

I do not see the bug in this implementation:
CREATE FUNCTION foo(anyelement) RETURNS SETOF int AS $f$
SELECT id FROM unnest(array[1,2,3]) t(id)
WHERE CASE WHEN (pg_typeof($1)::text)='integer' THEN $1::int>2 ELSE true END
$f$ LANGUAGE SQL IMMUTABLE;
SELECT * FROM foo(123); -- OK!
SELECT * FROM foo('test'::text); -- BUG
Is this some kind of PostgreSQL bug or a non-documented restriction on anyelement datatype?
Interesting: when isolated the CASE clause works fine:
CREATE FUNCTION bar(anyelement) RETURNS boolean AS $f$
SELECT CASE WHEN (pg_typeof($1)::text)='integer' THEN $1::int>2;
$f$ LANGUAGE SQL IMMUTABLE;
SELECT bar('test'::text), bar(123), bar(1); -- works fine!
Your problem is due to how SQL statements are planned. SQL is very rigid about data types. Postgres functions provide some flexibility with the polymorphic pseudo-type ANYELEMENT, but the SQL statement is still planned with the given types statically.
While the expression $1::int>2 is never executed if $1 is not an integer (you could avoid division by zero this way), this cannot save you from the syntax error that arises at the earlier stage of planning the query.
You can still do something with the function you have. Use an untyped string literal:
CREATE OR REPLACE FUNCTION foo(anyelement)
RETURNS SETOF int AS
$func$
SELECT id FROM unnest(array[1,2,3]) id
WHERE CASE WHEN pg_typeof($1) = 'integer'::regtype
THEN $1 > '2' -- use a string literal!
ELSE true END
$func$ LANGUAGE sql IMMUTABLE;
This at least works for all character and numeric data types. The string literal is coerced to the provided data type. But it will still fail for other data types where '2' is not valid.
It's remarkable that your second example does not trigger the syntax error. It emerged from my tests on Postgres 9.5 that the syntax error is triggered if the function is not IMMUTABLE or for set-returning functions (RETURNS SETOF ... instead of RETURNS boolean) that are called in the FROM list: SELECT * FROM foo() instead of SELECT foo(). It would seem that query planning is handled differently for simple IMMUTABLE functions which can be inlined.
Aside, use:
pg_typeof($1) = 'integer'::regtype
instead of:
(pg_typeof($1)::text)='integer'
That's generally better. It's always better to cast the constant once instead of the computed value every time. And this works for known aliases of the type name as well.
PostgreSQL syntax error in parameterized query on "date $1"
It's definitely related to SQL planner/optimizer. Since the function is declared as IMMUTABLE, the optimizer tries to pre-evaluate the query parts. For some reason, it evaluates the expression $1::int>2 even if you call the function with text parameter.
If you change your foo function to VOLATILE it will work fine, because the query optimizer will not try to optimize/pre-evalute it.
But why bar function works fine even if it's IMMUTABLE? I guess the optimizer decides not to pre-evalute it as it does not use expressions in loops. What I mean is that $1::int>2 is evaluated only once, whereas in foo function it's evaluated multiple times.
Seems like there are some differences how SQL planner works for SQL and PLPGSQL language. The same function in PLPGSQL works fine.
CREATE FUNCTION foo2(anyelement) RETURNS SETOF int AS $f$
DECLARE
i INTEGER;
BEGIN
FOR i IN SELECT id FROM unnest(array[1,2,3]) t(id)
WHERE
CASE WHEN pg_typeof($1) = 'integer'::regtype
THEN $1::int > 2
ELSE true END
LOOP
RETURN NEXT i;
END LOOP;
END;
$f$ LANGUAGE plpgsql IMMUTABLE;
SELECT * FROM foo2('test'::text); -- works fine

PLPGSQL Function to Calculate Bearing

Basically I can't get my head around the syntax of plpgsql and would appreciate some help with the following efforts.
I have a table containing 1000's of wgs84 points. The following SQL will retrieve a set of points within a bounding box on this table:
SELECT id, ST_X(wgs_geom), ST_Y(wgs_geom), ST_Z(wgs_geom)
FROM points_table
INNER JOIN
(SELECT ST_Transform(ST_GeomFromText('POLYGON((-1.73576102027 1.5059743629,
-1.73591122397 51.5061067655,-1.73548743495 51.5062838333,-1.73533186682
1.5061514313,-1.73576102027 51.5059743629))', 4326, 27700)
) AS bgeom
) AS t2
ON ST_Within(local_geom, t2.bgeom)
What I need to do is add a bearing/azimuth column to the results that describes the bearing at each point in the returned data set.
So the approach I'm trying to implement is to build a plpgsql function that can select the data as per above and calculate the bearing between each set of points in a loop.
However my efforts at understanding basic data access and handling within a plpgsql function are failing miserably.
An example of the current version of the function I'm trying to create is as follows:
CREATE TYPE bearing_type AS (x numeric, y numeric, z numeric, bearing numeric);
--DROP FUNCTION IF EXISTS get_bearings_from_points();
CREATE OR REPLACE FUNCTION get_bearings_from_points()
RETURNS SETOF bearing_type AS
$BODY$
DECLARE
rowdata points_table%rowtype;
returndata bearing_type;
BEGIN
FOR rowdata IN
SELECT nav_id, wgs_geom
FROM points_table INNER JOIN
(SELECT ST_Transform(ST_GeomFromText('POLYGON((-1.73576102027
3.5059743629,-1.73591122397 53.5061067655,-1.73548743495
53.5062838333,-1.73533186682 53.5061514313,-1.73576102027
53.5059743629))', 4326), 27700)
AS bgeom)
AS t2 ON ST_Within(local_geom, t2.bgeom)
LOOP
returndata.x := ST_X(rowdata.wgs_geom);
returndata.y := ST_Y(rowdata.wgs_geom);
returndata.z := ST_Z(rowdata.wgs_geom);
returndata.bearing := ST_Azimuth(<current_point> , <next_point>)
RETURN NEXT returndata;
END LOOP;
RETURN;
END
$BODY$
LANGUAGE plpgsql;
I would like to just call this function as follows:
SELECT get_bearings_from_points();
and get the desired result.
Basically the problems are understanding how to access the rowdata properly such that I can read the current and next points.
In the above example I've had various problems from how to call the ST_X etc SQL functions and have tried EXECUTE select statements with errors re geometry data types.
Any insights/help would be much appreciated.
In PL/pgSQL it's most effective to do as much as is elegantly possible in basic SQL queries at once. You can largely simplify.
I didn't get a definition of the sort order out of your question and left ??? to fill in for you:
CREATE OR REPLACE FUNCTION get_bearings_from_points(_bgeom geometry)
RETURNS TABLE (x numeric, y numeric, z numeric, bearing numeric) AS
$func$
BEGIN
FOR x, y, z, bearing IN
SELECT ST_X(t.wgs_geom), ST_Y(t.wgs_geom), ST_Z(t.wgs_geom)
, ST_Azimuth(t.wgs_geom, lead(t.wgs_geom) OVER (ORDER BY ???))
FROM points_table t
WHERE ST_Within(t.local_geom, _bgeom)
ORDER BY ???
LOOP
RETURN NEXT;
END LOOP;
END
$func$ LANGUAGE plpgsql;
The window function lead() references a column from the next row according to sort order.
This can be simplified further to a single SQL query - possibly wrapped into an SQL function:
CREATE OR REPLACE FUNCTION get_bearings_from_points(_bgeom geometry)
RETURNS TABLE (x numeric, y numeric, z numeric, bearing numeric) AS
$func$
SELECT ST_X(t.wgs_geom), ST_Y(t.wgs_geom), ST_Z(t.wgs_geom)
, ST_Azimuth(t.wgs_geom, lead(t.wgs_geom) OVER (ORDER BY ???))
FROM points_table t
WHERE ST_Within(t.local_geom, $1) -- use numbers in pg 9.1 or older
ORDER BY ???
$func$ LANGUAGE sql;
Parameter names can be referenced in pg 9.2 or later. Per release notes of pg 9.2:
Allow SQL-language functions to reference parameters by name (Matthew
Draper)

SQL function "RETURNS TABLE" syntax error in PostgreSQL 8.1

In a user defined function how do you return a table formed from the projection of joining other tables?
Here is a simple example
CREATE FUNCTION something123(character varying(100)) RETURNS TABLE (a integer, b character varying(300)) AS
$$
SELECT b.year, p.materialUsed FROM bestBefore b join packaged p on b.id=p.id WHERE id=$1;
$$
LANGUAGE SQL
;
It always errors at TABLE. How would you get the contents of that select statement to return?
I'm using Postgres 8.1.21
Your code is fine, but your PostgreSQL version isn't. It doesn't support RETURNS TABLE, as per the PostgreSQL 8.1 documentation for CREATE FUNCTION.
On extremely old PostgreSQL versions like 8.1 you must declare RETURNS SETOF RECORD without using RETURNS TABLE, as it wasn't supported by that old version. RETURNS SETOF RECORD causes the function to return an anonymous record set. You must then specify the record structure at the call site, like this:
regress=# CREATE FUNCTION something123_legacy(character varying(100))
RETURNS SETOF RECORD AS
$$
SELECT 1, 'fred'::varchar(300);
$$
LANGUAGE SQL;
regress=# SELECT * FROM something123_legacy('blah') somethingresult(col1name integer, col2name character varying(300));
col1name | col2name
----------+----------
1 | fred
(1 row)
Alternately, you can CREATE TYPE to create a defined rowtype, or use an existing table type since every table has a rowtype of the same name. Your function can then return that rowtype.
regress=# CREATE TYPE something123type AS (col1name integer, col2name character varying(300));
CREATE TYPE
regress=# CREATE FUNCTION something123_legacy2(character varying(100))
RETURNS SETOF something123type AS
$$
SELECT 1, 'fred'::varchar(300);
$$
LANGUAGE SQL;
CREATE FUNCTION
regress=# SELECT * FROM something123_legacy2('blah');
col1name | col2name
----------+----------
1 | fred
(1 row)
You could also try using OUT parameters, but I seem to vaguely remember that they were only supported for PL/PgSQL (not SQL functions) at one point and I'm not sure they work in 8.1. Try it:
CREATE FUNCTION something123( IN character varying(100), OUT integer, OUT character varying(300) ) RETURNS setof record AS
$$
SELECT b.year, p.materialUsed FROM bestBefore b join packaged p on b.id=p.id WHERE id=$1;
$$
LANGUAGE SQL;
WARNING: Your PostgreSQL version is unsupported and has been for two years. It is not getting security or bug fixes. Eventually you're going to have to upgrade, and the longer you wait the harder it's going to get. Start planning your upgrade now. Read the release notes of every .0 version (8.2.0, 8.3.0, etc) between yours and the current version, paying particular attention to the upgrade notes and compatibility notes. Watch out for the removal of implicit casts to text, bytea_output change, and standard_conforming strings change. Read the upgrade section of the manual for the new version and take note of advice like using the new version's pg_dump.