Reputation: 16397
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?
Upvotes: 2
Views: 631
Reputation: 16397
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/
Upvotes: 0
Reputation: 33
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;
Upvotes: 1
Reputation: 36244
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;
Upvotes: 4