连续 ID 块上的 PostgresQL 窗口函数

Posted

技术标签:

【中文标题】连续 ID 块上的 PostgresQL 窗口函数【英文标题】:PostgresQL window function over blocks of continuous IDs 【发布时间】: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 函数也是一个可接受的解决方案。

感谢您的建议,

马蒂

【问题讨论】:

【参考方案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);

【讨论】:

以上是关于连续 ID 块上的 PostgresQL 窗口函数的主要内容,如果未能解决你的问题,请参考以下文章

在 PostgreSQL 中优化窗口函数以使用索引

在窗口函数 postgresql 中选择条件

如何连续更新一堆表格单元格上的滑块?

截断数据块上的表

使用窗口函数确定 PostgreSQL 中的 30 天运行计数

在 django ORM 中使用 postgresql 窗口函数的干净方法?