I have a table in PostgreSQL (mixbest) with 73018 rows. The fields that I want to select are:
sample integer
m integer,
pciv double precision,
aggrec double precision
soil character(1)
I'm trying a SELECT but I get the following error SQLstate: 22003 numeric overflow. My select:
SELECT sample, m, 1-(EXP(SUM(LN(1-pciv)))) pciv, avg (aggrec) aggrec_avg, soil
FROM mixbest
GROUP BY sample, m, soil;
I know the problem is the EXP() due to I've tried the same select with the expression (SUM(LN(1-pciv)))) and I don't get the same error.
I tried to execute the select only in a few lines, and it works:
SELECT sample, m, 1-(EXP(SUM(LN(1-pciv)))) pciv, avg (aggrec) aggrec_avg, soil
FROM mixbest
WHERE sample< 4492 GROUP BY sample, m, soil;
Do you have any suggestion?
Something like this, I guess:
create or replace function mixbest_avg(out sample int, out m int, out pciv double precision, out aggrec_avg double precision, out soil character(1))
returns setof record as
$$
declare
rec record;
begin
for rec in
SELECT t.sample _sample, t.m _m, SUM(LN(1-t.pciv)) _pciv, avg(t.aggrec) _aggrec, t.soil _soil
FROM mixbest t
GROUP BY t.sample, t.m, t.soil
loop
begin
rec._pciv = 1 - exp(rec._pciv);
exception
when numeric_value_out_of_range then -- here we catch an exception
rec._pciv = 0; -- or other default value
end;
select rec._sample, rec._m, rec._pciv, rec._aggrec, rec._soil into sample, m, pciv, aggrec_avg, soil;
return next;
end loop;
end
$$ language plpgsql;
Related
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 try take all records from two columns with different tables and Divide Between each other but i have only result from first row not from all rows How i can fix that?
CREATE FUNCTION human() RETURNS integer AS $$
DECLARE person integer;
size integer;
def integer;
Begin
Select sizex into size from region;
Select defx into def from humans;
osoba = def / size;
return person;
END;
$$ LANGUAGE 'plpgsql';
select human();
Assuming your "humans" table and "region" table both have an ID field. And also assuming your humans table has a regionId field creating a relationship between your two tables, I would suggest doing the following:
CREATE FUNCTION human(arg_humanId int) RETURNS decimal AS $$
DECLARE
div_result decimal;
Begin
SELECT h.defx/r.sizex
INTO result
FROM humans h
JOIN region r on r.ID = h.RegionID
WHERE h.ID = arg_humanId;
RETURN div_result;
END;
$$ LANGUAGE 'plpgsql';
SELECT human(h.ID)
FROM humans h;
Note I've changed the data type from integer to decimal since division usually results in decimal places.
Another option is to return a setof decimals and do all of the logic inside your function:
CREATE FUNCTION human() RETURNS setof decimal AS $$
DECLARE
div_result decimal;
Begin
RETURN QUERY
SELECT h.defx/r.sizex
FROM humans h
JOIN region r on r.ID = h.RegionID;
END;
$$ LANGUAGE 'plpgsql';
SELECT *
FROM human();
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();
I've been asked how to do a standard deviation on a variable list of values within a row. For example:
select
name, x, y, z, stddev (x, y, z)
from foo;
or
select
order_no, a, b, c, d, e, f, stddev (a, b, c, d, e, f)
from foo;
So essentially just like min => least and max => greatest, I'd like a similar way to turn the aggregate stddev into a "normal" function.
I have been able to create a custom function to calculate standard deviation based on the standard formula, but I can't help but prefer to use the built-in function, if possible. I tried this:
CREATE OR REPLACE FUNCTION std_deviation(variadic inputs numeric[])
RETURNS numeric AS
$BODY$
DECLARE
result numeric;
BEGIN
select stddev (unnest (inputs))
into result;
return result;
end
$BODY$
LANGUAGE plpgsql VOLATILE
COST 100;
And it complains:
ERROR: set-valued function called in context that cannot accept a set
There is no shortage of traffic on this error message, but I can't quite figure out how to apply the fix to my simple function.
Or, is there a better way to do this from the beginning?
Set-returning functions (SRF) -- such as unnest -- in the SELECT clause is a PostgreSQL specific extension of the SQL standard. And usually doesn't worth to use it (because it's not what it looks like). Also, SRFs cannot be used within aggregate functions.
Use these SRF functions in the FROM clause instead (and use sub-selects where needed):
SELECT name, x, y, z, (SELECT stddev(v) FROM unnest(ARRAY[x, y, z]) v)
FROM foo
If you really want to write a function for that, use the SQL language (it's more clear & PostgreSQL can optimize their use):
CREATE OR REPLACE FUNCTION std_deviation(variadic inputs numeric[])
RETURNS numeric AS
$BODY$
SELECT stddev(v) FROM unnest(inputs) v
$BODY$
LANGUAGE SQL IMMUTABLE;
This seems to do the trick.
CREATE OR REPLACE FUNCTION public.std_deviation(VARIADIC inputs numeric[])
RETURNS numeric AS
$BODY$
DECLARE
result numeric;
BEGIN
with foo as (
select unnest (inputs) as bar
)
select stddev (bar)
into result
from foo;
return result;
end
$BODY$
LANGUAGE plpgsql VOLATILE
COST 100;
It turns out pgnumerics already has a function for this.
-- test=# select pgnumerics.stdev('{1345,1301,1368,1322,1310,1370,1318,1350,1303,1299}');
-- stdev
-- ------------------
-- 27.4639157198435
-- (1 row)
CREATE OR REPLACE FUNCTION pgnumerics.stdev (
X double precision []
) RETURNS double precision
AS $$
DECLARE
s double precision;
N integer;
i integer;
xx double precision;
sx double precision;
BEGIN
N := array_upper(X,1) - array_lower(X,1) + 1;
xx:= 0.0;
sx:= 0.0;
for i in 1..N loop
xx:= xx + X[i]*X[i];
sx:= sx + X[i];
end loop;
s := sqrt((N*xx - sx*sx) / (N*(N-1.0)));
return s;
END;
$$ LANGUAGE 'plpgsql';
http://pgnumerics.projects.pgfoundry.org/
I have to convert from lat and long to geom to use PostGIS. My problem, I have various tables from different locations and I want to pass the table as a parameter to the function. I'm trying this:
CREATE or REPLACE FUNCTION convert_from_lon_lat(float,float,character varying)
RETURNS integer AS $$
select id from $3 as vertices
order by vertices.geom <-> ST_SetSrid(ST_MakePoint($1,$2),4326) LIMIT 1;
$$ LANGUAGE SQL;
but I get a syntax error.
EDIT1:
So I changed the previous code to this:
CREATE or REPLACE FUNCTION convert_from_lon_lat(long float, lat float, _table character varying) RETURNS integer AS $$
BEGIN
EXECUTE('select id from _table as vertices order by vertices.geom <-> ST_SetSrid(ST_MakePoint(long,lat),4326) LIMIT 1;');
END;
$$ LANGUAGE plpgsql;
it creates without any problem, but when I call it `convert_from_lon_lat(long1, long2, my_table)
I get and error:
ERROR: relation "_table" does not exist
It's not passing the table name as an argument
EDIT 2:
CREATE or REPLACE FUNCTION convert_from_lon_lat(long float, lat float, tbl character varying) RETURNS integer AS $func$
BEGIN
EXECUTE format('select id from %s order by %s.the_geom <-> ST_SetSrid(ST_MakePoint('|| long || ','|| lat ||'),4326) LIMIT 1;', tbl, tbl);
END;
$func$ LANGUAGE plpgsql;
Now when I call the function, I get an `ERROR: control reached end of function without RETURN``
I tried RETURN QUERY EXECUTE format('... but I get a ERROR: cannot use RETURN QUERY in a non-SETOF function
AS #dezso mentioned, you'll need dynamic SQL in this case.
Dynamic SQL with EXECUTE
So, you're on the right track; forming a dynamic SQL statement using PL/pgSQL, but you just need the finishing touches:
CREATE or REPLACE FUNCTION convert_from_lon_lat(long float, lat float, _table text)
RETURNS integer AS $$
BEGIN
RETURN QUERY EXECUTE format('SELECT id FROM %I AS vertices
ORDER BY vertices.geom <->ST_SetSrid(ST_MakePoint(long,lat),4326) LIMIT 1;',_table);
END
$$ LANGUAGE plpgsql;
I believe this should solve your issues.
Note: We've discovered an error with the above solution and using SETOF, I've attempted to correct the issues below.
EDIT:
A few edits here, hopefully one solution will fix your issue. Also, please excuse any syntax errors in my previous & current solutions; I don't have time to test them right now. :(
1) You could just try returning a SETOF integers, knowing that of course you'll only return the one. Your return type in this case will then be a single, one-column row containing an integer.
CREATE or REPLACE FUNCTION convert_from_lon_lat(long float, lat float, _table text)
RETURNS SETOF integer AS $$
BEGIN
RETURN QUERY EXECUTE format('SELECT id FROM %I AS vertices
ORDER BY vertices.geom <->ST_SetSrid(ST_MakePoint(long,lat),4326) LIMIT 1;',_table);
END
$$ LANGUAGE plpgsql;
and then call as:
SELECT * FROM convert_from_lon_lat(...);
2) To specifically return an integer, I think you can try this:
CREATE or REPLACE FUNCTION convert_from_lon_lat(long float, lat float, _table text)
RETURNS integer AS $$
DECLARE
return_id integer;
BEGIN
EXECUTE format('SELECT id FROM %I AS vertices
ORDER BY vertices.geom <->ST_SetSrid(ST_MakePoint(long,lat),4326) LIMIT 1;',_table)
INTO return_id;
RETURN return_id;
END
$$ LANGUAGE plpgsql;