问题
I'm trying to implement a custom aggregate function in Postgres which will average directions in degrees - i.e. I want to able to do:
SELECT circavg(direction) FROM sometable;
This can be done using the formula:
xbar = atan2(sum(sin(xi), sum(cos(xi)))
I think I need to define an sfunc which will take a direction, and add the sine and cosine of that into two accumulators. The final function then converts the two components back into a direction using atan2.
I can't work out how to define the sfunc so that the current state consists of two components e.g. (float, float). The documentation is a bit short on concrete examples, so any help is appreciated.
回答1:
You can make use of an ARRAY type internally. Argument type can still be any numeric type. Demonstrating with float (= double precision):
CREATE OR REPLACE FUNCTION f_circavg (float[], float)
RETURNS float[] LANGUAGE sql STRICT AS
'SELECT ARRAY[$1[1] + sin($2), $1[2] + cos($2), 1]';
CREATE OR REPLACE FUNCTION f_circavg_final (float[])
RETURNS float LANGUAGE sql AS
'SELECT CASE WHEN $1[3] > 0 THEN atan2($1[1], $1[2]) END';
CREATE AGGREGATE circavg (float) (
sfunc = f_circavg
, stype = float[]
, finalfunc = f_circavg_final
, initcond = '{0,0,0}'
);
The transition function f_circavg() is defined STRICT, so it ignores rows with NULL input. It also sets a third array element to identify sets with one or more input rows - else the CASE the final function returns NULL.
Temporary table for testing:
CREATE TEMP TABLE t (x float);
INSERT INTO t VALUES (2), (NULL), (3), (4), (5);
I threw in a NULL value to also test the STRICT magic. Call:
SELECT circavg(x) FROM t;
circavg
-------------------
-2.78318530717959
Cross check:
SELECT atan2(sum(sin(x)), sum(cos(x))) FROM t;
atan2
-------------------
-2.78318530717959
Returns the same. Seems to work. In test with a bigger table the last expression with regular aggregate functions was 4x faster than the custom aggregate.
Test for zero input rows / only NULL input:
SELECT circavg(x) FROM t WHERE false; -- no input rows
SELECT circavg(x) FROM t WHERE x IS NULL; -- only NULL input
Returns NULL in both cases.
回答2:
PostgreSQL provides you with a set of geometry types, POINT being the fundamental one.
Use this type as the input parameter for your function.
You can create your custom type as an alternative if you prefer.
来源:https://stackoverflow.com/questions/10225357/custom-postgresql-aggregate-for-circular-average