Postgresql顺序add/remove缩减操作

Postgresql sequential add/remove reduction operation

我有一个带有行号的 table 和标识符的“定义”或“未定义”事件。示例:

line_no | def | undef
--------------------
1       | 'a' | NULL
2       | 'b' | NULL
3       | NULL| 'a'
... 
42      | NULL| 'b'

我想计算每一行的“实时变量”信息。迭代地,我只编写如下伪代码的代码:

live = [] 
for each r in table:
    if r.def:
        live.append(r.def)
    else:
        live.remove(r.undef)
    store(r.line_no, live) 

预期结果是 table,如:

line_no | live
1.      | ['a']
2.      | ['a', 'b'] 
3.      | ['b'] 
... 
42.     | [] 

我设法将等效的顺序循环编写为 plpgsql 函数。但是,我想知道是否有一种(可能更优雅的)方式作为 SQL 查询?我使用 lag() 尝试了各种方法,但不知何故从未导致我正在寻找的这种“减少”操作?

(如果查询还可以对字段进行分区,则加分,例如'filename')

如果您想要一个纯粹的 SQL 解决方案,请使用递归查询。

with recursive cte as (
    select line_no, array[def] as list
    from my_table
    where line_no = 1
union all
    select 
        t.line_no, 
        case when def is null 
            then array_remove(list, undef)
            else array_append(list, def)
        end
    from my_table t
    join cte c on c.line_no = t.line_no- 1
)
select *
from cte;

但是,在这种情况下,更有效、更灵活的解决方案可能是创建一个函数。

create or replace function list_of_vars()
returns table (line_no int, list text[])
language plpgsql as $$
declare
    arr text[];
    rec record;
begin
    for rec in
        select *
        from my_table
        order by line_no
    loop
        if rec.def is null then
            arr := array_remove(arr, rec.undef);
        else
            arr := array_append(arr, rec.def);
        end if;
        line_no := rec.line_no;
        list := arr;
        return next;
    end loop;
end
$$;

使用:

select *
from list_of_vars()
order by line_no

db<>fiddle.

中测试

实现此目的的一种方法是使用递归查询,它最终与您正在考虑的迭代非常相似。

with recursive lines as (
  select line_no, 
         (case when def is not null then jsonb_build_array(def) else '[]'::jsonb end) - coalesce(undef, '') live
  from the_table
  where line_no = 1
  union all
  select c.line_no, 
         (p.live || case when def is not null then jsonb_build_array(c.def) else '[]'::jsonb end) - coalesce(c.undef, '')
  from the_table c
    join lines p on c.line_no - 1 = p.line_no
)
select *
from lines;

jsonb_build_array 很乐意包含 NULL 值,这就是为什么我们需要有点复杂的 CASE 表达式来将单个值转换为数组(具有一个或零个元素)。

jsonb 的 - 运算符从数组中删除右侧的元素。然而,右侧的 null 值将删除 所有 元素,因此 coalesce()

这要求 line_no 的值没有任何间隙(并且从 1 开始)。如果不是这种情况,则需要另一个步骤来为每一行生成一个 gap-less 数字(这会使它变得更慢)

Online example

我怀疑这是否真的比“适当的”程序解决方案更快。