【问题标题】:PostgresQL window function over blocks of continuous IDs连续 ID 块上的 PostgresQL 窗口函数
【发布时间】:2021-04-14 11:43:53
【问题描述】:

我有一个带有部分连续整数 id 的表,即有诸如 1,2,3, 6,7,8, 10, 23,24,25,26 之类的块。

  • 间隙大小是动态的
  • 块的长度是动态的

我对从表格中选择的简单解决方案感到头疼 并包含一列,其中值对应于相应块的第一个 id。

即像这样的

select id, first(id) over <what goes here?> first from table;

结果应该如下所示

| id | first |
|----|-------|
| 1  | 1     |
| 2  | 1     |
| 3  | 1     |
| 6  | 6     |
| 7  | 6     |
| 8  | 6     |
| 10 | 10    |
| 23 | 23    |
| 24 | 23    |
| 25 | 23    |
| 26 | 23    |

之后我可以将此列与partition by 窗口函数子句很好地结合使用。

到目前为止,我想出的总是与此类似,但没有成功:

WITH foo AS (
    SELECT LAG(id) OVER (ORDER BY id)  AS previous_id,
           id                          AS id,
           id - LAG(id, 1, id) OVER (ORDER BY id) AS first_in_sequence
    FROM table)
SELECT *,
       FIRST_VALUE(id) OVER (ORDER BY id) AS first
FROM foo
ORDER BY id;

定义一个自定义的 postgres 函数也是一个可接受的解决方案。

感谢您的建议,

马蒂

【问题讨论】:

    标签: postgresql aggregate-functions window-functions


    【解决方案1】:

    在 Postgres 中你可以create a custom aggregate. 示例:

    create or replace function first_in_series_func(int[], int)
    returns int[] language sql immutable
    as $$ 
        select case 
            when $1[2] is distinct from $2- 1 then array[$2, $2]
            else array[$1[1], $2] end; 
    $$;
    
    create or replace function first_in_series_final(int[])
    returns int language sql immutable
    as $$
        select $1[1]
    $$;
    
    create aggregate first_in_series(int) (
        sfunc = first_in_series_func,
        finalfunc = first_in_series_final,
        stype = int[]
    );
    

    Db<>fiddle.

    阅读文档:User-Defined Aggregates

    【讨论】:

    • 太棒了。我接受这一点,因为我觉得它是解决它的更具声明性的方式。它可能也是一个更容易转移到类似问题的解决方案,并指出我相应的文档:)
    • 创建聚合函数的能力是 Postgres 的一大亮点。我很乐意提供自定义聚合的示例,因为人们认为这很困难,但实际上比乍一看容易。
    【解决方案2】:

    这是一个如何做到这一点的想法。不过,隐式游标的效率并不高。

    create or replace function ff()
    returns table (r_id integer, r_first integer)
    language plpgsql as
    $$
    declare
      running_previous integer;
      running_id integer;
      running_first integer := null;
    begin 
        for running_id in select id from _table order by id loop
            if running_previous is distinct from running_id - 1 then
                running_first := running_id;
            end if;
            r_id := running_id;
            r_first := running_first;
            running_previous := running_id;
            return next;
        end loop;
    end
    $$;
    -- test
    select * from ff() as t(id, first);
    

    【讨论】:

      猜你喜欢
      • 2014-09-24
      • 2022-11-25
      • 2018-12-15
      • 2016-09-02
      • 2018-12-08
      • 2016-05-19
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多