我正在尝试定义一个PostgreSQL聚合函数,该函数知道在frame子句中要求但是缺少的行。具体来说,让我们考虑一个聚合函数framer
,其作用是返回一个数组,该数组由通过它聚合的值组成,并且帧中的任何缺失值都返回为null
。所以,
select
n,
v,
framer(v) over (order by v rows between 2 preceding and 2 following) arr
from (values (1, 3200), (2, 2400), (3, 1600), (4, 2900), (5, 8200)) as v (n, v)
order by v
应该返回
"n" "v" "arr"
3 1600 {null,null,1600,2400,2900}
2 2400 {null,1600,2400,2900,3200}
4 2900 {1600,2400,2900,3200,8200}
1 3200 {2400,2900,3200,8200,null}
5 8200 {2900,3200,8200,null,null}
基本上我想在每个值周围获取一系列值,对我来说,知道我是否在左侧或右侧(或可能两者)都缺少任何值,这一点很重要。看起来很简单。我期待这样的事情发挥作用:
create aggregate framer(anyelement) (
sfunc = array_append,
stype = anyarray,
initcond = '{}'
);
但它返回
"n" "v" "arr"
3 1600 {1600,2400,2900}
2 2400 {1600,2400,2900,3200}
4 2900 {1600,2400,2900,3200,8200}
1 3200 {2400,2900,3200,8200}
5 8200 {2900,3200,8200}
所以sfunc
实际上只有在缺少两个值时才被调用三次。
我无法想到任何非荒谬的方法来捕获那些丢失的行。似乎应该有一个简单的解决方案,比如在聚合运行之前以某种方式在数据之前添加/附加一些sentinel空值,或者可能以某种方式传递索引(和帧值)以及函数的实际值...
我希望将其作为聚合实现,因为它为我想要做的事情提供了最好的面向用户体验。还有更好的办法吗?
FWIW,我在postgres 9.6。
答案 0 :(得分:3)
好的,这是一个有趣的。 :)
我创建了一个聚合framer(anyarray, anyelement, int)
,因此我们可以定义它
数据大小根据窗口大小。
首先,我们将array_append
替换为我们自己的framer_msfunc
:
CREATE OR REPLACE FUNCTION public.framer_msfunc(arr anyarray, val anyelement, size_ integer)
RETURNS anyarray
LANGUAGE plpgsql
AS $function$
DECLARE
result ALIAS FOR $0;
null_ val%TYPE := NULL; -- NULL of the same type as `val`
BEGIN
IF COALESCE(array_length(arr, 1), 0) = 0 THEN
-- create an array of nulls with the size of `size_`
result := array_fill(null_, ARRAY[size_]);
ELSE
result := arr;
END IF;
IF result[size_] IS NULL THEN
-- first run or after `minvfunc`.
-- a NULL inserted at the end in `minvfunc` so we want to replace that.
result[size_] := val;
ELSE
-- `minvfunc` not yet called so we just append and drop the first.
result := (array_append(result, val))[2:];
END IF;
RETURN result;
END;
$function$
然后我们创建一个minvfunc
,因为它需要移动聚合。
CREATE OR REPLACE FUNCTION public.framer_minvfunc(arr anyarray, val anyelement, size_ integer)
RETURNS anyarray
LANGUAGE plpgsql
AS $function$
BEGIN
-- drop the first in the array and append a null
RETURN array_append(arr[2:], NULL);
END;
$function$
然后我们用移动的聚合参数定义聚合:
create aggregate framer(anyelement, int) (
sfunc = framer_msfunc,
stype = anyarray,
msfunc = framer_msfunc,
mstype = anyarray,
minvfunc = framer_minvfunc,
minitcond = '{}'
);
我们也将framer_msfunc
设为sfunc
,因为sfunc
是必需的,
但它并没有真正起作用。它可以用功能代替
相同的参数,但实际上只是在里面调用array_append
,所以它实际上会做一些有用的事情。
这是你的例子,但有更多的输入值。
帧大小应至少为窗口大小。它不适用于较小的尺寸。
select
n,
v,
framer(v, 5) over (order by v rows between 2 preceding and 2 following) arr
from (values (1, 3200), (2, 2400), (3, 1600), (4, 2900), (5, 8200), (6, 2333), (7, 1500)) as v (n, v)
order by v
;
n | v | arr
---+------+----------------------------
7 | 1500 | {NULL,NULL,1500,1600,2333}
3 | 1600 | {NULL,1500,1600,2333,2400}
6 | 2333 | {1500,1600,2333,2400,2900}
2 | 2400 | {1600,2333,2400,2900,3200}
4 | 2900 | {2333,2400,2900,3200,8200}
1 | 3200 | {2400,2900,3200,8200,NULL}
5 | 8200 | {2900,3200,8200,NULL,NULL}
(7 rows)
如果可以从窗口的大小推断尺寸,那就太好了, 但我无法找到是否可以做到。