PostgreSQL聚合函数和缺少帧行

时间:2017-07-06 08:34:56

标签: postgresql aggregate-functions window-functions

我正在尝试定义一个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。

1 个答案:

答案 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)

如果可以从窗口的大小推断尺寸,那就太好了, 但我无法找到是否可以做到。