【问题标题】:PostgreSQL aggregate function and missing frame rowsPostgreSQL 聚合函数和缺失的帧行
【发布时间】:2017-12-10 03:43:25
【问题描述】:

我正在尝试定义一个 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实际上只在缺少两个值时被调用了三次。

我想不出任何非可笑的方法来捕捉那些缺失的行。似乎应该有一个简单的解决方案,例如在聚合运行之前以某种方式将一些前哨空值添加/附加到数据中,或者以某种方式将索引(和帧值)以及实际值传递给函数......

我想将它作为一个聚合来实现,因为它为我想做的事情提供了最好的面向用户的体验。有没有更好的办法?

FWIW,我在 postgres 9.6 上。

【问题讨论】:

    标签: postgresql aggregate-functions window-functions


    【解决方案1】:

    好的,这很有趣。 :)

    我创建了一个聚合 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)
    

    如果大小可以从窗口的大小推断出来,那就太好了, 但我找不到它是否可以完成。

    【讨论】:

    • 我真的很喜欢这个,但是当size_大于输入量时它不起作用。考虑select n, v, framer(v, 3) over (order by v rows between 1 preceding and 1 following) arr from (values (1, 32), (2, 24)) as v (n, v) order by v; 应该返回{null, 24, 32}, {24, 32, null},而是返回{null, 24, 32}, {null, 24, 32}。 Postgres 两次调用framer_msfunc 来构建第一个结果,并且再也不会重复使用第二个结果的答案。因为 Postgres 愿意缓存结果,所以我想知道它是否会出现其他任何异常情况。
    • 呃...找不到任何可以使用窗口聚合跳过缓存的内容...不过似乎用 C 编写的自定义窗口函数可能是解决方案。
    • 点赞!如果您不介意在移动聚合函数中澄清什么是 postges 缓存?
    猜你喜欢
    • 2021-02-06
    • 2015-08-03
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-11-29
    • 1970-01-01
    相关资源
    最近更新 更多